<< Go Back to PicketBox Overview
PicketBox (formerly JBoss Security) supports facilities to map a Principal, Role(s) and Attribute(s) in a security process.
Need for mapping
It is important for any security framework to provide facilities to map principal or roles from one form to another.
Examples include:
- The authentication has been performed using X509 Certificates. Now you want to convert the principal from the certificate to a logical name that is meaningful to your application such as display purposes.
- The authentication process derived a set of roles as part of the security domain. But you want to associate a few more roles with the current subject as part of the deployment archive.
Read more below:
Role Mapping
The conversion of roles during a particular security event may be important for the following reasons:
- You want to add more roles to the subject than what the authentication process derived.
- You want to replace/remove one or more roles.
Let us start with some code:
import java.security.Principal; import java.util.HashMap; import java.util.Map; import javax.security.auth.Subject; import junit.framework.TestCase; import org.jboss.security.AuthenticationManager; import org.jboss.security.SimplePrincipal; import org.jboss.security.identity.RoleGroup; import org.jboss.security.identity.plugins.SimpleRole; import org.jboss.security.mapping.MappingContext; import org.jboss.security.mapping.MappingManager; import org.jboss.security.mapping.MappingType; import org.picketbox.config.PicketBoxConfiguration; import org.picketbox.factories.SecurityFactory; import org.picketbox.util.PicketBoxUtil; public void testRoleMapping() { String securityDomainName = "role-mapping-test"; SecurityFactory.prepare(); try { String configFile = "config/mapping.conf"; PicketBoxConfiguration idtrustConfig = new PicketBoxConfiguration(); idtrustConfig.load(configFile); AuthenticationManager am = SecurityFactory.getAuthenticationManager(securityDomainName); assertNotNull(am); Subject subject = new Subject(); Principal principal = new SimplePrincipal("anil"); Object credential = new String("pass"); boolean result = am.isValid(principal, credential); assertTrue("Valid Auth", result); result = am.isValid(principal, credential, subject); assertTrue("Valid Auth", result); assertTrue("Subject has principals", subject.getPrincipals().size() > 0); RoleGroup roles = PicketBoxUtil.getRolesFromSubject(subject); if(roles == null) throw new RuntimeException("Roles obtained from subject are null"); //Lets do the role mapping now MappingManager mm = SecurityFactory.getMappingManager(securityDomainName); MappingContext<RoleGroup> mc = mm.getMappingContext(MappingType.ROLE.name()); Map<String,Object> contextMap = new HashMap<String,Object>(); mc.performMapping(contextMap, roles); RoleGroup mappedRoles = mc.getMappingResult().getMappedObject(); assertNotNull(mappedRoles); //We added two extra roles to the role group assertEquals("3 roles", 3, mappedRoles.getRoles().size()); assertTrue("Contains AuthorizedUser", mappedRoles.containsRole(new SimpleRole("AuthorizedUser"))); assertTrue("Contains InternalUser", mappedRoles.containsRole(new SimpleRole("InternalUser"))); } finally { SecurityFactory.release(); } }
The entire mapping.conf is shown below:
<?xml version='1.0'?> <policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:security-config:5.0" xmlns="urn:jboss:security-config:5.0" xmlns:jbxb="urn:jboss:security-config:5.0"> <application-policy name = "role-mapping-test"> <authentication> <login-module code = "org.jboss.security.auth.spi.UsersRolesLoginModule" flag = "required"> </login-module> </authentication> <mapping> <mapping-module code="org.jboss.security.mapping.providers.OptionsRoleMappingProvider" type="role"> <module-option name="rolesMap" > <java:properties xmlns:java="urn:jboss:java-properties" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="urn:jboss:java-properties resource:java-properties_1_0.xsd"> <java:property> <java:key>validuser</java:key> <java:value>AuthorizedUser,InternalUser</java:value> </java:property> </java:properties> </module-option> <module-option name="replaceRoles">false</module-option> </mapping-module> </mapping> </application-policy> <application-policy name = "principal-mapping-test"> <authentication> <login-module code = "org.jboss.security.auth.spi.UsersRolesLoginModule" flag = "required"> </login-module> </authentication> <mapping> <mapping-module code="org.jboss.security.mapping.providers.principal.SimplePrincipalMappingProvider" type="principal"> <module-option name="principalsMap" > <java:properties xmlns:java="urn:jboss:java-properties" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="urn:jboss:java-properties resource:java-properties_1_0.xsd"> <java:property> <java:key>anil</java:key> <java:value>security-anil</java:value> </java:property> </java:properties> </module-option> <module-option name="replaceRoles">false</module-option> </mapping-module> </mapping> </application-policy> </policy>
Here we are trying to use an out of the box Mapping Provider named "org.jboss.security.mapping.providers.OptionsRoleMappingProvider" that takes in a properties style mapping of role name (key) with a comma separated list of roles (values). In this example, we also specify the module option "replaceRoles" as false such that the mapped roles are added to the subject and not replaced.
Principal Mapping
Please refer to the section above on "Need for Mapping".
Let us look at some code:
import java.security.Principal; import java.util.HashMap; import java.util.Map; import javax.security.auth.Subject; import junit.framework.TestCase; import org.jboss.security.AuthenticationManager; import org.jboss.security.SimplePrincipal; import org.jboss.security.identity.RoleGroup; import org.jboss.security.identity.plugins.SimpleRole; import org.jboss.security.mapping.MappingContext; import org.jboss.security.mapping.MappingManager; import org.jboss.security.mapping.MappingType; import org.picketbox.config.PicketBoxConfiguration; import org.picketbox.factories.SecurityFactory; import org.picketbox.util.PicketBoxUtil; public void testPrincipalMapping() { String securityDomainName = "principal-mapping-test"; SecurityFactory.prepare(); try { String configFile = "config/mapping.conf"; PicketBoxConfiguration idtrustConfig = new PicketBoxConfiguration(); idtrustConfig.load(configFile); AuthenticationManager am = SecurityFactory.getAuthenticationManager(securityDomainName); assertNotNull(am); Subject subject = new Subject(); Principal principal = new SimplePrincipal("anil"); Object credential = new String("pass"); boolean result = am.isValid(principal, credential); assertTrue("Valid Auth", result); result = am.isValid(principal, credential, subject); assertTrue("Valid Auth", result); assertTrue("Subject has principals", subject.getPrincipals().size() > 0); //Lets do the role mapping now MappingManager mm = SecurityFactory.getMappingManager(securityDomainName); MappingContext<Principal> mc = mm.getMappingContext(MappingType.PRINCIPAL.name()); Map<String,Object> contextMap = new HashMap<String,Object>(); mc.performMapping(contextMap, principal); Principal mappedPrincipal = mc.getMappingResult().getMappedObject(); assertTrue("security-anil".equals(mappedPrincipal.getName())); } finally { SecurityFactory.release(); } }
The mapping.conf is shown above.
This is an example where we change the name of the principal from "anil" to "security-anil". Toward this, we utilize a mapping provider called as "org.jboss.security.mapping.providers.principal.SimplePrincipalMappingProvider" that takes in a Java properties style module option. We specify a principal ("anil") with another "security-anil".
Attribute Mapping
There is an LDAP Attribute Mapping Provider provided out of the box:
org.jboss.security.mapping.providers.attribute.LdapAttributeMappingProvider
Aim: Maps attributes from LDAP
Module Options: The options include whatever options your LDAP JNDI provider supports.
Examples of standard property names are:
- Context.INITIAL_CONTEXT_FACTORY = "java.naming.factory.initial"
- Context.SECURITY_PROTOCOL = "java.naming.security.protocol"
- Context.PROVIDER_URL = "java.naming.provider.url"
- Context.SECURITY_AUTHENTICATION = "java.naming.security.authentication"
Other Module Options:-
- bindDN:The DN used to bind against the ldap server for the user and roles queries. This is some DN with read/search permissions on the baseCtxDN and rolesCtxDN values. bindCredential: The password for the bindDN. This can be encrypted if the jaasSecurityDomain is specified.
- baseCtxDN: The fixed DN of the context to start the user search from.
- baseFilter:A search filter used to locate the context of the user to authenticate. The input username/userDN as obtained from the login module callback will be substituted into the filter anywhere a "{0}" expression is seen. This substituion behavior comes from the standard __DirContext.search(Name, String, Object[], SearchControls cons)__ method. An common example search filter is "(uid={0})".
- searchTimeLimit:The timeout in milliseconds for the user/role searches.Defaults to 10000 (10 seconds).
- attributeList: A comma-separated list of attributes for the user (Example: mail,cn,sn,employeeType,employeeNumber)
- jaasSecurityDomain: The JMX ObjectName of the JaasSecurityDomain to useto decrypt the java.naming.security.principal. The encrypted form of the password is that returned by the JaasSecurityDomain#encrypt64(byte[]) method. The org.jboss.security.plugins.PBEUtils can also be used to generate the encrypted form.
Note: jaasSecurityDomain option works only in JBoss Application Server.
Lets look at an example:
public void testLDAPAttributes() throws Exception { //Get the Mapping Manager via the SecurityFactory etc... .... <snip/> .... MappingManager mm; ..... MappingContext<List<Attribute<String>>> mc = mm.getMappingContext(MappingType.ATTRIBUTE.name()); map.put(SecurityConstants.PRINCIPAL_IDENTIFIER, new SimplePrincipal("jduke")); List<Attribute<String>> attList = new ArrayList<Attribute<String>>(); mc.performMapping(map, attList); attList = (List<Attribute<String>>) mc.getMappingResult().getMappedObject(); boolean foundEmail = false; boolean foundEmployeeType = false; boolean foundEmployeeNumber = false; for(Attribute<String> att: attList) { String attName = att.getName(); if(attName.equals(Attribute.TYPE.EMAIL_ADDRESS.get())) { assertEquals("theduke@somecastle.man",att.getValue()); foundEmail = true; } if(attName.equals("employeeType")) { assertEquals("permanent",att.getValue()); foundEmployeeType = true; } if(attName.equals("employeeNumber")) { assertEquals("007",att.getValue()); foundEmployeeNumber = true; } } assertTrue("Found Email", foundEmail); assertTrue("Found Emp Type", foundEmployeeType); assertTrue("Found Emp Number", foundEmployeeNumber); }
Let us look at the ldap-attributes-config.xml that defines the mapping configuration at the security domain level.
<policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:jboss:security-config:5.0" xmlns="urn:jboss:security-config:5.0" xmlns:jbxb="urn:jboss:security-config:5.0"> <application-policy name="test"> <mapping> <mapping-module code="org.jboss.security.mapping.providers.attribute.LdapAttributeMappingProvider" type="attribute"> <module-option name="attributeList">mail,cn,commonname,givenname,surname,employeeType,employeeNumber,facsimileTelephoneNumber</module-option> <module-option name="bindDN">cn=Directory Manager</module-option> <module-option name="bindCredential">password</module-option> <module-option name="baseFilter">(uid={0})</module-option> <module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option> <module-option name="java.naming.provider.url">ldap://localhost:10389</module-option> <module-option name="baseCtxDN">ou=People,dc=jboss,dc=org</module-option> </mapping-module> </mapping> </application-policy> </policy>
We did populate the LDAP server with an ldif ldapAttributes.ldif
You will have to refer to your ldap server documentation to figure out how to populate your ldap server or
reuse already existing DIT.
dn: dc=jboss,dc=org objectclass: dcObject objectclass: organization o: JBoss dc: JBoss dn: ou=People,dc=jboss,dc=org objectclass: top objectclass: organizationalUnit ou: People dn: uid=jduke,ou=People,dc=jboss,dc=org objectclass: top objectclass: uidObject objectclass: person objectclass: inetOrgPerson uid: jduke cn: Java Duke sn: Duke userPassword: theduke mail: theduke@somecastle.man employeeType: permanent employeeNumber: 007
PicketBox Mapping using Java Annotations
Instead of an external xml configuration, you can always provide the information via the @SecurityMapping annotation.
import org.jboss.security.annotation.Authentication; import org.jboss.security.annotation.Module; import org.jboss.security.annotation.ModuleOption; import org.jboss.security.annotation.SecurityMapping; import org.jboss.security.auth.spi.UsersRolesLoginModule; import org.jboss.security.mapping.providers.OptionsRoleMappingProvider; @Authentication(modules = {@Module(code = UsersRolesLoginModule.class, options = {@ModuleOption})}) @SecurityMapping(modules = {@Module(code = OptionsRoleMappingProvider.class, type="role", options = {@ModuleOption(key="rolesMap",value="validuser=AuthorizedUser,InternalUser", valueType=VALUE_TYPE.JAVA_PROPERTIES), @ModuleOption(key="replaceRoles", value="false")})}) public class AuthPlusMappingAnnotatedPOJO { }
The test code is:
@Test public void testAuthenticationAndMappingAnnotation() throws Exception { AuthPlusMappingAnnotatedPOJO pojo = new AuthPlusMappingAnnotatedPOJO(); PicketBoxProcessor processor = new PicketBoxProcessor(); processor.setSecurityInfo("anil", "pass"); processor.process(pojo); Principal anil = new SimplePrincipal("anil"); assertEquals("Principal == anil", anil, processor.getCallerPrincipal()); Subject callerSubject = processor.getCallerSubject(); assertNotNull("Subject is not null", callerSubject); assertTrue("Subject contains principal anil", callerSubject.getPrincipals().contains(anil)); RoleGroup callerRoles = processor.getCallerRoles(); assertTrue("InternalUser is a role", callerRoles.containsRole(new SimpleRole("InternalUser"))); assertTrue("AuthorizedUser is a role", callerRoles.containsRole(new SimpleRole("AuthorizedUser"))); }
@SecurityMapping Annotation
More details available at PicketBoxSecurityAnnotations
References:
Comments