All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.acosix.alfresco.keycloak.repo.authentication.DefaultAuthorityExtractor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2019 - 2021 Acosix GmbH
 *
 * 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 de.acosix.alfresco.keycloak.repo.authentication;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.alfresco.service.cmr.security.AuthorityType;
import org.alfresco.util.ParameterCheck;
import org.alfresco.util.PropertyCheck;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessToken.Access;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;

import de.acosix.alfresco.keycloak.repo.roles.RoleNameFilter;
import de.acosix.alfresco.keycloak.repo.roles.RoleNameMapper;

/**
 * Instances of this class provide a generalised default authority mapping / extraction logic for Keycloak authenticated users. The mapping
 * / extraction processes both realm and client- / resource-specific roles, and provides configurable authority name transformation (e.g.
 * consistent casing, authority type prefixes, potential subsystem prefixes for differentiation).
 *
 * @author Axel Faust
 */
public class DefaultAuthorityExtractor implements InitializingBean, AuthorityExtractor
{

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultAuthorityExtractor.class);

    protected boolean enabled;

    protected AdapterConfig adapterConfig;

    protected boolean processRealmRoles;

    protected boolean processResourceRoles;

    protected RoleNameFilter realmRoleNameFilter;

    protected RoleNameMapper realmRoleNameMapper;

    protected RoleNameFilter defaultResourceRoleNameFilter;

    protected RoleNameMapper defaultResourceRoleNameMapper;

    protected Map resourceRoleNameFilter;

    protected Map resourceRoleNameMapper;

    /**
     *
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet()
    {
        if (this.enabled && this.processRealmRoles)
        {
            PropertyCheck.mandatory(this, "realmRoleNameMapper", this.realmRoleNameMapper);
        }

        if (this.enabled && this.processResourceRoles)
        {
            PropertyCheck.mandatory(this, "adapterConfig", this.adapterConfig);
            PropertyCheck.mandatory(this, "defaultResourceRoleNameMapper", this.defaultResourceRoleNameMapper);

            if (this.resourceRoleNameMapper == null)
            {
                this.resourceRoleNameMapper = new HashMap<>();
            }
            this.resourceRoleNameMapper.put(this.adapterConfig.getResource(), this.defaultResourceRoleNameMapper);

            if (this.defaultResourceRoleNameFilter != null)
            {
                if (this.resourceRoleNameFilter == null)
                {
                    this.resourceRoleNameFilter = new HashMap<>();
                }
                this.resourceRoleNameFilter.put(this.adapterConfig.getResource(), this.defaultResourceRoleNameFilter);
            }
        }
    }

    /**
     * @param enabled
     *            the enabled to set
     */
    public void setEnabled(final boolean enabled)
    {
        this.enabled = enabled;
    }

    /**
     * @param adapterConfig
     *            the adapterConfig to set
     */
    public void setAdapterConfig(final AdapterConfig adapterConfig)
    {
        this.adapterConfig = adapterConfig;
    }

    /**
     * @param processRealmRoles
     *            the processRealmRoles to set
     */
    public void setProcessRealmRoles(final boolean processRealmRoles)
    {
        this.processRealmRoles = processRealmRoles;
    }

    /**
     * @param processResourceRoles
     *            the processResourceRoles to set
     */
    public void setProcessResourceRoles(final boolean processResourceRoles)
    {
        this.processResourceRoles = processResourceRoles;
    }

    /**
     * @param realmRoleNameFilter
     *            the realmRoleNameFilter to set
     */
    public void setRealmRoleNameFilter(final RoleNameFilter realmRoleNameFilter)
    {
        this.realmRoleNameFilter = realmRoleNameFilter;
    }

    /**
     * @param realmRoleNameMapper
     *            the realmRoleNameMapper to set
     */
    public void setRealmRoleNameMapper(final RoleNameMapper realmRoleNameMapper)
    {
        this.realmRoleNameMapper = realmRoleNameMapper;
    }

    /**
     * @param defaultResourceRoleNameFilter
     *            the defaultResourceRoleNameFilter to set
     */
    public void setDefaultResourceRoleNameFilter(final RoleNameFilter defaultResourceRoleNameFilter)
    {
        this.defaultResourceRoleNameFilter = defaultResourceRoleNameFilter;
    }

    /**
     * @param defaultResourceRoleNameMapper
     *            the defaultResourceRoleNameMapper to set
     */
    public void setDefaultResourceRoleNameMapper(final RoleNameMapper defaultResourceRoleNameMapper)
    {
        this.defaultResourceRoleNameMapper = defaultResourceRoleNameMapper;
    }

    /**
     * @param resourceRoleNameFilter
     *            the resourceRoleNameFilter to set
     */
    public void setResourceRoleNameFilter(final Map resourceRoleNameFilter)
    {
        this.resourceRoleNameFilter = resourceRoleNameFilter;
    }

    /**
     * @param resourceRoleNameMapper
     *            the resourceRoleNameMapper to set
     */
    public void setResourceRoleNameMapper(final Map resourceRoleNameMapper)
    {
        this.resourceRoleNameMapper = resourceRoleNameMapper;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set extractAuthorities(final AccessToken accessToken)
    {
        final Set authorities;

        if (this.enabled)
        {
            if (this.processRealmRoles || this.processResourceRoles)
            {
                authorities = new HashSet<>();

                if (this.processRealmRoles)
                {
                    final Access realmAccess = accessToken.getRealmAccess();
                    if (realmAccess != null)
                    {
                        LOGGER.debug("Mapping authorities from realm access");

                        final Set realmAuthorites = this.processAccess(realmAccess, this.realmRoleNameFilter,
                                this.realmRoleNameMapper);

                        LOGGER.debug("Mapped authorities from realm access: {}", realmAuthorites);

                        authorities.addAll(realmAuthorites);
                    }
                    else
                    {
                        LOGGER.debug("No realm access provided in access token");
                    }
                }
                else
                {
                    LOGGER.debug("Mapping authorities from realm access is not enabled");
                }

                if (this.processResourceRoles)
                {
                    final Map resourceAccess = accessToken.getResourceAccess();

                    resourceAccess.forEach((r, a) -> {
                        if (this.resourceRoleNameMapper.containsKey(r))
                        {
                            LOGGER.debug("Mapping authorities from resource access on {}", r);

                            final Set resourceAuthorites = this.processAccess(a, this.resourceRoleNameFilter.get(r),
                                    this.resourceRoleNameMapper.get(r));

                            LOGGER.debug("Mapped authorities from resource access on {}: {}", r, resourceAuthorites);

                            authorities.addAll(resourceAuthorites);
                        }
                    });
                }
                else
                {
                    LOGGER.debug("Mapping authorities from resource access is not enabled");
                }
            }
            else
            {
                LOGGER.debug("Mapping authorities is not enabled for either realm or resource access");
                authorities = Collections.emptySet();
            }
        }
        else
        {
            LOGGER.debug("Mapping authorities from access token is not enabled");
            authorities = Collections.emptySet();
        }

        return authorities;
    }

    /**
     * Maps / extracts authorities from a Keycloak access representation.
     *
     * @param access
     *            the access representation component of an access token
     * @param roleNameFilter
     *            the role name filter to use or {@code null} if no filtering should be applied
     * @param roleNameMapper
     *            the role name mapper - can never be {@code null}
     * @return the authorities mapped / extracted from the access representation
     */
    protected Set processAccess(final Access access, final RoleNameFilter roleNameFilter, final RoleNameMapper roleNameMapper)
    {
        ParameterCheck.mandatory("access", access);
        ParameterCheck.mandatory("roleNameMapper", roleNameMapper);

        final Set authorities;

        final Set accessRoles = access.getRoles();
        if (accessRoles != null && !accessRoles.isEmpty())
        {
            LOGGER.debug("Mapping / filtering access roles {}", accessRoles);

            Stream roleStream = accessRoles.stream();
            if (roleNameFilter != null)
            {
                roleStream = roleStream.filter(roleNameFilter::isRoleExposed);
            }
            authorities = roleStream.map(roleNameMapper::mapRoleName).filter(Optional::isPresent).map(Optional::get).map(r -> {
                final AuthorityType authorityType = AuthorityType.getAuthorityType(r);
                String result = r;
                if (authorityType != AuthorityType.GROUP && authorityType != AuthorityType.ROLE)
                {
                    result = AuthorityType.ROLE.getPrefixString() + r;
                }
                return result;
            }).collect(Collectors.toSet());
        }
        else
        {
            LOGGER.debug("Access representation contains no roles");
            authorities = Collections.emptySet();
        }

        return authorities;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy