Skip to content

Commit

Permalink
Support for initial CRUD operations when managing admin permissions
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Igor <[email protected]>
  • Loading branch information
pedroigor committed Dec 12, 2024
1 parent ad679b8 commit a6f0d03
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class AbstractPolicyRepresentation {
private Logic logic = Logic.POSITIVE;
private DecisionStrategy decisionStrategy = DecisionStrategy.UNANIMOUS;
private String owner;
private String resourceType;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<ResourceRepresentation> resourcesData;
Expand Down Expand Up @@ -186,4 +187,13 @@ public void setScopesData(Set<ScopeRepresentation> scopesData) {
public Set<ScopeRepresentation> getScopesData() {
return scopesData;
}
}

public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}

public String getResourceType() {
return resourceType;
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,8 @@
*/
public class ScopePermissionRepresentation extends AbstractPolicyRepresentation {

private String resourceType;

@Override
public String getType() {
return "scope";
}

public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}

public String getResourceType() {
return resourceType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.keycloak.admin.client.resource;

import java.util.List;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
Expand Down Expand Up @@ -45,4 +47,12 @@ public interface ScopePermissionsResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
ScopePermissionRepresentation findByName(@QueryParam("name") String name);

@GET
@Produces(MediaType.APPLICATION_JSON)
List<ScopePermissionRepresentation> findAll(@QueryParam("policyId") String id,
@QueryParam("name") String name,
@QueryParam("resource") String resource,
@QueryParam("first") Integer firstResult,
@QueryParam("max") Integer maxResult);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2024 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authorization;

import java.util.Arrays;
import java.util.HashSet;

import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.store.StoreFactory;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.ResourceType;

public class AdminPermissionsAuthorizationSchema extends AuthorizationSchema {

public static final ResourceType USERS = new ResourceType("Users", new HashSet<>(Arrays.asList("manage")));
public static final AdminPermissionsAuthorizationSchema INSTANCE = new AdminPermissionsAuthorizationSchema();

private AdminPermissionsAuthorizationSchema() {
super(USERS);
}

public Resource getOrCreateResource(KeycloakSession session, String type, String id) {
RealmModel realm = session.getContext().getRealm();

if (!realm.isAdminPermissionsEnabled()) {
return null;
}

ClientModel permissionClient = realm.getAdminPermissionsClient();

if (permissionClient == null) {
throw new IllegalStateException("Permission client not found");
}

StoreFactory storeFactory = getStoreFactory(session);
ResourceServer resourceServer = storeFactory.getResourceServerStore().findByClient(permissionClient);
String resourceName = null;

if (USERS.getType().equals(type)) {
resourceName = resolveUser(session, id, realm);
}

if (resourceName == null) {
throw new IllegalStateException("Could not map resource object with type [" + type + "] and id [" + id + "]");
}

return getOrCreateResource(session, resourceServer, resourceName);
}

private String resolveUser(KeycloakSession session, String id, RealmModel realm) {
UserModel user = session.users().getUserById(realm, id);

if (user == null) {
user = session.users().getUserByUsername(realm, id);
}

return user == null ? null : user.getId();
}

private Resource getOrCreateResource(KeycloakSession session, ResourceServer resourceServer, String id) {
StoreFactory storeFactory = getStoreFactory(session);
Resource resource = storeFactory.getResourceStore().findByName(resourceServer, id);

if (resource == null) {
return storeFactory.getResourceStore().create(resourceServer, id, resourceServer.getClientId());
}

return resource;
}

private StoreFactory getStoreFactory(KeycloakSession session) {
AuthorizationProvider authzProvider = session.getProvider(AuthorizationProvider.class);
return authzProvider.getStoreFactory();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -295,18 +297,24 @@ public Policy create(ResourceServer resourceServer, AbstractPolicyRepresentation

if (resources != null) {
representation.setResources(resources.stream().map(id -> {
Resource resource = storeFactory.getResourceStore().findById(resourceServer, id);
Resource resource = AdminPermissionsAuthorizationSchema.INSTANCE.getOrCreateResource(keycloakSession, representation.getResourceType(), id);

if (resource == null) {
resource = storeFactory.getResourceStore().findByName(resourceServer, id);
}
resource = storeFactory.getResourceStore().findById(resourceServer, id);

if (resource == null) {
throw new RuntimeException("Resource [" + id + "] does not exist or is not owned by the resource server.");
if (resource == null) {
resource = storeFactory.getResourceStore().findByName(resourceServer, id);
}

if (resource == null) {
throw new RuntimeException("Resource [" + id + "] does not exist or is not owned by the resource server.");
}

return resource.getId();
}

return resource.getId();
}).collect(Collectors.toSet()));
return Optional.ofNullable(resource).map(Resource::getId).orElse(null);
}).filter(Objects::nonNull).collect(Collectors.toSet()));
}

Set<String> scopes = representation.getScopes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.broker.social.SocialIdentityProviderFactory;
import org.keycloak.common.util.CertificateUtils;
Expand Down Expand Up @@ -88,10 +89,8 @@
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.common.Profile;
import org.keycloak.representations.idm.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.representations.idm.authorization.AuthorizationSchema;
import org.keycloak.representations.idm.authorization.ResourceServerRepresentation;
import org.keycloak.representations.idm.authorization.ResourceType;
import org.keycloak.representations.idm.authorization.ScopeRepresentation;

import static org.keycloak.utils.StreamsUtil.closing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.otp.OTPApplicationProvider;
import org.keycloak.authorization.AdminPermissionsAuthorizationSchema;
import org.keycloak.authorization.AuthorizationProvider;
import org.keycloak.authorization.AuthorizationProviderFactory;
import org.keycloak.authorization.model.PermissionTicket;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@
String ref() default "";

String realmRef() default "";

boolean createClient() default true;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.keycloak.test.framework.realm;

import java.util.List;

import jakarta.ws.rs.core.Response;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.representations.idm.ClientRepresentation;
Expand Down Expand Up @@ -34,11 +36,22 @@ public ManagedClient getValue(InstanceContext<ManagedClient, InjectClient> insta
clientRepresentation.setClientId(clientId);
}

Response response = realm.admin().clients().create(clientRepresentation);
String uuid = ApiUtil.handleCreatedResponse(response);
clientRepresentation.setId(uuid);
List<ClientRepresentation> clients = realm.admin().clients().findByClientId(clientRepresentation.getClientId());

if (instanceContext.getAnnotation().createClient()) {
if (!clients.isEmpty()) {
throw new IllegalStateException("Client already exist with client id " + clientRepresentation.getClientId() + ". To use the existing client configure the injection point to skip creating the client.");
}
Response response = realm.admin().clients().create(clientRepresentation);
clientRepresentation.setId(ApiUtil.handleCreatedResponse(response));
} else {
if (clients.isEmpty()) {
throw new IllegalStateException("No client found for ref: " + instanceContext.getAnnotation().ref());
}
clientRepresentation = clients.get(0);
}

ClientResource clientResource = realm.admin().clients().get(uuid);
ClientResource clientResource = realm.admin().clients().get(clientRepresentation.getId());
return new ManagedClient(clientRepresentation, clientResource);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ public RealmConfigBuilder defaultSignatureAlgorithm(String algorithm) {
return this;
}

public RealmConfigBuilder adminPermissionsEnabled(boolean enabled) {
rep.setAdminPermissionsEnabled(enabled);
return this;
}

public RealmConfigBuilder eventsListeners(String... eventListeners) {
if (rep.getEventsListeners() == null) {
rep.setEventsListeners(new LinkedList<>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.smallrye.config.SmallRyeConfig;
import org.eclipse.microprofile.config.spi.ConfigSource;
import org.keycloak.common.Profile;
import org.keycloak.common.Profile.Feature;

import java.util.Arrays;
import java.util.HashMap;
Expand Down Expand Up @@ -194,7 +195,7 @@ public Set<Dependency> toDependencies() {
}

private Set<String> toFeatureStrings(Profile.Feature... features) {
return Arrays.stream(features).map(f -> f.name().toLowerCase().replace('_', '-')).collect(Collectors.toSet());
return Arrays.stream(features).map(Feature::getVersionedKey).collect(Collectors.toSet());
}

public enum LogHandlers {
Expand Down
Loading

0 comments on commit a6f0d03

Please sign in to comment.