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

com.sun.enterprise.security.acl.RoleMapper Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2016-2018] [Payara Foundation and/or its affiliates]

package com.sun.enterprise.security.acl;

import static com.sun.enterprise.security.common.AppservAccessController.privileged;
import static com.sun.logging.LogDomains.SECURITY_LOGGER;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.security.auth.Subject;

import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.deployment.common.RootDeploymentDescriptor;
import org.glassfish.deployment.common.SecurityRoleMapper;
import org.glassfish.internal.api.Globals;
import org.glassfish.internal.data.ApplicationInfo;
import org.glassfish.internal.data.ApplicationRegistry;
import org.glassfish.security.common.Group;
import org.glassfish.security.common.PrincipalImpl;
import org.glassfish.security.common.Role;

import com.sun.enterprise.config.serverbeans.SecurityService;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.BundleDescriptor;
import com.sun.enterprise.security.common.AppservAccessController;
import com.sun.logging.LogDomains;

/**
 * This class maintains a mapping of users and groups to application specific roles.
 *
 * 

* Using this class the mapping information can be maintained and queried at a later time. * * @author Harpreet Singh */ public class RoleMapper implements Serializable, SecurityRoleMapper { private static final long serialVersionUID = -4455830942007736853L; private static final Logger _logger = LogDomains.getLogger(RoleMapper.class, SECURITY_LOGGER); private String appName; private final Map roleToSubject = new HashMap(); // Default mapper to emulate Servlet default p2r mapping semantics private String defaultP2RMappingClassName = null; private DefaultRoleToSubjectMapping defaultRTSM = new DefaultRoleToSubjectMapping(); /* * the following 2 Maps are a copy of roleToSubject. This is added as a support for deployment. Should think of * optimizing this. */ private final Map> roleToPrincipal = new HashMap>(); private final Map> roleToGroup = new HashMap>(); /* The following objects are used to detect conflicts during deployment */ /* * .....Mapping of module (or application) that is presently calling assignRole(). It is set by the startMappingFor() * method. After all the subjects have been assigned, stopMappingFor() is called and then the mappings can be checked * against those previously assigned. */ private Mapping currentMapping; // These override roles mapped in submodules. private Set topLevelRoles; // used to identify the application level mapping file private static final String TOP_LEVEL = "sun-application.xml mapping file"; // used to log a warning only one time private boolean conflictLogged; // store roles that have a conflict so they are not re-mapped private Set conflictedRoles; /* End conflict detection objects */ private Boolean appDefaultMapping; private transient SecurityService secService = null; RoleMapper(String appName) { this.appName = appName; secService = Globals.getDefaultHabitat().getService(SecurityService.class, ServerEnvironment.DEFAULT_INSTANCE_NAME); defaultP2RMappingClassName = getDefaultP2RMappingClassName(); } /** * Copy constructor. This is called from the JSR88 implementation. This is not stored into the internal rolemapper maps. */ public RoleMapper(RoleMapper r) { this.appName = r.getName(); for (Iterator it = r.getRoles(); it.hasNext();) { String role = it.next(); // recover groups Enumeration groups = r.getGroupsAssignedTo(new Role(role)); Set groupsToRole = new HashSet(); for (; groups.hasMoreElements();) { Group gp = groups.nextElement(); groupsToRole.add(new Group(gp.getName())); addRoleToPrincipal(gp, role); } this.roleToGroup.put(role, groupsToRole); // Recover principles Enumeration users = r.getUsersAssignedTo(new Role(role)); Set usersToRole = new HashSet(); for (; users.hasMoreElements();) { PrincipalImpl gp = (PrincipalImpl) users.nextElement(); usersToRole.add(new PrincipalImpl(gp.getName())); addRoleToPrincipal(gp, role); } this.roleToPrincipal.put(role, usersToRole); } } private boolean getAppDefaultRoleMapping() { if (appDefaultMapping != null) { return appDefaultMapping; } appDefaultMapping = false; if (secService != null) { appDefaultMapping = Boolean.parseBoolean(secService.getActivateDefaultPrincipalToRoleMapping()); if (appDefaultMapping) { // if set explicitly in the security service allow default mapping return appDefaultMapping; } } ApplicationRegistry appRegistry = Globals.getDefaultHabitat().getService(ApplicationRegistry.class); ApplicationInfo appInfo = appRegistry.get(appName); if (appInfo == null) { return appDefaultMapping; } Application app = appInfo.getMetaData(Application.class); BundleDescriptor bd = app.getModuleByUri(appName); appDefaultMapping = bd == null ? app.isDefaultGroupPrincipalMapping() : app.getModuleByUri(appName).isDefaultGroupPrincipalMapping(); return appDefaultMapping; } /** * @return The application/module name for this RoleMapper */ @Override public String getName() { return appName; } /** * @param name The application/module name */ @Override public void setName(String name) { this.appName = name; } /** * @param principal A principal that corresponds to the role * @param role A role corresponding to this principal */ private void addRoleToPrincipal(Principal principal, String role) { Subject subject = roleToSubject.get(role); Subject sub = subject == null ? new Subject() : subject; privileged(() -> sub.getPrincipals().add(principal)); roleToSubject.put(role, sub); } /** * Remove the given role-principal mapping * * @param role, Role object * @param principal, the principal */ @Override public void unassignPrincipalFromRole(Role role, Principal principal) { String mrole = role.getName(); final Subject sub = roleToSubject.get(mrole); final Principal p = principal; if (sub != null) { AppservAccessController.doPrivileged(new PrivilegedAction() { @Override public java.lang.Object run() { sub.getPrincipals().remove(p); return null; } }); roleToSubject.put(mrole, sub); } if (principal instanceof Group) { Set groups = roleToGroup.get(mrole); if (groups != null) { groups.remove(principal); roleToGroup.put(mrole, groups); } } else { Set principals = roleToPrincipal.get(mrole); if (principals != null) { principals.remove(principal); roleToPrincipal.put(mrole, principals); } } } // @return true or false depending on activation of // the mapping via domain.xml or property in the glassfish descriptor boolean isDefaultRTSMActivated() { return (defaultP2RMappingClassName != null) && getAppDefaultRoleMapping(); } /** * Returns the RoleToSubjectMapping for the RoleMapping * * @return Map of role->subject mapping */ @Override public Map getRoleToSubjectMapping() { // this causes the last currentMapping information to be added checkAndAddMappings(); assert roleToSubject != null; if (roleToSubject.isEmpty() && isDefaultRTSMActivated()) { return defaultRTSM; } return roleToSubject; } // The method that does the work for assignRole(). private void internalAssignRole(Principal p, Role r) { String role = r.getName(); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "SECURITY:RoleMapper Assigning Role " + role + " to " + p.getName()); } addRoleToPrincipal(p, role); if (p instanceof Group) { Set groups = roleToGroup.get(role); if (groups == null) { groups = new HashSet(); } groups.add((Group) p); roleToGroup.put(role, groups); } else { Set principals = roleToPrincipal.get(role); if (principals == null) { principals = new HashSet(); } principals.add(p); roleToPrincipal.put(role, principals); } } /** * Assigns a Principal to the specified role. This method delegates work to internalAssignRole() after checking for * conflicts. RootDeploymentDescriptor added as a fix for: https://glassfish.dev.java.net/issues/show_bug.cgi?id=2475 * * The first time this is called, a new Mapping object is created to store the role mapping information. When called * again from a different module, the old mapping information is checked and stored and a new Mapping object is created. * * @param p The principal that needs to be assigned to the role. * @param r The Role the principal is being assigned to. * @param rdd The descriptor of the module containing the role mapping */ @Override public void assignRole(Principal p, Role r, RootDeploymentDescriptor rdd) { assert rdd != null; String callingModuleID = getModuleID(rdd); if (currentMapping == null) { currentMapping = new Mapping(callingModuleID); } else if (!callingModuleID.equals(currentMapping.owner)) { checkAndAddMappings(); currentMapping = new Mapping(callingModuleID); } // when using the top level mapping if (callingModuleID.equals(TOP_LEVEL) && topLevelRoles == null) { topLevelRoles = new HashSet(); } // store principal and role temporarily until stopMappingFor called currentMapping.addMapping(p, r); } /** * Returns an enumeration of roles for this rolemapper. */ @Override public Iterator getRoles() { assert roleToSubject != null; return roleToSubject.keySet().iterator(); // All the roles } /** * Returns an enumeration of Groups assigned to the given role * * @param The Role to which the groups are assigned to. */ @Override public Enumeration getGroupsAssignedTo(Role r) { assert roleToGroup != null; Set s = roleToGroup.get(r.getName()); if (s == null) { return Collections.enumeration(Collections.emptySet()); } return Collections.enumeration(s); } /** * Returns an enumeration of Principals assigned to the given role * * @param The Role to which the principals are assigned to. */ @Override public Enumeration getUsersAssignedTo(Role r) { assert roleToPrincipal != null; Set s = roleToPrincipal.get(r.getName()); if (s == null) { return Collections.enumeration(Collections.emptySet()); } return Collections.enumeration(s); } @Override public void unassignRole(Role r) { if (r != null) { String role = r.getName(); roleToSubject.remove(role); roleToPrincipal.remove(role); roleToGroup.remove(role); } } /** * @return String. String representation of the RoleToPrincipal Mapping */ @Override public String toString() { StringBuilder s = new StringBuilder("RoleMapper:"); for (Iterator e = this.getRoles(); e.hasNext();) { String r = e.next(); s.append("\n\tRole (").append(r).append(") has Principals("); Subject sub = roleToSubject.get(r); Iterator it = sub.getPrincipals().iterator(); for (; it.hasNext();) { Principal p = it.next(); s.append(p.getName()).append(" "); } s.append(")"); } if (_logger.isLoggable(Level.FINER)) { _logger.log(Level.FINER, s.toString()); } return s.toString(); } /** * @returns the class name used for default Principal to role mapping return null if default P2R mapping is not * supported. */ private String getDefaultP2RMappingClassName() { String className = null; try { if (secService != null) { className = secService.getMappedPrincipalClass(); } if (className == null || "".equals(className)) { className = Group.class.getName(); } if (className == null) { return null; } Class clazz = Class.forName(className); Class[] argClasses = new Class[] { String.class }; Object[] arg = new Object[] { "anystring" }; Constructor c = clazz.getConstructor(argClasses); // To avoid a failure later make sure we can instantiate now Principal principal = (Principal) c.newInstance(arg); return className; } catch (Exception e) { _logger.log(Level.SEVERE, "pc.getDefaultP2RMappingClass: " + e); return null; } } /* * Only web/ejb BundleDescriptor and Application descriptor objects are used for role mapping currently. If other * subtypes of RootDeploymentDescriptor are used in the future, they should be added here. */ private String getModuleID(RootDeploymentDescriptor rdd) { // V3: Can we use this : return rdd.getModuleID(); /* * V3:Comment if (rdd instanceof Application) { return TOP_LEVEL; } else if (rdd instanceof BundleDescriptor) { return * ((BundleDescriptor) rdd).getModuleDescriptor().getArchiveUri(); } else { // cannot happen unless glassfish code is * changed throw new AssertionError(rdd.getClass() + " is not a known descriptor type"); } */ if (rdd.isApplication()) { return TOP_LEVEL; } else if (rdd.getModuleDescriptor() != null) { return rdd.getModuleDescriptor().getArchiveUri(); } else { // cannot happen unless glassfish code is changed throw new AssertionError(rdd.getClass() + " is not a known descriptor type"); } } /* * For each role in the current mapping: First check that the role does not already exist in the top-level mapping. If * it does, then the top-level role mapping overrides the current one and we do not need to check if they conflict. Just * continue with the next role. If the current mapping is from the top-level file, then check to see if the role has * already been mapped. If so, do not need to check for conflicts. Simply override and assign the role. If the above * cases do not apply, check for conflicts with roles already set. If there is a conflict, it is between two submodules, * so the role should be unmapped in the existing role mappings. */ private void checkAndAddMappings() { if (currentMapping == null) { return; } for (Role r : currentMapping.getRoles()) { if (topLevelRoles != null && topLevelRoles.contains(r)) { logConflictWarning(); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Role " + r + " from module " + currentMapping.owner + " is being overridden by top-level mapping."); } continue; } if (currentMapping.owner.equals(TOP_LEVEL)) { topLevelRoles.add(r); if (roleToSubject.keySet().contains(r.getName())) { logConflictWarning(); if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Role " + r + " from top-level mapping descriptor is " + "overriding existing role in sub module."); } unassignRole(r); } } else if (roleConflicts(r, currentMapping.getPrincipals(r))) { // detail message already logged logConflictWarning(); unassignRole(r); continue; } // no problems, so assign role for (Principal p : currentMapping.getPrincipals(r)) { internalAssignRole(p, r); } } // clear current mapping currentMapping = null; } private boolean roleConflicts(Role r, Set ps) { // check to see if there has been a previous conflict if (conflictedRoles != null && conflictedRoles.contains(r)) { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Role " + r + " from module " + currentMapping.owner + " has already had a conflict with other modules."); } return true; } // if role not previously mapped, no conflict if (!roleToSubject.keySet().contains(r.getName())) { return false; } // check number of mappings first int targetNumPrin = ps.size(); int actualNum = 0; Set pSet = roleToPrincipal.get(r.getName()); Set gSet = roleToGroup.get(r.getName()); actualNum += (pSet == null) ? 0 : pSet.size(); actualNum += (gSet == null) ? 0 : gSet.size(); if (targetNumPrin != actualNum) { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Module " + currentMapping.owner + " has different number of mappings for role " + r.getName() + " than other mapping files"); } if (conflictedRoles == null) { conflictedRoles = new HashSet(); } conflictedRoles.add(r); return true; } // check the principals and groups boolean fail = false; for (Principal p : ps) { if (p instanceof Group) { if (gSet != null && !gSet.contains(p)) { fail = true; } } else if (pSet != null && !pSet.contains(p)) { fail = true; } if (fail) { if (_logger.isLoggable(Level.FINE)) { _logger.log(Level.FINE, "Role " + r + " in module " + currentMapping.owner + " is not included in other modules."); } if (conflictedRoles == null) { conflictedRoles = new HashSet(); } conflictedRoles.add(r); return true; } } // no conflicts return false; } private void logConflictWarning() { if (!conflictLogged) { _logger.log(WARNING, "java_security.role_mapping_conflict", getName()); conflictLogged = true; } } /* * Used to represent the role mapping of a single descriptor file. */ private static class Mapping implements Serializable { private static final long serialVersionUID = 5863982599500877228L; private final String owner; private final Map> roleMap = new HashMap>(); Mapping(String owner) { this.owner = owner; } void addMapping(Principal principal, Role role) { roleMap.computeIfAbsent(role, e -> new HashSet()) .add(principal); } Set getRoles() { return roleMap.keySet(); } Set getPrincipals(Role r) { return roleMap.get(r); } } class DefaultRoleToSubjectMapping extends HashMap { private static final long serialVersionUID = 3074733840327132690L; private final HashMap roleMap = new HashMap(); Principal getSameNamedPrincipal(String roleName) { try { return (Principal) Class.forName(defaultP2RMappingClassName) .getConstructor(new Class[] { String.class }) .newInstance(new Object[] { roleName }); } catch (Exception e) { _logger.log(SEVERE, "rm.getSameNamedPrincipal", new Object[] { roleName, e }); throw new RuntimeException("Unable to get principal by default p2r mapping"); } } // Do not map '**' to a Principal as this represents the any authenticated user role @Override public Subject get(Object key) { synchronized (roleMap) { Subject subject = roleMap.get(key); if (subject == null && key instanceof String && !"**".equals(key)) { Subject fs = new Subject(); String roleName = (String) key; privileged(() -> fs.getPrincipals().add(getSameNamedPrincipal(roleName))); roleMap.put(roleName, fs); subject = fs; } return subject; } } } }