/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.web.security;

import static org.jboss.as.web.WebMessages.MESSAGES;

import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.jacc.PolicyContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.web.WebLogger;
import org.jboss.as.web.deployment.WarMetaData;
import org.jboss.metadata.javaee.spec.SecurityRoleRefMetaData;
import org.jboss.metadata.javaee.spec.SecurityRoleRefsMetaData;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.ServletMetaData;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.AuthorizationManager;
import org.jboss.security.CacheableManager;
import org.jboss.security.CertificatePrincipal;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityRolesAssociation;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.audit.AuditEvent;
import org.jboss.security.audit.AuditLevel;
import org.jboss.security.audit.AuditManager;
import org.jboss.security.auth.callback.CallbackHandlerPolicyContextHandler;
import org.jboss.security.auth.callback.DigestCallbackHandler;
import org.jboss.security.auth.certs.SubjectDNMapping;
import org.jboss.security.authorization.ResourceKeys;
import org.jboss.security.callbacks.SecurityContextCallbackHandler;
import org.jboss.security.identity.Role;
import org.jboss.security.identity.RoleGroup;
import org.jboss.security.javaee.AbstractWebAuthorizationHelper;
import org.jboss.security.javaee.SecurityHelperFactory;
import org.jboss.security.mapping.MappingContext;
import org.jboss.security.mapping.MappingManager;
import org.jboss.security.mapping.MappingType;

/**
 * A {@code RealmBase} implementation
 *
 * @author Scott.Stark@jboss.org
 * @author <a href="mailto:Anil.Saldhana@jboss.org">Anil Saldhana</a>
 */
public class JBossWebRealm extends RealmBase {

    protected static final String name = "JBossWebRealm";

    /**
     * The {@code AuditManager} instance that can audit security events
     */
    protected AuditManager auditManager = null;

    /**
     * The {@code AuthenticationManager} instance that can perform authentication
     */
    protected AuthenticationManager authenticationManager = null;

    /**
     * The {@code AuthorizationManager} instance that is used for authorization as well as get roles
     */
    protected AuthorizationManager authorizationManager = null;

    /**
     * The {@code MappingManager} instance to perform principal, role, attribute and credential mapping
     */
    protected MappingManager mappingManager = null;

    /**
     * The converter from X509 certificate chain to Principal
     */
    protected CertificatePrincipal certMapping = new SubjectDNMapping();

    /**
     * The {@code DeploymentUnit} associated with the Realm
     */
    protected DeploymentUnit deploymentUnit;

    /**
     * MetaData associated with the DeploymentUnit
     */
    protected JBossWebMetaData metaData;

    /**
     * JBoss specific role mapping set in the MetaData
     */
    protected Map<String, Set<String>> principalVersusRolesMap;

    /**
     * Is JBoss authorization framework enabled?
     */
    protected boolean useJBossAuthorization = false;

    /**
     * Is Audit disabled?
     */
    protected boolean disableAudit = false;

    /**
     * Set the {@code AuthenticationManager}
     *
     * @param authenticationManager
     */
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    /**
     * Set the {@code AuditManager}
     *
     * @param auditManager
     */
    public void setAuditManager(AuditManager auditManager) {
        this.auditManager = auditManager;
    }

    /**
     * Set the {@code AuthorizationManager}
     *
     * @param authorizationManager
     */
    public void setAuthorizationManager(AuthorizationManager authorizationManager) {
        this.authorizationManager = authorizationManager;
    }

    /**
     * Set the {@code MappingManager}
     *
     * @param mappingManager
     */
    public void setMappingManager(MappingManager mappingManager) {
        this.mappingManager = mappingManager;
    }

    /**
     * Set the {@code DeploymentUnit}
     *
     * @param deploymentUnit
     */
    public void setDeploymentUnit(DeploymentUnit deploymentUnit) {
        this.deploymentUnit = deploymentUnit;
        metaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY).getMergedJBossWebMetaData();
        principalVersusRolesMap = metaData.getSecurityRoles().getPrincipalVersusRolesMap();
        useJBossAuthorization = metaData.isUseJBossAuthorization();
        disableAudit = metaData.isDisableAudit();
    }

    /**
     * Returns the principal versus roles map
     *
     * @return map
     */
    public Map<String, Set<String>> getPrincipalVersusRolesMap() {
        return principalVersusRolesMap;
    }

    @Override
    public Principal authenticate(String username, String credentials) {
        if (username == null && credentials == null)
            return null;

        if (authenticationManager == null)
            throw new IllegalStateException("Authentication Manager has not been set");
        if (authorizationManager == null)
            throw new IllegalStateException("Authorization Manager has not been set");

        Principal userPrincipal = getPrincipal(username);
        Subject subject = new Subject();
        try {
            boolean isValid = authenticationManager.isValid(userPrincipal, credentials, subject);
            if (isValid) {
                WebLogger.WEB_SECURITY_LOGGER.tracef("User: " + userPrincipal + " is authenticated");
                SecurityContext sc = SecurityActions.getSecurityContext();
                if (sc == null)
                    throw new IllegalStateException("No SecurityContext found!");
                userPrincipal = getPrincipal(subject);
                sc.getUtil().createSubjectInfo(userPrincipal, credentials, subject);
                SecurityContextCallbackHandler scb = new SecurityContextCallbackHandler(sc);
                if (mappingManager != null) {
                    // if there are mapping modules let them handle the role mapping
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc != null && mc.hasModules()) {
                        SecurityRolesAssociation.setSecurityRoles(principalVersusRolesMap);
                    }
                }
                RoleGroup roles = authorizationManager.getSubjectRoles(subject, scb);
                List<Role> rolesAsList = roles.getRoles();
                List<String> rolesAsStringList = new ArrayList<String>();
                for (Role role : rolesAsList) {
                    rolesAsStringList.add(role.getRoleName());
                }
                if (mappingManager != null) {
                    // if there are no mapping modules handle role mapping here
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc == null || !mc.hasModules()) {
                        rolesAsStringList = mapUserRoles(rolesAsStringList);
                    }
                } else
                    // if mapping manager is not set, handle role mapping here too
                    rolesAsStringList = mapUserRoles(rolesAsStringList);
                if (authenticationManager instanceof CacheableManager) {
                    @SuppressWarnings("unchecked")
                    CacheableManager<?, Principal> cm = (CacheableManager<?, Principal>) authenticationManager;
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, credentials, cm, subject);
                    successAudit(userPrincipal, null);
                    return userPrincipal;
                } else {
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, credentials, null, subject);
                    successAudit(userPrincipal, null);
                    return userPrincipal;
                }
            }
        } catch (Exception e) {
            WebLogger.WEB_SECURITY_LOGGER.authenticateError(e);
            userPrincipal = null;
            exceptionAudit(userPrincipal, null, e);
        }

        userPrincipal = super.authenticate(username, credentials);
        if (userPrincipal != null) {
            successAudit(userPrincipal, null);
        } else {
            failureAudit(userPrincipal, null);
        }
        return userPrincipal;
    }

    @Override
    public Principal authenticate(X509Certificate[] certs) {
        if ((certs == null) || (certs.length < 1))
            return (null);
        if (authenticationManager == null)
            throw MESSAGES.noAuthenticationManager();
        if (authorizationManager == null)
            throw MESSAGES.noAuthorizationManager();

        Principal userPrincipal = null;
        try {
            userPrincipal = certMapping.toPrincipal(certs);
            Subject subject = new Subject();
            boolean isValid = authenticationManager.isValid(userPrincipal, certs, subject);
            if (isValid) {
                WebLogger.WEB_SECURITY_LOGGER.tracef("User: " + userPrincipal + " is authenticated");
                SecurityContext sc = SecurityActions.getSecurityContext();
                if (sc == null)
                    throw new IllegalStateException("No SecurityContext found!");
                userPrincipal = getPrincipal(subject);
                sc.getUtil().createSubjectInfo(userPrincipal, certs, subject);
                SecurityContextCallbackHandler scb = new SecurityContextCallbackHandler(sc);
                if (mappingManager != null) {
                    // if there are mapping modules let them handle the role mapping
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc != null && mc.hasModules()) {
                        SecurityRolesAssociation.setSecurityRoles(principalVersusRolesMap);
                    }
                }
                RoleGroup roles = authorizationManager.getSubjectRoles(subject, scb);
                List<Role> rolesAsList = roles.getRoles();
                List<String> rolesAsStringList = new ArrayList<String>();
                for (Role role : rolesAsList) {
                    rolesAsStringList.add(role.getRoleName());
                }
                if (mappingManager != null) {
                    // if there are no mapping modules handle role mapping here
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc == null || !mc.hasModules()) {
                        rolesAsStringList = mapUserRoles(rolesAsStringList);
                    }
                } else
                    // if mapping manager is not set, handle role mapping here too
                    rolesAsStringList = mapUserRoles(rolesAsStringList);
                if (authenticationManager instanceof CacheableManager) {
                    @SuppressWarnings("unchecked")
                    CacheableManager<?, Principal> cm = (CacheableManager<?, Principal>) authenticationManager;
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, certs, cm, subject);
                } else
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, certs, null, subject);
            } else {
                WebLogger.WEB_SECURITY_LOGGER.tracef("User: " + userPrincipal + " is NOT authenticated");
                userPrincipal = null;
            }
        } catch (Exception e) {
            WebLogger.WEB_SECURITY_LOGGER.authenticateErrorCert(e);
            exceptionAudit(userPrincipal, null, e);
        }

        if (userPrincipal != null) {
            successAudit(userPrincipal, null);
        }

        return userPrincipal;
    }

    @Override
    public Principal authenticate(String username, byte[] credentials) {
        return authenticate(username, new String(credentials));
    }

    @Override
    public Principal authenticate(String username, String clientDigest, String nOnce, String nc, String cnonce, String qop,
            String realm, String md5a2) {
        if (authenticationManager == null)
            throw MESSAGES.noAuthenticationManager();
        if (authorizationManager == null)
            throw MESSAGES.noAuthorizationManager();
        Principal userPrincipal = null;
        SecurityContext sc = SecurityActions.getSecurityContext();
        if (sc == null)
            throw MESSAGES.noSecurityContext();
        Principal caller = sc.getUtil().getUserPrincipal();
        if (caller == null && username == null && clientDigest == null) {
            return null;
        }
        try {
            DigestCallbackHandler handler = new DigestCallbackHandler(username, nOnce, nc, cnonce, qop, realm, md5a2);
            CallbackHandlerPolicyContextHandler.setCallbackHandler(handler);
            userPrincipal = getPrincipal(username);
            Subject subject = new Subject();
            boolean isValid = authenticationManager.isValid(userPrincipal, clientDigest, subject);
            if (isValid) {
                WebLogger.WEB_SECURITY_LOGGER.tracef("User: " + userPrincipal + " is authenticated");
                userPrincipal = getPrincipal(subject);
                sc.getUtil().createSubjectInfo(userPrincipal, clientDigest, subject);
                SecurityContextCallbackHandler scb = new SecurityContextCallbackHandler(sc);
                if (mappingManager != null) {
                    // if there are mapping modules let them handle the role mapping
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc != null && mc.hasModules()) {
                        SecurityRolesAssociation.setSecurityRoles(principalVersusRolesMap);
                    }
                }
                RoleGroup roles = authorizationManager.getSubjectRoles(subject, scb);
                List<Role> rolesAsList = roles.getRoles();
                List<String> rolesAsStringList = new ArrayList<String>();
                for (Role role : rolesAsList) {
                    rolesAsStringList.add(role.getRoleName());
                }
                if (mappingManager != null) {
                    // if there are no mapping modules handle role mapping here
                    MappingContext<RoleGroup> mc = mappingManager.getMappingContext(MappingType.ROLE.name());
                    if (mc == null || !mc.hasModules()) {
                        rolesAsStringList = mapUserRoles(rolesAsStringList);
                    }
                } else
                    // if mapping manager is not set, handle role mapping here too
                    rolesAsStringList = mapUserRoles(rolesAsStringList);
                if (authenticationManager instanceof CacheableManager) {
                    @SuppressWarnings("unchecked")
                    CacheableManager<?, Principal> cm = (CacheableManager<?, Principal>) authenticationManager;
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, clientDigest, cm, subject);
                } else
                    userPrincipal = new JBossGenericPrincipal(this, userPrincipal.getName(), null, rolesAsStringList,
                            userPrincipal, null, clientDigest, null, subject);
            } else {
                WebLogger.WEB_SECURITY_LOGGER.tracef("User: " + userPrincipal + " is NOT authenticated");
                userPrincipal = null;
            }
        } catch (Exception e) {
            WebLogger.WEB_SECURITY_LOGGER.authenticateErrorDigest(e);
        }

        if (userPrincipal != null) {
            successAudit(userPrincipal, null);
        } else {
            failureAudit(userPrincipal, null);
        }

        return userPrincipal;
    }

    @Override
    protected String getName() {
        return name;
    }

    @Override
    protected String getPassword(String username) {
        return null;
    }

    @Override
    protected Principal getPrincipal(String username) {
        return new SimplePrincipal(username);
    }

    protected List<String> mapUserRoles(List<String> rolesList) {
        if (principalVersusRolesMap != null && principalVersusRolesMap.size() > 0) {
            List<String> mappedRoles = new ArrayList<String>();
            for (String role : rolesList) {
                Set<String> roles = principalVersusRolesMap.get(role);
                if (roles != null && roles.size() > 0) {
                    for (String r : roles) {
                        if (!mappedRoles.contains(r))
                            mappedRoles.add(r);
                    }
                } else {
                    if (!mappedRoles.contains(role))
                        mappedRoles.add(role);
                }
            }
            return mappedRoles;
        }

        return rolesList;
    }

    /**
     * Get the Principal given the authenticated Subject. Currently the first principal that is not of type {@code Group} is
     * considered or the single principal inside the CallerPrincipal group.
     *
     * @param subject
     * @return the authenticated principal
     */
    protected Principal getPrincipal(Subject subject) {
        Principal principal = null;
        Principal callerPrincipal = null;
        if (subject != null) {
            Set<Principal> principals = subject.getPrincipals();
            if (principals != null && !principals.isEmpty()) {
                for (Principal p : principals) {
                    if (!(p instanceof Group) && principal == null) {
                        principal = p;
                    }
                    if (p instanceof Group) {
                        Group g = Group.class.cast(p);
                        if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
                            Enumeration<? extends Principal> e = g.members();
                            if (e.hasMoreElements())
                                callerPrincipal = e.nextElement();
                        }
                    }
                }
            }
        }
        return callerPrincipal == null ? principal : callerPrincipal;
    }

    @Override
    public boolean hasResourcePermission(Request request, Response response, SecurityConstraint[] constraints, Context context)
            throws IOException {
        boolean authzDecision = true;
        boolean baseDecision = super.hasResourcePermission(request, response, constraints, context);

        // if the RealmBase check has passed, then we can go to authz framework
        if (baseDecision && useJBossAuthorization) {
            SecurityContext sc = SecurityActions.getSecurityContext();
            Subject caller = sc.getUtil().getSubject();
            if (caller == null)
                caller = getSubjectFromRequestPrincipal(request.getPrincipal());
            Map<String, Object> contextMap = new HashMap<String, Object>();
            contextMap.put(ResourceKeys.RESOURCE_PERM_CHECK, Boolean.TRUE);
            contextMap.put("securityConstraints", constraints);

            AbstractWebAuthorizationHelper helper = null;
            try {
                helper = SecurityHelperFactory.getWebAuthorizationHelper(sc);
            } catch (Exception e) {
                WebLogger.WEB_SECURITY_LOGGER.noAuthorizationHelper(e);
                return false;
            }

            authzDecision = helper.checkResourcePermission(contextMap, request, response, caller, PolicyContext.getContextID(),
                    requestURI(request), getPrincipalRoles(request));
        }
        boolean finalDecision = baseDecision && authzDecision;
        WebLogger.WEB_SECURITY_LOGGER.tracef("hasResourcePermission:RealmBase says:" + baseDecision + "::Authz framework says:" + authzDecision
                + ":final=" + finalDecision);
        if (!finalDecision) {
            if (!disableAudit) {
                Map<String, Object> entries = new HashMap<String, Object>();
                entries.put("Step", "hasResourcePermission");
                failureAudit(request.getUserPrincipal(), entries);
            }
            response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("realmBase.forbidden"));
        } else {
            if (!disableAudit) {
                Map<String, Object> entries = new HashMap<String, Object>();
                entries.put("Step", "hasResourcePermission");
                successAudit(request.getUserPrincipal(), entries);
            }
        }
        return finalDecision;
    }

    @Override
    public boolean hasRole(Principal principal, String role) {
        boolean authzDecision = true;
        boolean baseDecision = super.hasRole(principal, role);
        // if the RealmBase check has passed, then we can go to authz framework
        if (baseDecision && useJBossAuthorization) {
            Request request = SecurityContextAssociationValve.getActiveRequest();
            String servletName = null;
            Wrapper servlet = request.getWrapper();
            if (servlet != null) {
                servletName = getServletName(servlet);
            }
            if (servletName == null)
                throw new IllegalStateException("servletName is null");
            String roleName = role;
            ServletMetaData servletMD = metaData.getServlets().get(servletName);
            SecurityRoleRefsMetaData roleRefs = null;
            if (servletMD != null)
                roleRefs = servletMD.getSecurityRoleRefs();
            if (roleRefs != null) {
                for (SecurityRoleRefMetaData ref : roleRefs) {
                    if (ref.getRoleLink().equals(role)) {
                        roleName = ref.getName();
                        break;
                    }
                }
            }

            SecurityContext sc = SecurityActions.getSecurityContext();
            AbstractWebAuthorizationHelper helper = null;
            try {
                helper = SecurityHelperFactory.getWebAuthorizationHelper(sc);
            } catch (Exception e) {
                WebLogger.WEB_SECURITY_LOGGER.noAuthorizationHelper(e);
            }
            Subject callerSubject = sc.getUtil().getSubject();
            if (callerSubject == null) {
                // During hasResourcePermission check, Catalina calls hasRole. But we have not established
                // a subject yet in the security context. So we will get the subject from the cached principal
                callerSubject = getSubjectFromRequestPrincipal(principal);
            }

            authzDecision = helper.hasRole(roleName, principal, servletName, getPrincipalRoles(principal),
                    PolicyContext.getContextID(), callerSubject, getPrincipalRoles(request));
        }
        boolean finalDecision = baseDecision && authzDecision;
        WebLogger.WEB_SECURITY_LOGGER.tracef("hasRole:RealmBase says:" + baseDecision + "::Authz framework says:" + authzDecision + ":final="
                + finalDecision);
        if (finalDecision) {
            if (!disableAudit) {
                Map<String, Object> entries = new HashMap<String, Object>();
                entries.put("Step", "hasRole");
                successAudit(principal, entries);
            }
        } else {
            if (!disableAudit) {
                Map<String, Object> entries = new HashMap<String, Object>();
                entries.put("Step", "hasRole");
                failureAudit(principal, entries);
            }
        }

        return finalDecision;
    }

    @Override
    public boolean hasUserDataPermission(Request request, Response response, SecurityConstraint[] constraints)
            throws IOException {
        boolean ok = super.hasUserDataPermission(request, response, constraints);
        // if the RealmBase check has passed, then we can go to authz framework
        if (ok && useJBossAuthorization) {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("securityConstraints", constraints);
            map.put(ResourceKeys.USERDATA_PERM_CHECK, Boolean.TRUE);

            SecurityContext sc = SecurityActions.getSecurityContext();
            AbstractWebAuthorizationHelper helper = null;
            try {
                helper = SecurityHelperFactory.getWebAuthorizationHelper(sc);
            } catch (Exception e) {
                WebLogger.WEB_SECURITY_LOGGER.noAuthorizationHelper(e);
            }

            Subject callerSubject = sc.getUtil().getSubject();
            // JBAS-6419:CallerSubject has no bearing on the user data permission check
            if (callerSubject == null)
                callerSubject = new Subject();

            ok = helper.hasUserDataPermission(map, request, response, PolicyContext.getContextID(), callerSubject,
                    getPrincipalRoles(request));
        }

        return ok;
    }

    /**
     * Retrieve the Subject stored in the Principal
     *
     * @param principal the Principal present in the Request
     * @return the authenticated Subject
     */
    protected Subject getSubjectFromRequestPrincipal(Principal principal) {
        Subject subject = null;
        if (principal instanceof JBossGenericPrincipal) {
            subject = JBossGenericPrincipal.class.cast(principal).getSubject();
        }
        return subject;
    }

    /**
     * Access the set of role Principals associated with the given caller principal.
     *
     * @param principal - the Principal mapped from the authentication principal and visible from the
     *        HttpServletRequest.getUserPrincipal
     * @return a possible null Set<Principal> for the caller roles
     */
    protected Set<Principal> getPrincipalRoles(Principal principal) {
        if (!(principal instanceof GenericPrincipal))
            throw MESSAGES.illegalPrincipalType(principal.getClass());
        GenericPrincipal gp = GenericPrincipal.class.cast(principal);
        String[] roleNames = gp.getRoles();
        Set<Principal> userRoles = new HashSet<Principal>();
        if (roleNames != null) {
            for (int n = 0; n < roleNames.length; n++) {
                Principal sp = getPrincipal(roleNames[n]);
                userRoles.add(sp);
            }
        }
        return userRoles;
    }

    /**
     * Get the roles that is stored in the authenticated {@link GenericPrincipal}
     *
     * @param request
     * @return
     */
    protected List<String> getPrincipalRoles(Request request) {
        List<String> roles = null;
        Principal principal = request.getPrincipal();
        if (principal != null) {
            if (principal instanceof GenericPrincipal) {
                GenericPrincipal gc = GenericPrincipal.class.cast(principal);
                roles = Arrays.asList(gc.getRoles());
            }
        }

        return roles;
    }

    /**
     * Get the canonical request URI from the request mapping data requestPath
     *
     * @param request
     * @return the request URI path
     */
    protected String requestURI(Request request) {
        String uri = request.getMappingData().requestPath.getString();
        if (uri == null || uri.equals("/")) {
            uri = "";
        }
        return uri;
    }

    /**
     * Jacc Specification : Appendix B.19 Calling isUserInRole from JSP not mapped to a Servlet Checking a WebRoleRefPermission
     * requires the name of a Servlet to identify the scope of the reference to role translation. The name of a scoping servlet
     * has not been established for an unmapped JSP.
     *
     * Resolution- For every security role in the web application add a WebRoleRefPermission to the corresponding role. The name
     * of all such permissions shall be the empty string, and the actions of each permission shall be the corresponding role
     * name. When checking a WebRoleRefPermission from a JSP not mapped to a servlet, use a permission with the empty string as
     * its name and with the argument to is UserInRole as its actions.
     *
     * @param servlet Wrapper
     * @return empty string if it is for an unmapped jsp or name of the servlet for others
     */
    private String getServletName(Wrapper servlet) {
        // For jsp, the mapping will be (*.jsp, *.jspx)
        String[] mappings = servlet.findMappings();
        WebLogger.WEB_SECURITY_LOGGER.tracef("[getServletName:servletmappings=" + mappings + ":servlet.getName()=" + servlet.getName() + "]");
        if ("jsp".equals(servlet.getName()) && (mappings != null && mappings[0].indexOf("*.jsp") > -1))
            return "";
        else
            return servlet.getName();
    }

    /**
     * Get the original http request using the JACC mandated api
     *
     * @return
     */
    private HttpServletRequest getServletRequest() {
        try {
            return (HttpServletRequest) PolicyContext.getContext(SecurityConstants.WEB_REQUEST_KEY);
        } catch (Exception e) {
            WebLogger.WEB_SECURITY_LOGGER.tracef("Exception in getting servlet request:", e);
        }
        return null;
    }

    // Audit Methods
    private void successAudit(Principal userPrincipal, Map<String, Object> entries) {
        if (userPrincipal != null && !disableAudit) {
            if (auditManager != null) {
                AuditEvent auditEvent = new AuditEvent(AuditLevel.SUCCESS);
                Map<String, Object> ctxMap = new HashMap<String, Object>();
                ctxMap.put("principal", userPrincipal);
                HttpServletRequest hsr = getServletRequest();
                if (hsr != null) {
                    ctxMap.put("request", WebUtil.deriveUsefulInfo(hsr));
                }
                ctxMap.put("Source", getClass().getCanonicalName());
                if (entries != null) {
                    ctxMap.putAll(entries);
                }
                auditEvent.setContextMap(ctxMap);
                auditManager.audit(auditEvent);
            }
        }
    }

    private void failureAudit(Principal userPrincipal, Map<String, Object> entries) {
        if (auditManager != null && !disableAudit) {
            AuditEvent auditEvent = new AuditEvent(AuditLevel.FAILURE);
            Map<String, Object> ctxMap = new HashMap<String, Object>();
            ctxMap.put("principal", userPrincipal);
            HttpServletRequest hsr = getServletRequest();
            if (hsr != null) {
                ctxMap.put("request", WebUtil.deriveUsefulInfo(hsr));
            }
            ctxMap.put("Source", getClass().getCanonicalName());
            if (entries != null) {
                ctxMap.putAll(entries);
            }
            auditEvent.setContextMap(ctxMap);
            auditManager.audit(auditEvent);
        }
    }

    private void exceptionAudit(Principal userPrincipal, Map<String, Object> entries, Exception e) {
        if (auditManager != null && !disableAudit) {
            AuditEvent auditEvent = new AuditEvent(AuditLevel.ERROR);
            Map<String, Object> ctxMap = new HashMap<String, Object>();
            ctxMap.put("principal", userPrincipal);
            ctxMap.putAll(entries);
            HttpServletRequest hsr = getServletRequest();
            if (hsr != null) {
                ctxMap.put("request", WebUtil.deriveUsefulInfo(hsr));
            }
            ctxMap.put("source", getClass().getCanonicalName());
            if (entries != null) {
                ctxMap.putAll(entries);
            }
            auditEvent.setContextMap(ctxMap);
            auditEvent.setUnderlyingException(e);
            auditManager.audit(auditEvent);
        }
    }
}
