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 (") to avoid parsing errors (e.g.
+ * <code>"printer:5thFloor:print,info"</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