package org.gcube.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 java.util.TreeMap; 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.GroupResource; 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.GroupRepresentation; 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 Map instances = Collections .synchronizedMap(new TreeMap()); 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 (!instances.containsKey(serverUrl)) { instances.put(serverUrl, new KeycloakHelper(serverUrl)); } return instances.get(serverUrl); } 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(); } 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()); newClientRepresentation.setFullScopeAllowed(Boolean.FALSE); 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 addPublicClient(RealmResource realm, String clientId, String name, String description, String rootUrl, String loginTheme) 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(true); newClientRepresentation.setProtocol("openid-connect"); if (loginTheme != null) { newClientRepresentation.getAttributes().put("login_theme", loginTheme); } newClientRepresentation.setAuthorizationSettings(new ResourceServerRepresentation()); try (Response response = realm.clients().create(newClientRepresentation)) { if (!response.getStatusInfo().equals(Response.Status.CREATED)) { throw new KeycloakResourceCreationException("While creating new public 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 GroupResource findGroupByPath(RealmResource realm, String groupPath) throws UnsupportedEncodingException { GroupRepresentation group = realm.getGroupByPath(groupPath); if (group != null) { return realm.groups().group(group.getId()); } else { return null; } } public void mapGroupToCLientRole(GroupResource group, ClientResource client, String roleName) { mapGroupToCLientRole(group, client, client.roles().get(roleName)); } public void mapGroupToCLientRole(GroupResource group, ClientResource client, RoleResource role) { group.roles().clientLevel(client.toRepresentation().getId()) .add(Collections.singletonList(role.toRepresentation())); } 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(); } }