Java tutorial
/* * Copyright (C) 2005 - 2014 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.com. * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.jaspersoft.jasperserver.api.security.externalAuth.processors; import com.jaspersoft.jasperserver.api.JSException; import com.jaspersoft.jasperserver.api.common.domain.ExecutionContext; import com.jaspersoft.jasperserver.api.common.domain.impl.ExecutionContextImpl; import com.jaspersoft.jasperserver.api.metadata.common.service.impl.hibernate.PersistentObjectResolver; import com.jaspersoft.jasperserver.api.metadata.user.domain.Role; import com.jaspersoft.jasperserver.api.metadata.user.domain.User; import com.jaspersoft.jasperserver.api.metadata.user.domain.client.RoleImpl; import com.jaspersoft.jasperserver.api.metadata.user.service.UserAuthorityService; import com.jaspersoft.jasperserver.api.metadata.user.service.impl.ExternalUserService; import com.jaspersoft.jasperserver.api.security.externalAuth.ExternalAuthProperties; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.Predicate; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.springframework.security.GrantedAuthority; import org.springframework.security.userdetails.UserDetails; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.jaspersoft.jasperserver.api.security.externalAuth.processors.ProcessorData.Key.*; /** * User: dlitvak * Date: 8/22/12 */ public class ExternalUserSetupProcessor extends AbstractExternalUserProcessor { private static final Logger logger = LogManager.getLogger(ExternalUserSetupProcessor.class); private static final String ROLE_SUFFIX = "|*"; // roles that will be created automatically for each user once he is authenticated. private List defaultInternalRoles; private ExternalAuthProperties externalAuthProperties = new ExternalAuthProperties(); private Map<String, String> organizationRoleMap = Collections.emptyMap(); private String permittedExternalRoleNameRegex = "[A-Za-z0-9_]+"; private List<String> adminUsernames; private List<String> defaultAdminRoles; private String conflictingExternalInternalRoleNameSuffix = "EXT"; private Pattern permittedExternalRoleNamePattern = null; private List<Pattern> permittedRolesRegex = new ArrayList<Pattern>(); @Override public void afterPropertiesSet() throws Exception { // Assert.notNull(this.defaultInternalRoles, "Please specify non-null internal default role"); // Assert.notEmpty(this.defaultInternalRoles, "Please specify at least one internal default role"); super.afterPropertiesSet(); permittedExternalRoleNamePattern = Pattern.compile(permittedExternalRoleNameRegex); //clean up organizationRoleMap of invalid internal role values if (!this.organizationRoleMap.isEmpty()) { Map<String, String> cleanedupOrganizationRoleMap = new HashMap<String, String>( organizationRoleMap.size()); for (Map.Entry<String, String> rolePair : organizationRoleMap.entrySet()) { final String rolePairKey = rolePair.getKey(); final String rolePairValue = rolePair.getValue(); if (rolePairValue != null) { String roleNameToValidate = rolePairValue.trim(); roleNameToValidate = roleNameToValidate.endsWith(ROLE_SUFFIX) ? roleNameToValidate.substring(0, roleNameToValidate.length() - ROLE_SUFFIX.length()) : roleNameToValidate; String validAuthorityName = purgeRoleNameOfInvalidChars(roleNameToValidate); if (validAuthorityName.length() == 0) { ProcessorData processorData = ProcessorData.getInstance(); UserDetails userDetails = (UserDetails) processorData.getData(EXTERNAL_AUTH_DETAILS); logger.error("External role " + roleNameToValidate + " has forbidden characters only " + "according to permittedExternalRoleNameRegex: " + permittedExternalRoleNameRegex + ". Skipping it for user: " + (userDetails != null && userDetails.getUsername() != null ? userDetails.getUsername() : "")); continue; } if (rolePairValue.trim().endsWith(ROLE_SUFFIX)) validAuthorityName += ROLE_SUFFIX; cleanedupOrganizationRoleMap.put(rolePairKey.toUpperCase().trim(), validAuthorityName.toUpperCase().trim()); } } this.organizationRoleMap = cleanedupOrganizationRoleMap; } } protected User getUser() { ProcessorData processorData = ProcessorData.getInstance(); UserDetails userDetails = (UserDetails) processorData.getData(EXTERNAL_AUTH_DETAILS); if (logger.isDebugEnabled()) logger.debug("Getting user obj for username " + (userDetails != null ? userDetails.getUsername() : ": userDetails is null.")); return getUserAuthorityService().getUser(new ExecutionContextImpl(), userDetails.getUsername()); } @Override public void process() { ProcessorData processorData = ProcessorData.getInstance(); UserDetails userDetails = (UserDetails) processorData.getData(EXTERNAL_AUTH_DETAILS); try { String userName = userDetails.getUsername(); if (logger.isDebugEnabled()) logger.debug("Setting up external user: " + userName); User user = getUser(); String logoutUrl = externalAuthProperties != null ? externalAuthProperties.getLogoutUrl() : null; if (user == null) { user = createNewExternalUser(userName); } else if (!user.isEnabled()) { throw new JSException("External user " + user.getUsername() + " was disabled on jasperserver. Please contact an admin user to re-enable. " + (logoutUrl != null && logoutUrl.length() > 0 ? "Click <a href=\"" + logoutUrl + "\">logout</a> to exit from external system." : "")); } else if (!user.isExternallyDefined()) { throw new JSException("Internally defined user " + user.getUsername() + " already exists. Please contact an admin user to resolve the issue. " + (logoutUrl != null && logoutUrl.length() > 0 ? "Click <a href=\"" + logoutUrl + "\">logout</a> to exit from external system." : "")); } GrantedAuthority[] grantedAuthorities = (GrantedAuthority[]) processorData .getData(EXTERNAL_AUTHORITIES); final String tenantId = (String) processorData.getData(EXTERNAL_JRS_USER_TENANT_ID); Set<Role> externalRoles = convertGrantedAuthoritiesToRoles(grantedAuthorities, tenantId); user.setTenantId(tenantId); alignInternalAndExternalUser(externalRoles, user); if (logger.isDebugEnabled()) logger.debug("External user " + userName + " has been synchronized."); ((ExternalUserService) getUserAuthorityService()).makeUserLoggedIn(user); } catch (RuntimeException e) { String userName = (userDetails != null ? userDetails.getUsername() : ""); logger.error("Error processing external user " + userName + ": " + e.getMessage()); throw e; } } /** * New user created from given authentication details. No password is set or needed. * Roles are set elsewhere. * * @param userName * @return created User */ protected User createNewExternalUser(String userName) { User user = getUserAuthorityService().newUser(new ExecutionContextImpl()); user.setUsername(userName); // If it is externally authenticated, no save of password //user.setPassword(userDetails.getPassword()); user.setFullName(userName); // We don't know the real name user.setExternallyDefined(true); user.setEnabled(true); logger.warn("Created new external user: " + user.getUsername()); return user; } protected Set persistRoles(Set<Role> roles) { Set<Role> persistedRoles = new HashSet<Role>(); for (Iterator<Role> iter = roles.iterator(); iter.hasNext();) { Role r = iter.next(); persistedRoles.add(getOrCreateRole(r)); } return persistedRoles; } protected void alignInternalAndExternalUser(Set remoteExternalUserRoles, User user) { Set<Role> jrsUserRoles = user.getRoles(); logger.info("Starting align for user: " + user.getFullName() + " with remoteExternalUserRoles at size of " + remoteExternalUserRoles.size()); Collection jrsInternalUserRoles = CollectionUtils.select(jrsUserRoles, new Predicate() { public boolean evaluate(Object input) { if (!(input instanceof Role)) { return false; } return !((Role) input).isExternallyDefined(); } }); if (logger.isDebugEnabled()) { logger.debug("jrsInternalUserRoles: " + roleCollectionToString(jrsInternalUserRoles)); } Collection<Role> jrsInternalRolesNotInRoleMap = CollectionUtils.select(jrsInternalUserRoles, new Predicate() { public boolean evaluate(Object input) { return !getOrganizationRoleMap().containsValue(((Role) input).getRoleName()) && !getOrganizationRoleMap() .containsValue(((Role) input).getRoleName() + ROLE_SUFFIX); } }); if (logger.isDebugEnabled()) { logger.debug("jrsInternalRolesNotInRoleMap: " + roleCollectionToString(jrsInternalRolesNotInRoleMap)); } //assign default internal roles if needed Collection<Role> defaultInternalRolesToAdd = CollectionUtils .subtract(getNewDefaultInternalRoles(user.getUsername()), jrsInternalRolesNotInRoleMap); jrsInternalRolesNotInRoleMap.addAll(defaultInternalRolesToAdd); Collection<Role> newUserRoles = remoteExternalUserRoles; newUserRoles.addAll(jrsInternalRolesNotInRoleMap); if (logger.isDebugEnabled()) { logger.debug("internal and external roles to persist: " + roleCollectionToString(newUserRoles)); } persistRoles(new HashSet<Role>(newUserRoles)); user.setRoles(new HashSet<Role>(newUserRoles)); updateUserAttributes(user); getUserAuthorityService().putUser(new ExecutionContextImpl(), user); } /** * A 'hook' method to extend when additional user columns need to be populated (e.g. emailAddress) * * @param user */ protected void updateUserAttributes(User user) { } /** * Get a set of roles based on the given GrantedAuthority[]. Roles are created * in the metadata if they do not exist. * * * @param authorities from authenticated user * @param tenantId * @return Set of externally defined Roles * * protected scope for unit testing */ protected Set<Role> convertGrantedAuthoritiesToRoles(GrantedAuthority[] authorities, String tenantId) { Set<Role> set = new HashSet<Role>(); if (authorities == null || authorities.length == 0) return set; final UserAuthorityService userAuthorityService = getUserAuthorityService(); for (GrantedAuthority auth : authorities) { String authorityName = auth.getAuthority(); if (authorityName == null || !isRoleSynchronizable(authorityName)) continue; Role role = userAuthorityService.newRole(new ExecutionContextImpl()); String internalRoleName = organizationRoleMap.get(authorityName.toUpperCase()); if (internalRoleName != null) { if (internalRoleName.endsWith(ROLE_SUFFIX)) { internalRoleName = internalRoleName.substring(0, internalRoleName.length() - ROLE_SUFFIX.length()); role.setTenantId(tenantId); } role.setRoleName(internalRoleName); role.setExternallyDefined(false); //role is mapped to internal } else { String authorityNameValid = purgeRoleNameOfInvalidChars(authorityName); if (authorityNameValid.length() == 0) { if (Level.WARN.isGreaterOrEqual(logger.getEffectiveLevel())) { ProcessorData processorData = ProcessorData.getInstance(); UserDetails userDetails = (UserDetails) processorData.getData(EXTERNAL_AUTH_DETAILS); logger.warn("External role " + authorityName + " has forbidden characters only " + "according to permittedExternalRoleNameRegex: " + permittedExternalRoleNameRegex + ". Skipping it for user: " + (userDetails != null && userDetails.getUsername() != null ? userDetails.getUsername() : "")); } continue; } authorityName = authorityNameValid.toUpperCase().trim(); role.setRoleName(authorityName); role.setExternallyDefined(true); role.setTenantId(tenantId); } set.add(role); } return set; } /** * Extention point for deciding whether the role is synchronizable. By default, the regex list is empty causing all * the roles to be entered into db. * * @param roleName - role to test whether it matches any of regex's in #permittedRolesRegex * @return whether the role matches any regex. */ protected boolean isRoleSynchronizable(String roleName) { if (this.permittedRolesRegex.isEmpty()) return true; for (Pattern regex : this.permittedRolesRegex) { if (regex.matcher(roleName).matches()) return true; } logger.warn("Role " + roleName + " did not match any regex in permittedRolesRegex list"); return false; } private String roleCollectionToString(Collection coll) { Iterator it = coll.iterator(); StringBuffer rolesPrint = new StringBuffer(); while (it.hasNext()) { String s = ((Role) it.next()).getRoleName(); rolesPrint.append(s).append("\n"); } return rolesPrint.toString(); } /** * Get a set of roles that are the defaults for a new external user. Roles are created * in the metadata if they do not exist. * * @return Set of internally defined Roles */ private Set getNewDefaultInternalRoles(String username) { ExecutionContext executionContext = new ExecutionContextImpl(); Set<Role> set = new HashSet<Role>(); List<String> internalRoles = Collections.<String>emptyList(); if (this.adminUsernames != null && this.adminUsernames.contains(username) && this.defaultAdminRoles != null && this.defaultAdminRoles.size() > 0) internalRoles = this.defaultAdminRoles; else if (this.defaultInternalRoles != null && this.defaultInternalRoles.size() > 0) internalRoles = this.defaultInternalRoles; else return set; for (String roleName : internalRoles) { Role role = getUserAuthorityService().getRole(executionContext, roleName); if (role == null) { role = getUserAuthorityService().newRole(executionContext); role.setRoleName(roleName); role.setExternallyDefined(false); getUserAuthorityService().putRole(executionContext, role); } set.add(role); } return set; } private Role getOrCreateRole(Role role) { Role existingRole = null; UserAuthorityService userAuthorityService = getUserAuthorityService(); if (userAuthorityService instanceof PersistentObjectResolver) existingRole = (Role) ((PersistentObjectResolver) userAuthorityService).getPersistentObject(role); //when internal role name&tenantId coincide with the external role, modify the external // role name in order to avoid overwriting the roles (bug 31324). if (existingRole != null && role.isExternallyDefined() && !existingRole.isExternallyDefined()) { role.setRoleName(role.getRoleName() + "_" + this.conflictingExternalInternalRoleNameSuffix); existingRole = null; } //role does not exist. Need to create it. if (existingRole == null) userAuthorityService.putRole(new ExecutionContextImpl(), role); return role; } public List getDefaultInternalRoles() { return defaultInternalRoles; } public void setDefaultInternalRoles(List defaultInternalRoles) { this.defaultInternalRoles = defaultInternalRoles; } public void setExternalAuthProperties(ExternalAuthProperties externalAuthProperties) { this.externalAuthProperties = externalAuthProperties; } public ExternalAuthProperties getExternalAuthProperties() { return externalAuthProperties; } public String getPermittedExternalRoleNameRegex() { return permittedExternalRoleNameRegex; } public void setPermittedExternalRoleNameRegex(String permittedExternalRoleNameRegex) { this.permittedExternalRoleNameRegex = permittedExternalRoleNameRegex; } public void setOrganizationRoleMap(Map<String, String> organizationRoleMapParam) { this.organizationRoleMap = organizationRoleMapParam; } /** Method replacing invalid role chars (according to permittedExternalRoleNamePattern) with _ */ private String purgeRoleNameOfInvalidChars(String authorityName) { Matcher authorityMatcher = permittedExternalRoleNamePattern.matcher(authorityName); StringBuffer validAuthorityNameBuff = new StringBuffer(); while (authorityMatcher.find()) { String matchedSubstr = authorityMatcher.group().trim(); if (matchedSubstr.length() > 0) { if (validAuthorityNameBuff.length() > 0) validAuthorityNameBuff.append("_"); validAuthorityNameBuff.append(matchedSubstr); } } if (logger.isDebugEnabled()) { logger.debug("External role " + authorityName + " has forbidden characters " + "according to permittedExternalRoleNameRegex: " + permittedExternalRoleNameRegex + ". Replacing those character sequences with _. Result: " + validAuthorityNameBuff); } return validAuthorityNameBuff.toString(); } /** * Names of external users that are converted into admins * @param adminUsernames */ public void setAdminUsernames(List<String> adminUsernames) { this.adminUsernames = adminUsernames; } public List<String> getAdminUsernames() { return adminUsernames; } /** * Default admin roles that are assigned to the users in {@link #adminUsernames} * @param defaultAdminRoles */ public void setDefaultAdminRoles(List<String> defaultAdminRoles) { this.defaultAdminRoles = defaultAdminRoles; } public List<String> getDefaultAdminRoles() { return defaultAdminRoles; } public void setConflictingExternalInternalRoleNameSuffix(String conflictingExternalInternalRoleNameSuffix) { this.conflictingExternalInternalRoleNameSuffix = conflictingExternalInternalRoleNameSuffix; } public String getConflictingExternalInternalRoleNameSuffix() { return conflictingExternalInternalRoleNameSuffix; } public Map<String, String> getOrganizationRoleMap() { return organizationRoleMap; } public void setPermittedRolesRegex(List<String> permittedRoleRegexList) { for (String r : permittedRoleRegexList) this.permittedRolesRegex.add(Pattern.compile(r)); } }