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

com.icthh.xm.commons.permission.service.rolestrategy.MultiRoleStrategy Maven / Gradle / Ivy

The newest version!
package com.icthh.xm.commons.permission.service.rolestrategy;

import com.icthh.xm.commons.exceptions.SkipPermissionException;
import com.icthh.xm.commons.permission.access.ResourceFactory;
import com.icthh.xm.commons.permission.access.subject.Subject;
import com.icthh.xm.commons.permission.domain.EnvironmentVariable;
import com.icthh.xm.commons.permission.domain.Permission;
import com.icthh.xm.commons.permission.domain.ReactionStrategy;
import com.icthh.xm.commons.permission.service.AuthenticationSecurityExpressionMethods;
import com.icthh.xm.commons.permission.service.PermissionEvaluationContextBuilder;
import com.icthh.xm.commons.permission.service.PermissionService;
import com.icthh.xm.commons.permission.service.RoleService;
import com.icthh.xm.commons.permission.service.translator.SpelTranslator;
import com.icthh.xm.commons.permission.utils.RequestHeaderUtils;
import com.icthh.xm.commons.security.XmAuthenticationContext;
import com.icthh.xm.commons.security.XmAuthenticationContextHolder;
import com.icthh.xm.commons.tenant.TenantContextHolder;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.event.Level;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.SpelNode;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.icthh.xm.commons.permission.constants.RoleConstant.SUPER_ADMIN;
import static com.icthh.xm.commons.permission.utils.CollectionUtils.listsNotEqualsIgnoreOrder;
import static com.icthh.xm.commons.tenant.TenantContextUtils.getRequiredTenantKeyValue;
import static java.lang.String.format;
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isEmpty;

@Slf4j
@RequiredArgsConstructor
@Component("multiRoleStrategy")
public class MultiRoleStrategy implements RoleStrategy {

    private static final String LOG_KEY = "log";

    private final XmAuthenticationContextHolder xmAuthenticationContextHolder;
    private final TenantContextHolder tenantContextHolder;
    private final PermissionService permissionService;
    private final ResourceFactory resourceFactory;
    private final RoleService roleService;
    private final PermissionEvaluationContextBuilder permissionEvaluationContextBuilder;

    /**
     * Check permission for role and privilege key only.
     *
     * @param authentication the authentication
     * @param privilege the privilege key
     * @return true if permitted
     */
    public boolean hasPermission(Authentication authentication, Object privilege) {
        return checkRole(authentication, privilege, true)
            || checkPermission(authentication, null, privilege, false, true);
    }

    /**
     * Check permission for role, privilege key and resource condition.
     *
     * @param authentication the authentication
     * @param resource the resource
     * @param privilege the privilege key
     * @return true if permitted
     */
    public boolean hasPermission(
        Authentication authentication,
        Object resource,
        Object privilege
    ) {
        boolean logPermission = isLogPermission(resource);
        return checkRole(authentication, privilege, logPermission)
            || checkPermission(authentication, resource, privilege, true, logPermission);
    }

    /**
     * Check permission for role, privilege key, new resource and old resource.
     *
     * @param authentication the authentication
     * @param resource the old resource
     * @param resourceType the resource type
     * @param privilege the privilege key
     * @return true if permitted
     */
    @SuppressWarnings("unchecked")
    public boolean hasPermission(
        Authentication authentication,
        Serializable resource,
        String resourceType,
        Object privilege
    ) {
        boolean logPermission = isLogPermission(resource);
        if (checkRole(authentication, privilege, logPermission)) {
            return true;
        }
        if (resource != null) {
            Object resourceId = ((Map) resource).get("id");
            if (resourceId != null) {
                ((Map) resource).put(resourceType,
                    resourceFactory.getResource(resourceId, resourceType));
            }
        }
        return checkPermission(authentication, resource, privilege, true, logPermission);
    }

    /**
     * Create condition with replaced subject variables.
     *
     * 

SpEL condition translated to SQL condition with replacing #returnObject to returnObject * and enriching #subject.* from Subject object (see {@link Subject}). * *

As an option, SpEL could be translated to SQL * via {@link SpelExpression} method {@code getAST()} with traversing through {@link SpelNode} nodes and building SQL * expression. * * @param authentication the authentication * @param privilegeKey the privilege key * @param translator the spel translator * @return condition if permitted, or null */ public String createCondition( Authentication authentication, Object privilegeKey, SpelTranslator translator ) { if (!hasPermission(authentication, privilegeKey)) { throw new AccessDeniedException("Access is denied"); } Collection roleKeys = getRoleKeys(authentication); if (roleKeys.contains(SUPER_ADMIN)) { return EMPTY; } Map subjects = getSubjects(roleKeys); Collection permissionsRoles = getPermissions(roleKeys, privilegeKey).stream() .filter(permission -> nonNull(permission.getResourceCondition())) .map(permission -> translator .translate(permission.getResourceCondition().getExpressionString(), subjects.get(permission.getRoleKey()))) .map(condition -> format("(%s)", condition)) .collect(toList()); if (permissionsRoles.size() > 1) { return String.join(" OR ", permissionsRoles); } return permissionsRoles.stream() .findAny() .orElse(EMPTY); } @SuppressWarnings("unchecked") @SneakyThrows boolean checkPermission( Authentication authentication, Object resource, Object privilegeKey, boolean checkCondition, boolean logPermission ) { Collection roleKey = getRoleKeys(authentication); Map resources = new HashMap<>(); if (resource != null) { resources.putAll((Map) resource); } resources.put("subject", getSubjects(roleKey).values()); resources.put("oauth2", new AuthenticationSecurityExpressionMethods(authentication)); Map env = new HashMap<>(); env.put( EnvironmentVariable.IP.getName(), xmAuthenticationContextHolder.getContext().getRemoteAddress().orElse(null) ); resources.put("env", env); EvaluationContext context = permissionEvaluationContextBuilder.build(resources);; Collection permissions = getPermissions(roleKey, privilegeKey); if (!isPermissionEnabled(permissions)) { log(logPermission, Level.ERROR, "access denied: privilege={}, role={}, userKey={} due to privilege is not permitted", privilegeKey, roleKey, getUserKey() ); return false; } boolean validCondition = true; if (!isConditionValid(permissions, context, Permission::getEnvCondition)) { log(logPermission, Level.ERROR, "access denied: privilege={}, role={}, userKey={} due to env condition: {} with context [{}]", privilegeKey, roleKey, getUserKey(), permissions.stream() .map(Permission::getEnvCondition) .filter(Objects::nonNull) .map(Expression::getExpressionString) .collect(toList()), resources ); validCondition = false; } if (checkCondition && !isConditionValid(permissions, context, Permission::getResourceCondition)) { log(logPermission, Level.ERROR, "access denied: privilege={}, role={}, userKey={} due to resource condition: {} with context [{}] ", privilegeKey, roleKey, getUserKey(), permissions.stream() .map(Permission::getResourceCondition) .filter(Objects::nonNull) .map(Expression::getExpressionString) .collect(toList()), resources ); validCondition = false; } List reactionStrategies = permissions.stream() .map(Permission::getReactionStrategy) .filter(Objects::nonNull) .collect(toList()); if (!validCondition && reactionStrategies.contains(ReactionStrategy.SKIP)) { throw new SkipPermissionException( "Skip permission", permissions.stream() .map(permission -> permission.getRoleKey() + ":" + permission.getPrivilegeKey()) .collect(toList()) ); } else if (!validCondition) { return false; } log(logPermission, Level.INFO, "access granted: privilege={}, role={}, userKey={}", privilegeKey, roleKey, getUserKey() ); return true; } private boolean checkRole(Authentication authentication, Object privilege, boolean logPermission) { Collection roleKeys = getRoleKeys(authentication); if (roleKeys.contains(SUPER_ADMIN)) { log(logPermission, Level.INFO, "access granted: privilege={}, role=SUPER-ADMIN, userKey={}", privilege, getUserKey() ); return true; } if (listsNotEqualsIgnoreOrder( roleService.getRoles(getRequiredTenantKeyValue(tenantContextHolder.getContext())).keySet(), roleKeys )) { log( logPermission, Level.ERROR, "access denied: privilege={}, role={}, userKey={} due to role is missing", privilege, roleKeys, getUserKey() ); throw new AccessDeniedException("Access is denied"); } return false; } private boolean isConditionValid(Collection permissions, EvaluationContext context, Function func) { return permissions.stream() .anyMatch(permission -> { Expression expression = func.apply(permission); if (isNull(expression) || isEmpty(expression.getExpressionString())) { return true; } try { return expression.getValue(context, Boolean.class); } catch (Exception e) { log.error("Exception while getting value ", e); return false; } } ); } private boolean isPermissionEnabled(Collection permissions) { return permissions.stream() .filter(Objects::nonNull) .anyMatch(permission -> !permission.isDisabled()); } Map getSubjects(Collection roleKeys) { XmAuthenticationContext authContext = xmAuthenticationContextHolder.getContext(); return roleKeys.stream() .collect(Collectors.toMap( roleKey -> roleKey, roleKey -> new Subject( authContext.getLogin().orElse(null), authContext.getUserKey().orElse(null), roleKey ) ) ); } private String getUserKey() { return xmAuthenticationContextHolder.getContext().getUserKey().orElse(null); } Collection getRoleKeys(Authentication authentication) { return authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(toList()); } Collection getPermissions(Collection roleKeys, Object privilegeKey) { Map permissions = permissionService .getPermissions(getRequiredTenantKeyValue(tenantContextHolder.getContext())); List rolesPermissions = roleKeys.stream() .map(roleKey -> permissions.get(roleKey + ":" + privilegeKey)) .filter(Objects::nonNull) .collect(toList()); return rolesPermissions.stream() .collect(groupingBy(Permission::getPrivilegeKey)) .entrySet().stream() .flatMap(this::toPermissions) .collect(toList()); } private Stream toPermissions(Entry> privilegeKeyPermissions) { Optional permissionAllowsAll = privilegeKeyPermissions.getValue().stream() .filter(permission -> isNull(permission.getResourceCondition())) .filter(permission -> !permission.isDisabled()) .findAny(); if (permissionAllowsAll.isPresent()) { return permissionAllowsAll.stream(); } return privilegeKeyPermissions.getValue().stream(); } @SuppressWarnings("unchecked") private static boolean isLogPermission(Object resource) { if (resource != null && resource instanceof Map) { Map resourceMap = (Map) resource; Object logFlag = resourceMap.get(LOG_KEY); if (logFlag != null && logFlag instanceof Boolean) { resourceMap.remove(LOG_KEY); return (Boolean) logFlag; } } return true; } private static void log(boolean allowToLog, Level logLevel, String logMessage, Object... logArgs) { if (!allowToLog) { return; } switch (logLevel) { case INFO: log.info(logMessage, logArgs); break; case ERROR: log.error(logMessage, logArgs); break; default: break; } } @SneakyThrows private static Method lookupGetRequestHeaderMethod() { return RequestHeaderUtils.class.getDeclaredMethod("getRequestHeader", String.class); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy