
org.ow2.jonas.web.tomcat6.security.Realm Maven / Gradle / Ivy
Show all versions of jonas-web-container-tomcat-6.0 Show documentation
/**
* 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