405533 Implement special role ** for security constraints

This commit is contained in:
Jan Bartel 2013-04-19 15:53:00 +10:00
parent a7073d05a6
commit 87d4690462
13 changed files with 246 additions and 125 deletions

View File

@ -58,7 +58,6 @@ public class SecuredHelloHandler
security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
security.setAuthenticator(new BasicAuthenticator());
security.setLoginService(loginService);
security.setStrict(false);
HelloHandler hh = new HelloHandler();

View File

@ -128,12 +128,6 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
{
return ConstraintSecurityHandler.createConstraint(servlet.getName(), rolesAllowed, permitOrDeny, transport);
}

View File

@ -81,7 +81,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
Constraint constraint = new Constraint();
constraint.setAuthenticate(true);
constraint.setRoles(new String[]{"*"});
constraint.setRoles(new String[]{"**"}); //allow any authenticated user
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/secure");
mapping.setConstraint(constraint);
@ -89,7 +89,6 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
securityHandler.addConstraintMapping(mapping);
securityHandler.setAuthenticator(authenticator);
securityHandler.setLoginService(loginService);
securityHandler.setStrict(false);
securityHandler.setHandler(handler);
start(securityHandler);

View File

@ -0,0 +1,95 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.security;
import java.util.Set;
import org.eclipse.jetty.server.Authentication.User;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.UserIdentity.Scope;
/**
* AbstractUserAuthentication
*
*
* Base class for representing an authenticated user.
*/
public abstract class AbstractUserAuthentication implements User
{
protected String _method;
protected UserIdentity _userIdentity;
public AbstractUserAuthentication(String method, UserIdentity userIdentity)
{
_method = method;
_userIdentity = userIdentity;
}
@Override
public String getAuthMethod()
{
return _method;
}
@Override
public UserIdentity getUserIdentity()
{
return _userIdentity;
}
@Override
public boolean isUserInRole(Scope scope, String role)
{
String roleToTest = null;
if (scope!=null && scope.getRoleRefMap()!=null)
roleToTest=scope.getRoleRefMap().get(role);
if (roleToTest==null)
roleToTest=role;
//Servlet Spec 3.1 pg 125 if testing special role **
if ("**".equals(roleToTest.trim()))
{
//if ** is NOT a declared role name, the we return true
//as the user is authenticated. If ** HAS been declared as a
//role name, then we have to check if the user has that role
if (!declaredRolesContains("**"))
return true;
else
return _userIdentity.isUserInRole(role, scope);
}
return _userIdentity.isUserInRole(role, scope);
}
public boolean declaredRolesContains(String roleName)
{
SecurityHandler security=SecurityHandler.getCurrentSecurityHandler();
if (security==null)
return false;
if (security instanceof ConstraintAware)
{
Set<String> declaredRoles = ((ConstraintAware)security).getRoles();
return (declaredRoles != null) && declaredRoles.contains(roleName);
}
return false;
}
}

View File

@ -49,8 +49,10 @@ import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */
/**
* ConstraintSecurityHandler
*
* Handler to enforce SecurityConstraints. This implementation is servlet spec
* 3.0 compliant and pre-computes the constraint combinations for runtime
* 3.1 compliant and pre-computes the constraint combinations for runtime
* efficiency.
*
*/
@ -61,7 +63,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
private final Set<String> _roles = new CopyOnWriteArraySet<>();
private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
private boolean _strict = true;
/* ------------------------------------------------------------ */
/**
@ -268,36 +270,7 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
}
/* ------------------------------------------------------------ */
/** Get the strict mode.
* @return true if the security handler is running in strict mode.
*/
public boolean isStrict()
{
return _strict;
}
/* ------------------------------------------------------------ */
/** Set the strict mode of the security handler.
* <p>
* When in strict mode (the default), the full servlet specification
* will be implemented.
* If not in strict mode, some additional flexibility in configuration
* is allowed:<ul>
* <li>All users do not need to have a role defined in the deployment descriptor
* <li>The * role in a constraint applies to ANY role rather than all roles defined in
* the deployment descriptor.
* </ul>
*
* @param strict the strict to set
* @see #setRoles(Set)
* @see #setConstraintMappings(List, Set)
*/
public void setStrict(boolean strict)
{
_strict = strict;
}
/* ------------------------------------------------------------ */
/**
@ -408,8 +381,16 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
{
_constraintMappings.add(mapping);
if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null)
{
//allow for lazy role naming: if a role is named in a security constraint, try and
//add it to the list of declared roles (ie as if it was declared with a security-role
for (String role : mapping.getConstraint().getRoles())
{
if ("*".equals(role) || "**".equals(role))
continue;
addRole(role);
}
}
if (isStarted())
{
@ -424,8 +405,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
@Override
public void addRole(String role)
{
//add to list of declared roles
boolean modified = _roles.add(role);
if (isStarted() && modified && isStrict())
if (isStarted() && modified)
{
// Add the new role to currently defined any role role infos
for (Map<String,RoleInfo> map : _constraintMap.values())
@ -593,26 +575,29 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
//add in the roles
boolean checked = mapping.getConstraint().getAuthenticate();
ri.setChecked(checked);
if (ri.isChecked())
{
if (mapping.getConstraint().isAnyRole())
{
if (_strict)
{
// * means "all defined roles"
for (String role : _roles)
ri.addRole(role);
}
else
// * means any role
ri.setAnyRole(true);
}
else
{
{
// * means matches any defined role
for (String role : _roles)
ri.addRole(role);
ri.setAnyRole(true);
}
else if (mapping.getConstraint().isAnyAuth())
{
//being authenticated is sufficient, not necessary to check roles
ri.setAnyAuth(true);
}
else
{
//user must be in one of the named roles
String[] newRoles = mapping.getConstraint().getRoles();
for (String role : newRoles)
{
if (_strict &&!_roles.contains(role))
//check role has been defined
if (!_roles.contains(role))
throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
ri.addRole(role);
}
@ -753,14 +738,35 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
return true;
}
if (roleInfo.isAnyRole() && request.getAuthType()!=null)
//handle ** role constraint
if (roleInfo.isAnyAuth() && request.getUserPrincipal() != null)
{
return true;
}
//check if user is any of the allowed roles
boolean isUserInRole = false;
for (String role : roleInfo.getRoles())
{
if (userIdentity.isUserInRole(role, null))
return true;
{
isUserInRole = true;
break;
}
}
//handle * role constraint
if (roleInfo.isAnyRole() && request.getUserPrincipal() != null && isUserInRole)
{
return true;
}
//normal role check
if (isUserInRole)
{
return true;
}
return false;
}

View File

@ -22,6 +22,7 @@ import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* RoleInfo
*
* Badly named class that holds the role and user data constraint info for a
* path/http method combination, extracted and combined from security
@ -31,11 +32,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
*/
public class RoleInfo
{
private boolean _isAnyAuth;
private boolean _isAnyRole;
private boolean _checked;
private boolean _forbidden;
private UserDataConstraint _userDataConstraint;
/**
* List of permitted roles
*/
private final Set<String> _roles = new CopyOnWriteArraySet<String>();
public RoleInfo()
@ -55,6 +60,7 @@ public class RoleInfo
_forbidden=false;
_roles.clear();
_isAnyRole=false;
_isAnyAuth=false;
}
}
@ -71,6 +77,7 @@ public class RoleInfo
_checked = true;
_userDataConstraint = null;
_isAnyRole=false;
_isAnyAuth=false;
_roles.clear();
}
}
@ -84,10 +91,19 @@ public class RoleInfo
{
this._isAnyRole=anyRole;
if (anyRole)
{
_checked = true;
_roles.clear();
}
}
public boolean isAnyAuth ()
{
return _isAnyAuth;
}
public void setAnyAuth(boolean anyAuth)
{
this._isAnyAuth=anyAuth;
if (anyAuth)
_checked = true;
}
public UserDataConstraint getUserDataConstraint()
@ -126,6 +142,8 @@ public class RoleInfo
setChecked(true);
else if (other._isAnyRole)
setAnyRole(true);
else if (other._isAnyAuth)
setAnyAuth(true);
else if (!_isAnyRole)
{
for (String r : other._roles)

View File

@ -18,39 +18,20 @@
package org.eclipse.jetty.security;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.UserIdentity.Scope;
/**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*/
public class UserAuthentication implements Authentication.User
public class UserAuthentication extends AbstractUserAuthentication
{
private final String _method;
private final UserIdentity _userIdentity;
public UserAuthentication(String method, UserIdentity userIdentity)
{
_method = method;
_userIdentity = userIdentity;
}
public String getAuthMethod()
{
return _method;
super(method, userIdentity);
}
public UserIdentity getUserIdentity()
{
return _userIdentity;
}
public boolean isUserInRole(Scope scope, String role)
{
return _userIdentity.isUserInRole(role, scope);
}
@Override
public String toString()

View File

@ -29,16 +29,15 @@ import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
import org.eclipse.jetty.security.AbstractUserAuthentication;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.UserIdentity.Scope;
import org.eclipse.jetty.server.session.AbstractSession;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class SessionAuthentication implements Authentication.User, Serializable, HttpSessionActivationListener, HttpSessionBindingListener
public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener
{
private static final Logger LOG = Log.getLogger(SessionAuthentication.class);
@ -48,35 +47,17 @@ public class SessionAuthentication implements Authentication.User, Serializable,
public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity";
private final String _method;
private final String _name;
private final Object _credentials;
private transient UserIdentity _userIdentity;
private transient HttpSession _session;
public SessionAuthentication(String method, UserIdentity userIdentity, Object credentials)
{
_method = method;
_userIdentity = userIdentity;
_name=_userIdentity.getUserPrincipal().getName();
super(method, userIdentity);
_name=userIdentity.getUserPrincipal().getName();
_credentials=credentials;
}
public String getAuthMethod()
{
return _method;
}
public UserIdentity getUserIdentity()
{
return _userIdentity;
}
public boolean isUserInRole(Scope scope, String role)
{
return _userIdentity.isUserInRole(role, scope);
}
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException

View File

@ -81,7 +81,8 @@ public class ConstraintTest
SessionHandler _session = new SessionHandler();
HashLoginService _loginService = new HashLoginService(TEST_REALM);
_loginService.putUser("user",new Password("password"));
_loginService.putUser("user0", new Password("password"), new String[]{});
_loginService.putUser("user",new Password("password"), new String[] {"user"});
_loginService.putUser("user2",new Password("password"), new String[] {"user"});
_loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"});
_loginService.putUser("user3", new Password("password"), new String[] {"foo"});
@ -173,7 +174,16 @@ public class ConstraintTest
mapping6.setPathSpec("/data/*");
mapping6.setConstraint(constraint6);
return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6);
Constraint constraint7 = new Constraint();
constraint7.setAuthenticate(true);
constraint7.setName("** constraint");
constraint7.setRoles(new String[]{Constraint.ANY_AUTH,"user"}); //the "user" role is superfluous once ** has been defined
ConstraintMapping mapping7 = new ConstraintMapping();
mapping7.setPathSpec("/starstar/*");
mapping7.setConstraint(constraint7);
return Arrays.asList(mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6, mapping7);
}
@Test
@ -246,7 +256,6 @@ public class ConstraintTest
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
_server.start();
String response;
@ -331,7 +340,6 @@ public class ConstraintTest
public void testFormDispatch() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",true));
_security.setStrict(false);
_server.start();
String response;
@ -386,7 +394,6 @@ public class ConstraintTest
public void testFormRedirect() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
_security.setStrict(false);
_server.start();
String response;
@ -443,7 +450,6 @@ public class ConstraintTest
public void testFormPostRedirect() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
_security.setStrict(false);
_server.start();
String response;
@ -513,7 +519,6 @@ public class ConstraintTest
public void testFormNoCookies() throws Exception
{
_security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false));
_security.setStrict(false);
_server.start();
String response;
@ -586,7 +591,7 @@ public class ConstraintTest
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
"Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403"));
@ -660,9 +665,9 @@ public class ConstraintTest
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 35\r\n" +
"Content-Length: 36\r\n" +
"\r\n" +
"j_username=user&j_password=password\r\n");
"j_username=user0&j_password=password\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("Location"));
assertThat(response,containsString("/ctx/auth/info"));
@ -771,9 +776,9 @@ public class ConstraintTest
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 35\r\n" +
"Content-Length: 36\r\n" +
"\r\n" +
"j_username=user&j_password=password\r\n");
"j_username=user3&j_password=password\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("Location"));
assertThat(response,containsString("/ctx/auth/info"));
@ -816,12 +821,35 @@ public class ConstraintTest
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check user2 does not have right role to access /admin/*
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403"));
assertThat(response,containsString("!role"));
//log in as user3, who doesn't have a valid role, but we are checking a constraint
//of ** which just means they have to be authenticated
response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("testLoginPage"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponses("POST /ctx/j_security_check HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"Content-Type: application/x-www-form-urlencoded\r\n" +
"Content-Length: 36\r\n" +
"\r\n" +
"j_username=user3&j_password=password\r\n");
assertThat(response,startsWith("HTTP/1.1 302 "));
assertThat(response,containsString("Location"));
assertThat(response,containsString("/ctx/starstar/info"));
session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx"));
response = _connector.getResponses("GET /ctx/starstar/info HTTP/1.0\r\n" +
"Cookie: JSESSIONID=" + session + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
// log in again as admin
@ -900,7 +928,7 @@ public class ConstraintTest
RoleCheckHandler check=new RoleCheckHandler();
_security.setHandler(check);
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
//_security.setStrict(false);
_server.start();
@ -932,7 +960,7 @@ public class ConstraintTest
public void testDeferredBasic() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
//_security.setStrict(false);
_server.start();
String response;
@ -959,7 +987,7 @@ public class ConstraintTest
public void testRelaxedMethod() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
//_security.setStrict(false);
_server.start();
String response;

View File

@ -239,7 +239,6 @@ public class SpecExampleConstraintTest
{
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
_server.start();
String response;

View File

@ -23,6 +23,8 @@ import java.util.Arrays;
/* ------------------------------------------------------------ */
/**
* Constraint
*
* Describe an auth and/or data constraint.
*
*
@ -65,6 +67,8 @@ public class Constraint implements Cloneable, Serializable
public final static String NONE = "NONE";
public final static String ANY_ROLE = "*";
public final static String ANY_AUTH = "**"; //Servlet Spec 3.1 pg 140
/* ------------------------------------------------------------ */
private String _name;
@ -74,6 +78,8 @@ public class Constraint implements Cloneable, Serializable
private int _dataConstraint = DC_UNSET;
private boolean _anyRole = false;
private boolean _anyAuth = false;
private boolean _authenticate = false;
@ -119,9 +125,15 @@ public class Constraint implements Cloneable, Serializable
{
_roles = roles;
_anyRole = false;
_anyAuth = false;
if (roles != null)
for (int i = roles.length; !_anyRole && i-- > 0;)
{
for (int i = roles.length; i-- > 0;)
{
_anyRole |= ANY_ROLE.equals(roles[i]);
_anyAuth |= ANY_AUTH.equals(roles[i]);
}
}
}
/* ------------------------------------------------------------ */
@ -132,6 +144,16 @@ public class Constraint implements Cloneable, Serializable
{
return _anyRole;
}
/* ------------------------------------------------------------ */
/** Servlet Spec 3.1, pg 140
* @return True if any authenticated user is permitted (ie a role "**" was specified in the constraint).
*/
public boolean isAnyAuth()
{
return _anyAuth;
}
/* ------------------------------------------------------------ */
/**

View File

@ -155,7 +155,6 @@ public class JdbcLoginServiceTest
security.setConstraintMappings(Collections.singletonList(mapping), knownRoles);
security.setAuthenticator(new BasicAuthenticator());
security.setLoginService(loginService);
security.setStrict(false);
ServletContextHandler root = new ServletContextHandler();
root.setContextPath("/");

View File

@ -15,7 +15,7 @@ This page contains several links to test the authentication constraints:
<li><a href="auth2">auth2/index.html</a> - Authenticated (tests FormAuthenticator.setAlwaysSaveUri()) </li>
<li><a href="dump/auth/noaccess/info">dump/auth/noaccess/*</a> - Forbidden</li>
<li><a href="dump/auth/relax/info">dump/auth/relax/*</a> - Allowed</li>
<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user</li>
<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user with any role</li>
<li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
<li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li>
<li><a href="rego/info">rego/info/*</a> - Authenticated admin role from programmatic security (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>