[keycloak-user] Adding permissions programmatically

Lucian Ochian okianl at yahoo.com
Mon Jun 12 11:48:43 EDT 2017


You can use the REST Admin API or if you use Java, you can use the Admin Client library.You have to decide how you want to assign the permissions. 
In my case, I decided to have for each resource scopes and when I want to assign a user to a resource with a given scope, I check if the user has a user policy, if not create one and then create a scope permission for each user policy/resource combination where the scopes are set.
I have some code in here just to give you an idea... This is for keycloak 2.5.x
You can get to the permissions API by using the ClientResource....


public class KeycloakGateway {

    @Autowired
    private KeycloakIdentityUtils identityUtils;

    @Autowired
    private ScopedPermissionMapper scopedPermissionMapper;

    private KeycloakDeployment deployment;



    /**
     * the client scopes; the key is the scope name, the value is the representation
     */
    private Map<String, ScopeRepresentation> clientScopes = Collections.synchronizedMap(new HashMap<>());

    /**
     * this field needs to be lazy loaded so that we can do testing when the realm is added after the framework starts
     */
    private Keycloak _keycloak;

    

    /**
     * lazy loads the keycloak; double check idiom
     * http://www.javaworld.com/article/2077568/learn-java/java-tip-67--lazy-instantiation.html
     *
     * @return
     */
    private Keycloak keycloak() {
        if (this._keycloak == null) {
            synchronized (KeycloakGatewayImpl.class) {
                if (this._keycloak == null) {
                    this._keycloak = KeycloakBuilder.builder()
                            .serverUrl(getDeployment().getAuthServerBaseUrl())
                            .realm(getDeployment().getRealm())
                            .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
                            .clientId(getDeployment().getResourceName())
                            .clientSecret((String) deployment.getResourceCredentials().get("secret"))
                            .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
                            .build();
                }
            }
        }
        return _keycloak;
    }

    public void removeKeycloakForServiceAccount() {
        this._keycloak = null;
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public CreateUserResult createUser(String username, String firstName, String lastName, String email, boolean enabled) {

        Response response;
        Response.StatusType statusInfo;
        ErrorRepresentation message;
        UserRepresentation representation = new UserRepresentation();
        representation.setUsername(username);
        representation.setFirstName(firstName);
        representation.setLastName(lastName);
        representation.setEmail(email);
        representation.setEnabled(enabled);

        response = realm(getUserToken()).users().create(representation);
        statusInfo = response.getStatusInfo();

        message = null;
        if (statusInfo.getStatusCode() != Response.Status.CREATED.getStatusCode()) {
            message = response.readEntity(ErrorRepresentation.class);
        }
        response.close();

        if (statusInfo.getStatusCode() == Response.Status.CREATED.getStatusCode()) {
            String userUuid = ApiUtil.getCreatedId(response);


            return CreateUserResult.success(userUuid);
        }

        //noinspection ConstantConditions
        return CreateUserResult.failure(message.getErrorMessage());

    }

    private RealmResource realm() {
        return keycloak().realm(getDeployment().getRealm());
    }

    private RealmResource realm(String token) {
        KeycloakDeployment deployment = getDeployment();
        return Keycloak.getInstance(deployment.getAuthServerBaseUrl(), deployment.getRealm(), deployment.getResourceName(), token).realm(deployment.getRealm());
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public UpdateUserResult updateUser(String idmId, String username, String firstName, String lastName, String email, boolean enabled, Set<String> roles) {

        UserRepresentation representation = new UserRepresentation();
        representation.setUsername(username);
        representation.setFirstName(firstName);
        representation.setLastName(lastName);
        representation.setEmail(email);
        representation.setEnabled(enabled);

        try {
            realm(getUserToken()).users().get(idmId).update(representation);
        } catch (ClientErrorException e) {
//                String s = e.getResponse().readEntity(String.class);
//                return UpdateUserResult.failure(idmId, s);
            ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
            return UpdateUserResult.failure(idmId, error.getErrorMessage());
        }

        updateRoles(idmId, roles, realm());
        return UpdateUserResult.success(idmId);
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public UpdateUserResult enable(String idmId, boolean enabled) {
        try {
            UserResource userResource = realm(getUserToken()).users().get(idmId);
            UserRepresentation userRepresentation = userResource.toRepresentation();
            userRepresentation.setEnabled(enabled);
            userResource.update(userRepresentation);
        } catch (ClientErrorException e) {
            ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
            return UpdateUserResult.failure(idmId, error.getErrorMessage());
        }
        return UpdateUserResult.success(idmId);
    }

    @Override
    public void updateRoles(String idmId, Set<String> roles) {
        updateRoles(idmId, roles, getUserToken());
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public void updateScopePermission(ScopePermission permission) {
        ResourceRepresentation resource = findResource(permission.getResourceUri(), permission.getResourceType());
        setPermission(permission.getUserId(), resource.getId(), permission.getScopeNames());
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public void updatePermissions(List<ScopePermission> permissions) {
        permissions.forEach(this::updateScopePermission);
    }

    @Override
    public List<ScopePermission> userScopePermissions(String userId) {
        List<PolicyRepresentation> scopePermissions = findScopePermissions(userId);
        List<ScopePermission> result = scopePermissions.stream().map(policy -> {
            ScopePermission permission2 = new ScopePermission();
            permission2.setUserId(scopedPermissionMapper.userId(policy.getName()));
            String resourceId = scopedPermissionMapper.resourceId(policy.getName());
            ResourceRepresentation resource = findResource(resourceId);
            permission2.setResourceUri(resource.getUri());
            permission2.setResourceType(resource.getType());

            List<String> scopeNames = authorizationResource().policies().policy(policy.getId()).scopes()
                    .stream().map(ScopeRepresentation::getName).collect(Collectors.toList());

            permission2.setScopeNames(scopeNames);
            return permission2;
        }).collect(Collectors.toList());
        return result;
    }

    private String getUserToken() {
        RefreshableKeycloakSecurityContext context = identityUtils.getRefreshableKeycloakSecurityContext();
        return context.getTokenString();
    }

    
    private synchronized void setDeployment(KeycloakDeployment deployment) {
        this.deployment = deployment;
    }


    public KeycloakDeployment getDeployment() {
        //http://www.javaworld.com/article/2077568/learn-java/java-tip-67--lazy-instantiation.html
        if (deployment == null) {
            synchronized (KeycloakGatewayImpl.class) {
                if (deployment == null) {
                    setDeployment(identityUtils.getDeployment());
                }
            }
        }
        return deployment;
    }

    private String getRealmName() {
        return getDeployment().getRealm();
    }

    

    private List<ScopeRepresentation> getScopeRepresentations(List<String> scopeNames) {
        return scopeNames.stream()
                .map(scopeId -> scopesMap().get(scopeId)).collect(Collectors.toList());
    }

    @Override
    public ResourceRepresentation findResource(String id) {
        //noinspection UnnecessaryLocalVariable
        ResourceRepresentation representation = getClientResources().resource(id).toRepresentation();
        return representation;
    }

    @Override
    public ResourceRepresentation findResource(String uri, String type) {
        List<ResourceRepresentation> representations = getClientResources().find(null, uri, null, type, null, 0, 10);
        Preconditions.checkState(representations.size() < 2, String.format("More than 1 resource was found with type:%s and uri:%s ", type, uri));
        return representations.isEmpty() ? null : representations.get(0);
    }

    @Override
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public String createOrUpdateResource(CreateUpdateResourceRequest request) {
        ResourceRepresentation existing = findResource(request.getUri(), request.getType());
        Set<ScopeRepresentation> scopeRepresentations = new HashSet<>(getScopeRepresentations(request.getScopeNames()));

        ResourceRepresentation representation = new ResourceRepresentation(request.getName(), scopeRepresentations, request.getUri(), request.getType());
        if (existing == null) {
            Response response = getClientResources().create(representation);
            representation = response.readEntity(ResourceRepresentation.class);
            response.close();
        } else {
            representation.setId(existing.getId());
            getClientResources().resource(representation.getId()).update(representation);
        }
        return representation.getId();
    }

    private String getClientId() {
        //noinspection ConstantConditions
        return ApiUtil.findClientByClientId(realm(), getDeployment().getResourceName()).toRepresentation().getId();
    }

    private ResourcesResource getClientResources() {
        //noinspection ConstantConditions
        return ApiUtil.findClientByClientId(realm(), getDeployment().getResourceName())
                .authorization().resources();
    }

    private AuthorizationResource getClientResources(String clientName) {
        //noinspection ConstantConditions
        return ApiUtil.findClientByClientId(realm(), clientName)
                .authorization();
    }

    public AuthorizationResource authorizationResource() {
        //noinspection ConstantConditions
        return getClientResources(getDeployment().getResourceName());
    }

    public Map<String, ScopeRepresentation> scopesMap() {
        if (clientScopes.isEmpty()) {
            loadScopes();
        }
        return Collections.unmodifiableMap(clientScopes);
    }

    @Override
    public List<ScopeRepresentation> clientScopes() {
        if (clientScopes.isEmpty()) {
            loadScopes();
        }
        return new ArrayList<>(clientScopes.values());
    }

    @Override
    public void setPermission(String userId, String resourceId, List<String> scopeNames) {
        UserRepresentation user = findApplicationUser(userId);
        ResourceRepresentation resource = findResource(resourceId);

        PolicyRepresentation permission = findOrCreateUserPermission(user, resource, scopeNames);
        //create if new and it has scopes
        if (permission.getId() == null && !scopeNames.isEmpty()) {
            Response response = authorizationResource().policies().create(permission);
            permission = response.readEntity(PolicyRepresentation.class);
            response.close();
            return;
        }

        //do nothing if it's new and no scopes
        if (scopeNames.isEmpty() && permission.getId() == null) {
            return;
        }
        // remove if it exists and has no scopes
        if (scopeNames.isEmpty() && permission.getId() != null) {
            authorizationResource().policies().policy(permission.getId()).remove();
            return;
        }

        // update scopes if it exists
        if (!scopeNames.isEmpty() && permission.getId() != null) {
            permission.setConfig(buildPermissionConfig(resource, findOrCreateUserPolicy(user), scopeNames));
            authorizationResource().policies().policy(permission.getId()).update(permission);
            return;
        }

    }

    /**
     * @param user       the user
     * @param resource   the resource
     * @param scopeNames the scope names
     * @return a policy representation if it exists, or it will create a new one that is not in the IAM yet
     */
    public PolicyRepresentation findOrCreateUserPermission(UserRepresentation user, ResourceRepresentation resource, List<String> scopeNames) {
        PolicyRepresentation permission = findScopePermission(user, resource);

        if (permission == null) {
            PolicyRepresentation policy = findOrCreateUserPolicy(user);

            permission = new PolicyRepresentation();
            permission.setType("scope");
            permission.setDecisionStrategy(DecisionStrategy.UNANIMOUS);
            permission.setLogic(Logic.POSITIVE);
            permission.setName(scopedPermissionMapper.scopePermissionName(user, resource));
            permission.setDescription(String.format("User(%s) permission for resource(%s)", user.getId(), resource.getId()));
            permission.setConfig(buildPermissionConfig(resource, policy, scopeNames));
        }
        return permission;
    }

    private Map<String, String> buildPermissionConfig(ResourceRepresentation resource, PolicyRepresentation userPolicy, List<String> scopeNames) {
        Map<String, String> config = new HashMap<>();
        config.put("applyPolicies", String.format("[\"%s\"]", userPolicy.getId()));
        config.put("resources", String.format("[\"%s\"]", resource.getId()));
        List<String> scopeIds = scopeNames.stream().map(s -> String.format("\"%s\"", scopesMap().get(s).getId())).collect(Collectors.toList());
        config.put("scopes", scopeIds.toString());
        return config;
    }

    public PolicyRepresentation findOrCreateUserPolicy(UserRepresentation user) {
        PolicyRepresentation policy = findUserPolicy(user.getId());
        if (policy == null) {
            policy = new PolicyRepresentation();
            policy.setName(user.getId());
            policy.setDescription(String.format("User policy for userId=%s", user.getId()));
            policy.setType("user");
            policy.setLogic(Logic.POSITIVE);
            Map<String, String> config = new HashMap<>();
            config.put("users", String.format("[%s]", user.getId()));
            policy.setConfig(config);
            Response response = authorizationResource().policies().create(policy);
            policy = response.readEntity(PolicyRepresentation.class);
            response.close();
        }
        return policy;
    }

    /**
     * @param name the name used in the search(the user id is used here)
     * @return a "user policy" that includes in the name the id of the user, or null otherwise
     */
    public PolicyRepresentation findUserPolicy(String name) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("Authorization", String.format("Bearer %s", keycloak().tokenManager().getAccessTokenString()));

        final HttpEntity<PolicyRepresentation> entity = new HttpEntity<>(headers);
        String urlBase = getDeployment().getAuthServerBaseUrl() + "/admin/realms/" + getDeployment().getRealm() +
                "/clients/" + getClientId() +
                "/authz/resource-server/policy";

        String url = UriComponentsBuilder.fromUriString(urlBase)
                .queryParam("first", 0)
                .queryParam("max", 20)
                .queryParam("permission", "false")
                .queryParam("type", "user")
                .queryParam("name", name)
//                .queryParam("name", "default")
//                .queryParam("resource", "")
                .build().toUri().toString();

        ResponseEntity<PolicyRepresentation[]> response = template.exchange(url, HttpMethod.GET, entity, PolicyRepresentation[].class);
        PolicyRepresentation[] body = response.getBody();
        List<PolicyRepresentation> policies = Arrays.asList(body);
        Preconditions.checkState(policies.size() < 2, String.format("User %s has more than 1 user policies", name));
        return policies.size() == 0 ? null : policies.get(0);
    }

    /**
     * @param user     IAM user
     * @param resource IAM resource
     * @return a scope permission that has the name in the format of "userId-resourceId-scoped"(max 1), or null if none found
     */
    public PolicyRepresentation findScopePermission(UserRepresentation user, ResourceRepresentation resource) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("Authorization", String.format("Bearer %s", keycloak().tokenManager().getAccessTokenString()));

        final HttpEntity<PolicyRepresentation> entity = new HttpEntity<>(headers);
        //I believed this URL changed in Keycloak 3
        String urlBase = getDeployment().getAuthServerBaseUrl() + "/admin/realms/" + getDeployment().getRealm() +
                "/clients/" + getClientId() +
                "/authz/resource-server/policy";

        String url = UriComponentsBuilder.fromUriString(urlBase)
                .queryParam("first", 0)
                .queryParam("max", 20)
                .queryParam("permission", "true")
                .queryParam("type", "scope")
                .queryParam("name", scopedPermissionMapper.scopePermissionName(user, resource))
                .build().toUri().toString();

        ResponseEntity<PolicyRepresentation[]> response = template.exchange(url, HttpMethod.GET, entity, PolicyRepresentation[].class);
        PolicyRepresentation[] body = response.getBody();
        List<PolicyRepresentation> policies = Arrays.asList(body);
        Preconditions.checkState(policies.size() < 2, String.format("User %s has more than 1 resource permissions", user.getId()));
        return policies.size() == 0 ? null : policies.get(0);
    }


    /**
     * @param userId IAM user
     * @return all scope permission that has the name in the format of "userId-resourceId-scoped"
     */
    public List<PolicyRepresentation> findScopePermissions(String userId) {
        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Type", "application/json");
        headers.add("Authorization", String.format("Bearer %s", keycloak().tokenManager().getAccessTokenString()));

        final HttpEntity<PolicyRepresentation> entity = new HttpEntity<>(headers);
        //I believed this URL changed in Keycloak 3
        String urlBase = getDeployment().getAuthServerBaseUrl() + "/admin/realms/" + getDeployment().getRealm() +
                "/clients/" + getClientId() +
                "/authz/resource-server/policy";

        String url = UriComponentsBuilder.fromUriString(urlBase)
                .queryParam("first", 0)
                .queryParam("max", 1001)
                .queryParam("permission", "true")
                .queryParam("type", "scope")
                .queryParam("name", userId)
                .build().toUri().toString();

        ResponseEntity<PolicyRepresentation[]> response = template.exchange(url, HttpMethod.GET, entity, PolicyRepresentation[].class);
        PolicyRepresentation[] body = response.getBody();
        //noinspection UnnecessaryLocalVariable
        List<PolicyRepresentation> policies = Arrays.asList(body);
        Preconditions.checkState(policies.size() <= 1000, "Too many scoped permissions for user:" + userId);
        return policies;
    }


    public void loadScopes() {
        ClientResource clientResource = ApiUtil.findClientByClientId(realm(), deployment.getResourceName());
        //noinspection ConstantConditions
        List<ScopeRepresentation> scopes = clientResource.authorization().scopes().scopes();
        Map<String, ScopeRepresentation> scopesAsMap = scopes.stream().collect(Collectors.toMap(ScopeRepresentation::getName, s -> s));
        clientScopes.clear();
        clientScopes.putAll(scopesAsMap);
    }

    /**
     * @return all the user's realm roles using the service account.
     */
    @Retryable(value = RuntimeException.class, backoff = @Backoff(delay = 1000, multiplier = 2))
    public List<RoleRepresentation> getUserRealmRoles(String userId) {
        List<RoleRepresentation> userAppRoles;
        userAppRoles = realm().users().get(userId).roles().realmLevel().listAll();
        return userAppRoles;
    }
}

On Monday, June 12, 2017, 10:16:13 AM CDT, matteo restelli <teoreste at gmail.com> wrote:

Hi guys,
how can I add permissions programmatically for a specific resource?

Thank you in advance,
Matteo
_______________________________________________
keycloak-user mailing list
keycloak-user at lists.jboss.org
https://lists.jboss.org/mailman/listinfo/keycloak-user


More information about the keycloak-user mailing list