You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by ad...@apache.org on 2009/03/11 06:40:54 UTC

svn commit: r752380 [6/9] - in /incubator/jsecurity/trunk: ./ core/src/org/apache/ki/ core/src/org/apache/ki/aop/ core/src/org/apache/ki/authc/ core/src/org/apache/ki/authc/credential/ core/src/org/apache/ki/authc/pam/ core/src/org/apache/ki/authz/ cor...

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.activedirectory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.ki.authc.AuthenticationInfo;
+import org.apache.ki.authc.AuthenticationToken;
+import org.apache.ki.authc.SimpleAuthenticationInfo;
+import org.apache.ki.authc.UsernamePasswordToken;
+import org.apache.ki.authz.AuthorizationInfo;
+import org.apache.ki.authz.SimpleAuthorizationInfo;
+import org.apache.ki.realm.Realm;
+import org.apache.ki.realm.ldap.AbstractLdapRealm;
+import org.apache.ki.realm.ldap.LdapContextFactory;
+import org.apache.ki.realm.ldap.LdapUtils;
+import org.apache.ki.subject.PrincipalCollection;
+
+
+/**
+ * <p>An {@link Realm} that authenticates with an active directory LDAP
+ * server to determine the roles for a particular user.  This implementation
+ * queries for the user's groups and then maps the group names to roles using the
+ * {@link #groupRolesMap}.</p>
+ *
+ * @author Tim Veil
+ * @author Jeremy Haile
+ * @since 0.1
+ */
+public class ActiveDirectoryRealm extends AbstractLdapRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    private static final Log log = LogFactory.getLog(ActiveDirectoryRealm.class);    
+
+    private static final String ROLE_NAMES_DELIMETER = ",";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+
+    /**
+     * Mapping from fully qualified active directory
+     * group names (e.g. CN=Group,OU=Company,DC=MyDomain,DC=local)
+     * as returned by the active directory LDAP server to role names.
+     */
+    private Map<String, String> groupRolesMap;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    public void setGroupRolesMap(Map<String, String> groupRolesMap) {
+        this.groupRolesMap = groupRolesMap;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+
+    /**
+     * <p>Builds an {@link AuthenticationInfo} object by querying the active directory LDAP context for the
+     * specified username.  This method binds to the LDAP server using the provided username and password -
+     * which if successful, indicates that the password is correct.</p>
+     *
+     * <p>This method can be overridden by subclasses to query the LDAP server in a more complex way.</p>
+     *
+     * @param token              the authentication token provided by the user.
+     * @param ldapContextFactory the factory used to build connections to the LDAP server.
+     * @return an {@link AuthenticationInfo} instance containing information retrieved from LDAP.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
+
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+
+        // Binds using the username and password provided by the user.
+        LdapContext ctx = null;
+        try {
+            ctx = ldapContextFactory.getLdapContext(upToken.getUsername(), String.valueOf(upToken.getPassword()));
+        } finally {
+            LdapUtils.closeContext(ctx);
+        }
+
+        return buildAuthenticationInfo(upToken.getUsername(), upToken.getPassword());
+    }
+
+    protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
+        return new SimpleAuthenticationInfo(username, password, getName());
+    }
+
+
+    /**
+     * <p>Builds an {@link org.apache.ki.authz.AuthorizationInfo} object by querying the active directory LDAP context for the
+     * groups that a user is a member of.  The groups are then translated to role names by using the
+     * configured {@link #groupRolesMap}.</p>
+     *
+     * <p>This implementation expects the <tt>principal</tt> argument to be a String username.
+     *
+     * <p>Subclasses can override this method to determine authorization data (roles, permissions, etc) in a more
+     * complex way.  Note that this default implementation does not support permissions, only roles.</p>
+     *
+     * @param principals         the principal of the Subject whose account is being retrieved.
+     * @param ldapContextFactory the factory used to create LDAP connections.
+     * @return the AuthorizationInfo for the given Subject principal.
+     * @throws NamingException if an error occurs when searching the LDAP server.
+     */
+    protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory) throws NamingException {
+
+        String username;
+
+
+        username = (String) principals.fromRealm(getName()).iterator().next();
+
+        // Perform context search
+        LdapContext ldapContext = ldapContextFactory.getSystemLdapContext();
+
+        Set<String> roleNames;
+
+        try {
+            roleNames = getRoleNamesForUser(username, ldapContext);
+        } finally {
+            LdapUtils.closeContext(ldapContext);
+        }
+
+        return buildAuthorizationInfo(roleNames);
+    }
+
+    protected AuthorizationInfo buildAuthorizationInfo(Set<String> roleNames) {
+        return new SimpleAuthorizationInfo(roleNames);
+    }
+
+    private Set<String> getRoleNamesForUser(String username, LdapContext ldapContext) throws NamingException {
+        Set<String> roleNames;
+        roleNames = new LinkedHashSet<String>();
+
+        SearchControls searchCtls = new SearchControls();
+        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
+
+        String userPrincipalName = username;
+        if( principalSuffix != null ) {
+            userPrincipalName += principalSuffix;
+        }
+
+        String searchFilter = "(&(objectClass=*)(userPrincipalName=" + userPrincipalName + "))";
+
+        NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchCtls);
+
+        while (answer.hasMoreElements()) {
+            SearchResult sr = (SearchResult) answer.next();
+
+            if (log.isDebugEnabled()) {
+                log.debug("Retrieving group names for user [" + sr.getName() + "]");
+            }
+
+            Attributes attrs = sr.getAttributes();
+
+            if (attrs != null) {
+                NamingEnumeration ae = attrs.getAll();
+                while (ae.hasMore()) {
+                    Attribute attr = (Attribute) ae.next();
+
+                    if (attr.getID().equals("memberOf")) {
+
+                        Collection<String> groupNames = LdapUtils.getAllAttributeValues(attr);
+
+                        if (log.isDebugEnabled()) {
+                            log.debug("Groups found for user [" + username + "]: " + groupNames);
+                        }
+
+                        Collection<String> rolesForGroups = getRoleNamesForGroups(groupNames);
+                        roleNames.addAll(rolesForGroups);
+                    }
+                }
+            }
+        }
+        return roleNames;
+    }
+
+    /**
+     * This method is called by the default implementation to translate Active Directory group names
+     * to role names.  This implementation uses the {@link #groupRolesMap} to map group names to role names.
+     *
+     * @param groupNames the group names that apply to the current user.
+     * @return a collection of roles that are implied by the given role names.
+     */
+    protected Collection<String> getRoleNamesForGroups(Collection<String> groupNames) {
+        Set<String> roleNames = new HashSet<String>(groupNames.size());
+
+        if (groupRolesMap != null) {
+            for (String groupName : groupNames) {
+                String strRoleNames = groupRolesMap.get(groupName);
+                if (strRoleNames != null) {
+                    for (String roleName : strRoleNames.split(ROLE_NAMES_DELIMETER)) {
+
+                        if (log.isDebugEnabled()) {
+                            log.debug("User is member of group [" + groupName + "] so adding role [" + roleName + "]");
+                        }
+
+                        roleNames.add(roleName);
+
+                    }
+                }
+            }
+        }
+        return roleNames;
+    }
+
+
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/activedirectory/ActiveDirectoryRealm.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,370 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.jdbc;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.sql.DataSource;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.ki.authc.AccountException;
+import org.apache.ki.authc.AuthenticationException;
+import org.apache.ki.authc.AuthenticationInfo;
+import org.apache.ki.authc.AuthenticationToken;
+import org.apache.ki.authc.SimpleAuthenticationInfo;
+import org.apache.ki.authc.UnknownAccountException;
+import org.apache.ki.authc.UsernamePasswordToken;
+import org.apache.ki.authz.AuthorizationException;
+import org.apache.ki.authz.AuthorizationInfo;
+import org.apache.ki.authz.SimpleAuthorizationInfo;
+import org.apache.ki.realm.AuthorizingRealm;
+import org.apache.ki.subject.PrincipalCollection;
+import org.apache.ki.util.JdbcUtils;
+
+
+/**
+ * <p>
+ * Realm that allows authentication and authorization via JDBC calls.  The default queries suggest a potential schema
+ * for retrieving the user's password for authentication, and querying for a user's roles and permissions.  The
+ * default queries can be overridden by setting the query properties of the realm.
+ * </p>
+ *
+ * <p>
+ * If the default implementation
+ * of authentication and authorization cannot handle your schema, this class can be subclassed and the
+ * appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(org.apache.ki.authc.AuthenticationToken)},
+ * {@link #getRoleNamesForUser(java.sql.Connection,String)}, and/or {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}
+ * </p>
+ *
+ * <p>
+ * This realm supports caching by extending from {@link org.apache.ki.realm.AuthorizingRealm}.
+ * </p>
+ *
+ * @author Jeremy Haile
+ * @since 0.2
+ */
+public class JdbcRealm extends AuthorizingRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * The default query used to retrieve account data for the user.
+     */
+    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
+
+    /**
+     * The default query used to retrieve the roles that apply to a user.
+     */
+    protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
+
+    /**
+     * The default query used to retrieve permissions that apply to a particular role.
+     */
+    protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
+
+    private static final Log log = LogFactory.getLog(JdbcRealm.class);    
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    protected DataSource dataSource;
+
+    protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
+
+    protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
+
+    protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
+
+    protected boolean permissionsLookupEnabled = false;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Sets the datasource that should be used to retrieve connections used by this realm.
+     *
+     * @param dataSource the SQL data source.
+     */
+    public void setDataSource(DataSource dataSource) {
+        this.dataSource = dataSource;
+    }
+
+    /**
+     * Overrides the default query used to retrieve a user's password during authentication.  When using the default
+     * implementation, this query must take the user's username as a single parameter and return a single result
+     * with the user's password as the first column.  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthenticationInfo(org.apache.ki.authc.AuthenticationToken)} or
+     * just {@link #getPasswordForUser(java.sql.Connection,String)}
+     *
+     * @param authenticationQuery the query to use for authentication.
+     * @see #DEFAULT_AUTHENTICATION_QUERY
+     */
+    public void setAuthenticationQuery(String authenticationQuery) {
+        this.authenticationQuery = authenticationQuery;
+    }
+
+    /**
+     * Overrides the default query used to retrieve a user's roles during authorization.  When using the default
+     * implementation, this query must take the user's username as a single parameter and return a row
+     * per role with a single column containing the role name.  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthorizationInfo(PrincipalCollection)} or just
+     * {@link #getRoleNamesForUser(java.sql.Connection,String)}
+     *
+     * @param userRolesQuery the query to use for retrieving a user's roles.
+     * @see #DEFAULT_USER_ROLES_QUERY
+     */
+    public void setUserRolesQuery(String userRolesQuery) {
+        this.userRolesQuery = userRolesQuery;
+    }
+
+    /**
+     * <p>
+     * Overrides the default query used to retrieve a user's permissions during authorization.  When using the default
+     * implementation, this query must take a role name as the single parameter and return a row
+     * per permission with three columns containing the fully qualified name of the permission class, the permission
+     * name, and the permission actions (in that order).  If you require a solution that does not match this query
+     * structure, you can override {@link #doGetAuthorizationInfo(org.apache.ki.subject.PrincipalCollection)} or just
+     * {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}</p>
+     *
+     * <p><b>Permissions are only retrieved if you set {@link #permissionsLookupEnabled} to true.  Otherwise,
+     * this query is ignored.</b></p>
+     *
+     * @param permissionsQuery the query to use for retrieving permissions for a role.
+     * @see #DEFAULT_PERMISSIONS_QUERY
+     * @see #setPermissionsLookupEnabled(boolean)
+     */
+    public void setPermissionsQuery(String permissionsQuery) {
+        this.permissionsQuery = permissionsQuery;
+    }
+
+    /**
+     * Enables lookup of permissions during authorization.  The default is "false" - meaning that only roles
+     * are associated with a user.  Set this to true in order to lookup roles <b>and</b> permissions.
+     *
+     * @param permissionsLookupEnabled true if permissions should be looked up during authorization, or false if only
+     *                                 roles should be looked up.
+     */
+    public void setPermissionsLookupEnabled(boolean permissionsLookupEnabled) {
+        this.permissionsLookupEnabled = permissionsLookupEnabled;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+
+        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
+        String username = upToken.getUsername();
+
+        // Null username is invalid
+        if (username == null) {
+            throw new AccountException("Null usernames are not allowed by this realm.");
+        }
+
+        Connection conn = null;
+        AuthenticationInfo info = null;
+        try {
+            conn = dataSource.getConnection();
+
+            String password = getPasswordForUser(conn, username);
+
+            if (password == null) {
+                throw new UnknownAccountException("No account found for user [" + username + "]");
+            }
+
+            info = buildAuthenticationInfo(username, password.toCharArray());
+
+        } catch (SQLException e) {
+            final String message = "There was a SQL error while authenticating user [" + username + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+
+            // Rethrow any SQL errors as an authentication exception
+            throw new AuthenticationException(message, e);
+        } finally {
+            JdbcUtils.closeConnection(conn);
+        }
+
+        return info;
+    }
+
+    protected AuthenticationInfo buildAuthenticationInfo(String username, char[] password) {
+        return new SimpleAuthenticationInfo(username, password, getName());
+    }
+
+    private String getPasswordForUser(Connection conn, String username) throws SQLException {
+
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        String password = null;
+        try {
+            ps = conn.prepareStatement(authenticationQuery);
+            ps.setString(1, username);
+
+            // Execute query
+            rs = ps.executeQuery();
+
+            // Loop over results - although we are only expecting one result, since usernames should be unique
+            boolean foundResult = false;
+            while (rs.next()) {
+
+                // Check to ensure only one row is processed
+                if (foundResult) {
+                    throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
+                }
+
+                password = rs.getString(1);
+
+                foundResult = true;
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+
+        return password;
+    }
+
+    /**
+     * This implementation of the interface expects the principals collection to return a String username keyed off of
+     * this realm's {@link #getName() name}
+     *
+     * @see AuthorizingRealm#getAuthorizationInfo(org.apache.ki.subject.PrincipalCollection)
+     */
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+
+        //null usernames are invalid
+        if (principals == null) {
+            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
+        }
+
+        String username = (String) principals.fromRealm(getName()).iterator().next();
+
+        Connection conn = null;
+        Set<String> roleNames = null;
+        Set<String> permissions = null;
+        try {
+            conn = dataSource.getConnection();
+
+            // Retrieve roles and permissions from database
+            roleNames = getRoleNamesForUser(conn, username);
+            if( permissionsLookupEnabled ) {
+                permissions = getPermissions(conn, username, roleNames);
+            }
+
+        } catch (SQLException e) {
+            final String message = "There was a SQL error while authorizing user [" + username + "]";
+            if (log.isErrorEnabled()) {
+                log.error(message, e);
+            }
+
+            // Rethrow any SQL errors as an authorization exception
+            throw new AuthorizationException(message, e);
+        } finally {
+            JdbcUtils.closeConnection(conn);
+        }
+
+        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
+        info.setStringPermissions( permissions );
+        return info;
+
+    }
+
+    protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        Set<String> roleNames = new LinkedHashSet<String>();
+        try {
+            ps = conn.prepareStatement(userRolesQuery);
+            ps.setString(1, username);
+
+            // Execute query
+            rs = ps.executeQuery();
+
+            // Loop over results and add each returned role to a set
+            while (rs.next()) {
+
+                String roleName = rs.getString(1);
+
+                // Add the role to the list of names if it isn't null
+                if (roleName != null) {
+                    roleNames.add(roleName);
+                } else {
+                    if (log.isWarnEnabled()) {
+                        log.warn("Null role name found while retrieving role names for user [" + username + "]");
+                    }
+                }
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+        return roleNames;
+    }
+
+    protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
+        PreparedStatement ps = null;
+        ResultSet rs = null;
+        Set<String> permissions = new LinkedHashSet<String>();
+        try {
+            for (String roleName : roleNames) {
+
+                ps = conn.prepareStatement(permissionsQuery);
+                ps.setString(1, roleName);
+
+                // Execute query
+                rs = ps.executeQuery();
+
+                // Loop over results and add each returned role to a set
+                while (rs.next()) {
+
+                    String permissionString = rs.getString(1);
+
+                    // Add the permission to the set of permissions
+                    permissions.add(permissionString);
+                }
+
+            }
+        } finally {
+            JdbcUtils.closeResultSet(rs);
+            JdbcUtils.closeStatement(ps);
+        }
+
+        return permissions;
+    }
+
+}
\ No newline at end of file

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jdbc/JdbcRealm.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.jndi;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.ki.jndi.JndiLocator;
+import org.apache.ki.realm.Realm;
+import org.apache.ki.realm.RealmFactory;
+import org.apache.ki.util.StringUtils;
+
+
+/**
+ * Looks up one or more Realm instances from JNDI using specified {@link #setJndiNames jndiNames}.
+ *
+ * <p>This is primarily provided to support Realm instances configured in JEE and EJB environments, but will
+ * work in any environment where {@link Realm Realm} instances are bound in JNDI instead of using
+ * programmatic or text-based configuration.
+ *
+ * @author Les Hazlewood
+ * @since 0.9
+ */
+public class JndiRealmFactory extends JndiLocator implements RealmFactory {
+
+    Collection<String> jndiNames = null;
+
+    /**
+     * Returns the JNDI names that will be used to look up Realm(s) from JNDI.
+     *
+     * @return the JNDI names that will be used to look up Realm(s) from JNDI.
+     * @see #setJndiNames(String)
+     * @see #setJndiNames(Collection)
+     */
+    public Collection<String> getJndiNames() {
+        return jndiNames;
+    }
+
+    /**
+     * Sets the JNDI names that will be used to look up Realm(s) from JNDI.
+     * <p/>
+     * The order of the collection determines the order that the Realms will be returned to the SecurityManager.
+     * <p/>
+     * If you find it easier to specify these names as a comma-delmited string, you may use the
+     * {@link #setJndiNames(String)} method instead.
+     *
+     * @param jndiNames the JNDI names that will be used to look up Realm(s) from JNDI.
+     * @see #setJndiNames(String)
+     */
+    public void setJndiNames(Collection<String> jndiNames) {
+        this.jndiNames = jndiNames;
+    }
+
+    /**
+     * Specifies a comma-delimited list of JNDI names to lookup, each one corresponding to a jndi-bound
+     * {@link Realm Realm}.  The Realms will be made available to the SecurityManager in the order
+     * they are defined.
+     *
+     * @param commaDelimited a comma-delimited list of JNDI names, each representing the JNDI name used to
+     *                       look up a corresponding jndi-bound Realm.
+     * @throws IllegalStateException if the specified argument is null or the empty string.
+     */
+    public void setJndiNames(String commaDelimited) throws IllegalStateException {
+        String arg = StringUtils.clean(commaDelimited);
+        if (arg == null) {
+            String msg = "One or more comma-delimited jndi names must be specified for the " +
+                    getClass().getName() + " to locate Realms.";
+            throw new IllegalStateException(msg);
+        }
+        String[] names = StringUtils.tokenizeToStringArray(arg, ",");
+        setJndiNames(Arrays.asList(names));
+    }
+
+    /**
+     * Performs the JNDI lookups for each specified {@link #getJndiNames() JNDI name} and returns all
+     * discovered Realms in an ordered collection.
+     *
+     * <p>The returned Collection is in the same order as the specified
+     * {@link #setJndiNames(java.util.Collection) jndiNames}
+     *
+     * @return an ordered collection of the {@link #setJndiNames(java.util.Collection) specified Realms} found in JNDI.
+     * @throws IllegalStateException if any of the JNDI names fails to successfully look up a Realm instance.
+     */
+    public Collection<Realm> getRealms() throws IllegalStateException {
+        Collection<String> jndiNames = getJndiNames();
+        if (jndiNames == null || jndiNames.isEmpty()) {
+            String msg = "One or more jndi names must be specified for the " +
+                    getClass().getName() + " to locate Realms.";
+            throw new IllegalStateException(msg);
+        }
+        List<Realm> realms = new ArrayList<Realm>(jndiNames.size());
+        for (String name : jndiNames) {
+            try {
+                Realm realm = (Realm) lookup(name, Realm.class);
+                realms.add(realm);
+            } catch (Exception e) {
+                throw new IllegalStateException("Unable to look up realm with jndi name '" + name + "'.", e);
+            }
+        }
+        return realms.isEmpty() ? null : realms;
+    }
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/jndi/JndiRealmFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,240 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.ldap;
+
+import javax.naming.NamingException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.ki.authc.AuthenticationException;
+import org.apache.ki.authc.AuthenticationInfo;
+import org.apache.ki.authc.AuthenticationToken;
+import org.apache.ki.authz.AuthorizationInfo;
+import org.apache.ki.realm.AuthorizingRealm;
+import org.apache.ki.subject.PrincipalCollection;
+
+/**
+ * <p>A {@link org.apache.ki.realm.Realm} that authenticates with an LDAP
+ * server to build the Subject for a user.  This implementation only returns roles for a
+ * particular user, and not permissions - but it can be subclassed to build a permission
+ * list as well.</p>
+ *
+ * <p>Implementations would need to implement the
+ * {@link #queryForAuthenticationInfo(org.apache.ki.authc.AuthenticationToken ,LdapContextFactory)} and
+ * {@link #queryForAuthorizationInfo(org.apache.ki.subject.PrincipalCollection ,LdapContextFactory)} abstract methods.</p>
+ *
+ * <p>By default, this implementation will create an instance of {@link DefaultLdapContextFactory} to use for
+ * creating LDAP connections using the principalSuffix, searchBase, url, systemUsername, and systemPassword properties
+ * specified on the realm.  The remaining settings use the defaults of {@link DefaultLdapContextFactory}, which are usually
+ * sufficient.  If more customized connections are needed, you should inject a custom {@link LdapContextFactory}, which
+ * will cause these properties specified on the realm to be ignored.</p>
+ *
+ * @author Jeremy Haile
+ * @author Les Hazlewood
+ * @see #queryForAuthenticationInfo(org.apache.ki.authc.AuthenticationToken , LdapContextFactory)
+ * @see #queryForAuthorizationInfo(org.apache.ki.subject.PrincipalCollection , LdapContextFactory)
+ * @since 0.1
+ */
+public abstract class AbstractLdapRealm extends AuthorizingRealm {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+
+    private static final Log log = LogFactory.getLog(AbstractLdapRealm.class);    
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    protected String principalSuffix = null;
+
+    protected String searchBase = null;
+
+    protected String url = null;
+
+    protected String systemUsername = null;
+
+    protected String systemPassword = null;
+
+    private LdapContextFactory ldapContextFactory = null;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param principalSuffix the suffix.
+     * @see DefaultLdapContextFactory#setPrincipalSuffix(String)
+     */
+    public void setPrincipalSuffix(String principalSuffix) {
+        this.principalSuffix = principalSuffix;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param searchBase the search base.
+     * @see DefaultLdapContextFactory#setSearchBase(String)
+     */
+    public void setSearchBase(String searchBase) {
+        this.searchBase = searchBase;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param url the LDAP url.
+     * @see DefaultLdapContextFactory#setUrl(String)
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param systemUsername the username to use when logging into the LDAP server for authorization.
+     * @see DefaultLdapContextFactory#setSystemUsername(String)
+     */
+    public void setSystemUsername(String systemUsername) {
+        this.systemUsername = systemUsername;
+    }
+
+
+    /**
+     * Used when initializing the default {@link LdapContextFactory}.  This property is ignored if a custom
+     * <tt>LdapContextFactory</tt> is specified.
+     *
+     * @param systemPassword the password to use when logging into the LDAP server for authorization.
+     * @see DefaultLdapContextFactory#setSystemPassword(String)
+     */
+    public void setSystemPassword(String systemPassword) {
+        this.systemPassword = systemPassword;
+    }
+
+
+    /**
+     * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
+     * authentication and authorization.  If this is set, the {@link LdapContextFactory} provided will be used.
+     * Otherwise, a {@link DefaultLdapContextFactory} instance will be created based on the properties specified
+     * in this realm.
+     *
+     * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
+     */
+    public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
+        this.ldapContextFactory = ldapContextFactory;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S                |
+    ============================================*/
+
+    protected void afterAuthorizationCacheSet() {
+        if (ldapContextFactory == null) {
+
+            if (log.isDebugEnabled()) {
+                log.debug("No LdapContextFactory is specified, so a default instance is being created.");
+            }
+
+            DefaultLdapContextFactory defaultFactory = new DefaultLdapContextFactory();
+            defaultFactory.setPrincipalSuffix(this.principalSuffix);
+            defaultFactory.setSearchBase(this.searchBase);
+            defaultFactory.setUrl(this.url);
+            defaultFactory.setSystemUsername(this.systemUsername);
+            defaultFactory.setSystemPassword(this.systemPassword);
+
+            ldapContextFactory = defaultFactory;
+        }
+    }
+
+
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+        AuthenticationInfo info = null;
+        try {
+            info = queryForAuthenticationInfo(token, this.ldapContextFactory);
+        } catch (NamingException e) {
+            if (log.isErrorEnabled()) {
+                final String message = "LDAP naming error while attempting to authenticate user.";
+                log.error(message, e);
+            }
+        }
+
+        return info;
+    }
+
+
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
+        AuthorizationInfo info = null;
+        try {
+            info = queryForAuthorizationInfo(principals, this.ldapContextFactory);
+        } catch (NamingException e) {
+            if (log.isErrorEnabled()) {
+                final String message = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
+                log.error(message, e);
+            }
+        }
+
+        return info;
+    }
+
+
+    /**
+     * <p>Abstract method that should be implemented by subclasses to builds an
+     * {@link AuthenticationInfo} object by querying the LDAP context for the
+     * specified username.</p>
+     *
+     * @param token              the authentication token given during authentication.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException;
+
+
+    /**
+     * <p>Abstract method that should be implemented by subclasses to builds an
+     * {@link AuthorizationInfo} object by querying the LDAP context for the
+     * specified principal.</p>
+     *
+     * @param principal          the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
+     * @param ldapContextFactory factory used to retrieve LDAP connections.
+     * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
+     * @throws NamingException if any LDAP errors occur during the search.
+     */
+    protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal, LdapContextFactory ldapContextFactory) throws NamingException;
+
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/AbstractLdapRealm.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.ldap;
+
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.ldap.InitialLdapContext;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * <p>Default implementation of {@link LdapContextFactory} that can be configured or extended to
+ * customize the way {@link javax.naming.ldap.LdapContext} objects are retrieved.</p>
+ *
+ * <p>This implementation of {@link LdapContextFactory} is used by the {@link AbstractLdapRealm} if a
+ * factory is not explictly configured.</p>
+ *
+ * <p>Connection pooling is enabled by default on this factory, but can be disabled using the
+ * {@link #usePooling} property.</p>
+ *
+ * @author Jeremy Haile
+ * @since 0.2
+ */
+public class DefaultLdapContextFactory implements LdapContextFactory {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    /**
+     * The Sun LDAP property used to enable connection pooling.  This is used in the default implementation
+     * to enable LDAP connection pooling.
+     */
+    protected static final String SUN_CONNECTION_POOLING_PROPERTY = "com.sun.jndi.ldap.connect.pool";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+
+    private static final Log log = LogFactory.getLog(DefaultLdapContextFactory.class);
+
+    protected String authentication = "simple";
+
+    protected String principalSuffix = null;
+
+    protected String searchBase = null;
+
+    protected String contextFactoryClassName = "com.sun.jndi.ldap.LdapCtxFactory";
+
+    protected String url = null;
+
+    protected String referral = "follow";
+
+    protected String systemUsername = null;
+
+    protected String systemPassword = null;
+
+    private boolean usePooling = true;
+
+    private Map<String, String> additionalEnvironment;
+
+    /*--------------------------------------------
+    |         C O N S T R U C T O R S           |
+    ============================================*/
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Sets the type of LDAP authentication to perform when connecting to the LDAP server.  Defaults to "simple"
+     *
+     * @param authentication the type of LDAP authentication to perform.
+     */
+    public void setAuthentication(String authentication) {
+        this.authentication = authentication;
+    }
+
+    /**
+     * A suffix appended to the username. This is typically for
+     * domain names.  (e.g. "@MyDomain.local")
+     *
+     * @param principalSuffix the suffix.
+     */
+    public void setPrincipalSuffix(String principalSuffix) {
+        this.principalSuffix = principalSuffix;
+    }
+
+    /**
+     * The search base for the search to perform in the LDAP server.
+     * (e.g. OU=OrganizationName,DC=MyDomain,DC=local )
+     *
+     * @param searchBase the search base.
+     */
+    public void setSearchBase(String searchBase) {
+        this.searchBase = searchBase;
+    }
+
+    /**
+     * The context factory to use. This defaults to the SUN LDAP JNDI implementation
+     * but can be overridden to use custom LDAP factories.
+     *
+     * @param contextFactoryClassName the context factory that should be used.
+     */
+    public void setContextFactoryClassName(String contextFactoryClassName) {
+        this.contextFactoryClassName = contextFactoryClassName;
+    }
+
+    /**
+     * The LDAP url to connect to. (e.g. ldap://<ldapDirectoryHostname>:<port>)
+     *
+     * @param url the LDAP url.
+     */
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    /**
+     * Sets the LDAP referral property.  Defaults to "follow"
+     *
+     * @param referral the referral property.
+     */
+    public void setReferral(String referral) {
+        this.referral = referral;
+    }
+
+    /**
+     * The system username that will be used when connecting to the LDAP server to retrieve authorization
+     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
+     * only authentication.
+     *
+     * @param systemUsername the username to use when logging into the LDAP server for authorization.
+     */
+    public void setSystemUsername(String systemUsername) {
+        this.systemUsername = systemUsername;
+    }
+
+
+    /**
+     * The system password that will be used when connecting to the LDAP server to retrieve authorization
+     * information about a user.  This must be specified for LDAP authorization to work, but is not required for
+     * only authentication.
+     *
+     * @param systemPassword the password to use when logging into the LDAP server for authorization.
+     */
+    public void setSystemPassword(String systemPassword) {
+        this.systemPassword = systemPassword;
+    }
+
+    /**
+     * Determines whether or not LdapContext pooling is enabled for connections made using the system
+     * user account.  In the default implementation, this simply
+     * sets the <tt>com.sun.jndi.ldap.connect.pool</tt> property in the LDAP context environment.  If you use an
+     * LDAP Context Factory that is not Sun's default implementation, you will need to override the
+     * default behavior to use this setting in whatever way your underlying LDAP ContextFactory
+     * supports.  By default, pooling is enabled.
+     *
+     * @param usePooling true to enable pooling, or false to disable it.
+     */
+    public void setUsePooling(boolean usePooling) {
+        this.usePooling = usePooling;
+    }
+
+    /**
+     * These entries are added to the environment map before initializing the LDAP context.
+     *
+     * @param additionalEnvironment additional environment entries to be configured on the LDAP context.
+     */
+    public void setAdditionalEnvironment(Map<String, String> additionalEnvironment) {
+        this.additionalEnvironment = additionalEnvironment;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+
+    public LdapContext getSystemLdapContext() throws NamingException {
+        return getLdapContext(systemUsername, systemPassword);
+    }
+
+    public LdapContext getLdapContext(String username, String password) throws NamingException {
+        if (searchBase == null) {
+            throw new IllegalStateException("A search base must be specified.");
+        }
+        if (url == null) {
+            throw new IllegalStateException("An LDAP URL must be specified of the form ldap://<hostname>:<port>");
+        }
+
+        if (username != null && principalSuffix != null) {
+            username += principalSuffix;
+        }
+
+        Hashtable<String, String> env = new Hashtable<String, String>();
+
+        env.put(Context.SECURITY_AUTHENTICATION, authentication);
+        if (username != null) {
+            env.put(Context.SECURITY_PRINCIPAL, username);
+        }
+        if (password != null) {
+            env.put(Context.SECURITY_CREDENTIALS, password);
+        }
+        env.put(Context.INITIAL_CONTEXT_FACTORY, contextFactoryClassName);
+        env.put(Context.PROVIDER_URL, url);
+        env.put(Context.REFERRAL, referral);
+
+        // Only pool connections for system contexts
+        if (usePooling && username != null && username.equals(systemUsername)) {
+            // Enable connection pooling
+            env.put(SUN_CONNECTION_POOLING_PROPERTY, "true");
+        }
+
+        if (additionalEnvironment != null) {
+            env.putAll(additionalEnvironment);
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Initializing LDAP context using URL [" + url + "] and username [" + systemUsername + "] " +
+                    "with pooling [" + (usePooling ? "enabled" : "disabled") + "]");
+        }
+
+        return new InitialLdapContext(env, null);
+    }
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/DefaultLdapContextFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.ldap;
+
+import javax.naming.NamingException;
+import javax.naming.ldap.LdapContext;
+
+/**
+ * Interface that encapsulates the creation of <tt>LdapContext</tt> objects that are used by subclasses
+ * of {@link AbstractLdapRealm} to query for <tt>AuthenticationInfo</tt> security data (roles, permissions, etc) of particular
+ * Subjects (users).
+ *
+ * @author Jeremy Haile
+ * @since 0.2
+ */
+public interface LdapContextFactory {
+
+    /**
+     * Creates (or retrieves from a pool) a <tt>LdapContext</tt> connection bound using the system account, or anonymously
+     * if no system account is configured.
+     *
+     * @return a <tt>LdapContext</tt> bound by the system account, or bound anonymously if no system account
+     *         is configured.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     */
+    LdapContext getSystemLdapContext() throws NamingException;
+
+    /**
+     * Creates (or retrieves from a pool) a <tt>LdapContext</tt> connection bound using the username and password
+     * specified.
+     *
+     * @param username the username to use when creating the connection.
+     * @param password the password to use when creating the connection.
+     * @return a <tt>LdapContext</tt> bound using the given username and password.
+     * @throws javax.naming.NamingException if there is an error creating the context.
+     */
+    LdapContext getLdapContext(String username, String password) throws NamingException;
+
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapContextFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.ldap;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.ldap.LdapContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Utility class providing static methods to make working with LDAP
+ * easier.
+ *
+ * @author Jeremy Haile
+ * @since 0.2
+ */
+public class LdapUtils {
+
+    /** Private internal log instance. */
+    private static final Log log = LogFactory.getLog(LdapUtils.class);
+
+    /**
+     * Private constructor to prevent instantiation
+     */
+    private LdapUtils() {
+    }
+
+    /**
+     * Closes an LDAP context, logging any errors, but not throwing
+     * an exception if there is a failure.
+     *
+     * @param ctx the LDAP context to close.
+     */
+    public static void closeContext(LdapContext ctx) {
+        try {
+            if (ctx != null) {
+                ctx.close();
+            }
+        } catch (NamingException e) {
+            if (log.isErrorEnabled()) {
+                log.error("Exception while closing LDAP context. ", e);
+            }
+        }
+    }
+
+
+    /**
+     * Helper method used to retrieve all attribute values from a particular context attribute.
+     *
+     * @param attr the LDAP attribute.
+     * @return the values of the attribute.
+     * @throws javax.naming.NamingException if there is an LDAP error while reading the values.
+     */
+    public static Collection<String> getAllAttributeValues(Attribute attr) throws NamingException {
+        Set<String> values = new HashSet<String>();
+        for (NamingEnumeration e = attr.getAll(); e.hasMore();) {
+            String value = (String) e.next();
+            values.add(value);
+        }
+        return values;
+    }
+
+
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/ldap/LdapUtils.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,368 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.text;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.Properties;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.ki.JSecurityException;
+import org.apache.ki.cache.CacheManager;
+import org.apache.ki.io.ResourceUtils;
+import org.apache.ki.util.Destroyable;
+
+
+/**
+ * A subclass of <tt>SimpleAccountRealm</tt> that defers all logic to the parent class, but just enables
+ * {@link java.util.Properties Properties} based configuration in addition to the parent class's String configuration.
+ *
+ * <p>This class allows processing of a single .properties file for user, role, and
+ * permission configuration.
+ *
+ * <p>For convenience, if the {@link #setResourcePath resourcePath} attribute is not set, this class defaults to lookup
+ * the properties file definition from <tt>classpath:jsecurity-users.properties</tt> (root of the classpath).
+ * This allows you to use this implementation by simply defining this file at the classpath root, instantiating this
+ * class, and then calling {@link #init init()}.
+ *
+ * <p>Or, you may of course specify any other file path using the <tt>url:</tt>, <tt>file:</tt>, or <tt>classpath:</tt>
+ * prefixes.</p>
+ *
+ * <p>If none of these are specified, and the jsecurity-users.properties is not included at the root of the classpath,
+ * a default failsafe configuration will be used.  This is not recommended as it only contains a few simple users and
+ * roles which are probably of little value to production applications.</p>
+ *
+ * <p>The Properties format understood by this implementation must be written as follows:
+ *
+ * <p>Each line's key/value pair represents either a user-to-role(s) mapping <em>or</em> a role-to-permission(s)
+ * mapping.
+ *
+ * <p>The user-to-role(s) lines have this format:</p>
+ *
+ * <p><code><b>user.</b><em>username</em> = <em>password</em>,role1,role2,...</code></p>
+ *
+ * <p>Note that each key is prefixed with the token <tt><b>user.</b></tt>  Each value must adhere to the
+ * the {@link #setUserDefinitions(String) setUserDefinitions(String)} JavaDoc.</p>
+ *
+ * <p>The role-to-permission(s) lines have this format:</p>
+ *
+ * <p><code><b>role.</b><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
+ *
+ * <p>where each key is prefixed with the token <tt><b>role.</b></tt> and the value adheres to the format specified in
+ * the {@link #setRoleDefinitions(String) setRoleDefinitions(String)} JavaDoc.
+ *
+ * <p>Here is an example of a very simple properties definition that conforms to the above format rules and corresponding
+ * method JavaDocs:
+ *
+ * <code>user.root = <em>rootPassword</em>,administrator<br/>
+ * user.jsmith = <em>jsmithPassword</em>,manager,engineer,employee<br/>
+ * user.abrown = <em>abrownPassword</em>,qa,employee<br/>
+ * user.djones = <em>djonesPassword</em>,qa,contractor<br/>
+ * <br/>
+ * role.administrator = org.jsecurity.authz.support.AllPermission<br/>
+ * role.manager = com.domain.UserPermission,*,read,write;com.domain.FilePermission,/usr/local/emailManagers.sh,execute<br/>
+ * role.engineer = com.domain.FilePermission,/usr/local/tomcat/bin/startup.sh,read,execute<br/>
+ * role.employee = com.domain.IntranetPermission,useWiki<br/>
+ * role.qa = com.domain.QAServerPermission,*,view,start,shutdown,restart;com.domain.ProductionServerPermission,*,view<br/>
+ * role.contractor = com.domain.IntranetPermission,useTimesheet</code>
+ *
+ * @author Les Hazlewood
+ * @author Jeremy Haile
+ * @since 0.2
+ */
+public class PropertiesRealm extends TextConfigurationRealm implements Destroyable, Runnable {
+
+    //TODO - complete JavaDoc
+
+    /*--------------------------------------------
+    |             C O N S T A N T S             |
+    ============================================*/
+    private static final int DEFAULT_RELOAD_INTERVAL_SECONDS = 10;
+    private static final String USERNAME_PREFIX = "user.";
+    private static final String ROLENAME_PREFIX = "role.";
+    private static final String DEFAULT_RESOURCE_PATH = "classpath:ki-users.properties";
+    private static final String FAILSAFE_RESOURCE_PATH = "classpath:org/apache/ki/realm/text/default-ki-users.properties";
+
+    /*--------------------------------------------
+    |    I N S T A N C E   V A R I A B L E S    |
+    ============================================*/
+    private static final Log log = LogFactory.getLog(PropertiesRealm.class);
+
+    protected ExecutorService scheduler = null;
+    protected boolean useXmlFormat = false;
+    protected String resourcePath = DEFAULT_RESOURCE_PATH;
+    protected long fileLastModified;
+    protected int reloadIntervalSeconds = DEFAULT_RELOAD_INTERVAL_SECONDS;
+
+    public PropertiesRealm() {
+        init();
+    }
+
+    public PropertiesRealm( CacheManager cacheManager ) {
+        if ( cacheManager == null ) {
+            throw new IllegalArgumentException( "cacheManager argument cannot be null." );
+        }
+        setCacheManager(cacheManager);
+        init();
+    }
+
+    public void afterRoleCacheSet() {
+        try {
+            loadProperties();
+        } catch (Exception e) {
+            if (log.isInfoEnabled()) {
+                log.info("Unable to find a ki-users.properties file at location [" + this.resourcePath + "].  " +
+                        "Defaulting to JSecurity's failsafe properties file (demo use only).");
+            }
+            this.resourcePath = FAILSAFE_RESOURCE_PATH;
+            loadProperties();
+        }
+        //we can only determine if files have been modified at runtime (not classpath entries or urls), so only
+        //start the thread in this case:
+        if (this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && scheduler != null) {
+            startReloadThread();
+        }
+    }
+
+    public void destroy() {
+        try {
+            if (scheduler != null) {
+                scheduler.shutdown();
+            }
+        } catch (Exception e) {
+            if (log.isInfoEnabled()) {
+                log.info("Unable to cleanly shutdown Scheduler.  Ignoring (shutting down)...", e);
+            }
+        }
+    }
+
+    protected void startReloadThread() {
+        if (this.reloadIntervalSeconds > 0) {
+            this.scheduler = Executors.newSingleThreadScheduledExecutor();
+            ((ScheduledExecutorService) this.scheduler).scheduleAtFixedRate(this, reloadIntervalSeconds, reloadIntervalSeconds, TimeUnit.SECONDS);
+        }
+    }
+
+    public void run() {
+        try {
+            reloadPropertiesIfNecessary();
+        } catch (Exception e) {
+            if (log.isErrorEnabled()) {
+                log.error("Error while reloading property files for realm.", e);
+            }
+        }
+    }
+
+    /*--------------------------------------------
+    |  A C C E S S O R S / M O D I F I E R S    |
+    ============================================*/
+
+    /**
+     * Determines whether or not the properties XML format should be used.  For more information, see
+     * {@link Properties#loadFromXML(java.io.InputStream)}
+     *
+     * @param useXmlFormat true to use XML or false to use the normal format.  Defaults to false.
+     */
+    public void setUseXmlFormat(boolean useXmlFormat) {
+        this.useXmlFormat = useXmlFormat;
+    }
+
+    /**
+     * Sets the path of the properties file to load user, role, and permission information from.  The properties
+     * file will be loaded using {@link ResourceUtils#getInputStreamForPath(String)} so any convention recongized
+     * by that method is accepted here.  For example, to load a file from the classpath use
+     * <tt>classpath:myfile.properties</tt>; to load a file from disk simply specify the full path; to load
+     * a file from a URL use <tt>url:www.mysite.com/myfile.properties</tt>.
+     *
+     * @param resourcePath the path to load the properties file from.  This is a required property.
+     */
+    public void setResourcePath(String resourcePath) {
+        this.resourcePath = resourcePath;
+    }
+
+    /**
+     * Sets the interval in seconds at which the property file will be checked for changes and reloaded.  If this is
+     * set to zero or less, property file reloading will be disabled.  If it is set to 1 or greater, then a
+     * separate thread will be created to monitor the propery file for changes and reload the file if it is updated.
+     *
+     * @param reloadIntervalSeconds the interval in seconds at which the property file should be examined for changes.
+     *                              If set to zero or less, reloading is disabled.
+     */
+    public void setReloadIntervalSeconds(int reloadIntervalSeconds) {
+        this.reloadIntervalSeconds = reloadIntervalSeconds;
+    }
+
+    /*--------------------------------------------
+    |               M E T H O D S               |
+    ============================================*/
+    private void loadProperties() {
+        if (resourcePath == null || resourcePath.length() == 0) {
+            throw new IllegalStateException("The resourcePath property is not set.  " +
+                    "It must be set prior to this realm being initialized.");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Loading user security information from file [" + resourcePath + "]...");
+        }
+
+        Properties properties = loadProperties(resourcePath);
+        createRealmEntitiesFromProperties(properties);
+    }
+
+    private Properties loadProperties(String resourcePath) {
+        Properties props = new Properties();
+
+        InputStream is = null;
+        try {
+
+            if (log.isDebugEnabled()) {
+                log.debug("Opening input stream for path [" + resourcePath + "]...");
+            }
+
+            is = ResourceUtils.getInputStreamForPath(resourcePath);
+            if (useXmlFormat) {
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Loading properties from path [" + resourcePath + "] in XML format...");
+                }
+
+                props.loadFromXML(is);
+            } else {
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Loading properties from path [" + resourcePath + "]...");
+                }
+
+                props.load(is);
+            }
+
+        } catch (IOException e) {
+            throw new JSecurityException("Error reading properties path [" + resourcePath + "].  " +
+                    "Initializing of the realm from this file failed.", e);
+        } finally {
+            ResourceUtils.close(is);
+        }
+
+        return props;
+    }
+
+
+    private void reloadPropertiesIfNecessary() {
+        if (isSourceModified()) {
+            restart();
+        }
+    }
+
+    private boolean isSourceModified() {
+        //we can only check last modified times on files - classpath and URL entries can't tell us modification times
+        return this.resourcePath.startsWith(ResourceUtils.FILE_PREFIX) && isFileModified();
+    }
+
+    private boolean isFileModified() {
+        File propertyFile = new File(this.resourcePath);
+        long currentLastModified = propertyFile.lastModified();
+        if (currentLastModified > this.fileLastModified) {
+            this.fileLastModified = currentLastModified;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private void restart() {
+        if (resourcePath == null || resourcePath.length() == 0) {
+            throw new IllegalStateException("The resourcePath property is not set.  " +
+                    "It must be set prior to this realm being initialized.");
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Loading user security information from file [" + resourcePath + "]...");
+        }
+
+        try {
+            destroy();
+        } catch (Exception e) {
+            //ignored
+        }
+        init();
+    }
+
+    @SuppressWarnings("unchecked")
+    private void createRealmEntitiesFromProperties(Properties properties) {
+
+        StringBuffer userDefs = new StringBuffer();
+        StringBuffer roleDefs = new StringBuffer();
+
+        Enumeration<String> propNames = (Enumeration<String>) properties.propertyNames();
+
+        while (propNames.hasMoreElements()) {
+
+            String key = propNames.nextElement().trim();
+            String value = properties.getProperty(key).trim();
+            if (log.isTraceEnabled()) {
+                log.trace("Processing properties line - key: [" + key + "], value: [" + value + "].");
+            }
+
+            if (isUsername(key)) {
+                String username = getUsername(key);
+                userDefs.append(username).append(" = ").append(value).append("\n");
+            } else if (isRolename(key)) {
+                String rolename = getRolename(key);
+                roleDefs.append(rolename).append(" = ").append(value).append("\n");
+            } else {
+                String msg = "Encountered unexpected key/value pair.  All keys must be prefixed with either '" +
+                        USERNAME_PREFIX + "' or '" + ROLENAME_PREFIX + "'.";
+                throw new IllegalStateException(msg);
+            }
+        }
+
+        setUserDefinitions(userDefs.toString());
+        setRoleDefinitions(roleDefs.toString());
+        processDefinitions();
+    }
+
+    protected String getName(String key, String prefix) {
+        return key.substring(prefix.length(), key.length());
+    }
+
+    protected boolean isUsername(String key) {
+        return key != null && key.startsWith(USERNAME_PREFIX);
+    }
+
+    protected boolean isRolename(String key) {
+        return key != null && key.startsWith(ROLENAME_PREFIX);
+    }
+
+    protected String getUsername(String key) {
+        return getName(key, USERNAME_PREFIX);
+    }
+
+    protected String getRolename(String key) {
+        return getName(key, ROLENAME_PREFIX);
+    }
+}
\ No newline at end of file

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/PropertiesRealm.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java
URL: http://svn.apache.org/viewvc/incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java?rev=752380&view=auto
==============================================================================
--- incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java (added)
+++ incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java Wed Mar 11 05:40:38 2009
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ki.realm.text;
+
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.apache.ki.authc.SimpleAccount;
+import org.apache.ki.authz.Permission;
+import org.apache.ki.authz.SimpleRole;
+import org.apache.ki.realm.SimpleAccountRealm;
+import org.apache.ki.subject.PrincipalCollection;
+import org.apache.ki.util.PermissionUtils;
+import org.apache.ki.util.StringUtils;
+
+
+/**
+ * <p>a SimpleAccountRealm that enables text-based configuration of the initial User, Role, and Permission objects
+ * created at startup.
+ *
+ * <p>Each User account definition specifies the username, password, and roles for a user.  Each Role definition
+ * specifies a name and an optional collection of assigned Permissions.  Users can be assigned Roles, and Roles can be
+ * assigned Permissions.  By transitive association, each User 'has' all of their Role's Permissions.</p>
+ *
+ * <p>User and user-to-role definitinos are specified via the {@link #setUserDefinitions} method and
+ * Role-to-permission definitions are specified via the {@link #setRoleDefinitions} method.
+ *
+ * @author Les Hazlewood
+ * @since 0.9
+ */
+public class TextConfigurationRealm extends SimpleAccountRealm {
+
+    //TODO - complete JavaDoc
+
+    private String userDefinitions;
+    private String roleDefinitions;
+
+    public TextConfigurationRealm() {
+    }
+
+    public String getUserDefinitions() {
+        return userDefinitions;
+    }
+
+    /**
+     * <p>Sets a newline (\n) delimited String that defines user-to-password-and-role(s) key/value pairs according
+     * to the following format:
+     *
+     * <p><code><em>username</em> = <em>password</em>, role1, role2,...</code></p>
+     *
+     * <p>Here are some examples of what these lines might look like:</p>
+     *
+     * <p><code>root = <em>reallyHardToGuessPassword</em>, administrator<br/>
+     * jsmith = <em>jsmithsPassword</em>, manager, engineer, employee<br/>
+     * abrown = <em>abrownsPassword</em>, qa, employee<br/>
+     * djones = <em>djonesPassword</em>, qa, contractor<br/>
+     * guest = <em>guestPassword</em></code></p>
+     *
+     * @param userDefinitions the user definitions to be parsed and converted to Map.Entry elements
+     */
+    public void setUserDefinitions(String userDefinitions) {
+        this.userDefinitions = userDefinitions;
+    }
+
+    public String getRoleDefinitions() {
+        return roleDefinitions;
+    }
+
+    /**
+     * Sets a newline (\n) delimited String that defines role-to-permission definitions.
+     *
+     * <p>Each line within the string must define a role-to-permission(s) key/value mapping with the
+     * equals character signifies the key/value separation, like so:</p>
+     *
+     * <p><code><em>rolename</em> = <em>permissionDefinition1</em>, <em>permissionDefinition2</em>, ...</code></p>
+     *
+     * <p>where <em>permissionDefinition</em> is an arbitrary String, but must people will want to use
+     * Strings that conform to the {@link org.apache.ki.authz.permission.WildcardPermission WildcardPermission}
+     * format for ease of use and flexibility.  Note that if an individual <em>permissionDefnition</em> needs to
+     * be internally comma-delimited (e.g. <code>printer:5thFloor:print,info</code>), you will need to surround that
+     * definition with double quotes (&quot;) to avoid parsing errors (e.g.
+     * <code>&quot;printer:5thFloor:print,info&quot;</code>).
+     *
+     * <p><b>NOTE:</b> if you have roles that don't require permission associations, don't include them in this
+     * definition - just defining the role name in the {@link #setUserDefinitions(String) userDefinitions} is
+     * enough to create the role if it does not yet exist.  This property is really only for configuring realms that
+     * have one or more assigned Permission.
+     *
+     * @param roleDefinitions the role definitions to be parsed at initialization
+     */
+    public void setRoleDefinitions(String roleDefinitions) {
+        this.roleDefinitions = roleDefinitions;
+    }
+
+    protected void accountAndRoleCachesCreated() {
+        processDefinitions();
+    }
+
+    protected void processDefinitions() {
+        try {
+            processRoleDefinitions();
+            processUserDefinitions();
+        } catch (ParseException e) {
+            String msg = "Unable to parse user and/or role definitions.";
+            throw new IllegalStateException(msg, e);
+        }
+    }
+
+    protected void processRoleDefinitions() throws ParseException {
+        String roleDefinitions = getRoleDefinitions();
+        if (roleDefinitions == null) {
+            return;
+        }
+        Map<String, String> roleDefs = toMap(toLines(roleDefinitions));
+        if (roleDefs == null || roleDefs.isEmpty()) {
+            return;
+        }
+
+        for (String rolename : roleDefs.keySet()) {
+            String value = roleDefs.get(rolename);
+
+            SimpleRole role = getRole(rolename);
+            if (role == null) {
+                role = new SimpleRole(rolename);
+                add(role);
+            }
+
+            Set<Permission> permissions = PermissionUtils.resolveDelimitedPermissions(value, getPermissionResolver());
+            role.setPermissions(permissions);
+        }
+    }
+
+    protected void processUserDefinitions() throws ParseException {
+
+        String userDefinitions = getUserDefinitions();
+        if (userDefinitions == null) {
+            return;
+        }
+
+        Map<String, String> userDefs = toMap(toLines(userDefinitions));
+        if (userDefs == null || userDefs.isEmpty()) {
+            return;
+        }
+
+        for (String username : userDefs.keySet()) {
+
+            String value = userDefs.get(username);
+
+            String[] passwordAndRolesArray = StringUtils.split(value);
+
+            String password = passwordAndRolesArray[0];
+
+            SimpleAccount account = getUser(username);
+            if (account == null) {
+                account = new SimpleAccount(username, password, getName());
+                add(account);
+            }
+            account.setCredentials(password);
+
+            if (passwordAndRolesArray.length > 1) {
+                for (int i = 1; i < passwordAndRolesArray.length; i++) {
+                    String rolename = passwordAndRolesArray[i];
+                    account.addRole(rolename);
+
+                    SimpleRole role = getRole(rolename);
+                    if (role != null) {
+                        account.addObjectPermissions(role.getPermissions());
+                    }
+                }
+            } else {
+                account.setRoles(null);
+            }
+        }
+    }
+
+    protected static Set<String> toLines(String s) {
+        LinkedHashSet<String> set = new LinkedHashSet<String>();
+        Scanner scanner = new Scanner(s);
+        while (scanner.hasNextLine()) {
+            set.add(scanner.nextLine());
+        }
+        return set;
+    }
+
+    protected static Map<String, String> toMap(Collection<String> keyValuePairs) throws ParseException {
+        if (keyValuePairs == null || keyValuePairs.isEmpty()) {
+            return null;
+        }
+
+        Map<String, String> pairs = new HashMap<String, String>();
+        for (String pairString : keyValuePairs) {
+            String[] pair = StringUtils.splitKeyValue(pairString);
+            if( pair != null ) {
+                pairs.put(pair[0].trim(), pair[1].trim());
+            }
+        }
+
+        return pairs;
+    }
+
+    public void onLogout(PrincipalCollection accountPrincipal) {
+        //override parent method of removing user from cache
+        //we don't want that to happen on cache-only realm since that would permanently
+        //remove the user from the realm.
+    }
+}

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision Id Author

Propchange: incubator/jsecurity/trunk/core/src/org/apache/ki/realm/text/TextConfigurationRealm.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain