312 lines
15 KiB
Java
312 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 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 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();
|
|
}
|
|
|
|
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());
|
|
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();
|
|
}
|
|
|
|
}
|