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

org.ow2.jonas.web.tomcat6.security.Realm Maven / Gradle / Ivy

The newest version!
/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2007 Bull S.A.S.
 * Contact: [email protected]
 *
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id: Realm.java 10798 2007-07-01 20:56:02Z benoitf $
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.tomcat6.security;

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.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.login.AccountExpiredException;
import javax.security.auth.login.CredentialExpiredException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.Constants;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.catalina.realm.RealmBase;
import org.apache.catalina.util.StringManager;
import org.objectweb.jonas.security.SecurityService;
import org.objectweb.jonas.security.auth.callback.NoInputCallbackHandler;
import org.objectweb.jonas.security.realm.factory.JResource;
import org.objectweb.jonas.security.realm.factory.JResourceException;
import org.objectweb.jonas.security.realm.principals.JUser;
import org.objectweb.jonas.service.manager.ServiceManager;
import org.objectweb.jonas.web.lib.PermissionManager;
import org.objectweb.security.context.SecurityContext;
import org.objectweb.security.context.SecurityCurrent;
import org.ow2.util.log.Log;
import org.ow2.util.log.LogFactory;

/**
 * 

* Implementation of a Realm. (by a wrapper)
* Use any JOnAS realm by specifying the resource name This implementation * manages the security with JACC specification It implements Cloneable to allow * clones. Each context must have its own Realm. A realm can not be shared * across different contexts or an engine. This is because each realm is * associated to a permission manager Extends the Realmbase class of the Tomcat * Server.
* This Realm can use also the LoginModule for the authentication * @author Florent Benoit */ public class Realm extends RealmBase implements Cloneable { /** * Descriptive information about this Realm implementation. */ private static final String NAME = Realm.class.getSimpleName(); /** * Descriptive information about this Realm implementation. */ private static final String INFO = Realm.class.getName() + "/1.0"; /** * Name used in the JAAS config file. */ private static final String JAAS_CONFIG_NAME = "tomcat"; /** * The string manager for this package. */ private static StringManager sm = StringManager.getManager(Constants.Package); /** * The logger used in JOnAS. */ private static Log logger = LogFactory.getLog(Realm.class); /** * The resource we will use to authenticate users and identify associated * roles. */ private JResource jResource = null; /** * The name of the resource. */ private String resourceName = null; /** * Reference to the JOnAS security service. */ private SecurityService securityService = null; /** * Permission manager used by this realm for JACC permissions. */ private PermissionManager permissionManager = null; /** * Last request that has been send to hasUserDataPermission or * hasResourcePermission methods Used in hasRole to know the current servlet * name. This request is store in a local thread. */ private ThreadLocal lastRequestThread = new ThreadLocal(); /** * Context (used to retrieve web.xml informations). */ private Context context = null; /** * Name of this Realm for traces. */ private String realmName = NAME; /** * Return descriptive information about this Realm implementation and the * corresponding version number, in the format. * <description>/<version>. * @return the info. */ public String getInfo() { return INFO; } /** * Return the resource name we will be using. * @return the resource name. */ public String getResourceName() { return resourceName; } /** * Set the resource name we will be using. * @param resourceName The new resource name */ public void setResourceName(final String resourceName) { this.resourceName = resourceName; } /** * Set the permission manager used by this realm. * @param permissionManager the permission manager to use */ public void setPermissionManager(final PermissionManager permissionManager) { this.permissionManager = permissionManager; } /** * Return the SecurityConstraints configured to guard the request URI for * this request, or null if there is no such constraint. * @param request Request we are processing * @param context Context the Request is mapped to * @return security constraints configured to guard the request URI */ public SecurityConstraint[] findSecurityConstraints(final Request request, final Context context) { // Use super Method return super.findSecurityConstraints(request, context); } /** * Perform access control based on the specified authorization constraint. * @return true if this constraint is satisfied and * processing should continue, or false otherwise. * @param request Request we are processing * @param response Response we are creating * @param constraints Security constraint we are enforcing * @param context The Context to which client of this class is attached. * @exception IOException if an input/output error occurs */ public boolean hasResourcePermission(final Request request, final Response response, final SecurityConstraint[] constraints, final Context context) throws IOException { // Update request lastRequestThread.set(request); // --- Use code of RealmBase for the Login / Error pages // Specifically allow access to the form login and form error pages // and the "j_security_check" action LoginConfig config = context.getLoginConfig(); if (config != null && Constants.FORM_METHOD.equals(config.getAuthMethod())) { String requestURI = request.getDecodedRequestURI(); String loginPage = context.getPath() + config.getLoginPage(); if (loginPage.equals(requestURI)) { logger.debug("{0}: Allow access to login page {1}", realmName, loginPage); return true; } String errorPage = context.getPath() + config.getErrorPage(); if (errorPage.equals(requestURI)) { logger.debug("{0}: Allow access to error page {1}", realmName, errorPage); return true; } if (requestURI.endsWith(Constants.FORM_ACTION)) { logger.debug("{0}: Allow access to username/password submission", realmName); return true; } } // Which user principal have we already authenticated? Principal principal = request.getUserPrincipal(); // --- End of code from RealmBase class String[] roles = null; String principalName = null; if (principal instanceof GenericPrincipal) { roles = ((GenericPrincipal) principal).getRoles(); principalName = principal.getName(); } if (permissionManager == null) { logger.error("{0}: No permission manager is set. Realm used without using the JOnAS deployer but only Tomcat.", realmName); return false; } boolean hasResourcePermission = permissionManager.checkWebResourcePermission(request, principalName, roles); // Need to send HTTP status code as invoke() method of Authenticator // expect that it is // done by the realm. if (!hasResourcePermission) { // Return a "Forbidden" message denying access to this resource response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("realmBase.forbidden")); } return hasResourcePermission; } /** * @return true if the specified Principal has the specified * security role, within the context of this Realm; otherwise return * false. This method can be overridden by Realm * implementations, but the default is adequate when an instance of * GenericPrincipal is used to represent * authenticated Principals from this Realm. * @param principal Principal for whom the role is to be checked * @param role Security role to be checked */ public boolean hasRole(final Principal principal, final String role) { if ((principal == null) || (role == null) || !(principal instanceof GenericPrincipal)) { return false; } if (logger.isDebugEnabled()) { logger.debug("{0}: Principal = {1}", realmName, principal); logger.debug("{0}: Role = {1}", realmName, role); } if (context == null) { logger.error("{0}: Cannot find a servlet name for isUserInRole() as no context was found", realmName); return false; } Request req = (Request) lastRequestThread.get(); if (req == null) { logger.error("{0}: Cannot find a servlet name for isUserInRole(). No previous request !", realmName); return false; } String servletName = findServletName(req); String[] roles = null; String principalName = null; if (principal instanceof GenericPrincipal) { roles = ((GenericPrincipal) principal).getRoles(); principalName = principal.getName(); } if (permissionManager == null) { logger.error( "{0}: No permission manager is set. Using this realm without using the JOnAS deployer but only Tomcat.", realmName); return false; } boolean hasRole = permissionManager.checkWebRoleRefPermission(req, servletName, principalName, roles, role); return hasRole; } /** * @return the name of the servlet (or "" if it's a JSP for example) This * servlet name is used to build JACC permission for the hasRole * method * @param request the servlet request with which we have to find servlet * name */ private String findServletName(final Request request) { // Pattern of the user (remove path) String userPattern = request.getRequestURI().substring(request.getContextPath().length()); if (logger.isDebugEnabled()) { logger.debug("{0}: User Pattern = {1}", realmName, userPattern); } String servletName = ""; String[] patterns = context.findServletMappings(); boolean foundServlet = false; String pattern = ""; int i = 0; // Try to search servlet name while ((i < patterns.length) && !foundServlet) { pattern = patterns[i]; if (logger.isDebugEnabled()) { logger.debug("{0}: Pattern found = {1}", realmName, pattern); logger.debug("{0}: Servlet name for pattern = {1}", realmName, context.findServletMapping(pattern)); } // Extension pattern and ends with extension if (pattern.startsWith("*.") && userPattern.endsWith(pattern.substring(1))) { foundServlet = true; continue; } // Exact Servlet name (ie pattern = /ServletName and userPattern = // /ServletName) if (pattern.equals(userPattern)) { foundServlet = true; continue; } i++; } if (foundServlet) { servletName = context.findServletMapping(pattern); // JSP case. servlet name must be empty as required by JACC // specification if (servletName.equals("jsp")) { servletName = ""; } if (logger.isDebugEnabled()) { logger.debug("{0}: Found servlet name = {1}", realmName, servletName); } } return servletName; } /** * Enforce any user data constraint required by the security constraint * guarding this request URI. * @return true if this constraint was not violated and * processing should continue, or false if we have * created a response already. * @param request Request we are processing * @param response Response we are creating * @param constraints Security constraints being checked * @exception IOException if an input/output error occurs */ public boolean hasUserDataPermission(final Request request, final Response response, final SecurityConstraint[] constraints) throws IOException { // Update request lastRequestThread.set(request); // ---- Start of copy from RealmBase class --- // Validate the request against the user data constraint if (request.getRequest().isSecure()) { if (logger.isDebugEnabled()) { logger.debug("{0}: User data constraint already satisfied", realmName); } return true; } // Which user principal have we already authenticated? Principal principal = ((HttpServletRequest) request).getUserPrincipal(); // ---- End of copy from RealmBase class --- String[] roles = null; String principalName = null; if (principal instanceof GenericPrincipal) { roles = ((GenericPrincipal) principal).getRoles(); principalName = principal.getName(); } // ---- Start of copy from RealmBase class --- for (int i = 0; i < constraints.length; i++) { SecurityConstraint constraint = constraints[i]; // Use redirect only if it the transport protocol is integral or // confidential String userConstraint = constraint.getUserConstraint(); // Redirect only if the constraint is INTEGRAL or CONFIDENTIAL ! if (userConstraint != null && (userConstraint.equals(Constants.INTEGRAL_TRANSPORT) || userConstraint .equals(Constants.CONFIDENTIAL_TRANSPORT))) { // Initialize variables we need to determine the appropriate // action int redirectPort = request.getConnector().getRedirectPort(); // Is redirecting disabled? if (redirectPort <= 0) { if (logger.isDebugEnabled()) { logger.debug("{0}: SSL redirect is disabled", realmName); } response.sendError(HttpServletResponse.SC_FORBIDDEN, request.getRequestURI()); return false; } // Redirect to the corresponding SSL port StringBuffer file = new StringBuffer(); String protocol = "https"; String host = request.getServerName(); // Protocol file.append(protocol).append("://"); // Host with port file.append(host).append(":").append(redirectPort); // URI file.append(request.getRequestURI()); String requestedSessionId = request.getRequestedSessionId(); if ((requestedSessionId != null) && request.isRequestedSessionIdFromURL()) { file.append(";jsessionid="); file.append(requestedSessionId); } String queryString = request.getQueryString(); if (queryString != null) { file.append('?'); file.append(queryString); } if (logger.isDebugEnabled()) { logger.debug("{0}: Redirecting to {1}", realmName, file); } response.sendRedirect(file.toString()); return false; } } // ---- End of copy from RealmBase --- if (permissionManager == null) { logger.error("{0}: No permission manager is set. Realm used without using the JOnAS deployer but only Tomcat.", realmName); return false; } // If Transport protocol is NONE : boolean hasUserDataPermission = permissionManager.checkWebUserDataPermission(request, principalName, roles); return hasUserDataPermission; } /** * Return the Principal associated with the specified username and * credentials, if there is one; otherwise return null. * @param username Username of the Principal to look up * @param credentials Password or other credentials to use in authenticating * this username * @return the principal associated */ public Principal authenticate(final String username, final String credentials) { // Use JOnAS resource if present if (jResource != null) { return authenticateResource(username, credentials); } else { // else use JAAS mechanism return authenticateJAAS(username, credentials); } } /** * Return the Principal associated with the specified username and * credentials, if there is one; otherwise return null. * @param username Username of the Principal to look up * @param credentials Password or other credentials to use in authenticating * this username * @return the principal associated */ public Principal authenticateResource(final String username, final String credentials) { // No authentication can be made with a null username if (username == null) { if (logger.isDebugEnabled()) { logger.debug("{0}: No username so no authentication", realmName); } return null; } // Does a user with this username exist? JUser user = null; try { user = jResource.findUser(username); } catch (Exception jre) { // could not retrieve user logger.error("{0}: Cannot find the user {1}", realmName, username, jre); return null; } // User was not found if (user == null) { if (logger.isDebugEnabled()) { logger.debug("{0}: User {1} not found.", realmName, username); } return null; } boolean validated = jResource.isValidUser(user, credentials); if (!validated) { logger.error("{0}: The password for the user {1} is not valid", realmName, username); return null; } ArrayList combinedRoles = null; try { combinedRoles = jResource.getArrayListCombinedRoles(user); } catch (JResourceException jre) { logger.error("{0}: Cannot get the roles from the user {1}", realmName, username, jre); return null; } GenericPrincipal principal = new GenericPrincipal(this, user.getName(), user.getPassword(), combinedRoles); SecurityContext ctx = new SecurityContext(principal.getName(), combinedRoles); SecurityCurrent current = SecurityCurrent.getCurrent(); current.setSecurityContext(ctx); return principal; } /** * Return the Principal associated with the specified username and * credentials, if there is one; otherwise return null. * @param username Username of the Principal to look up * @param credentials Password or other credentials to use in authenticating * this username * @return the principal associated */ public Principal authenticateJAAS(final String username, final String credentials) { // No authentication can be made with a null username if (username == null) { logger.error("{0}: No username so no authentication", realmName); return null; } // Establish a LoginContext to use for authentication LoginContext loginContext = null; try { loginContext = new LoginContext(JAAS_CONFIG_NAME, new NoInputCallbackHandler(username, credentials)); } catch (LoginException e) { logger.error("{0}: LoginException for user {1}", realmName, username, e); return null; } // Negotiate a login via this LoginContext Subject subject = null; try { loginContext.login(); subject = loginContext.getSubject(); if (subject == null) { logger.error("{0}: No subject for the user {1}", realmName, username); return null; } } catch (AccountExpiredException e) { logger.error("{0}: Account expired for the user {1}", realmName, username, e); return null; } catch (CredentialExpiredException e) { logger.error("{0}: Credential expired for the user {1}", realmName, username, e); return null; } catch (FailedLoginException e) { logger.error("{0}: Failed Login for the user {1}", realmName, username, e); return null; } catch (LoginException e) { logger.error("{0}: Login exception for the user {1}", realmName, username, e); return null; } // Get credentials iterators from the subject Iterator credentialsIterator = subject.getPrivateCredentials().iterator(); String credential = (String) credentialsIterator.next(); // Retrieve first principal name found (without groups) Iterator iterator = subject.getPrincipals(Principal.class).iterator(); String userName = null; while (iterator.hasNext() && (userName == null)) { Principal principal = iterator.next(); if (!(principal instanceof Group)) { userName = principal.getName(); } } // No name --> error if (userName == null) { logger.error("{0}: No Username found in the subject", realmName); return null; } // Retrieve all roles of the user (Roles are members of the Group.class) Set groups = subject.getPrincipals(Group.class); List roles = new ArrayList(); for (Group group : groups) { Enumeration e = group.members(); while (e.hasMoreElements()) { Principal p = (Principal) e.nextElement(); roles.add(p.getName()); } } GenericPrincipal principal = new GenericPrincipal(this, userName, credential, roles); // instanciation of the security context SecurityContext ctx = new SecurityContext(userName, roles); SecurityCurrent current = SecurityCurrent.getCurrent(); current.setSecurityContext(ctx); return principal; } /** * Return the Principal associated with the specified chain of X509 client * certificates. If there is none, return null. * @param cert Array of client certificates, with the first one in the array * being the certificate of the client itself. * @return the associated Principal */ public Principal authenticate(final X509Certificate[] cert) { String dn = cert[0].getSubjectDN().getName(); return authenticate(dn, "tomcat"); } /** * Return a short name for this Realm implementation. * @return the name */ protected String getName() { return NAME; } /** * Return the password associated with the given principal's user name. * @param username the given principal's user name. * @return the password associated. */ protected String getPassword(final String username) { return null; } /** * Return the Principal associated with the given user name. * @param username the given principal's user name. * @return the Principal associated */ protected Principal getPrincipal(final String username) { return null; } /** * Set the context of this Realm. This is used to retrieve xml information * of the web.xml file * @param context Context for this realm */ public void setContext(final Context context) { this.context = context; StringBuffer sb = new StringBuffer(); sb.append("["); sb.append(NAME); sb.append(":"); sb.append(resourceName); sb.append(":"); if (context != null) { sb.append(context.getName()); } sb.append("] "); this.realmName = sb.toString(); } /** * Prepare for active use of the public methods of this Component. * @exception LifecycleException if this component detects a fatal error * that prevents it from being started */ public synchronized void start() throws LifecycleException { // Get the Security Service try { securityService = (SecurityService) ServiceManager.getInstance().getSecurityService(); } catch (Exception e) { // Can't retrieve Security service throw new LifecycleException("can't retrieve Security service"); } // Get the resource from the security service if working with resource // mode if (resourceName != null) { jResource = securityService.getJResource(resourceName); if (jResource == null) { throw new LifecycleException("Can't retrieve resource '" + resourceName + "' from the security service"); } } // Perform normal superclass initialization super.start(); } /** * Gracefully shut down active use of the public methods of this Component. * @exception LifecycleException if this component detects a fatal error * that needs to be reported */ public synchronized void stop() throws LifecycleException { // Perform normal superclass finalization super.stop(); // Release reference to our resource jResource = null; } /** * Creates and returns a copy of this object. * @return copy of this object. * @throws CloneNotSupportedException if the copy fails */ public Object clone() throws CloneNotSupportedException { Realm jRealm = new Realm(); jRealm.setResourceName(resourceName); return jRealm; } /** * @return the permission manager used by this realm. */ public PermissionManager getPermissionManager() { return permissionManager; } }