oidc-keycloak-library/src/main/java/org/gcube/oidc/keycloak/KeycloakHelper.java

317 lines
15 KiB
Java

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<String, KeycloakHelper> instances = Collections
.synchronizedMap(new TreeMap<String, KeycloakHelper>());
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<UserRepresentation> 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<RoleRepresentation> 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<ClientRepresentation> 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<ClientRepresentation> 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<ScopeRepresentation> scopes, Set<String> 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<String> resources,
Set<String> scopes,
String name, Logic logic, Map<String, Set<String>> 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<String> resources, String name, DecisionStrategy decisionStrategy,
Set<String> 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 extends JsonWebToken> T verifyAndGetToken(Class<T> tokenClass, String tokenString, PublicKey publicKey)
throws VerificationException {
return TokenVerifier.create(tokenString, tokenClass).publicKey(publicKey).verify().getToken();
}
}