From 72a502c5de95e5510e7eef4cfef23fe391d24511 Mon Sep 17 00:00:00 2001 From: Mauro Mugnaini Date: Thu, 23 Mar 2023 18:19:49 +0100 Subject: [PATCH] [#24701, #23356] Improved and tested version, ready for trial implementation --- .../gcube/common/iam/AbstractIAMResponse.java | 136 +++++++++ .../common/iam/D4ScienceCustomClaims.java | 2 +- .../gcube/common/iam/D4ScienceIAMClient.java | 279 +++++------------- .../common/iam/D4ScienceIAMClientAuthn.java | 48 +++ .../common/iam/D4ScienceIAMClientAuthz.java | 44 +++ .../org/gcube/common/iam/IAMResponse.java | 33 +++ .../common/iam/TestD4ScienceIAMClient.java | 107 ++++++- 7 files changed, 448 insertions(+), 201 deletions(-) create mode 100644 src/main/java/org/gcube/common/iam/AbstractIAMResponse.java create mode 100644 src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthn.java create mode 100644 src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthz.java create mode 100644 src/main/java/org/gcube/common/iam/IAMResponse.java diff --git a/src/main/java/org/gcube/common/iam/AbstractIAMResponse.java b/src/main/java/org/gcube/common/iam/AbstractIAMResponse.java new file mode 100644 index 0000000..4c87941 --- /dev/null +++ b/src/main/java/org/gcube/common/iam/AbstractIAMResponse.java @@ -0,0 +1,136 @@ +package org.gcube.common.iam; + +import java.util.HashSet; +import java.util.Set; + +import org.gcube.common.keycloak.KeycloakClientException; +import org.gcube.common.keycloak.model.AccessToken; +import org.gcube.common.keycloak.model.ModelUtils; +import org.gcube.common.keycloak.model.RefreshToken; +import org.gcube.common.keycloak.model.TokenResponse; + +public class AbstractIAMResponse implements IAMResponse { + + private D4ScienceIAMClient iamClient; + private TokenResponse tokenResponse; + + public AbstractIAMResponse(D4ScienceIAMClient iamClient, TokenResponse tokenResponse) { + setIamClient(iamClient); + setTokenResponse(tokenResponse); + } + + public void setIamClient(D4ScienceIAMClient iamClient) { + this.iamClient = iamClient; + } + + public D4ScienceIAMClient getIamClient() { + return iamClient; + } + + public void setTokenResponse(TokenResponse tokenResponse) { + this.tokenResponse = tokenResponse; + } + + protected TokenResponse getTokenResponse() { + return tokenResponse; + } + + @Override + public AccessToken getAccessToken() throws D4ScienceIAMClientException { + try { + return ModelUtils.getAccessTokenFrom(getTokenResponse()); + } catch (Exception e) { + throw new D4ScienceIAMClientException(e); + } + } + + public String getAccessTokenString() { + return getTokenResponse().getAccessToken(); + } + + @Override + public boolean isExpired() throws D4ScienceIAMClientException { + try { + return ModelUtils.getAccessTokenFrom(getTokenResponse()).isExpired(); + } catch (Exception e) { + throw new D4ScienceIAMClientException(e); + } + } + + @Override + public boolean canBeRefreshed() throws D4ScienceIAMClientException { + try { + RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(getTokenResponse()); + return refreshToken != null && !refreshToken.isExpired(); + } catch (Exception e) { + throw new D4ScienceIAMClientException(e); + } + } + + @Override + public void refresh() throws D4ScienceIAMClientException { + try { + this.tokenResponse = getIamClient().getKeycloakClient().refreshToken(getIamClient().getTokenEndpointURL(), + getTokenResponse()); + + } catch (KeycloakClientException e) { + throw new D4ScienceIAMClientException(e); + } + } + + @Override + public Set getRealmRoles() throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + return accessToken.getRealmAccess() != null ? accessToken.getRealmAccess().getRoles() : new HashSet<>(); + } + + @Override + public Set getRoles() throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + Set roles = getRealmRoles(); + accessToken.getResourceAccess().forEach((r, a) -> roles.addAll(a.getRoles())); + return roles; + } + + @Override + public Set getResourceRoles(String resource) throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + return accessToken.getResourceAccess() != null ? (accessToken.getResourceAccess().get(resource) != null + ? accessToken.getResourceAccess().get(resource).getRoles() + : new HashSet<>()) : new HashSet<>(); + } + + @Override + public Set getAudienceResourceRoles() throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + return accessToken.getResourceAccess() != null + ? (accessToken.getResourceAccess().get(accessToken.getAudience()[0]) != null + ? accessToken.getResourceAccess().get(accessToken.getAudience()[0]).getRoles() + : new HashSet<>()) + : new HashSet<>(); + } + + @Override + public String getName() throws D4ScienceIAMClientException { + return getAccessToken().getName(); + } + + @Override + public String getContactPerson() throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + return (String) (accessToken.getOtherClaims() != null + ? accessToken.getOtherClaims() + .get(D4ScienceCustomClaims.CLIENT_CONTACT_PERSON) + : null); + } + + @Override + public String getContactOrganization() throws D4ScienceIAMClientException { + AccessToken accessToken = getAccessToken(); + return (String) (accessToken.getOtherClaims() != null + ? accessToken.getOtherClaims() + .get(D4ScienceCustomClaims.CLIENT_CONTACT_ORGANISATION) + : null); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/common/iam/D4ScienceCustomClaims.java b/src/main/java/org/gcube/common/iam/D4ScienceCustomClaims.java index 5e10ae3..56c6993 100644 --- a/src/main/java/org/gcube/common/iam/D4ScienceCustomClaims.java +++ b/src/main/java/org/gcube/common/iam/D4ScienceCustomClaims.java @@ -15,6 +15,6 @@ public class D4ScienceCustomClaims { /** * The registered contact organization of a client with service account */ - public static final String CLIENT_CONTACT_ORGANIZATION = "contact_person"; + public static final String CLIENT_CONTACT_ORGANISATION = "contact_organisation"; } diff --git a/src/main/java/org/gcube/common/iam/D4ScienceIAMClient.java b/src/main/java/org/gcube/common/iam/D4ScienceIAMClient.java index 93da3d8..ddb6bad 100644 --- a/src/main/java/org/gcube/common/iam/D4ScienceIAMClient.java +++ b/src/main/java/org/gcube/common/iam/D4ScienceIAMClient.java @@ -1,235 +1,118 @@ package org.gcube.common.iam; import java.net.URL; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.gcube.common.keycloak.KeycloakClient; import org.gcube.common.keycloak.KeycloakClientException; import org.gcube.common.keycloak.KeycloakClientFactory; -import org.gcube.common.keycloak.model.AccessToken; -import org.gcube.common.keycloak.model.ModelUtils; -import org.gcube.common.keycloak.model.RefreshToken; -import org.gcube.common.keycloak.model.TokenResponse; /** - * Helper class that acts as IAM client providing + * Helper class that acts as IAM client providing authentication and authorization using the IAM hiding the underlying implementation * * @author Mauro Mugnaini - * */ public class D4ScienceIAMClient { private KeycloakClient keycloakClient; + private URL tokenEndpointURL; - public static D4ScienceIAMClient newInstance() { - return new D4ScienceIAMClient(); - } - - private D4ScienceIAMClient() { - keycloakClient = KeycloakClientFactory.newInstance(); - } - - private KeycloakClient getKeycloakClient() { - return keycloakClient; - } - - public D4ScienceIAMClientAutentication authenticate(String context, String clientId, String clientSecret) - throws D4ScienceIAMClientException { - - return new D4ScienceIAMClientAutentication(this, context, clientId, clientSecret); - } - - public D4ScienceIAMClientAutentication authenticate(URL tokenURL, String clientId, String clientSecret) - throws D4ScienceIAMClientException { - - return new D4ScienceIAMClientAutentication(this, tokenURL, clientId, clientSecret); - } - - public URL getTokenEndpointURL(String context) throws D4ScienceIAMClientException { + /** + * Creates a new client for the specific context, in the default IAM realm. + * @param context the context to be used to obtain the endpoint URL + * @return the client to be used for authn and authz requests + * @throws D4ScienceIAMClientException if an error occurs obtaining the base URL + */ + public static D4ScienceIAMClient newInstance(String context) throws D4ScienceIAMClientException { + KeycloakClient keycloakClient = KeycloakClientFactory.newInstance(); try { - return getKeycloakClient().getRealmBaseURL(context); + return new D4ScienceIAMClient(keycloakClient, + keycloakClient.getTokenEndpointURL(keycloakClient.getRealmBaseURL(context))); } catch (KeycloakClientException e) { throw new D4ScienceIAMClientException(e); } } - public class D4ScienceIAMClientAutentication { - - private URL tokenURL; - private TokenResponse tokenResponse; - - private D4ScienceIAMClientAutentication(D4ScienceIAMClient client, String context, String clientId, - String clientSecret) throws D4ScienceIAMClientException { - - this(client, client.getTokenEndpointURL(context), clientId, clientSecret); + /** + * Creates a new client for the specific context, in the default realm. + * @param context the context to be used to obtain the endpoint URL + * @param realm the IAM realm + * @return the client to be used for authn and authz requests + * @throws D4ScienceIAMClientException if an error occurs obtaining the base URL + */ + public static D4ScienceIAMClient newInstance(String context, String realm) throws D4ScienceIAMClientException { + KeycloakClient keycloakClient = KeycloakClientFactory.newInstance(); + try { + return new D4ScienceIAMClient(keycloakClient, + keycloakClient.getTokenEndpointURL(keycloakClient.getRealmBaseURL(context, realm))); + } catch (KeycloakClientException e) { + throw new D4ScienceIAMClientException(e); } + } - private D4ScienceIAMClientAutentication(D4ScienceIAMClient client, URL tokenURL, String clientId, - String clientSecret) - throws D4ScienceIAMClientException { + /** + * Creates a new client with the provided endpoint URL. + * @param tokenEndpointURL the endpoint URL + * @return the client to be used for authn and authz requests + */ + public static D4ScienceIAMClient newInstance(URL tokenEndpointURL) { + return new D4ScienceIAMClient(KeycloakClientFactory.newInstance(), tokenEndpointURL); + } - this.tokenURL = tokenURL; - try { - this.tokenResponse = getKeycloakClient().queryOIDCToken(tokenURL, clientId, clientSecret); - } catch (KeycloakClientException e) { - throw new D4ScienceIAMClientException(e); - } - } + private D4ScienceIAMClient(KeycloakClient keycloakClient, URL tokenEndpointURL) { + this.keycloakClient = keycloakClient; + this.tokenEndpointURL = tokenEndpointURL; + } - private TokenResponse getAuthenticationTokenResponse() { - return tokenResponse; - } + protected KeycloakClient getKeycloakClient() { + return this.keycloakClient; + } - public String getAccessToken() { - return getAuthenticationTokenResponse().getAccessToken(); - } + public URL getTokenEndpointURL() { + return this.tokenEndpointURL; + } - public boolean isExpired() throws D4ScienceIAMClientException { - try { - return ModelUtils.getAccessTokenFrom(getAuthenticationTokenResponse()).isExpired(); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } + /** + * Authenticates the client with provided id and secret, + * @param clientId the client id + * @param clientSecret the client secret + * @return the authn object + * @throws D4ScienceIAMClientException if an error occurs during authn process + */ + public D4ScienceIAMClientAuthn authenticate(String clientId, String clientSecret) + throws D4ScienceIAMClientException { - public boolean canBeRefreshed() throws D4ScienceIAMClientException { - try { - RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(getAuthenticationTokenResponse()); - return refreshToken != null && !refreshToken.isExpired(); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } + return authenticate(clientId, clientSecret, null); + } - public void refreshAuthentication() throws D4ScienceIAMClientException { - try { - this.tokenResponse = getKeycloakClient().refreshToken(tokenURL, getAuthenticationTokenResponse()); - } catch (KeycloakClientException e) { - throw new D4ScienceIAMClientException(e); - } - } + /** + * Authenticates the client with provided credentials, reducing the token audience to the requested. + * @param clientId the client id + * @param clientSecret the client secret + * @param audience the requested audience (e.g. a specific context) + * @return the authn object + * @throws D4ScienceIAMClientException if an error occurs during authn process + */ + public D4ScienceIAMClientAuthn authenticate(String clientId, String clientSecret, String audience) + throws D4ScienceIAMClientException { - public D4ScienceIAMClientAuthorization authorize(String audience, List permissions) - throws D4ScienceIAMClientException { + return new D4ScienceIAMClientAuthn(this, clientId, clientSecret, audience); + } - return new D4ScienceIAMClientAuthorization(audience, permissions); - } - - public class D4ScienceIAMClientAuthorization { - TokenResponse tokenResponse; - - private D4ScienceIAMClientAuthorization(String audience, List permissions) - throws D4ScienceIAMClientException { - try { - this.tokenResponse = getKeycloakClient().queryUMAToken(tokenURL, getAuthenticationTokenResponse(), - audience, permissions); - - } catch (KeycloakClientException e) { - throw new D4ScienceIAMClientException(e); - } - } - - private TokenResponse getAuthorizationTokenResponse() { - return tokenResponse; - } - - public String getAccessToken() { - return getAuthorizationTokenResponse().getAccessToken(); - } - - public boolean isExpired() throws D4ScienceIAMClientException { - try { - return ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()).isExpired(); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } - - public boolean canBeRefreshed() throws D4ScienceIAMClientException { - try { - RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(getAuthorizationTokenResponse()); - return refreshToken != null && !refreshToken.isExpired(); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } - - public void refreshAuthorization() throws D4ScienceIAMClientException { - try { - this.tokenResponse = getKeycloakClient().refreshToken(tokenURL, getAuthorizationTokenResponse()); - } catch (KeycloakClientException e) { - throw new D4ScienceIAMClientException(e); - } - } - - public Set getRoles() throws D4ScienceIAMClientException { - AccessToken accessToken; - try { - accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - Set allRoles = new HashSet<>(accessToken.getRealmAccess().getRoles()); - accessToken.getResourceAccess().forEach((r, a) -> allRoles.addAll(a.getRoles())); - return allRoles; - } - - public Set getResourceRoles(String resource) throws D4ScienceIAMClientException { - AccessToken accessToken; - try { - accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - Set resourceRoles = new HashSet<>(accessToken.getResourceAccess().get(resource).getRoles()); - return resourceRoles; - } - - public Set getAudienceResourceRoles() throws D4ScienceIAMClientException { - AccessToken accessToken; - try { - accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()); - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - Set resourceRoles = new HashSet<>( - accessToken.getResourceAccess().get(accessToken.getAudience()[0]).getRoles()); - return resourceRoles; - } - - public String getName() throws D4ScienceIAMClientException { - try { - return (String) ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()).getOtherClaims() - .get(D4ScienceCustomClaims.CLIENT_NAME); - - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } - - public String getContactPerson() throws D4ScienceIAMClientException { - try { - return (String) ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()).getOtherClaims() - .get(D4ScienceCustomClaims.CLIENT_CONTACT_PERSON); - - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } - - public String getContactOrganization() throws D4ScienceIAMClientException { - try { - return (String) ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse()).getOtherClaims() - .get(D4ScienceCustomClaims.CLIENT_CONTACT_ORGANIZATION); - - } catch (Exception e) { - throw new D4ScienceIAMClientException(e); - } - } - } + /** + * Directly authorizes the client by using the provided credentials, for the specific audience and with optional permissions + * @param clientId the client id + * @param clientSecret the client secret + * @param audience the requested audience (e.g. a specific context) + * @param permissions the optional permissions + * @return the authz object + * @throws D4ScienceIAMClientException if an error occurs during authz process + */ + public D4ScienceIAMClientAuthz authorize(String clientId, String clientSecret, String audience, + List permissions) + throws D4ScienceIAMClientException { + return new D4ScienceIAMClientAuthz(this, clientId, clientSecret, audience, permissions); } } \ No newline at end of file diff --git a/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthn.java b/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthn.java new file mode 100644 index 0000000..bf9be5c --- /dev/null +++ b/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthn.java @@ -0,0 +1,48 @@ +package org.gcube.common.iam; + +import java.util.List; + +import org.gcube.common.keycloak.KeycloakClientException; +import org.gcube.common.keycloak.model.TokenResponse; + +public class D4ScienceIAMClientAuthn extends AbstractIAMResponse implements IAMResponse { + + protected D4ScienceIAMClientAuthn(D4ScienceIAMClient iamClient, String clientId, String clientSecret) + throws D4ScienceIAMClientException { + + this(iamClient, clientId, clientSecret, null); + } + + protected D4ScienceIAMClientAuthn(D4ScienceIAMClient iamClient, String clientId, String clientSecret, + String audience) + throws D4ScienceIAMClientException { + + super(iamClient, performAuthn(iamClient, clientId, clientSecret, audience)); + } + + protected static final TokenResponse performAuthn(D4ScienceIAMClient iamClient, String clientId, String clientSecret, + String audience) throws D4ScienceIAMClientException { + + try { + return iamClient.getKeycloakClient().queryOIDCTokenWithContext(iamClient.getTokenEndpointURL(), clientId, + clientSecret, audience); + + } catch (KeycloakClientException e) { + throw new D4ScienceIAMClientException(e); + } + } + + /** + * Authorizes the client by using the authn already obtained, for the specific audience and with optional permissions. + * @param audience the requested audience (e.g. a specific context) + * @param permissions the optional permissions + * @return the authz object + * @throws D4ScienceIAMClientException if an error occurs during authz process + */ + public D4ScienceIAMClientAuthz authorize(String audience, List permissions) + throws D4ScienceIAMClientException { + + return new D4ScienceIAMClientAuthz(this, audience, permissions); + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthz.java b/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthz.java new file mode 100644 index 0000000..f7f31fe --- /dev/null +++ b/src/main/java/org/gcube/common/iam/D4ScienceIAMClientAuthz.java @@ -0,0 +1,44 @@ +package org.gcube.common.iam; + +import java.util.List; + +import org.gcube.common.keycloak.KeycloakClientException; +import org.gcube.common.keycloak.model.TokenResponse; + +public class D4ScienceIAMClientAuthz extends AbstractIAMResponse implements IAMResponse { + + protected D4ScienceIAMClientAuthz(D4ScienceIAMClientAuthn authn, String audience, List permissions) + throws D4ScienceIAMClientException { + + super(authn.getIamClient(), + performAuthz(authn.getIamClient(), authn.getTokenResponse(), audience, permissions)); + } + + private static final TokenResponse performAuthz(D4ScienceIAMClient iamClient, TokenResponse authnTR, + String audience, List permissions) throws D4ScienceIAMClientException { + try { + return iamClient.getKeycloakClient().queryUMAToken(iamClient.getTokenEndpointURL(), authnTR, audience, + permissions); + } catch (KeycloakClientException e) { + throw new D4ScienceIAMClientException(e); + } + } + + protected D4ScienceIAMClientAuthz(D4ScienceIAMClient iamClient, String clientId, String clientSecret, + String audience, List permissions) throws D4ScienceIAMClientException { + + super(iamClient, performAuthz(iamClient, clientId, clientSecret, audience, permissions)); + } + + private static final TokenResponse performAuthz(D4ScienceIAMClient iamClient, String clientId, String clientSecret, + String audience, List permissions) throws D4ScienceIAMClientException { + ; + try { + return iamClient.getKeycloakClient().queryUMAToken(iamClient.getTokenEndpointURL(), clientId, clientSecret, + audience, permissions); + } catch (KeycloakClientException e) { + throw new D4ScienceIAMClientException(e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/gcube/common/iam/IAMResponse.java b/src/main/java/org/gcube/common/iam/IAMResponse.java new file mode 100644 index 0000000..54b11e9 --- /dev/null +++ b/src/main/java/org/gcube/common/iam/IAMResponse.java @@ -0,0 +1,33 @@ +package org.gcube.common.iam; + +import java.util.Set; + +import org.gcube.common.keycloak.model.AccessToken; + +public interface IAMResponse { + + public AccessToken getAccessToken() throws D4ScienceIAMClientException; + + String getAccessTokenString(); + + boolean isExpired() throws D4ScienceIAMClientException; + + boolean canBeRefreshed() throws D4ScienceIAMClientException; + + void refresh() throws D4ScienceIAMClientException; + + String getContactOrganization() throws D4ScienceIAMClientException; + + String getContactPerson() throws D4ScienceIAMClientException; + + String getName() throws D4ScienceIAMClientException; + + Set getAudienceResourceRoles() throws D4ScienceIAMClientException; + + Set getResourceRoles(String resource) throws D4ScienceIAMClientException; + + Set getRoles() throws D4ScienceIAMClientException; + + Set getRealmRoles() throws D4ScienceIAMClientException; + +} \ No newline at end of file diff --git a/src/test/java/org/gcube/common/iam/TestD4ScienceIAMClient.java b/src/test/java/org/gcube/common/iam/TestD4ScienceIAMClient.java index 605e70c..219301e 100644 --- a/src/test/java/org/gcube/common/iam/TestD4ScienceIAMClient.java +++ b/src/test/java/org/gcube/common/iam/TestD4ScienceIAMClient.java @@ -1,5 +1,14 @@ package org.gcube.common.iam; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.gcube.common.keycloak.model.ModelUtils; import org.junit.After; import org.junit.Before; import org.junit.FixMethodOrder; @@ -13,8 +22,27 @@ public class TestD4ScienceIAMClient { protected static final Logger logger = LoggerFactory.getLogger(TestD4ScienceIAMClient.class); + protected static final String DEV_ROOT_CONTEXT = "/gcube"; + protected static final String CLIENT_ID = "keycloak-client-unit-test"; + protected static final String CLIENT_SECRET = "ebf6f82e-9511-408e-8321-203081e472d8"; + protected static final String TEST_AUDIENCE = "conductor-server"; + + protected static final Set REALM_ROLES = Collections.singleton("d4s-client"); + protected static final Set CONDUCTOR_SERVER_ROLES = Collections.singleton("dummy-test-role"); + protected static final Set OTHER_ROLES = new HashSet<>(Arrays.asList("uma_protection", "Member")); + + protected static final String NAME = "Keycloak-client unit-test"; + protected static final String CONTACT_PERSON = "mauro.mugnaini"; + protected static final String CONTACT_ORGANIZATION = "Nubisware S.r.l."; + + protected static Set ALL_ROLES; + @Before public void setUp() throws Exception { + ALL_ROLES = new HashSet<>(); + ALL_ROLES.addAll(REALM_ROLES); + ALL_ROLES.addAll(CONDUCTOR_SERVER_ROLES); + ALL_ROLES.addAll(OTHER_ROLES); } @After @@ -22,8 +50,83 @@ public class TestD4ScienceIAMClient { } @Test - public void test1() throws Exception { - logger.info("*** [1] "); + public void test1Authn() throws Exception { + logger.info("*** [1] Testing authentication"); + D4ScienceIAMClientAuthn authn = D4ScienceIAMClient.newInstance(DEV_ROOT_CONTEXT).authenticate(CLIENT_ID, CLIENT_SECRET); + logger.info("Authn scope: {}", authn.getTokenResponse().getScope()); + logger.info("Authn AUDIENCE: {}", (Object[]) authn.getAccessToken().getAudience()); + logger.info("Authn RESOURCE ACCESS: {}", authn.getAccessToken().getResourceAccess()); + logger.info("Authn ALL roles: {}", authn.getRoles()); + logger.info("Authn REALM roles: {}", authn.getRealmRoles()); + logger.info("Authn AUDIENCE's roles: {}", authn.getAudienceResourceRoles()); + logger.info("Authn D4S identity: '{}' by {} [{}]", authn.getName(), authn.getContactPerson(), authn.getContactOrganization()); + logger.info("Authn access token:\n{}", ModelUtils.getAccessTokenPayloadJSONStringFrom(authn.getTokenResponse())); + assertEquals(ALL_ROLES, authn.getRoles()); + assertEquals(REALM_ROLES, authn.getRealmRoles()); + assertEquals(NAME, authn.getName()); + assertEquals(CONTACT_PERSON, authn.getContactPerson()); + assertEquals(CONTACT_ORGANIZATION, authn.getContactOrganization()); } + @Test + public void test2AuthnWithSpecificAudience() throws Exception { + logger.info("*** [2] Testing authentication"); + D4ScienceIAMClientAuthn authn = D4ScienceIAMClient.newInstance(DEV_ROOT_CONTEXT).authenticate(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE); + logger.info("Authn scope: {}", authn.getTokenResponse().getScope()); + logger.info("Authn AUDIENCE: {}", (Object[]) authn.getAccessToken().getAudience()); + logger.info("Authn RESOURCE ACCESS: {}", authn.getAccessToken().getResourceAccess()); + logger.info("Authn ALL roles: {}", authn.getRoles()); + logger.info("Authn REALM roles: {}", authn.getRealmRoles()); + logger.info("Authn AUDIENCE's roles: {}", authn.getAudienceResourceRoles()); + logger.info("Authn D4S identity: '{}' by {} [{}]", authn.getName(), authn.getContactPerson(), authn.getContactOrganization()); + logger.info("Authn access token:\n{}", ModelUtils.getAccessTokenPayloadJSONStringFrom(authn.getTokenResponse())); + assertEquals(TEST_AUDIENCE, authn.getAccessToken().getAudience()[0]); +// assertEquals(ALL_ROLES, authn.getRoles()); // This depends on the mapper's "shrink" settings, so it's not safe to let it enabled + assertEquals(CONDUCTOR_SERVER_ROLES, authn.getAudienceResourceRoles()); + assertEquals(REALM_ROLES, authn.getRealmRoles()); + assertEquals(NAME, authn.getName()); + assertEquals(CONTACT_PERSON, authn.getContactPerson()); + assertEquals(CONTACT_ORGANIZATION, authn.getContactOrganization()); + } + + @Test + public void test3AuthnAndAuthz() throws Exception { + logger.info("*** [3] Testing authentication and then authorization"); + D4ScienceIAMClientAuthz authz = D4ScienceIAMClient.newInstance(DEV_ROOT_CONTEXT).authenticate(CLIENT_ID, CLIENT_SECRET).authorize(TEST_AUDIENCE, null); + logger.info("Authz scope: {}", authz.getTokenResponse().getScope()); + logger.info("Authn AUDIENCE: {}", (Object[]) authz.getAccessToken().getAudience()); + logger.info("Authn RESOURCE ACCESS: {}", authz.getAccessToken().getResourceAccess()); + logger.info("Authz ALL roles: {}", authz.getRoles()); + logger.info("Authz REALM roles: {}", authz.getRealmRoles()); + logger.info("Authz AUDIENCE's roles: {}", authz.getAudienceResourceRoles()); + logger.info("Authz D4S identity: '{}' by {} [{}]", authz.getName(), authz.getContactPerson(), authz.getContactOrganization()); + logger.info("Authz access token:\n{}", ModelUtils.getAccessTokenPayloadJSONStringFrom(authz.getTokenResponse())); + assertEquals(TEST_AUDIENCE, authz.getAccessToken().getAudience()[0]); + assertNotEquals(ALL_ROLES, authz.getRoles()); + assertEquals(CONDUCTOR_SERVER_ROLES, authz.getAudienceResourceRoles()); + assertEquals(CONDUCTOR_SERVER_ROLES, authz.getRoles()); + assertEquals(Collections.emptySet(), authz.getRealmRoles()); + } + + @Test + public void test4DirectAuthz() throws Exception { + logger.info("*** [4] Testing direct authorization"); + D4ScienceIAMClientAuthz authz = D4ScienceIAMClient.newInstance(DEV_ROOT_CONTEXT).authorize(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null); + logger.info("Authz scope: {}", authz.getTokenResponse().getScope()); + logger.info("Authn AUDIENCE: {}", (Object[]) authz.getAccessToken().getAudience()); + logger.info("Authn RESOURCE ACCESS: {}", authz.getAccessToken().getResourceAccess()); + logger.info("Authz ALL roles: {}", authz.getRoles()); + logger.info("Authz REALM roles: {}", authz.getRealmRoles()); + logger.info("Authz AUDIENCE's roles: {}", authz.getAudienceResourceRoles()); + logger.info("Authz D4S identity: '{}' by {} [{}]", authz.getName(), authz.getContactPerson(), authz.getContactOrganization()); + logger.info("Authz access token:\n{}", ModelUtils.getAccessTokenPayloadJSONStringFrom(authz.getTokenResponse())); + assertEquals(TEST_AUDIENCE, authz.getAccessToken().getAudience()[0]); + assertNotEquals(ALL_ROLES, authz.getRoles()); + assertEquals(TEST_AUDIENCE, authz.getAccessToken().getAudience()[0]); + assertEquals(CONDUCTOR_SERVER_ROLES, authz.getAudienceResourceRoles()); + assertEquals(CONDUCTOR_SERVER_ROLES, authz.getRoles()); + assertEquals(Collections.emptySet(), authz.getRealmRoles()); + } + + }