This commit is contained in:
parent
c87aba6ed5
commit
72a502c5de
|
@ -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<String> getRealmRoles() throws D4ScienceIAMClientException {
|
||||||
|
AccessToken accessToken = getAccessToken();
|
||||||
|
return accessToken.getRealmAccess() != null ? accessToken.getRealmAccess().getRoles() : new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> getRoles() throws D4ScienceIAMClientException {
|
||||||
|
AccessToken accessToken = getAccessToken();
|
||||||
|
Set<String> roles = getRealmRoles();
|
||||||
|
accessToken.getResourceAccess().forEach((r, a) -> roles.addAll(a.getRoles()));
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<String> 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<String> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,6 +15,6 @@ public class D4ScienceCustomClaims {
|
||||||
/**
|
/**
|
||||||
* The registered contact organization of a client with service account
|
* 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";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,235 +1,118 @@
|
||||||
package org.gcube.common.iam;
|
package org.gcube.common.iam;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.gcube.common.keycloak.KeycloakClient;
|
import org.gcube.common.keycloak.KeycloakClient;
|
||||||
import org.gcube.common.keycloak.KeycloakClientException;
|
import org.gcube.common.keycloak.KeycloakClientException;
|
||||||
import org.gcube.common.keycloak.KeycloakClientFactory;
|
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
|
* @author Mauro Mugnaini
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class D4ScienceIAMClient {
|
public class D4ScienceIAMClient {
|
||||||
|
|
||||||
private KeycloakClient keycloakClient;
|
private KeycloakClient keycloakClient;
|
||||||
|
private URL tokenEndpointURL;
|
||||||
|
|
||||||
public static D4ScienceIAMClient newInstance() {
|
/**
|
||||||
return new D4ScienceIAMClient();
|
* 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
|
||||||
private D4ScienceIAMClient() {
|
* @throws D4ScienceIAMClientException if an error occurs obtaining the base URL
|
||||||
keycloakClient = KeycloakClientFactory.newInstance();
|
*/
|
||||||
}
|
public static D4ScienceIAMClient newInstance(String context) throws D4ScienceIAMClientException {
|
||||||
|
KeycloakClient 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 {
|
|
||||||
try {
|
try {
|
||||||
return getKeycloakClient().getRealmBaseURL(context);
|
return new D4ScienceIAMClient(keycloakClient,
|
||||||
|
keycloakClient.getTokenEndpointURL(keycloakClient.getRealmBaseURL(context)));
|
||||||
} catch (KeycloakClientException e) {
|
} catch (KeycloakClientException e) {
|
||||||
throw new D4ScienceIAMClientException(e);
|
throw new D4ScienceIAMClientException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class D4ScienceIAMClientAutentication {
|
/**
|
||||||
|
* Creates a new client for the specific context, in the default realm.
|
||||||
private URL tokenURL;
|
* @param context the context to be used to obtain the endpoint URL
|
||||||
private TokenResponse tokenResponse;
|
* @param realm the IAM realm
|
||||||
|
* @return the client to be used for authn and authz requests
|
||||||
private D4ScienceIAMClientAutentication(D4ScienceIAMClient client, String context, String clientId,
|
* @throws D4ScienceIAMClientException if an error occurs obtaining the base URL
|
||||||
String clientSecret) throws D4ScienceIAMClientException {
|
*/
|
||||||
|
public static D4ScienceIAMClient newInstance(String context, String realm) throws D4ScienceIAMClientException {
|
||||||
this(client, client.getTokenEndpointURL(context), clientId, clientSecret);
|
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)
|
* Creates a new client with the provided endpoint URL.
|
||||||
throws D4ScienceIAMClientException {
|
* @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;
|
private D4ScienceIAMClient(KeycloakClient keycloakClient, URL tokenEndpointURL) {
|
||||||
try {
|
this.keycloakClient = keycloakClient;
|
||||||
this.tokenResponse = getKeycloakClient().queryOIDCToken(tokenURL, clientId, clientSecret);
|
this.tokenEndpointURL = tokenEndpointURL;
|
||||||
} catch (KeycloakClientException e) {
|
}
|
||||||
throw new D4ScienceIAMClientException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TokenResponse getAuthenticationTokenResponse() {
|
protected KeycloakClient getKeycloakClient() {
|
||||||
return tokenResponse;
|
return this.keycloakClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessToken() {
|
public URL getTokenEndpointURL() {
|
||||||
return getAuthenticationTokenResponse().getAccessToken();
|
return this.tokenEndpointURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExpired() throws D4ScienceIAMClientException {
|
/**
|
||||||
try {
|
* Authenticates the client with provided id and secret,
|
||||||
return ModelUtils.getAccessTokenFrom(getAuthenticationTokenResponse()).isExpired();
|
* @param clientId the client id
|
||||||
} catch (Exception e) {
|
* @param clientSecret the client secret
|
||||||
throw new D4ScienceIAMClientException(e);
|
* @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 {
|
return authenticate(clientId, clientSecret, null);
|
||||||
try {
|
}
|
||||||
RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(getAuthenticationTokenResponse());
|
|
||||||
return refreshToken != null && !refreshToken.isExpired();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new D4ScienceIAMClientException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshAuthentication() throws D4ScienceIAMClientException {
|
/**
|
||||||
try {
|
* Authenticates the client with provided credentials, reducing the token audience to the requested.
|
||||||
this.tokenResponse = getKeycloakClient().refreshToken(tokenURL, getAuthenticationTokenResponse());
|
* @param clientId the client id
|
||||||
} catch (KeycloakClientException e) {
|
* @param clientSecret the client secret
|
||||||
throw new D4ScienceIAMClientException(e);
|
* @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<String> permissions)
|
return new D4ScienceIAMClientAuthn(this, clientId, clientSecret, audience);
|
||||||
throws D4ScienceIAMClientException {
|
}
|
||||||
|
|
||||||
return new D4ScienceIAMClientAuthorization(audience, permissions);
|
/**
|
||||||
}
|
* Directly authorizes the client by using the provided credentials, for the specific audience and with optional permissions
|
||||||
|
* @param clientId the client id
|
||||||
public class D4ScienceIAMClientAuthorization {
|
* @param clientSecret the client secret
|
||||||
TokenResponse tokenResponse;
|
* @param audience the requested audience (e.g. a specific context)
|
||||||
|
* @param permissions the optional permissions
|
||||||
private D4ScienceIAMClientAuthorization(String audience, List<String> permissions)
|
* @return the authz object
|
||||||
throws D4ScienceIAMClientException {
|
* @throws D4ScienceIAMClientException if an error occurs during authz process
|
||||||
try {
|
*/
|
||||||
this.tokenResponse = getKeycloakClient().queryUMAToken(tokenURL, getAuthenticationTokenResponse(),
|
public D4ScienceIAMClientAuthz authorize(String clientId, String clientSecret, String audience,
|
||||||
audience, permissions);
|
List<String> permissions)
|
||||||
|
throws D4ScienceIAMClientException {
|
||||||
} 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<String> getRoles() throws D4ScienceIAMClientException {
|
|
||||||
AccessToken accessToken;
|
|
||||||
try {
|
|
||||||
accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new D4ScienceIAMClientException(e);
|
|
||||||
}
|
|
||||||
Set<String> allRoles = new HashSet<>(accessToken.getRealmAccess().getRoles());
|
|
||||||
accessToken.getResourceAccess().forEach((r, a) -> allRoles.addAll(a.getRoles()));
|
|
||||||
return allRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getResourceRoles(String resource) throws D4ScienceIAMClientException {
|
|
||||||
AccessToken accessToken;
|
|
||||||
try {
|
|
||||||
accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new D4ScienceIAMClientException(e);
|
|
||||||
}
|
|
||||||
Set<String> resourceRoles = new HashSet<>(accessToken.getResourceAccess().get(resource).getRoles());
|
|
||||||
return resourceRoles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getAudienceResourceRoles() throws D4ScienceIAMClientException {
|
|
||||||
AccessToken accessToken;
|
|
||||||
try {
|
|
||||||
accessToken = ModelUtils.getAccessTokenFrom(getAuthorizationTokenResponse());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new D4ScienceIAMClientException(e);
|
|
||||||
}
|
|
||||||
Set<String> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return new D4ScienceIAMClientAuthz(this, clientId, clientSecret, audience, permissions);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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<String> permissions)
|
||||||
|
throws D4ScienceIAMClientException {
|
||||||
|
|
||||||
|
return new D4ScienceIAMClientAuthz(this, audience, permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String> 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<String> 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<String> 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<String> permissions) throws D4ScienceIAMClientException {
|
||||||
|
;
|
||||||
|
try {
|
||||||
|
return iamClient.getKeycloakClient().queryUMAToken(iamClient.getTokenEndpointURL(), clientId, clientSecret,
|
||||||
|
audience, permissions);
|
||||||
|
} catch (KeycloakClientException e) {
|
||||||
|
throw new D4ScienceIAMClientException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<String> getAudienceResourceRoles() throws D4ScienceIAMClientException;
|
||||||
|
|
||||||
|
Set<String> getResourceRoles(String resource) throws D4ScienceIAMClientException;
|
||||||
|
|
||||||
|
Set<String> getRoles() throws D4ScienceIAMClientException;
|
||||||
|
|
||||||
|
Set<String> getRealmRoles() throws D4ScienceIAMClientException;
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,14 @@
|
||||||
package org.gcube.common.iam;
|
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.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.FixMethodOrder;
|
import org.junit.FixMethodOrder;
|
||||||
|
@ -13,8 +22,27 @@ public class TestD4ScienceIAMClient {
|
||||||
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(TestD4ScienceIAMClient.class);
|
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<String> REALM_ROLES = Collections.singleton("d4s-client");
|
||||||
|
protected static final Set<String> CONDUCTOR_SERVER_ROLES = Collections.singleton("dummy-test-role");
|
||||||
|
protected static final Set<String> 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<String> ALL_ROLES;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
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
|
@After
|
||||||
|
@ -22,8 +50,83 @@ public class TestD4ScienceIAMClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test1() throws Exception {
|
public void test1Authn() throws Exception {
|
||||||
logger.info("*** [1] ");
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue