commit 7ab87410abd1bf1abef0f3ad0c1569de09901b11 Author: Mauro Mugnaini Date: Thu May 21 15:47:28 2020 +0200 Intial GIT commit diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..5b0cf1c --- /dev/null +++ b/.classpath @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/.project b/.project new file mode 100644 index 0000000..ea4989c --- /dev/null +++ b/.project @@ -0,0 +1,36 @@ + + + oidc-library + + + + + + org.eclipse.wst.common.project.facet.core.builder + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jem.workbench.JavaEMFNature + org.eclipse.wst.common.modulecore.ModuleCoreNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + org.eclipse.wst.common.project.facet.core.nature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cac0df4 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..4528a36 --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles=gcube-developer +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.eclipse.wst.common.component b/.settings/org.eclipse.wst.common.component new file mode 100644 index 0000000..61fbcb3 --- /dev/null +++ b/.settings/org.eclipse.wst.common.component @@ -0,0 +1,6 @@ + + + + + + diff --git a/.settings/org.eclipse.wst.common.project.facet.core.xml b/.settings/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 0000000..fb95c45 --- /dev/null +++ b/.settings/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.settings/org.eclipse.wst.validation.prefs b/.settings/org.eclipse.wst.validation.prefs new file mode 100644 index 0000000..04cad8c --- /dev/null +++ b/.settings/org.eclipse.wst.validation.prefs @@ -0,0 +1,2 @@ +disabled=06target +eclipse.preferences.version=1 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ee2c27f --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + org.gcube.common + oidc-library + 0.2.0 + + maven-parent + org.gcube.tools + 1.1.0 + + + + 1.8 + 9.0.3 + 1.7.30 + 1.2.17 + + + + org.slf4j + slf4j-api + ${slf4j-version} + + + org.slf4j + slf4j-log4j12 + ${slf4j-version} + + + org.keycloak + keycloak-admin-client + ${keycloak-version} + + + org.bouncycastle + bcprov-jdk16 + 1.46 + + + org.apache.httpcomponents + httpclient + 4.5.8 + + + log4j + log4j + ${log4j-version} + + + org.keycloak + keycloak-authz-client + ${keycloak-version} + + + com.googlecode.json-simple + json-simple + 1.1 + + + + ${project.artifactId} + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/gcube/AbstractOIDCToSitesAndRolesMapper.java b/src/main/java/com/nubisware/oidc/gcube/AbstractOIDCToSitesAndRolesMapper.java new file mode 100644 index 0000000..0f4de67 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/AbstractOIDCToSitesAndRolesMapper.java @@ -0,0 +1,21 @@ +package com.nubisware.oidc.gcube; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractOIDCToSitesAndRolesMapper implements OIDCToSitesAndRolesMapper { + + protected static final Logger logger = LoggerFactory.getLogger(OIDCToSitesAndRolesMapper.class); + + protected Map> resourceName2AccessRoles; + + public AbstractOIDCToSitesAndRolesMapper(Map> resourceName2AccessRoles) { + super(); + this.resourceName2AccessRoles = resourceName2AccessRoles; + logger.info("Resource name to access roles: " + resourceName2AccessRoles); + } + +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/gcube/D4ScienceMappings.java b/src/main/java/com/nubisware/oidc/gcube/D4ScienceMappings.java new file mode 100644 index 0000000..0d56f9b --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/D4ScienceMappings.java @@ -0,0 +1,49 @@ +package com.nubisware.oidc.gcube; + +public class D4ScienceMappings { + + public enum Role { + + MEMBER("Member"), + + ACCOUNTING_MANAGER("Accounting-Manager"), + CATALOGUE_ADMIN("Catalogue-Admin"), + CATALOGUE_EDITOR("Catalogue-Editor"), + DATA_MANAGER("Data-Manager"), + DATAMINER_MANAGER("DataMiner-Manager"), + INFRASTRUCTURE_MANAGER("Infrastructure-Manager"), + VO_ADMIN("VO-Admin"), + VRE_DESIGNER("VRE-Designer"), + VRE_MANAGER("VRE-Manager"); + + private String str; + + Role(String str) { + this.str = str; + } + + public String asString() { + return str; + } + } + + public enum Scope { + + BELONGS("belongs"); + // TODO will be defined late + // LIST("list"), + // READ("read"), + // WRITE("write"), + // EXECUTE("execute"); + + private String str; + + Scope(String str) { + this.str = str; + } + + public String asString() { + return str; + } + } +} diff --git a/src/main/java/com/nubisware/oidc/gcube/OIDCToSitesAndRolesMapper.java b/src/main/java/com/nubisware/oidc/gcube/OIDCToSitesAndRolesMapper.java new file mode 100644 index 0000000..200768c --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/OIDCToSitesAndRolesMapper.java @@ -0,0 +1,7 @@ +package com.nubisware.oidc.gcube; + +public interface OIDCToSitesAndRolesMapper { + + Site map(String rootSite) throws SitesMapperExecption; + +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/gcube/Site.java b/src/main/java/com/nubisware/oidc/gcube/Site.java new file mode 100644 index 0000000..9051e02 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/Site.java @@ -0,0 +1,47 @@ +package com.nubisware.oidc.gcube; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class Site { + + private String name; + private List roles; + private Map children; + + public Site(String name, List roles) { + this.name = name; + this.roles = roles; + this.children = new TreeMap<>(); + } + + public String getName() { + return name; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public List getRoles() { + return roles; + } + + public Map getChildren() { + return children; + } + + public String dump() { + return dump(""); + } + + protected String dump(String indentString) { + StringBuilder sb = new StringBuilder(); + sb.append(indentString + "name: " + getName() + ", roles: " + getRoles() + "\n"); + for (String child : getChildren().keySet()) { + sb.append(getChildren().get(child).dump(indentString + " ")); + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/gcube/SitesMapperExecption.java b/src/main/java/com/nubisware/oidc/gcube/SitesMapperExecption.java new file mode 100644 index 0000000..3b8fbc1 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/SitesMapperExecption.java @@ -0,0 +1,27 @@ +package com.nubisware.oidc.gcube; + +public class SitesMapperExecption extends Exception { + + private static final long serialVersionUID = 956917439135370558L; + + public SitesMapperExecption() { + } + + public SitesMapperExecption(String message) { + super(message); + } + + public SitesMapperExecption(Throwable cause) { + super(cause); + } + + public SitesMapperExecption(String message, Throwable cause) { + super(message, cause); + } + + public SitesMapperExecption(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/com/nubisware/oidc/gcube/SlashSeparatedContextMapper.java b/src/main/java/com/nubisware/oidc/gcube/SlashSeparatedContextMapper.java new file mode 100644 index 0000000..a7b8baa --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/SlashSeparatedContextMapper.java @@ -0,0 +1,95 @@ +package com.nubisware.oidc.gcube; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SlashSeparatedContextMapper extends AbstractOIDCToSitesAndRolesMapper { + + private static final String SPLIT_REGEXP = "/"; + + private static final int MINIMUM_TOKENS = 2; + + private static final int EMPTY_TOKEN_INDEX = 0; + + private static final int ROOT_VO_TOKEN_INDEX = EMPTY_TOKEN_INDEX + 1; + + private static final int VO_TOKEN_INDEX = ROOT_VO_TOKEN_INDEX + 1; + + private static final int VRE_TOKEN_INDEX = VO_TOKEN_INDEX + 1; + + public SlashSeparatedContextMapper(Map> resourceName2AccessRoles) { + super(resourceName2AccessRoles); + } + + @Override + public Site map(String rootSite) throws SitesMapperExecption { + List sites = new ArrayList(resourceName2AccessRoles.keySet()); + Collections.sort(sites); + // Sorting sites, the containers site should come before contained one + Site gwSitesTree = null; + for (String site : sites) { + logger.info("Checking site: " + site); + List roles = resourceName2AccessRoles.get(site); + if (logger.isDebugEnabled()) { + logger.debug("Roles for site are: " + roles); + } + String[] siteTokens = site.split(SPLIT_REGEXP); + if (logger.isDebugEnabled()) { + logger.debug("Tokens are: " + siteTokens.length); + } + if (siteTokens.length < MINIMUM_TOKENS) { + throw new SitesMapperExecption( + "Found " + siteTokens.length + " tokens only. Minimum should be: " + MINIMUM_TOKENS); + } + String rootVO = siteTokens[ROOT_VO_TOKEN_INDEX]; + if (logger.isDebugEnabled()) { + logger.debug("Root VO is: " + rootVO); + } + if (!rootSite.equals(rootVO)) { + logger.info("Skipping evaluation of site tree not belonging to this Root VO: " + rootVO); + continue; + } else { + logger.info("Site belongs to this Root VO"); + } + if (siteTokens.length >= VO_TOKEN_INDEX + 1) { + if (gwSitesTree == null) { + logger.warn(rootVO + " Root VO's permissions are not set for user"); + gwSitesTree = new Site(rootVO, null); + } + String vo = siteTokens[VO_TOKEN_INDEX]; + if (logger.isDebugEnabled()) { + logger.debug("VO is: " + vo); + } + if (siteTokens.length == VRE_TOKEN_INDEX + 1) { + if (!gwSitesTree.getChildren().containsKey(vo)) { + logger.warn(vo + " VO's permissions are not set for user"); + gwSitesTree.getChildren().put(vo, new Site(vo, null)); + } + String vre = siteTokens[VRE_TOKEN_INDEX]; + if (logger.isDebugEnabled()) { + logger.debug("VRE is: " + vre); + } + logger.info("Adding leaf site: " + vre); + gwSitesTree.getChildren().get(vo).getChildren().put(vre, new Site(vre, roles)); + } else if (!gwSitesTree.getChildren().containsKey(vo)) { + logger.info("Creating site for VO: " + vo); + gwSitesTree.getChildren().put(vo, new Site(vo, roles)); + } + } else { + if (gwSitesTree == null) { + logger.info("Creating site for Root VO: " + rootVO); + gwSitesTree = new Site(rootVO, roles); + } else { + if (gwSitesTree.getRoles() == null) { + logger.info("Setting out of order roles for Root VO"); + } else { + logger.warn("Duplicated roles definition for Root VO"); + } + } + } + } + return gwSitesTree; + } +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/gcube/URLEncodedContextMapper.java b/src/main/java/com/nubisware/oidc/gcube/URLEncodedContextMapper.java new file mode 100644 index 0000000..857f257 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/gcube/URLEncodedContextMapper.java @@ -0,0 +1,28 @@ +package com.nubisware.oidc.gcube; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +public class URLEncodedContextMapper extends SlashSeparatedContextMapper { + + public URLEncodedContextMapper(Map> resourceName2AccessRoles) { + super(decodeContextNames(resourceName2AccessRoles)); + } + + private static Map> decodeContextNames(Map> resourceName2AccessRoles) { + Map> decodedResourceName2AccessRoles = new TreeMap<>(); + for (String encodedContext : resourceName2AccessRoles.keySet()) { + try { + decodedResourceName2AccessRoles.put(URLDecoder.decode(encodedContext, "UTF-8"), + resourceName2AccessRoles.get(encodedContext)); + } catch (UnsupportedEncodingException e) { + logger.error("Cannot URL decode context name: " + encodedContext, e); + } + } + return decodedResourceName2AccessRoles; + } + +} diff --git a/src/main/java/com/nubisware/oidc/keycloak/KeycloakAuthHelper.java b/src/main/java/com/nubisware/oidc/keycloak/KeycloakAuthHelper.java new file mode 100644 index 0000000..8eee8f6 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/keycloak/KeycloakAuthHelper.java @@ -0,0 +1,18 @@ +package com.nubisware.oidc.keycloak; + +import java.util.HashMap; +import java.util.Map; + +import org.keycloak.authorization.client.AuthzClient; +import org.keycloak.authorization.client.Configuration; + +public class KeycloakAuthHelper { + + public static void main(String[] args) { + Map credentials = new HashMap<>(); + Configuration configuration = new Configuration("https://nubis2.int.d4science.net/auth", "d4science", "portal", + credentials, null); + AuthzClient authzClient = AuthzClient.create(configuration); + } + +} diff --git a/src/main/java/com/nubisware/oidc/keycloak/KeycloakHelper.java b/src/main/java/com/nubisware/oidc/keycloak/KeycloakHelper.java new file mode 100644 index 0000000..94200e8 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/keycloak/KeycloakHelper.java @@ -0,0 +1,277 @@ +package com.nubisware.oidc.keycloak; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.core.Response; + +import org.jboss.resteasy.client.jaxrs.ResteasyClient; +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; +import org.keycloak.OAuth2Constants; +import org.keycloak.TokenVerifier; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.KeycloakBuilder; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.PolicyResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.ResourceResource; +import org.keycloak.admin.client.resource.RoleResource; +import org.keycloak.admin.client.resource.RolesResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.common.VerificationException; +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jwk.JWKParser; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.ResourcePermissionRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.representations.idm.authorization.RolePolicyRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.util.JWKSUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author Mauro Mugnaini (mauro.mugnaini@nubisware.com) + */ +public class KeycloakHelper { + + protected static Logger logger = LoggerFactory.getLogger(KeycloakHelper.class); + + private static KeycloakHelper instance; + + private String serverUrl; + private ResteasyClient resteasyClient; + + private KeycloakHelper(String serverUrl) throws KeyManagementException, NoSuchAlgorithmException { + this.serverUrl = serverUrl; + this.resteasyClient = (ResteasyClient) new ResteasyClientBuilder().build(); + } + + public static synchronized KeycloakHelper getInstance(String serverUrl) + throws KeyManagementException, NoSuchAlgorithmException { + + if (instance == null) { + instance = new KeycloakHelper(serverUrl); + } + return instance; + } + + public Keycloak newKeycloakAdmin(String username, String password) throws UnsupportedEncodingException { + return newKeycloak("master", username, password, "admin-cli"); + } + + public Keycloak newKeycloak(String realm, String username, String password, String clientId) + throws UnsupportedEncodingException { + + String encodedClientId = URLEncoder.encode(clientId, "UTF-8"); + return KeycloakBuilder.builder().serverUrl(serverUrl).realm(realm).username(username) + .password(password).clientId(encodedClientId).resteasyClient(resteasyClient).build(); + } + + public Keycloak newKeycloak(String realm, String clientId, String clientSecret) throws UnsupportedEncodingException { + String encodedClientId = URLEncoder.encode(clientId, "UTF-8"); + return KeycloakBuilder.builder().serverUrl(serverUrl).realm(realm).grantType(OAuth2Constants.CLIENT_CREDENTIALS) + .clientId(encodedClientId).clientSecret(clientSecret) + .resteasyClient(resteasyClient).build(); + } + + public PublicKey getRealmSigPublicKey(String realm) { + Response response = resteasyClient.target(serverUrl + "/realms/" + realm + "/protocol/openid-connect/certs") + .request().get(); + + JSONWebKeySet jsonWebKeySet = response.readEntity(JSONWebKeySet.class); + return JWKParser.create(JWKSUtils.getKeyForUse(jsonWebKeySet, JWK.Use.SIG)).toPublicKey(); + } + + // Realm is too complex to configure it in depth with this helper. Please do it with the Web UI + // public RealmResource addRealm(Keycloak keycloak, String realm, String displayName, String displayNameHtml, + // boolean enabled) throws KeycloakResourceCreationException { + // if (keycloak.realm(realm) != null) { + // throw new KeycloakResourceCreationException("Realm already present on server: " + realm, null); + // } + // RealmRepresentation newRealmRepresentation = new RealmRepresentation(); + // newRealmRepresentation.setRealm(realm); + // newRealmRepresentation.setId(realm); + // newRealmRepresentation.setDisplayName(displayName); + // newRealmRepresentation.setDisplayNameHtml(displayNameHtml); + // newRealmRepresentation.setEnabled(enabled); + // try { + // keycloak.realms().create(newRealmRepresentation); + // return keycloak.realms().realm(realm); + // } catch (ClientErrorException e) { + // throw new KeycloakResourceCreationException("While creating new realm: " + realm, null); + // } + // } + + public UserResource findUser(RealmResource realmResource, String username) { + List results = realmResource.users().search(username); + return results.size() > 0 ? realmResource.users().get(results.get(0).getId()) : null; + } + + public void mapRoleTo(UserResource userResource, String clientId, RoleResource roleResource) { + userResource.roles().clientLevel(clientId).add(Collections.singletonList(roleResource.toRepresentation())); + } + + public void mapRoleTo(UserResource userResource, ClientResource client, String roleName) { + RoleResource roleResource = client.roles().get(roleName); + userResource.roles().clientLevel(client.toRepresentation().getId()) + .add(Collections.singletonList(roleResource.toRepresentation())); + } + + public List getEffectiveClientRoles(RealmResource realm, UserResource userResource, + String clientId) { + ClientRepresentation cr = realm.clients().findByClientId(clientId).get(0); + return userResource.roles().clientLevel(cr.getId()).listEffective(); + } + + public ClientResource addClient(RealmResource realm, String clientId, String name, String description, + String rootUrl) throws KeycloakResourceCreationException, UnsupportedEncodingException { + + // Encoding clientId to be sure blocking chars are not used + String encodedClientId = URLEncoder.encode(clientId, "UTF-8"); + if (realm.clients().findByClientId(encodedClientId).size() > 0) { + throw new KeycloakResourceCreationException("Client with same clientId already exists: " + encodedClientId, null); + } + ClientRepresentation newClientRepresentation = new ClientRepresentation(); + newClientRepresentation.setClientId(encodedClientId); + newClientRepresentation.setName(name); + newClientRepresentation.setDescription(description); + if (rootUrl != null) { + newClientRepresentation.setRootUrl(rootUrl); + } + newClientRepresentation.setEnabled(true); + newClientRepresentation.setServiceAccountsEnabled(true); + newClientRepresentation.setStandardFlowEnabled(true); + newClientRepresentation.setAuthorizationServicesEnabled(true); + newClientRepresentation.setPublicClient(false); + newClientRepresentation.setProtocol("openid-connect"); + newClientRepresentation.setAuthorizationSettings(new ResourceServerRepresentation()); + try (Response response = realm.clients().create(newClientRepresentation)) { + if (!response.getStatusInfo().equals(Response.Status.CREATED)) { + throw new KeycloakResourceCreationException("While creating new client: " + clientId, response); + } + } + return realm.clients().get(realm.clients().findByClientId(encodedClientId).get(0).getId()); + } + + public ClientResource findClient(RealmResource realm, String clientId) throws UnsupportedEncodingException { + String encodedClientId = URLEncoder.encode(clientId, "UTF-8"); + List clientsFound = realm.clients().findByClientId(encodedClientId); + if (clientsFound != null && clientsFound.size() == 1) { + return realm.clients().get(clientsFound.get(0).getId()); + } else { + return null; + } + } + + public void removeClient(RealmResource realm, String clientId) throws UnsupportedEncodingException { + String encodedClientId = URLEncoder.encode(clientId, "UTF-8"); + List clientsFound = realm.clients().findByClientId(encodedClientId); + if (clientsFound != null && !clientsFound.isEmpty()) { + for (ClientRepresentation client : clientsFound) { + realm.clients().get(client.getId()).remove(); + } + } + } + + public RoleResource addRole(ClientResource clientResource, boolean clientRole, String id, String name, + String description, String containerId) { + + RolesResource rolesResource = clientResource.roles(); + RoleRepresentation newRoleRepresentation = new RoleRepresentation(); + newRoleRepresentation.setClientRole(clientRole); + newRoleRepresentation.setId(id); + newRoleRepresentation.setName(name); + newRoleRepresentation.setDescription(description); + if (containerId != null) { + newRoleRepresentation.setContainerId(containerId); + } + rolesResource.create(newRoleRepresentation); + return rolesResource.get(name); + } + + public ResourceResource addResource(ClientResource clientResource, String name, String type, String displayName, + boolean ownerManagedAccess, Set scopes, Set uris) + throws KeycloakResourceCreationException { + + ResourceRepresentation newResourceRepresentation = new ResourceRepresentation(); + newResourceRepresentation.setName(name); + newResourceRepresentation.setType(type); + newResourceRepresentation.setDisplayName(displayName); + if (scopes != null && !scopes.isEmpty()) { + newResourceRepresentation.setScopes(scopes); + } + if (uris != null && !uris.isEmpty()) { + newResourceRepresentation.setUris(uris); + } + try (Response response = clientResource.authorization().resources().create(newResourceRepresentation)) { + if (!response.getStatusInfo().equals(Response.Status.CREATED)) { + throw new KeycloakResourceCreationException("While creating new client resource: " + name, response); + } + return clientResource.authorization().resources() + .resource(clientResource.authorization().resources().findByName(name).get(0).getId()); + } + } + + public PolicyResource addRoleResourcePolicy(ClientResource clientResource, Set resources, + Set scopes, + String name, Logic logic, Map> clientRoles) throws KeycloakResourceCreationException { + + RolePolicyRepresentation newRolePolicyRepresentation = new RolePolicyRepresentation(); + newRolePolicyRepresentation.setName(name); + newRolePolicyRepresentation.setLogic(logic); + newRolePolicyRepresentation.setResources(resources); + if (scopes != null && !scopes.isEmpty()) { + newRolePolicyRepresentation.setScopes(scopes); + } + clientRoles.keySet().stream().forEach( + k -> clientRoles.get(k).stream().forEach(v -> newRolePolicyRepresentation.addClientRole(k, v, true))); + + try (Response response = clientResource.authorization().policies().role().create(newRolePolicyRepresentation)) { + if (!response.getStatusInfo().equals(Response.Status.CREATED)) { + throw new KeycloakResourceCreationException("While creating client's role resource policy", response); + } + return clientResource.authorization().policies() + .policy(clientResource.authorization().policies().role().findByName(name).getId()); + } + } + + public ResourcePermissionRepresentation addResourcePermission(ClientResource clientResource, + Set resources, String name, DecisionStrategy decisionStrategy, + Set policies) throws KeycloakResourceCreationException { + + ResourcePermissionRepresentation newRPR = new ResourcePermissionRepresentation(); + newRPR.setName(name); + newRPR.setResources(resources); + newRPR.setDecisionStrategy(decisionStrategy); + newRPR.setPolicies(policies); + try (Response response = clientResource.authorization().permissions().resource().create(newRPR);) { + if (!response.getStatusInfo().equals(Response.Status.CREATED)) { + throw new KeycloakResourceCreationException("While creating client's resource permission", response); + } + return clientResource.authorization().permissions().resource().findByName(name); + } + } + + public T verifyAndGetToken(Class tokenClass, String tokenString, PublicKey publicKey) + throws VerificationException { + + return TokenVerifier.create(tokenString, tokenClass).publicKey(publicKey).verify().getToken(); + } + +} diff --git a/src/main/java/com/nubisware/oidc/keycloak/KeycloakResourceCreationException.java b/src/main/java/com/nubisware/oidc/keycloak/KeycloakResourceCreationException.java new file mode 100644 index 0000000..3eb3d11 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/keycloak/KeycloakResourceCreationException.java @@ -0,0 +1,56 @@ +package com.nubisware.oidc.keycloak; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.StatusType; + +public class KeycloakResourceCreationException extends Exception { + + private static final long serialVersionUID = 4073975434440358303L; + + private StatusType responseStatus; + private String responseContent; + + public KeycloakResourceCreationException() { + } + + public KeycloakResourceCreationException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + + super(message, cause, enableSuppression, writableStackTrace); + } + + public KeycloakResourceCreationException(String message, Throwable cause, Response response) { + super(message, cause); + if (response != null) { + this.responseStatus = response.getStatusInfo(); + this.responseContent = response.readEntity(String.class); + } else { + this.responseStatus = null; + this.responseContent = ""; + } + } + + public KeycloakResourceCreationException(String message, Response response) { + this(message, null, response); + } + + public StatusType getResponseStatus() { + return responseStatus; + } + + public int getResponseStatusCode() { + return responseStatus.getStatusCode(); + } + + public String getResponseContent() { + return responseContent; + } + + @Override + public String getMessage() { + return super.getMessage() + (getResponseStatus() != null + ? "(REST details: [" + getResponseStatusCode() + "] " + getResponseContent() + ")" + : ""); + } + +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/keycloak/gcube/ClientsCreatorFromExport.java b/src/main/java/com/nubisware/oidc/keycloak/gcube/ClientsCreatorFromExport.java new file mode 100644 index 0000000..451de15 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/keycloak/gcube/ClientsCreatorFromExport.java @@ -0,0 +1,315 @@ +package com.nubisware.oidc.keycloak.gcube; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.xml.parsers.ParserConfigurationException; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.PolicyResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.ResourceResource; +import org.keycloak.admin.client.resource.RoleResource; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.xml.sax.SAXException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.nubisware.oidc.gcube.D4ScienceMappings.Role; +import com.nubisware.oidc.gcube.D4ScienceMappings.Scope; +import com.nubisware.oidc.keycloak.KeycloakHelper; +import com.nubisware.oidc.keycloak.KeycloakResourceCreationException; + +public class ClientsCreatorFromExport { + + private KeycloakHelper kh; + private Keycloak keycloak; + private ExportParser exportParser; + private String realm; + // public Map role2Scope; + + public ClientsCreatorFromExport(String keycloakURL, String adminUsername, String adminPassword, String realm, + FileInputStream exportFileFIS) throws SAXException, IOException, ParserConfigurationException, + KeyManagementException, NoSuchAlgorithmException { + + this.exportParser = new ExportParser(exportFileFIS); + this.kh = KeycloakHelper.getInstance(keycloakURL); + this.keycloak = kh.newKeycloakAdmin(adminUsername, adminPassword); + this.realm = realm; + // role2Scope = new TreeMap<>(); + // role2Scope.put(Role.ACCOUNTING_MANAGER, Scope.BELONGS); + } + + public void createClients() throws KeycloakResourceCreationException, UnsupportedEncodingException { + RealmResource realmResource = keycloak.realm(realm); + for (String contextClient : exportParser.getAllContexts()) { + System.out.println("adding client: " + contextClient); + ClientResource client = kh.addClient(realmResource, contextClient, + contextClient, + contextClient + "'s context", ""); + Map roleMap = new HashMap<>(); + for (Role roleToAdd : Role.values()) { + System.out.println("\tcreating role: " + roleToAdd); + RoleResource role = kh.addRole(client, true, roleToAdd.asString(), roleToAdd.asString(), + roleToAdd.asString() + " role", null); + + roleMap.put(roleToAdd, role); + } + Map scopeMap = new HashMap<>(); + for (Scope scopeToAdd : Scope.values()) { + ScopeRepresentation scope = new ScopeRepresentation(scopeToAdd.asString()); + scopeMap.put(scopeToAdd, scope); + } + Set resourceScopes = new HashSet<>(scopeMap.values()); + + String[] resources = getClientResources(contextClient); + if (resources.length > 0) { + for (String resourceToAdd : resources) { + System.out.println("\t\tadding resource: " + resourceToAdd); + // TODO Set also the resource type + String type = "urn:" + client.toRepresentation().getClientId() + ":resources:service"; + ResourceResource resource = kh.addResource(client, resourceToAdd, type, resourceToAdd, false, + resourceScopes, + null); + + configureClientResource(client, roleMap, resource.toRepresentation()); + } + } else { + configureClientResource(client, roleMap, client.authorization().resources().resources().get(0)); + } + } + } + + private String[] getClientResources(String contextClient) { + // TODO Implement when/if needed + return new String[] {}; + } + + private Role[] getInvolvedRoles(String resourceName) { + // TODO Implement when/if needed + return Role.values(); + } + + private Set getRoleResourceScopes(String resourceName, Role role) { + // TODO Implement when/if needed + return Collections.emptySet(); + } + + protected void configureClientResource(ClientResource client, Map roleMap, + ResourceRepresentation resource) throws KeycloakResourceCreationException { + + String resourceName = resource.getName(); + Set policies = new HashSet<>(); + for (Role role : getInvolvedRoles(resourceName)) { + Map> policyClientRoles = new HashMap<>(); + policyClientRoles.put(client.toRepresentation().getClientId(), + Collections.singleton(roleMap.get(role).toRepresentation().getName())); + + System.out.println("\t\t\tadding role resource policy for role: " + role); + Set roleResourceScopes = getRoleResourceScopes(resourceName, role); + PolicyResource newPR = kh.addRoleResourcePolicy(client, Collections.singleton(resourceName), + roleResourceScopes, role.asString() + "_policy", Logic.POSITIVE, + policyClientRoles); + + policies.add(newPR.toRepresentation().getName()); + } + System.out.println( + "\t\t\tdeleting default js policy that is no more needed"); + // This will also the delete default strategy + + client.authorization().policies().policy(client.authorization().policies().findByName("Default Policy").getId()) + .remove(); + + System.out.println( + "\t\t\tcreating new permission for role policies with affirmative strategy"); + + kh.addResourcePermission(client, Collections.singleton(resourceName), "Default Permission", + DecisionStrategy.AFFIRMATIVE, policies); + + System.out.println("\t\t\tupdating the default permission on server"); + } + + public void deleteClients() { + RealmResource realmResource = keycloak.realm(realm); + for (String contextClient : exportParser.getAllContexts()) { + System.out.println("- deleting: " + contextClient); + try { + kh.removeClient(realmResource, contextClient); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + } + + public void mapUsersWithRolesToClients() throws UnsupportedEncodingException { + RealmResource realmResource = keycloak.realm(realm); + Map>> usersToContextsAndRoles = exportParser.getAllUserContextsAndRoles(); + for (String userName : usersToContextsAndRoles.keySet()) { + System.out.println("- user: " + userName); + UserResource userResource = kh.findUser(realmResource, userName); + if (userResource != null) { + Map> userContextsAndRoles = usersToContextsAndRoles.get(userName); + for (String userContext : userContextsAndRoles.keySet()) { + String clientId = userContext; + System.out.println("\tcontext: " + userContext); + ClientResource clientResource = kh.findClient(realmResource, clientId); + if (clientResource != null) { + System.out.println("\t\tmapping default role: " + Role.MEMBER.asString()); + kh.mapRoleTo(userResource, clientResource, Role.MEMBER.asString()); + for (String role : userContextsAndRoles.get(userContext)) { + System.out.println("\t\tmapping role: " + role); + kh.mapRoleTo(userResource, clientResource, role); + } + } else { + System.err.println("Client not found on keycloak: " + userContext); + } + } + } else { + System.err.println("User not found on keycloak: " + userName); + } + System.out.println(); + } + } + + public ExportParser getExportParser() { + return exportParser; + } + + public class ExportParser { + + private ArrayNode rootNode; + + public ExportParser(FileInputStream exportFileFIS) + throws SAXException, IOException, ParserConfigurationException { + ObjectMapper objectMapper = new ObjectMapper(); + rootNode = (ArrayNode) objectMapper.readTree(exportFileFIS); + } + + public Set getAllUsers() { + Set users = new TreeSet<>(); + Iterator arrayIterator = rootNode.elements(); + while (arrayIterator.hasNext()) { + JsonNode entry = arrayIterator.next(); + users.add(entry.get("username").asText()); + } + return users; + } + + public Set getAllContexts() { + Set distinctContexts = new TreeSet<>(); + Iterator arrayIterator = rootNode.elements(); + while (arrayIterator.hasNext()) { + JsonNode entry = arrayIterator.next(); + ObjectNode contextsNode = (ObjectNode) entry.get("contexts"); + contextsNode.fieldNames().forEachRemaining(f -> distinctContexts.add(f)); + } + return distinctContexts; + } + + public Map> getContextsAndRoles(String user) { + Iterator arrayIterator = rootNode.elements(); + while (arrayIterator.hasNext()) { + JsonNode entry = arrayIterator.next(); + String username = entry.get("username").asText(); + if (!user.equals(username)) { + continue; + } + Map> contextAndRoles = new TreeMap<>(); + ObjectNode contextsNode = (ObjectNode) entry.get("contexts"); + Iterator contextIterator = contextsNode.fieldNames(); + while (contextIterator.hasNext()) { + String context = (String) contextIterator.next(); + Set roles = new TreeSet<>(); + ArrayNode rolesNodes = (ArrayNode) contextsNode.get(context); + rolesNodes.elements().forEachRemaining(r -> roles.add(r.asText())); + contextAndRoles.put(context, roles); + } + return contextAndRoles; + } + return Collections.emptyMap(); + } + + public Map>> getAllUserContextsAndRoles() { + Map>> usersToContextAndRoles = new TreeMap<>(); + Iterator arrayIterator = rootNode.elements(); + while (arrayIterator.hasNext()) { + JsonNode entry = arrayIterator.next(); + String username = entry.get("username").asText(); + Map> contextAndRoles = new TreeMap<>(); + ObjectNode contextsNode = (ObjectNode) entry.get("contexts"); + Iterator contextIterator = contextsNode.fieldNames(); + while (contextIterator.hasNext()) { + String context = (String) contextIterator.next(); + Set roles = new TreeSet<>(); + ArrayNode rolesNodes = (ArrayNode) contextsNode.get(context); + rolesNodes.elements().forEachRemaining(r -> roles.add(r.asText())); + contextAndRoles.put(context, roles); + } + usersToContextAndRoles.put(username, contextAndRoles); + } + return usersToContextAndRoles; + } + } + + public static void main(String[] args) throws Exception { + String serverURL = null; + String username = null; + String password = null; + String realm = null; + FileInputStream exportFileFIS = null; + if (args.length < 5) { + System.err.println("Missing params.\n\nUsage: " + ClientsCreatorFromExport.class.getName() + + " [serverURL] [username] [password] [realm] [export_file]"); + + return; + } else { + serverURL = args[0]; + username = args[1]; + password = args[2]; + realm = args[3]; + exportFileFIS = new FileInputStream(args[4]); + } + ClientsCreatorFromExport creator = new ClientsCreatorFromExport(serverURL, username, password, realm, + exportFileFIS); + + Date start = new Date(); + System.out.println("Start at " + start); + System.out.println("Deleting clients..."); + creator.deleteClients(); + System.out.println("\n\n * * * Creating clients * * *"); + creator.createClients(); + System.out.println("\n\n * * * Mapping users to client's roles * * *"); + creator.mapUsersWithRolesToClients(); + Date end = new Date(); + System.out.println("Elapsed seconds: " + new Long(end.getTime() - start.getTime()).floatValue() / 1000); + System.out.println("\nClients: " + creator.getExportParser().getAllContexts().size()); + System.out.println("Users: " + creator.getExportParser().getAllUsers().size()); + Map>> ucar = creator.getExportParser().getAllUserContextsAndRoles(); + float rolesPerUserMean = 0; + for (String user : ucar.keySet()) { + for (String context : ucar.get(user).keySet()) { + rolesPerUserMean += ucar.get(user).get(context).size() + 1; + } + } + System.out.println("Roles per user mean: " + rolesPerUserMean / creator.getExportParser().getAllUsers().size()); + } +} diff --git a/src/main/java/com/nubisware/oidc/rest/JWTToken.java b/src/main/java/com/nubisware/oidc/rest/JWTToken.java new file mode 100644 index 0000000..e5408a2 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/rest/JWTToken.java @@ -0,0 +1,209 @@ +package com.nubisware.oidc.rest; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nubisware.oidc.keycloak.KeycloakHelper; + +public class JWTToken implements Serializable { + + protected static Logger logger = LoggerFactory.getLogger(KeycloakHelper.class); + + private static final long serialVersionUID = -7063122428186284827L; + + private static String SHARE_PREFIX = "USER_"; + + public static String OIDC_TOKEN_ATTRIBUTE = SHARE_PREFIX + "OIDC_JWT"; + public static String RPT_TOKEN_ATTRIBUTE = SHARE_PREFIX + "UMA_RPT_JWT"; + + public static final String ACCOUNT_RESOURCE = "account"; + + private String raw; + private JSONObject token; + private JSONObject identity; + + public static JWTToken fromString(String tokenString) { + if (tokenString == null) { + return null; + } + try { + return new JWTToken(tokenString); + } catch (ParseException e) { + + return null; + } + } + + private JWTToken(String raw) throws ParseException { + this.raw = raw; + this.parse(); + } + + private void parse() throws ParseException { + token = (JSONObject) new JSONParser().parse(this.raw); + String[] parts = getAccessTokenString().split("\\."); + identity = (JSONObject) new JSONParser().parse(new String(Base64.getDecoder().decode(parts[1]))); + } + + public String getRaw() { + return raw; + } + + public String getAccessTokenString() { + return (String) token.get("access_token"); + } + + public String getRefreshTokenString() { + return (String) token.get("refresh_token"); + } + + public String getAsBearer() { + return "Bearer " + getAccessTokenString(); + } + + public JSONObject getIdentity() { + return identity; + } + + public String getExp() { + return (String) getIdentity().get("exp"); + } + + public Date getExpAsDate() { + return new Date(Long.getLong(getExp()) * 1000); + } + + public Calendar getExpAsCalendar() { + Calendar cal = Calendar.getInstance(); + cal.setTime(getExpAsDate()); + return cal; + } + + public boolean isExpired() { + return new Date().after(getExpAsDate()); + } + + public String getSub() { + return (String) getIdentity().get("sub"); + } + + public String getEmail() { + return (String) getIdentity().get("email"); + } + + public String getFamily() { + return (String) getIdentity().get("family_name"); + } + + public String getGiven() { + return (String) getIdentity().get("given_name"); + } + + public String getUserName() { + return (String) getIdentity().get("preferred_username"); + } + + public String getDisplayName() { + return (String) getIdentity().get("name"); + } + + protected JSONObject getResourceAccess() { + return (JSONObject) getIdentity().get("resource_access"); + } + + @SuppressWarnings("unchecked") + protected Iterator getResourceAccessKeys() { + return getResourceAccess().keySet().iterator(); + } + + public List getResourceAccessRoles(String resource) { + JSONArray rolesJsonArray = (JSONArray) ((JSONObject) getResourceAccess().get(resource)) + .get("roles"); + List roles = new ArrayList<>(rolesJsonArray.size()); + for (int i = 0; i < rolesJsonArray.size(); i++) { + roles.add((String) rolesJsonArray.get(i)); + } + return roles; + } + + public Map> getResourceNameToAccessRolesMap(List resourcesToSkip) { + Map> map = new HashMap<>(); + Iterator resourcesIterator = getResourceAccessKeys(); + while (resourcesIterator.hasNext()) { + String resource = resourcesIterator.next(); + if (resourcesToSkip.contains(resource)) { + continue; + } + map.put(resource, getResourceAccessRoles(resource)); + } + return map; + } + + /* + "authorization": { + "permissions": [ + { + "rsid": "e9afce09-baeb-4569-8e9a-67342ce39cf5", + "rsname": "a", + "resource_scopes" : [] + } + ] + } + */ + + protected JSONArray getAuthorizationPermissions() { + JSONObject authorization = (JSONObject) getIdentity().get("authorization"); + return (JSONArray) authorization.get("permissions"); + } + + public List getAuthorizationPermissionRSNames() { + List permissionsRSName = new ArrayList<>(); + JSONArray permissions = getAuthorizationPermissions(); + for (int i = 0; i < permissions.size(); i++) { + JSONObject permissionsEntry = (JSONObject) permissions.get(i); + permissionsRSName.add((String) permissionsEntry.get("rsname")); + } + return permissionsRSName; + } + + + public List getAuthorizationPermissionRSNameResourceScopes(String rsname) { + List scopes = new ArrayList<>(); + JSONArray permissions = getAuthorizationPermissions(); + for (int i = 0; i < permissions.size(); i++) { + JSONObject permissionsEntry = (JSONObject) permissions.get(i); + if (rsname.equals(permissionsEntry.get("rsname"))) { + JSONArray scopesJsonArray = (JSONArray) permissionsEntry.get("resource_scopes"); + if (scopesJsonArray != null) { + for (int j = 0; j < scopesJsonArray.size(); j++) { + scopes.add((String) scopesJsonArray.get(j)); + } + } + } + } + return scopes; + } + + public Map> getAuthorizationPermissionRSNameToResourceScopesMap() { + Map> map = new HashMap<>(); + for (String aprn : getAuthorizationPermissionRSNames() ) { + map.put(aprn, getAuthorizationPermissionRSNameResourceScopes(aprn)); + } + return map; + } + +} diff --git a/src/main/java/com/nubisware/oidc/rest/OpenIdConnectConfiguration.java b/src/main/java/com/nubisware/oidc/rest/OpenIdConnectConfiguration.java new file mode 100644 index 0000000..b7cfd62 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/rest/OpenIdConnectConfiguration.java @@ -0,0 +1,17 @@ +package com.nubisware.oidc.rest; + +public interface OpenIdConnectConfiguration { + + String getScope(); + + String getClientId(); + + String getIssuerUrl(); + + String getLogoutUrl(); + + String getTokenUrl(); + + String getAuthorizationUrl(); + +} \ No newline at end of file diff --git a/src/main/java/com/nubisware/oidc/rest/OpenIdConnectRESTHelper.java b/src/main/java/com/nubisware/oidc/rest/OpenIdConnectRESTHelper.java new file mode 100644 index 0000000..37e6d33 --- /dev/null +++ b/src/main/java/com/nubisware/oidc/rest/OpenIdConnectRESTHelper.java @@ -0,0 +1,151 @@ +package com.nubisware.oidc.rest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpenIdConnectRESTHelper { + + protected static final Logger logger = LoggerFactory.getLogger(OpenIdConnectRESTHelper.class); + + public static String buildLoginUrl(String kcLoginUrl, String clientId, String state, String redirectUri) + throws UnsupportedEncodingException { + + Map> params = new HashMap>(); + params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8"))); + params.put("response_type", Arrays.asList("code")); + params.put("scope", Arrays.asList("openid")); + params.put("state", Arrays.asList(URLEncoder.encode(state, "UTF-8"))); + params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectUri, "UTF-8"))); + params.put("login", Arrays.asList("true")); + String q = mapToQueryString(params); + return kcLoginUrl + "?" + q; + } + + public static String mapToQueryString(Map> params) { + String q = params.entrySet().stream().flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v)) + .reduce((p1, p2) -> p1 + "&" + p2).orElse(""); + + if (logger.isDebugEnabled()) { + logger.debug("Query string is: " + q); + } + System.out.println("Query string is: " + q); + return q; + } + + public static JWTToken queryToken(String clientId, String tokenUrl, String code, String scope, + String redirectUri) throws Exception { + + Map> params = new HashMap<>(); + params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8"))); + params.put("grant_type", Arrays.asList("authorization_code")); + params.put("scope", Arrays.asList(URLEncoder.encode(scope, "UTF-8"))); + params.put("code", Arrays.asList(URLEncoder.encode(code, "UTF-8"))); + params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectUri, "UTF-8"))); + return performQueryTokenWithPOST(tokenUrl, null, params); + } + + public static JWTToken performQueryTokenWithPOST(String tokenUrl, String authorization, + Map> params) + throws Exception { + + if (logger.isDebugEnabled()) { + logger.debug("Querying access token to Keycloak with URL: " + tokenUrl); + } + URL myurl = new URL(tokenUrl); + HttpURLConnection con = (HttpURLConnection) myurl.openConnection(); + con.setDoOutput(true); + con.setDoInput(true); + con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + con.setRequestProperty("Accept", "application/json"); + con.setRequestProperty("Method", "POST"); + if (authorization != null) { + con.setRequestProperty("Authorization", authorization); + } + System.err.println("Authorization: " + authorization); + OutputStream os = con.getOutputStream(); + os.write(mapToQueryString(params).getBytes("UTF-8")); + os.close(); + + StringBuilder sb = new StringBuilder(); + int HttpResult = con.getResponseCode(); + if (logger.isDebugEnabled()) { + logger.debug(String.valueOf(con.getResponseCode())); + logger.debug(con.getResponseMessage()); + } + if (HttpResult != HttpURLConnection.HTTP_OK) { + BufferedReader br = new BufferedReader(new InputStreamReader(con.getErrorStream(), "UTF-8")); + String line = null; + while ((line = br.readLine()) != null) { + sb.append(line + "\n"); + } + br.close(); + throw new Exception("Unable to get token " + sb); + } else { + BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8")); + String line = null; + while ((line = br.readLine()) != null) { + sb.append(line + "\n"); + } + br.close(); + } + return JWTToken.fromString(sb.toString()); + } + + public static JWTToken queryUMAToken(String tokenUrl, String authorizationToken, String audience, + List permissions) throws Exception { + + Map> params = new HashMap<>(); + params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket")); + params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8"))); + if (permissions != null && !permissions.isEmpty()) { + params.put( + "permission", permissions.stream().map(s -> { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return ""; + } + }).collect(Collectors.toList())); + } + return performQueryTokenWithPOST(tokenUrl, authorizationToken, params); + } + + public static void logout(JWTToken token, String kcLogoutUrl, String portalClientId) throws IOException { + Map> params = new HashMap<>(); + params.put("client_id", Arrays.asList(URLEncoder.encode(portalClientId, "UTF-8"))); + params.put("refresh_token", Arrays.asList(token.getRefreshTokenString())); + logger.info("Performing logut from Keycloak with URL: " + kcLogoutUrl); + URL myurl = new URL(kcLogoutUrl); + HttpURLConnection con = (HttpURLConnection) myurl.openConnection(); + con.setDoOutput(true); + con.setDoInput(true); + con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + con.setRequestProperty("Accept", "application/json"); + con.setRequestProperty("Method", "POST"); + con.setRequestProperty("Authorization", token.getAsBearer()); + OutputStream os = con.getOutputStream(); + os.write(mapToQueryString(params).getBytes("UTF-8")); + os.close(); + int responseCode = con.getResponseCode(); + if (responseCode == 204) { + logger.info("Logout performed correctly"); + } else { + logger.error("Cannot perfrom logout: [" + responseCode + "] " + con.getResponseMessage()); + } + + } +} \ No newline at end of file diff --git a/src/test/java/com/nubisware/oidc/keycloak/UglyKeycloakHelperTest.java b/src/test/java/com/nubisware/oidc/keycloak/UglyKeycloakHelperTest.java new file mode 100644 index 0000000..0c953af --- /dev/null +++ b/src/test/java/com/nubisware/oidc/keycloak/UglyKeycloakHelperTest.java @@ -0,0 +1,105 @@ +package com.nubisware.oidc.keycloak; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.PolicyResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.ResourceResource; +import org.keycloak.admin.client.resource.RoleResource; +import org.keycloak.common.VerificationException; +import org.keycloak.representations.idm.authorization.DecisionStrategy; +import org.keycloak.representations.idm.authorization.Logic; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; + +import com.nubisware.oidc.keycloak.KeycloakHelper; +import com.nubisware.oidc.keycloak.KeycloakResourceCreationException; + +public class UglyKeycloakHelperTest { + + static String clientPrefix = "client"; + static String realm = "d4science"; + + public UglyKeycloakHelperTest() { + } + + public static void maino(String[] args) throws KeyManagementException, NoSuchAlgorithmException, + VerificationException, MalformedURLException, IOException, KeycloakResourceCreationException { + + KeycloakHelper kh = KeycloakHelper.getInstance("https://nubis2.int.d4science.net/auth"); + // KeycloakHelper kh = getInstance("http://localhost:8080/auth"); + Keycloak keycloak = kh.newKeycloakAdmin("admin", "4dm1n"); + // Keycloak keycloak = keycloakHelper.newKeycloak(realm, "/gcube/devsec/devVRE", + // "12184fe2-f174-4c87-afac-b2d2bfaae4c0"); + + // RealmResource realmResource = kh.addRealm(keycloak, realm, realm, "

" + realm + "

Welcome

", true); + RealmResource realmResource = keycloak.realm(realm); + + for (int clientNum = 0; clientNum < 10; clientNum++) { + String clientName = clientPrefix + clientNum; + ClientResource client = kh.addClient(realmResource, clientName, clientName, clientName, null); + + RoleResource dataManager = kh.addRole(client, true, "Data-Manager", "Data-Manager", "Data-Manager", null); + RoleResource dataMinerManager = kh.addRole(client, true, "DataMiner-Manager", "DataMiner-Manager", + "DataMiner-Manager", null); + ScopeRepresentation read = new ScopeRepresentation("read"); + + ScopeRepresentation list = new ScopeRepresentation("list"); + ScopeRepresentation write = new ScopeRepresentation("write"); + ScopeRepresentation execute = new ScopeRepresentation("execute"); + Set resourceScopes = new HashSet<>(Arrays.asList(read, write, list, execute)); + ResourceResource resource1 = kh.addResource(client, "resource1", null, "resource1", false, resourceScopes, + null); + ResourceResource resource2 = kh.addResource(client, "resource2", null, "resource2", false, resourceScopes, + null); + Set resources = new HashSet<>( + Arrays.asList(resource1.toRepresentation().getName(), resource2.toRepresentation().getName())); + + Map> policyClientRoles = new HashMap<>(); + policyClientRoles.put(clientName, Collections.singleton(dataManager.toRepresentation().getName())); + PolicyResource dataManagerCanRead = kh.addRoleResourcePolicy(client, resources, + Collections.singleton(list.getName()), "canRead", Logic.POSITIVE, + policyClientRoles); + + kh.addResourcePermission(client, resources, "read", DecisionStrategy.UNANIMOUS, + Collections.singleton(dataManagerCanRead.toRepresentation().getName())); + + } + //// keycloakHelper.addClient(realmResource, "Gino", "gino", "Gino client", "http://gino.stilla.it"); + // UserResource user = keycloakHelper.findUser(realmResource, "mauro"); + // Map impersonation = user.impersonate(); + // System.out.println(impersonation); + // Keycloak keycloak = keycloakHelper.newKeycloak(realm, "lino", "lino", "portal"); + // TokenManager tokenManager = keycloak.tokenManager(); + // AccessToken accessToken = keycloakHelper.verifyAndGetToken(AccessToken.class, tokenManager.getAccessTokenString(), keycloakHelper.getRealmSigPublicKey(realm)); + // System.out.println(new ObjectMapper().writeValueAsString(accessToken)); + // for (String resourceAccess : accessToken.getResourceAccess().keySet()) { + // if ("account".equals(resourceAccess)) { + // continue; + // } + // Access access = accessToken.getResourceAccess(resourceAccess); + // System.out.println(resourceAccess + " -> " + access.getRoles()); + // } + // keycloak.realm(realm). + } + + public static void main(String[] args) throws Exception { + KeycloakHelper kh = KeycloakHelper.getInstance("https://nubis2.int.d4science.net/auth"); + Keycloak keycloak = kh.newKeycloakAdmin("admin", "4dm1n"); + RealmResource realmResource = keycloak.realm(realm); + for (int clientNum = 0; clientNum < 10; clientNum++) { + String clientName = clientPrefix + clientNum; + kh.removeClient(realmResource, clientName); + } + } +} diff --git a/src/test/resources/gcube-export.json b/src/test/resources/gcube-export.json new file mode 100644 index 0000000..a140b12 --- /dev/null +++ b/src/test/resources/gcube-export.json @@ -0,0 +1,618 @@ +[ + { + "username": "nikolaos.drakopoulos", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "roberto.cirillo", + "contexts": { + "/gcube": [ + "VO-Admin" + ], + "/gcube/devsec/SAI5": [ + "VRE-Designer" + ], + "/gcube/devsec/SAI4": [ + "VRE-Designer" + ], + "/gcube/devsec/SAIVRE3": [ + "VRE-Designer" + ], + "/gcube/devsec/SAI3": [ + "VRE-Designer" + ], + "/gcube/devsec/SAI2": [ + "VRE-Designer" + ], + "/gcube/devsec/devVRE": [ + "VRE-Manager" + ], + "/gcube/devsec/SAIVRE2": [ + "VRE-Designer" + ], + "/gcube/devsec/SAITest": [ + "VRE-Designer" + ], + "/gcube/devNext": [ + "VO-Admin" + ], + "/gcube/devsec/SAITest2": [ + "VRE-Designer" + ], + "/gcube/devsec": [ + "VO-Admin" + ], + "/gcube/devNext/NextNext": [] + } + }, + { + "username": "alessia.bardi", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "aureliano.gentile", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "ngalante", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "nicolas.bailly", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "taha.imzilen", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "lucio.lelii", + "contexts": { + "/gcube": [ + "Infrastructure-Manager" + ], + "/gcube/devsec/Test4StoHub": [ + "VRE-Manager" + ], + "/gcube/devsec/devVRE": [ + "VRE-Manager" + ], + "/gcube/devsec": [ + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devNext/NextNext": [ + "DataMiner-Manager" + ], + "/gcube/devNext": [ + "VRE-Designer", + "VRE-Manager", + "VO-Admin" + ] + } + }, + { + "username": "paul.taconet", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "gianpaolo.coro", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [ + "VO-Admin" + ], + "/gcube/devNext/NextNext": [ + "Data-Manager", + "Catalogue-Admin", + "DataMiner-Manager" + ], + "/gcube/devNext": [] + } + }, + { + "username": "ciro.formisano", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "valentina.marioli", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [ + "VO-Admin" + ], + "/gcube/devNext": [] + } + }, + { + "username": "andrea.dellamico", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext/NextNext": [], + "/gcube/devNext": [] + } + }, + { + "username": "panagiota.koltsida", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [ + "Data-Manager", + "VRE-Designer" + ], + "/gcube/devNext": [] + } + }, + { + "username": "konstantinos.giannousis", + "contexts": { + "/gcube": [] + } + }, + { + "username": "tommaso.piccioli", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "paolo.manghi", + "contexts": { + "/gcube": [] + } + }, + { + "username": "emmanuel.blondel", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "francesco.mangiacrapa", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [ + "Catalogue-Admin" + ], + "/gcube/devsec": [], + "/gcube/devNext/NextNext": [ + "Catalogue-Admin" + ], + "/gcube/devNext": [] + } + }, + { + "username": "ashtoash", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "anton.ellenbroek", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "pasquale.pagano", + "contexts": { + "/gcube": [ + "VO-Admin" + ], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [ + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devNext/NextNext": [ + "Data-Manager", + "Catalogue-Admin" + ], + "/gcube/devNext": [ + "VO-Admin" + ] + } + }, + { + "username": "leonardo.candela", + "contexts": { + "/gcube": [], + "/gcube/devsec/SAI5": [ + "VRE-Manager" + ], + "/gcube/devsec/SAI4": [ + "VRE-Manager" + ], + "/gcube/devsec/SAI3": [ + "VRE-Manager" + ], + "/gcube/devsec/SAIVRE3": [ + "VRE-Manager" + ], + "/gcube/devsec/SAI2": [ + "VRE-Manager" + ], + "/gcube/devsec/devVRE": [ + "VRE-Designer", + "VRE-Manager", + "Catalogue-Admin" + ], + "/gcube/devsec/SAIVRE2": [ + "VRE-Manager" + ], + "/gcube/devsec/SAITest": [ + "VRE-Manager" + ], + "/gcube/devNext": [], + "/gcube/devsec/SAITest2": [ + "VRE-Manager" + ], + "/gcube/devsec": [ + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devNext/NextNext": [ + "VRE-Manager", + "Catalogue-Admin" + ] + } + }, + { + "username": "donatella.castelli", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "julien.barde", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "giancarlo.panichi", + "contexts": { + "/gcube": [ + "Infrastructure-Manager" + ], + "/gcube/devsec/devVRE": [], + "/gcube/devsec/SAIVRE2": [], + "/gcube/devsec": [], + "/gcube/devNext": [], + "/gcube/devNext/NextNext": [ + "Data-Manager", + "VRE-Manager", + "Catalogue-Editor", + "DataMiner-Manager" + ] + } + }, + { + "username": "luca.frosini", + "contexts": { + "/gcube": [ + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devsec/devVRE": [ + "VRE-Manager", + "Catalogue-Admin" + ], + "/gcube/devsec": [], + "/gcube/devNext": [], + "/gcube/devNext/NextNext": [ + "Catalogue-Admin", + "Accounting-Manager" + ] + } + }, + { + "username": "fabio.sinibaldi", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [ + "Catalogue-Admin" + ], + "/gcube/devsec": [ + "VO-Admin", + "Catalogue-Admin" + ], + "/gcube/devNext": [ + "VRE-Designer", + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devNext/NextNext": [ + "Catalogue-Admin" + ] + } + }, + { + "username": "yannis.marketakis", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [], + "/gcube/devNext/NextNext": [], + "/gcube/devNext": [] + } + }, + { + "username": "paolo.fabriani", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "nikolas.laskaris", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "massimiliano.assante", + "contexts": { + "/gcube/devsec/Test4StoHub": [], + "/gcube": [ + "Infrastructure-Manager", + "VO-Admin" + ], + "/gcube/devsec/devVRE": [ + "VRE-Manager" + ], + "/gcube/devsec/SAIVRE2": [], + "/gcube/devsec": [ + "VRE-Manager", + "VO-Admin" + ], + "/gcube/devNext/NextNext": [], + "/gcube/devNext": [ + "Infrastructure-Manager", + "VRE-Manager", + "VO-Admin" + ] + } + }, + { + "username": "gkakas", + "contexts": { + "/gcube": [] + } + }, + { + "username": "gantzoulatos", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "enrico.anello", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "mister.orange", + "contexts": { + "/gcube": [] + } + }, + { + "username": "mister.blue", + "contexts": { + "/gcube": [] + } + }, + { + "username": "kostas.kakaletris", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [ + "Infrastructure-Manager" + ], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "leviwesterveld", + "contexts": { + "/gcube": [] + } + }, + { + "username": "dataminer", + "contexts": { + + } + }, + { + "username": "andrea.rossi", + "contexts": { + "/gcube": [], + "/gcube/devsec/Test4StoHub": [ + "VRE-Designer", + "VRE-Manager" + ], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [ + "VRE-Designer" + ], + "/gcube/devNext/NextNext": [], + "/gcube/devNext": [] + } + }, + { + "username": "efthymios", + "contexts": { + "/gcube": [], + "/gcube/devsec": [] + } + }, + { + "username": "miles", + "contexts": { + "/gcube": [], + "/gcube/devNext": [] + } + }, + { + "username": "kgiannakelos", + "contexts": { + "/gcube": [], + "/gcube/devNext": [] + } + }, + { + "username": "grsf.publisher", + "contexts": { + "/gcube": [], + "/gcube/devNext/NextNext": [ + "Catalogue-Admin" + ], + "/gcube/devNext": [] + } + }, + { + "username": "jsonws.user", + "contexts": { + "/gcube": [] + } + }, + { + "username": "ay", + "contexts": { + "/gcube": [], + "/gcube/devNext": [] + } + }, + { + "username": "salvam", + "contexts": { + "/gcube": [], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "nikos", + "contexts": { + "/gcube": [], + "/gcube/devNext": [] + } + }, + { + "username": "statistical.manager", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [], + "/gcube/devNext": [], + "/gcube/devNext/NextNext": [ + "Data-Manager" + ] + } + }, + { + "username": "manuele.simi", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devNext/NextNext": [] + } + }, + { + "username": "vfloros", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [ + "Catalogue-Editor" + ], + "/gcube/devsec": [], + "/gcube/devNext": [] + } + }, + { + "username": "rob.knapen", + "contexts": { + "/gcube": [] + } + }, + { + "username": "mister.white", + "contexts": { + "/gcube": [], + "/gcube/devNext/NextNext": [] + } + }, + { + "username": "chiamag", + "contexts": { + "/gcube": [], + "/gcube/devsec/devVRE": [], + "/gcube/devsec": [] + } + }, + { + "username": "m.lettere", + "contexts": { + "/gcube": [] + } + }, + { + "username": "mauro", + "contexts": { + "/gcube": [] + } + } +] \ No newline at end of file diff --git a/src/test/resources/log4j.xml b/src/test/resources/log4j.xml new file mode 100644 index 0000000..7a4fbee --- /dev/null +++ b/src/test/resources/log4j.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/token.json b/src/test/resources/token.json new file mode 100644 index 0000000..091e680 --- /dev/null +++ b/src/test/resources/token.json @@ -0,0 +1,73 @@ +{ + "exp": 1586418752, + "nbf": 0, + "iat": 1586418452, + "auth_time": 0, + "jti": "caf3c534-b6e0-4475-b643-883d535651f9", + "iss": "https://nubis2.int.d4science.net/auth/realms/d4science", + "aud": [ + "/gcube/devsec/devVRE", + "account" + ], + "sub": "696f7a5f-8142-4079-95f5-0751a497465b", + "typ": "Bearer", + "azp": "portal", + "nonce": null, + "session_state": "1a2d3d4f-0e28-4930-b3c3-a5e3660d87dc", + "at_hash": null, + "c_hash": null, + "name": "Pasquale Pagano", + "given_name": "Pasquale", + "family_name": "Pagano", + "middle_name": null, + "nickname": null, + "preferred_username": "lino", + "profile": null, + "picture": null, + "website": null, + "email": "themaxx76@gmail.com", + "email_verified": false, + "gender": null, + "birthdate": null, + "zoneinfo": null, + "locale": null, + "phone_number": null, + "phone_number_verified": null, + "address": null, + "updated_at": null, + "claims_locales": null, + "acr": "1", + "s_hash": null, + "trusted-certs": null, + "allowed-origins": [ + "*" + ], + "realm_access": { + "roles": [ + "offline_access", + "uma_authorization" + ], + "verify_caller": null + }, + "resource_access": { + "/gcube/devsec/devVRE": { + "roles": [ + "DataManager", + "DataProcessor" + ], + "verify_caller": null + }, + "account": { + "roles": [ + "manage-account", + "manage-account-links", + "view-profile" + ], + "verify_caller": null + } + }, + "authorization": null, + "cnf": null, + "scope": "profile email", + "groups": [] +} \ No newline at end of file