Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: P4ADEV-1274 validate access parameters for client-credentials grant type #94

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package it.gov.pagopa.payhub.auth.service;

import it.gov.pagopa.payhub.auth.exception.custom.InvalidGrantTypeException;
import it.gov.pagopa.payhub.auth.service.a2a.ClientCredentialService;
import it.gov.pagopa.payhub.auth.service.a2a.ValidateClientCredentialsService;
import it.gov.pagopa.payhub.auth.service.exchange.ExchangeTokenService;
import it.gov.pagopa.payhub.auth.service.exchange.ValidateExternalTokenService;
import it.gov.pagopa.payhub.auth.service.logout.LogoutService;
import it.gov.pagopa.payhub.auth.service.user.UserService;
import it.gov.pagopa.payhub.model.generated.AccessToken;
Expand All @@ -11,19 +15,25 @@
@Slf4j
@Service
public class AuthnServiceImpl implements AuthnService {
private final ClientCredentialService clientCredentialService;
private final ExchangeTokenService exchangeTokenService;
private final UserService userService;
private final LogoutService logoutService;

public AuthnServiceImpl(ExchangeTokenService exchangeTokenService, UserService userService, LogoutService logoutService) {
this.exchangeTokenService = exchangeTokenService;
this.userService = userService;
this.logoutService = logoutService;
public AuthnServiceImpl(ClientCredentialService clientCredentialService, ExchangeTokenService exchangeTokenService, UserService userService, LogoutService logoutService) {
this.clientCredentialService = clientCredentialService;
this.exchangeTokenService = exchangeTokenService;
this.userService = userService;
this.logoutService = logoutService;
}

@Override
public AccessToken postToken(String clientId, String grantType, String scope, String subjectToken, String subjectIssuer, String subjectTokenType, String clientSecret) {
return exchangeTokenService.postToken(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope);
return switch (grantType) {
case ValidateExternalTokenService.ALLOWED_GRANT_TYPE -> exchangeTokenService.postToken(clientId, subjectToken, subjectIssuer, subjectTokenType, scope);
case ValidateClientCredentialsService.ALLOWED_GRANT_TYPE -> clientCredentialService.postToken(clientId, scope, clientSecret);
default -> throw new InvalidGrantTypeException("Invalid grantType " + grantType);
};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package it.gov.pagopa.payhub.auth.service.a2a;

import it.gov.pagopa.payhub.model.generated.AccessToken;

public interface ClientCredentialService {
AccessToken postToken(String clientId, String scope, String clientSecret);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package it.gov.pagopa.payhub.auth.service.a2a;

import it.gov.pagopa.payhub.model.generated.AccessToken;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class ClientCredentialServiceImpl implements ClientCredentialService {

private final ValidateClientCredentialsService validateClientCredentialsService;

public ClientCredentialServiceImpl(ValidateClientCredentialsService validateClientCredentialsService) {
this.validateClientCredentialsService = validateClientCredentialsService;
}

@Override
public AccessToken postToken(String clientId, String scope, String clientSecret) {
log.info("Client {} requested authentication with client_credentials grant type and scope {}", clientId, scope);
validateClientCredentialsService.validate(scope, clientSecret);
return AccessToken.builder().accessToken("accessToken").build();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package it.gov.pagopa.payhub.auth.service.a2a;

import it.gov.pagopa.payhub.auth.model.Client;
import it.gov.pagopa.payhub.model.generated.ClientDTO;
import it.gov.pagopa.payhub.model.generated.ClientNoSecretDTO;

import java.util.List;
import java.util.Optional;

public interface ClientService {

ClientDTO registerClient(String clientName, String organizationIpaCode);
String getClientSecret(String organizationIpaCode, String clientId);
List<ClientNoSecretDTO> getClients(String organizationIpaCode);
Optional<Client> getClientByClientId(String clientId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
@Slf4j
Expand All @@ -27,7 +28,6 @@ public ClientServiceImpl(ClientRegistrationService clientRegistrationService, Cl

@Override
public ClientDTO registerClient(String clientName, String organizationIpaCode) {

Client client = clientRegistrationService.registerClient(clientName, organizationIpaCode);
return clientMapper.mapToDTO(client);
}
Expand All @@ -44,4 +44,9 @@ public List<ClientNoSecretDTO> getClients(String organizationIpaCode) {
return clientRetrieverService.getClients(organizationIpaCode);
}

public Optional<Client> getClientByClientId(String clientId) {
log.info("Retrieving client for {}", clientId);
return clientRetrieverService.getClientByClientId(clientId);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package it.gov.pagopa.payhub.auth.service.a2a;

import it.gov.pagopa.payhub.auth.exception.custom.InvalidExchangeRequestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

@Service
@Slf4j
public class ValidateClientCredentialsService {
public static final String ALLOWED_GRANT_TYPE = "client_credentials";
public static final String ALLOWED_SCOPE = "openid";

public void validate(String scope, String clientSecret) {
validateProtocolConfiguration(scope);
validateClientSecret(clientSecret);
log.debug("authorization granted");
}

private void validateProtocolConfiguration(String scope) {
if (!ALLOWED_SCOPE.equals(scope)){
throw new InvalidExchangeRequestException("Invalid scope " + scope);
}
}

private void validateClientSecret(String clientSecret) {
if (!StringUtils.hasText(clientSecret)) {
throw new InvalidExchangeRequestException("clientSecret is mandatory with client-credentials grant type");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
@Slf4j
Expand Down Expand Up @@ -41,4 +42,6 @@ public List<ClientNoSecretDTO> getClients(String organizationIpaCode) {
.toList();
}

public Optional<Client> getClientByClientId(String clientId) { return clientRepository.findById(clientId); }

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import it.gov.pagopa.payhub.model.generated.AccessToken;

public interface ExchangeTokenService {
AccessToken postToken(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope);
AccessToken postToken(String clientId, String subjectToken, String subjectIssuer, String subjectTokenType, String scope);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ public ExchangeTokenServiceImpl(
}

@Override
public AccessToken postToken(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) {
log.info("Client {} requested to exchange a {} token provided by {} asking for grant type {} and scope {}",
clientId, subjectTokenType, subjectIssuer, grantType, scope);
public AccessToken postToken(String clientId, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) {
log.info("Client {} requested to exchange a {} token provided by {} asking for token-exchange grant type and scope {}",
clientId, subjectTokenType, subjectIssuer, scope);
Dismissed Show dismissed Hide dismissed
if(SUBJECT_TOKEN_TYPE_FAKE.equals(subjectTokenType)){
return handleFakeAuth(subjectToken, subjectIssuer);
}
Map<String, Claim> claims = validateExternalTokenService.validate(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope);
Map<String, Claim> claims = validateExternalTokenService.validate(clientId, subjectToken, subjectIssuer, subjectTokenType, scope);
AccessToken accessToken = accessTokenBuilderService.build();
IamUserInfoDTO iamUser = idTokenClaimsMapper.apply(claims);
User registeredUser = iamUserRegistrationService.registerUser(iamUser);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public ValidateExternalTokenService(@Value("${jwt.audience:}")String allowedAudi
this.jwtValidator = jwtValidator;
}

public Map<String, Claim> validate(String clientId, String grantType, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) {
public Map<String, Claim> validate(String clientId, String subjectToken, String subjectIssuer, String subjectTokenType, String scope) {
validateClient(clientId);
validateProtocolConfiguration(grantType, subjectTokenType, scope);
validateProtocolConfiguration(subjectTokenType, scope);
validateSubjectTokenIssuer(subjectIssuer);
Map<String, Claim> claims = validateSubjectToken(subjectToken);
log.info("SubjectToken authorized");
Expand All @@ -50,13 +50,10 @@ public void validateClient(String clientId) {
}
}

private void validateProtocolConfiguration(String grantType, String subjectTokenType, String scope) {
private void validateProtocolConfiguration(String subjectTokenType, String scope) {
if (!StringUtils.hasText(subjectTokenType)) {
throw new InvalidExchangeRequestException("subjectTokenType is mandatory with token-exchange grant type");
}
if (!ALLOWED_GRANT_TYPE.equals(grantType)){
throw new InvalidGrantTypeException("Invalid grantType " + grantType);
}
if (!ALLOWED_SUBJECT_TOKEN_TYPE.equals(subjectTokenType)){
throw new InvalidTokenException("Invalid subjectTokenType " + subjectTokenType);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package it.gov.pagopa.payhub.auth.service;

import it.gov.pagopa.payhub.auth.exception.custom.InvalidGrantTypeException;
import it.gov.pagopa.payhub.auth.service.a2a.ClientCredentialService;
import it.gov.pagopa.payhub.auth.service.a2a.ValidateClientCredentialsService;
import it.gov.pagopa.payhub.auth.service.exchange.ExchangeTokenService;
import it.gov.pagopa.payhub.auth.service.exchange.ValidateExternalTokenService;
import it.gov.pagopa.payhub.auth.service.logout.LogoutService;
import it.gov.pagopa.payhub.auth.service.user.UserService;
import it.gov.pagopa.payhub.model.generated.AccessToken;
Expand All @@ -14,9 +18,13 @@
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertThrows;

@ExtendWith(MockitoExtension.class)
class AuthnServiceTest {

@Mock
private ClientCredentialService clientCredentialService;
@Mock
private ExchangeTokenService exchangeTokenServiceMock;
@Mock
Expand All @@ -28,31 +36,32 @@ class AuthnServiceTest {

@BeforeEach
void init(){
service = new AuthnServiceImpl(exchangeTokenServiceMock, userServiceMock, logoutServiceMock);
service = new AuthnServiceImpl(clientCredentialService, exchangeTokenServiceMock, userServiceMock, logoutServiceMock);
}

@AfterEach
void verifyNotMoreInteractions(){
Mockito.verifyNoMoreInteractions(
exchangeTokenServiceMock,
userServiceMock,
logoutServiceMock
clientCredentialService,
exchangeTokenServiceMock,
userServiceMock,
logoutServiceMock
);
}

@Test
void whenPostTokenThenCallExchangeService(){
// Given
String clientId="CLIENT_ID";
String grantType="GRANT_TYPE";
String subjectToken="SUBJECT_TOKEN";
String subjectIssuer="SUBJECT_ISSUER";
String subjectTokenType="SUBJECT_TOKEN_TYPE";
String scope="SCOPE";
String clientSecret = "CLIENT_SECRET";

String grantType= ValidateExternalTokenService.ALLOWED_GRANT_TYPE;
AccessToken expectedResult = new AccessToken();
Mockito.when(exchangeTokenServiceMock.postToken(clientId, grantType, subjectToken, subjectIssuer, subjectTokenType, scope))
Mockito.when(exchangeTokenServiceMock.postToken(clientId, subjectToken, subjectIssuer, subjectTokenType, scope))
.thenReturn(expectedResult);

// When
Expand All @@ -62,6 +71,43 @@ void whenPostTokenThenCallExchangeService(){
Assertions.assertSame(expectedResult, result);
}

@Test
void whenPostTokenThenCallClientCredentialService(){
// Given
String clientId="CLIENT_ID";
String subjectToken="SUBJECT_TOKEN";
String subjectIssuer="SUBJECT_ISSUER";
String subjectTokenType="SUBJECT_TOKEN_TYPE";
String scope="SCOPE";
String clientSecret = "CLIENT_SECRET";

String grantType= ValidateClientCredentialsService.ALLOWED_GRANT_TYPE;
AccessToken expectedResult = new AccessToken();
Mockito.when(clientCredentialService.postToken(clientId, scope, clientSecret)).thenReturn(expectedResult);

// When
AccessToken result = service.postToken(clientId, grantType, scope, subjectToken, subjectIssuer, subjectTokenType, clientSecret);

// Then
Assertions.assertSame(expectedResult, result);
}

@Test
void whenPostTokenWhenCallClientCredentialServiceThenInvalidGrantTypeException(){
// Given
String clientId="CLIENT_ID";
String subjectToken="SUBJECT_TOKEN";
String subjectIssuer="SUBJECT_ISSUER";
String subjectTokenType="SUBJECT_TOKEN_TYPE";
String scope="SCOPE";
String clientSecret = "CLIENT_SECRET";

String grantType="UNEXPECTED_GRANT_TYPE";
// When, Then
assertThrows(InvalidGrantTypeException.class, () ->
service.postToken(clientId, grantType, scope, subjectToken, subjectIssuer, subjectTokenType, clientSecret));
}

@Test
void whenGetUserInfoThenCallUserService(){
// Given
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package it.gov.pagopa.payhub.auth.service.a2a;

import it.gov.pagopa.payhub.model.generated.AccessToken;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class ClientCredentialsServiceTest {

@Mock
private ValidateClientCredentialsService validateClientCredentialsServiceMock;
private ClientCredentialService service;

@BeforeEach
void init() {
service = new ClientCredentialServiceImpl(validateClientCredentialsServiceMock);
}

@Test
void givenValidTokenWhenPostTokenThenSuccess(){
// Given
String clientId="CLIENT_ID";
String scope="SCOPE";
String clientSecret="CLIENT_SECRET";

Mockito.doNothing().when(validateClientCredentialsServiceMock).validate(scope, clientSecret);
AccessToken expectedAccessToken = AccessToken.builder().accessToken("accessToken").build();
//When
AccessToken result = service.postToken(clientId, scope, clientSecret);
//Then
Assertions.assertEquals(expectedAccessToken, result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;
import java.util.Optional;
import java.util.UUID;

@ExtendWith(MockitoExtension.class)
Expand Down Expand Up @@ -103,4 +104,17 @@ void givenOrganizationIpaCodeWhenGetClientsThenGetClientNoSecretDTOList() {
Assertions.assertEquals(List.of(dto1, dto2), result);
}

@Test
void givenClientIdWhenGetClientByClientIdThenInvokeClientService() {
// Given
String clientId = "clientId";
Client expectedClient = new Client();

Mockito.when(clientRetrieverServiceMock.getClientByClientId(clientId)).thenReturn(Optional.of(expectedClient));
//When
Optional<Client> result = service.getClientByClientId(clientId);
// Then
Assertions.assertEquals(Optional.of(expectedClient), result);
}

}
Loading
Loading