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 4 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, grantType, subjectToken, subjectIssuer, subjectTokenType, scope);
case ValidateClientCredentialsService.ALLOWED_GRANT_TYPE -> clientCredentialService.postToken(clientId, grantType, 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 grantType, 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 grantType, String scope, String clientSecret) {
macacia marked this conversation as resolved.
Show resolved Hide resolved
log.info("Client {} requested authentication with grant type {} and scope {}", clientId, grantType, scope);
validateClientCredentialsService.validate(clientId, grantType, 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,49 @@
package it.gov.pagopa.payhub.auth.service.a2a;

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

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

public ValidateClientCredentialsService(ClientService clientService) {
this.clientService = clientService;
}

public void validate(String clientId, String grantType, String scope, String clientSecret) {
validateClient(clientId);
macacia marked this conversation as resolved.
Show resolved Hide resolved
validateProtocolConfiguration(grantType, scope);
validateClientSecret(clientSecret);
log.info("authorization granted");
macacia marked this conversation as resolved.
Show resolved Hide resolved
}

private Client validateClient(String clientId) {
return clientService.getClientByClientId(clientId)
.orElseThrow(() -> new InvalidExchangeClientException("Invalid clientId:"+ clientId));
}

private void validateProtocolConfiguration(String grantType, String scope) {
if (!ALLOWED_GRANT_TYPE.equals(grantType)) {
throw new InvalidGrantTypeException("Invalid grantType " + grantType);
}
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
@@ -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,29 +36,30 @@ 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))
.thenReturn(expectedResult);
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, grantType, 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,40 @@
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 grantType="GRANT_TYPE";
String scope="SCOPE";
String clientSecret="CLIENT_SECRET";

Mockito.doNothing().when(validateClientCredentialsServiceMock).validate(clientId, grantType, scope, clientSecret);
AccessToken expectedAccessToken = AccessToken.builder().accessToken("accessToken").build();
//When
AccessToken result = service.postToken(clientId, grantType, 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