/* * Atricore IDBus * * Copyright (c) 2009, Atricore Inc. * * 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.atricore.idbus.idojos.memoryidentitystore; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.atricore.idbus.kernel.main.store.*; import org.atricore.idbus.kernel.main.store.exceptions.SSOIdentityException; import org.atricore.idbus.kernel.main.store.exceptions.NoSuchUserException; import org.atricore.idbus.kernel.main.store.exceptions.NoSuchRoleException; import org.atricore.idbus.kernel.main.authn.*; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Node; import org.w3c.dom.Element; import org.springframework.core.io.Resource; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import java.util.*; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; /** * @author <a href="mailto:sgonzalez@josso.org">Sebastian Gonzalez Oyuela</a> * @version $Id: MemoryIdentityStore.java 1210 2009-05-22 21:38:24Z ajadzinsky $ * @org.apache.xbean.XBean element="memory-store" * @org.apache.xbean.XBean element="memory-store" * <p/> * Memory based implementation of an IdentityStore and CredentialStore that reads * data from XML files. */ public class MemoryIdentityStore extends AbstractStore { private static final Log logger = LogFactory.getLog( MemoryIdentityStore.class ); // A map with BaseRole instances, the map key is the role name. private Map<String, Element> _roles; // A map with BaseUser instances, the key is the username. private Map<String, Element> _users; // Stores BaseRoles associated to each user. // The map key is the username. // The map value is a Set of rolenames. private Map<String, Set<String>> _userRoles; // Stores credential values (Object) associated to each user. // The map key is the username. // THe map value is another Map with credetinals (name=key/value) private Map<String, Element> _principalCredentials; private boolean _initialized; private Resource _credentialsFileName; private Resource _usersFileName; private String _principalUidAttributeID; public MemoryIdentityStore () { super(); logger.debug( "Creating new MemoryIdentityStore" ); _users = new HashMap<String, Element>( 7 ); _userRoles = new HashMap<String, Set<String>>( 11 ); _roles = new HashMap<String, Element>( 11 ); _principalCredentials = new HashMap<String, Element>( 11 ); _initialized = false; _principalUidAttributeID = "name"; } /** * Initializes the store, reads data from XML files. */ public synchronized void initialize () { try { // This store can work as an identityStore and as a credentialStore, so // configuration parameters are optional. if ( _usersFileName != null ) loadUsersData( _usersFileName ); if ( _credentialsFileName != null ) loadCredentialsData( _credentialsFileName ); _initialized = true; } catch ( Exception e ) { logger.error( e, e ); throw new RuntimeException( "Can't initialize memory store : " + e.getMessage(), e ); } } /** * Loads users from file. * @param fName the file containing user definitions. */ protected void loadUsersData ( Resource fName ) throws Exception { // First, users logger.info( "Reading users from : " + fName ); DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document doc = docBuilder.parse( fName.getInputStream() ); // normalize text representation (what for ?!) doc.getDocumentElement().normalize(); logger.debug( "Root element of the doc is " + doc.getDocumentElement().getNodeName() ); this.loadRoles( doc ); logger.info( "Loaded " + _roles.size() + " roles from : " + fName ); this.loadUsers( doc ); logger.info( "Loaded " + _users.size() + " users from : " + fName ); } protected void loadRoles ( Document doc ) throws Exception { NodeList listOfRoles = doc.getElementsByTagName( "role" ); int totalRoles = listOfRoles.getLength(); logger.debug( "Total roles: " + totalRoles ); for ( int i = 0; i < listOfRoles.getLength(); i++ ) { Node roleNode = listOfRoles.item( i ); if ( roleNode.getNodeType() == Node.ELEMENT_NODE ) { Element domRole = (Element) roleNode; Element domName = (Element) domRole.getElementsByTagName( "name" ).item( 0 ); // Store DOM Element as a role. logger.debug( "Storing role for name : [" + getTextContent( domName ) + "]" ); _roles.put( getTextContent( domName ), domRole ); } } } protected void loadUsers ( Document doc ) throws Exception { NodeList listOfUsers = doc.getElementsByTagName( "user" ); int totalUsers = listOfUsers.getLength(); logger.debug( "Total users: " + totalUsers ); for ( int i = 0; i < listOfUsers.getLength(); i++ ) { Node userNode = listOfUsers.item( i ); if ( userNode.getNodeType() == Node.ELEMENT_NODE ) { Element userElement = (Element) userNode; Node domName = userElement.getElementsByTagName( "name" ).item( 0 ); logger.debug( "Storing user for name : " + getTextContent( domName ) ); // Store DOM Element as a user. _users.put( getTextContent( domName ), userElement ); } } } /** * Loads credentials from file. * @param fName the file containing user definitions. */ protected void loadCredentialsData ( Resource fName ) throws Exception { logger.info( "Reading credentials from : " + fName ); DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document doc = docBuilder.parse( fName.getInputStream() ); // normalize text representation (what for ?!) doc.getDocumentElement().normalize(); loadCredentials( doc ); logger.info( "Loaded " + _principalCredentials.size() + " credential sets from : " + fName ); } protected void loadCredentials ( Document doc ) throws Exception { NodeList credentialSetLst = doc.getElementsByTagName( "credential-set" ); int totalCredentials = credentialSetLst.getLength(); logger.debug( "Total credential sets: " + totalCredentials ); for ( int i = 0; i < credentialSetLst.getLength(); i++ ) { // Each credential set has a key and a list of credentials. Element domCredentialSet = (Element) credentialSetLst.item( i ); Node domKey = domCredentialSet.getElementsByTagName( "key" ).item( 0 ); if ( domKey.getNodeType() != Node.ELEMENT_NODE || !domKey.getNodeName().equals( "key" ) ) throw new SSOIdentityException( "Credential set definitions need a 'key' element [" + domKey.getNodeName() + "]" ); String key = getTextContent( domKey ); logger.info( "Storing credentials for key : " + key ); _principalCredentials.put( key, domCredentialSet ); } } protected Collection<BaseUser> listUsers () throws SSOIdentityException { if ( !_initialized ) initialize(); Collection<Element> domUsers = _users.values(); List<BaseUser> ssoUsers = new ArrayList<BaseUser>( domUsers.size() ); for ( Element domUser : domUsers ) { ssoUsers.add( toBaseUser( domUser ) ); } return ssoUsers; } // ------------------------------------------------------------------------------ // IdentityStore // ------------------------------------------------------------------------------ // BaseUser related methods. public synchronized BaseUser loadUser ( UserKey key ) throws NoSuchUserException, SSOIdentityException { if ( !_initialized ) initialize(); if ( !( key instanceof SimpleUserKey) ) { throw new SSOIdentityException( "Unsupported key type : " + key.getClass().getName() ); } Element domUser = null; // select the user by the supplied property name if (_principalUidAttributeID != null) { Collection<Element> domUsers = _users.values(); for (Iterator<Element> iterator = domUsers.iterator(); iterator.hasNext();) { Element currUser = iterator.next(); NodeList propertiesLst = ( (Element) currUser ).getElementsByTagName( "property" ); for ( int i = 0; i < propertiesLst.getLength(); i++ ) { Element domProperty = (Element) propertiesLst.item( i ); Node domName = ( (Element) domProperty ).getElementsByTagName( "name" ).item( 0 ); if ( domName.getNodeType() != Node.ELEMENT_NODE || !domName.getNodeName().equals( "name" ) ) throw new SSOIdentityException( "Property definitions need a 'name' and 'value' element" ); Node domValue = ( (Element) domProperty ).getElementsByTagName( "value" ).item( 0 ); if ( domValue.getNodeType() != Node.ELEMENT_NODE || !domValue.getNodeName().equals( "value" ) ) throw new SSOIdentityException( "Property definitions need a 'name' and 'value' element" ); String name = getTextContent( domName ); String value = getTextContent( domValue ); if (name.equals(_principalUidAttributeID) && value.equals(((SimpleUserKey) key).getId() )) { domUser = currUser; break; } } } } else { domUser = _users.get( ( (SimpleUserKey) key ).getId() ); } if ( domUser == null ) throw new NoSuchUserException( key ); BaseUser user = toBaseUser( domUser ); if ( logger.isDebugEnabled() ) logger.debug( "[load(" + key + ")] : ok" ); return user; } /** * @param key * * @throws SSOIdentityException */ public synchronized BaseRole[] findRolesByUserKey ( UserKey key ) throws SSOIdentityException { // TODO : This should be added to the store lifecycle if ( !_initialized ) initialize(); List<BaseRole> roles = new ArrayList<BaseRole>(); SimpleUserKey simpleKey = (SimpleUserKey) key; String username = simpleKey.getId(); if (_principalUidAttributeID != null) { BaseUser user = loadUser( key ); username = user.getName(); } Set<String> roleNames = _userRoles.get( username ); if ( roleNames != null ) { Iterator it = roleNames.iterator(); while ( it.hasNext() ) { String roleName = (String) it.next(); BaseRole role = findRoleByName( roleName ); if ( role == null ) throw new SSOIdentityException( "Role '" + roleName + "' declared for user '" + key + "' not defined" ); roles.add( role ); } } return roles.toArray( new BaseRole[roles.size()] ); } // ------------------------------------------------------------------------------ // CredentialStore // ------------------------------------------------------------------------------ /** * Gets configured credentials for this principal. * @param key used to retrieve this credentials. * * @throws SSOIdentityException */ public Credential[] loadCredentials ( CredentialKey key, CredentialProvider cp ) throws SSOIdentityException { // TODO : This should be added to the store lifecycle if ( !_initialized ) initialize(); if ( !( key instanceof SimpleUserKey ) ) { throw new SSOIdentityException( "Unsupported key type : " + key.getClass().getName() ); } SimpleUserKey simpleKey = (SimpleUserKey) key; Element credentialElement = _principalCredentials.get( simpleKey.getId() ); if (credentialElement == null) { return new Credential[0]; } Credential[] creds = toCredentials( credentialElement, cp ); logger.debug( "Found " + creds.length + " credentials!" ); if ( logger.isDebugEnabled() ) { for ( int i = 0; i < creds.length; i++ ) { Credential cred = creds[ i ]; logger.debug( "Credential[" + i + "]=" + creds[ i ] ); } } return creds; } // ------------------------------------------------------------------------------ // utils .... // ------------------------------------------------------------------------------ /** * Transforms a DOM Node to a Credential instance * @return */ protected Credential[] toCredentials ( Element domCredentialSet, CredentialProvider cp ) throws SSOIdentityException { NodeList domCredentials = domCredentialSet.getElementsByTagName( "credential" ); List<Credential> creds = new ArrayList<Credential>(); // Each child must be a credential element for ( int i = 0; i < domCredentials.getLength(); i++ ) { Element domCredential = (Element) domCredentials.item( i ); if ( domCredential.getNodeType() != Node.ELEMENT_NODE || !domCredential.getNodeName().equals( "credential" ) ) continue; Node domName = domCredential.getElementsByTagName( "name" ).item( 0 ); if ( domName.getNodeType() != Node.ELEMENT_NODE || !domName.getNodeName().equals( "name" ) ) throw new SSOIdentityException( "Credential definitions need a 'name' and 'value' element" ); Node domValue = domCredential.getElementsByTagName( "value" ).item( 0 ); if ( domValue.getNodeType() != Node.ELEMENT_NODE || !domValue.getNodeName().equals( "value" ) ) throw new SSOIdentityException( "Credential definitions need a 'name' and 'value' element" ); String name = getTextContent( domName ); String value = getTextContent( domValue ); if ( logger.isDebugEnabled() ) logger.debug( "Creating credential [" + name + "/" + value + "] " ); Credential c = cp.newCredential( name, value ); if ( c != null ) creds.add( c ); } return creds.toArray( new Credential[creds.size()] ); } protected BaseRole toBaseRole ( Element domRole ) throws SSOIdentityException { Node domName = ( (Element) domRole ).getElementsByTagName( "name" ).item( 0 ); if ( domName.getNodeType() != Node.ELEMENT_NODE || !domName.getNodeName().equals( "name" ) ) throw new SSOIdentityException( "Role definitions need a 'name' element" ); return new BaseRoleImpl( getTextContent( domName ) ); } protected BaseUser toBaseUser ( Element domUser ) throws SSOIdentityException { // Build the user instance Node domUsername = ( (Element) domUser ).getElementsByTagName( "name" ).item( 0 ); if ( domUsername.getNodeType() != Node.ELEMENT_NODE || !domUsername.getNodeName().equals( "name" ) ) { throw new SSOIdentityException( "User definitions need a 'name'" ); } String username = getTextContent( domUsername ); BaseUser user = new BaseUserImpl(); UserKey key = new SimpleUserKey( username ); user.setName( username ); // Add user properties NodeList propertiesLst = ( (Element) domUser ).getElementsByTagName( "property" ); for ( int i = 0; i < propertiesLst.getLength(); i++ ) { Element domProperty = (Element) propertiesLst.item( i ); Node domName = ( (Element) domProperty ).getElementsByTagName( "name" ).item( 0 ); if ( domName.getNodeType() != Node.ELEMENT_NODE || !domName.getNodeName().equals( "name" ) ) throw new SSOIdentityException( "Property definitions need a 'name' and 'value' element" ); Node domValue = ( (Element) domProperty ).getElementsByTagName( "value" ).item( 0 ); if ( domValue.getNodeType() != Node.ELEMENT_NODE || !domValue.getNodeName().equals( "value" ) ) throw new SSOIdentityException( "Property definitions need a 'name' and 'value' element" ); String name = getTextContent( domName ); String value = getTextContent( domValue ); user.addProperty( new SSONameValuePair( name, value ) ); } // Add user roles !? NodeList rolesLst = ( (Element) domUser ).getElementsByTagName( "roles" ); if ( rolesLst.getLength() > 1 ) throw new SSOIdentityException( "Only one 'roles' element can be defined for a user" ); if ( rolesLst.getLength() > 0 ) { Set<String> roles = new HashSet<String>(); Node domRoles = rolesLst.item( 0 ); String stRoles = getTextContent( domRoles ); StringTokenizer st = new StringTokenizer( stRoles != null ? stRoles : "", "," ); while ( st.hasMoreTokens() ) { String roleName = st.nextToken().trim(); BaseRole role = findRoleByName( roleName ); roles.add( roleName ); logger.debug( "User is in role : " + role ); } _userRoles.put( username, roles ); } return user; } protected CredentialKey createCredentialKey ( String name ) { // TODO : Use proper key adapter. return new SimpleUserKey( name ); } public synchronized Set<String> getRoleKeys () throws SSOIdentityException { return _roles.keySet(); } public synchronized BaseRole loadRole ( RoleKey roleKey ) throws NoSuchRoleException, SSOIdentityException { BaseRole role = (BaseRole) _roles.get( roleKey ); if ( role == null ) throw new NoSuchRoleException( roleKey ); return role; } public synchronized BaseRole findRoleByName ( String name ) throws SSOIdentityException { Element domRole = _roles.get( name ); if ( domRole == null ) throw new SSOIdentityException( "No such role : " + name ); return toBaseRole( domRole ); } // ----------------------------------------------------------------------------------------- // Private Utils. // ----------------------------------------------------------------------------------------- protected UserKey createUserKey ( BaseUser user ) { // TODO : Use proper key adapter ... return new SimpleUserKey( user.getName() ); } protected BaseRole createRole ( String name ) { BaseRole role = new BaseRoleImpl(); role.setName( name ); return role; } protected RoleKey createRoleKey ( BaseRole role ) { // TODO : Use proper key adapter ... return new SimpleRoleKey( role.getName() ); } // --------------------------------------------------------------------- // Configuration properties // --------------------------------------------------------------------- public void setCredentialsFileName ( Resource credentialsFileName ) { logger.debug( "Setting crednetials file name to : " + credentialsFileName ); _credentialsFileName = credentialsFileName; } public Resource getCredentialsFileName () { return _credentialsFileName; } public void setUsersFileName ( Resource usersFileName ) { logger.debug( "Setting users file name to : " + usersFileName ); _usersFileName = usersFileName; } public Resource getUsersFileName() { return _usersFileName; } public String getPrincipalUidAttributeID() { return _principalUidAttributeID; } public void setPrincipalUidAttributeID(String principalUidAttributeID) { _principalUidAttributeID = principalUidAttributeID; } // Some utils ... protected String getTextContent ( Node node ) { try { // Only supported in earlier versions of DOM Method getTextContent = node.getClass().getMethod( "getTextContent" ); return (String) getTextContent.invoke( node ); } catch ( NoSuchMethodException e ) { logger.debug( "Using old DOM Java Api to get Node text content" ); } catch ( InvocationTargetException e ) { logger.warn( e.getMessage(), e ); } catch ( IllegalAccessException e ) { logger.warn( e.getMessage(), e ); } // Old DOM API usage to get node's text content NodeList children = node.getChildNodes(); for ( int i = 0; i < children.getLength(); i++ ) { Node child = children.item( i ); if ( child.getNodeType() == Node.TEXT_NODE ) { return child.getNodeValue(); } } return null; } }