/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.security.internal.authorization;
import org.apache.commons.lang.StringUtils;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.security.Role;
import org.seedstack.seed.security.RoleMapping;
import org.seedstack.seed.security.Scope;
import org.seedstack.seed.security.SecurityConfig;
import org.seedstack.seed.security.internal.SecurityErrorCode;
import org.seedstack.seed.security.principals.PrincipalProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Resolve the role mappings from a Configuration:
*
* <pre>
* admin = ADMIN, DEV
* </pre>
*
* Means that the admin local role will be given to any identified subject to which the realm(s) have given the ADMIN
* <strong>or</strong> DEV role.
* <p>
* Roles can be given automatically to any user (whatever their real authorizations given by the realms) by using
* the '*' wildcard:
*
* <pre>
* reader = *
* </pre>
*
* Means that every identified subject will have the reader role.
* <p>
* This implementation handles simple scopes:
*
* <pre>
* admin = ADMIN.{SCOPE}, DEV
* </pre>
*
* Means that subjects having the ADMIN.FR role from the realm(s) (like an LDAP directory) will be given the titi
* local role within the FR scope only.
*/
public class ConfigurationRoleMapping implements RoleMapping {
/**
* wildcard used to give role to every user
*/
private final static String GLOBAL_WILDCARD = "*";
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationRoleMapping.class);
/**
* map : role = mapped roles
*/
private final Map<String, Set<String>> map = new HashMap<>();
/**
* roles given to every user
*/
private final Set<String> everybodyRoles = new HashSet<>();
@Inject
private Map<String, Class<? extends Scope>> scopeClasses;
@Override
public Collection<Role> resolveRoles(Set<String> auths, Collection<PrincipalProvider<?>> principalProviders) {
Map<String, Role> roleMap = new HashMap<>();
for (String role : everybodyRoles) {
roleMap.put(role, new Role(role));
}
for (String auth : auths) {
if (map.containsKey(auth)) {
for (String roleName : map.get(auth)) {
if (!roleMap.containsKey(roleName)) {
roleMap.put(roleName, new Role(roleName));
}
}
} else {
// maybe a scoped auth
for (Map.Entry<String, Class<? extends Scope>> scopeClass : scopeClasses.entrySet()) {
for (String mapKey : map.keySet()) {
String wildcard = String.format("{%s}", scopeClass.getKey());
if (mapKey.contains(wildcard) && auth.matches(convertToRegex(mapKey, wildcard))) {
String scopeValue = findScope(wildcard, mapKey, auth);
Set<String> foundRoleNames = map.get(mapKey);
for (String foundRoleName : foundRoleNames) {
Role currentRole = getOrCreateRoleInMap(foundRoleName, roleMap);
Scope scope;
try {
Constructor<? extends Scope> constructor = scopeClass.getValue().getConstructor(String.class);
scope = constructor.newInstance(scopeValue);
} catch (Exception e) {
throw SeedException.wrap(e, SecurityErrorCode.UNABLE_TO_CREATE_SCOPE).put("scopeName", scopeClass.getValue().getName());
}
currentRole.getScopes().add(scope);
}
}
}
}
}
}
return roleMap.values();
}
private Role getOrCreateRoleInMap(String roleName, Map<String, Role> roleMap) {
Role currentRole;
if (roleMap.containsKey(roleName)) {
currentRole = roleMap.get(roleName);
} else {
currentRole = new Role(roleName);
roleMap.put(roleName, currentRole);
}
return currentRole;
}
@Inject
void readConfiguration(SecurityConfig securityConfig) {
if (securityConfig.getRoles().isEmpty()) {
LOGGER.warn("{} defined, but its configuration is empty.", getClass().getSimpleName());
return;
}
map.clear();
for (Map.Entry<String, Set<String>> entry : securityConfig.getRoles().entrySet()) {
for (String permission : entry.getValue()) {
if (GLOBAL_WILDCARD.equals(permission)) {
everybodyRoles.add(entry.getKey());
} else {
Set<String> roles = map.get(permission);
if (roles == null) {
roles = new HashSet<>();
}
roles.add(entry.getKey());
map.put(permission, roles);
}
}
}
}
/**
* Finds the scope in a string that corresponds to a role with {wildcard}.<br>
* For example, if wildcardAuth is toto.{SCOPE} and auth is toto.foo then
* scope is foo.
*
* @param wildcard the wildcard to search for
* @param wildcardAuth auth with {wildcard}
* @param auth auth that corresponds
* @return the scope.
*/
private String findScope(String wildcard, String wildcardAuth, String auth) {
String scope;
String before = StringUtils.substringBefore(wildcardAuth, wildcard);
String after = StringUtils.substringAfter(wildcardAuth, wildcard);
if (StringUtils.startsWith(wildcardAuth, wildcard)) {
scope = StringUtils.substringBefore(auth, after);
} else if (StringUtils.endsWith(wildcardAuth, wildcard)) {
scope = StringUtils.substringAfter(auth, before);
} else {
scope = StringUtils.substringBetween(auth, before, after);
}
return scope;
}
private String convertToRegex(String value, String wildcard) {
return String.format("\\Q%s\\E", value.replace(wildcard, "\\E.*\\Q"));
}
}