You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by aj...@apache.org on 2009/08/15 13:53:38 UTC
svn commit: r804457 [1/2] - in /incubator/jspwiki/trunk: ./ etc/ etc/ldap/
src/WebContent/templates/default/ src/java/org/apache/wiki/
src/java/org/apache/wiki/auth/ src/java/org/apache/wiki/auth/authorize/
src/java/org/apache/wiki/auth/login/ src/java...
Author: ajaquith
Date: Sat Aug 15 11:53:37 2009
New Revision: 804457
URL: http://svn.apache.org/viewvc?rev=804457&view=rev
Log:
Checked in LdapUserDatabase, and refactorings to other LDAP classes. The LdapUserDatabase is read-only. ProfileTab will respect this will print, rather than allow edits to, user profile information as a result. Any UserDatabase can now be flagged as "read-only" (via jspwiki.properties) to achieve the same effect.
Added:
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java
incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/user/LdapUserDatabaseTest.java
Modified:
incubator/jspwiki/trunk/ChangeLog
incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl
incubator/jspwiki/trunk/etc/ldap/test.ldif
incubator/jspwiki/trunk/src/WebContent/templates/default/AttachmentTab.jsp
incubator/jspwiki/trunk/src/WebContent/templates/default/ProfileTab.jsp
incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/UserDatabase.java
incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/UserProfileTag.java
incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java
incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/login/LdapLoginModuleTest.java
incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/user/AllTests.java
Modified: incubator/jspwiki/trunk/ChangeLog
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/ChangeLog?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/ChangeLog (original)
+++ incubator/jspwiki/trunk/ChangeLog Sat Aug 15 11:53:37 2009
@@ -1,5 +1,16 @@
2009-08-08 Andrew Jaquith <ajaquith AT apache DOT org>
+ * 3.0.0-svn-140
+
+ * Checked in LdapUserDatabase, and refactorings to other LDAP
+ classes. The LdapUserDatabase is read-only. ProfileTab will
+ respect this will print, rather than allow edits to, user
+ profile information as a result. Any UserDatabase can now be
+ flagged as "read-only" (via jspwiki.properties) to achieve
+ the same effect.
+
+2009-08-08 Andrew Jaquith <ajaquith AT apache DOT org>
+
* 3.0.0-svn-139
* Checked in revised LdapLoginModule and LdapAuthorizer. Notable new
Modified: incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl (original)
+++ incubator/jspwiki/trunk/etc/jspwiki.properties.tmpl Sat Aug 15 11:53:37 2009
@@ -434,6 +434,17 @@
# want to modify the security policy file WEB-INF/jspwiki.policy. See
# the policy file for more details.
#
+# KEYCHAIN
+#
+# Where secrets are kept. The default location is WEB-INF/keychain
+#jspwiki.keychainPath = keychain
+
+#
+# The keychain password, in plain text. If omitted, the keychain will
+# remain locked after startup. It is safer to omit the password.
+#jspwiki.keychainPassword = myPlaintextPassword
+
+#
# AUTHENTICATION
#
# For authentication, JSPWiki uses JAAS (Java Authentication and Authorization
@@ -539,11 +550,9 @@
# the DN containing role entries.
#
#jspwiki.authorizer = org.apache.wiki.auth.authorize.WebContainerAuthorizer
-#jspwiki.ldap.roleBase = ou\=roles,dc\=jspwiki,dc\=org
-#jspwiki.ldap.rolePattern = (&(objectClass\=groupOfUniqueNames)(cn\={0})(uniqueMember\={1}))
-#jspwiki.ldap.bindDN = cn\=Manager,dc\=jspwiki,dc\=org
-#jspwiki.ldap.bindPasswordAlias = ldap.ManagerDN
-
+#ldap.roleBase = ou\=roles,dc\=jspwiki,dc\=org
+#ldap.isInRolePattern = (&(objectClass\=groupOfUniqueNames)(cn\={0})(uniqueMember\={1}))
+#ldap.bindDN = cn\=Manager,dc\=jspwiki,dc\=org
# B) GROUPS
# As an additional source of authorization, users can belong to discretionary
@@ -593,14 +602,10 @@
#jspwiki.userdatabase = org.apache.wiki.auth.user.JDBCUserDatabase
-# If your JSPWiki user database shares login information with your
-# web container's authentication realm, you can configure JSPWiki to
-# add container users. At present, this only works with JDBCUserDatabase,
-# and only if you've configured your web container to use a database
-# with compatible columns and tables. If you don't know what this means,
-# then leave this property set to FALSE (the default).
+# If your user database is read-only, set this property to true.
+# LdapUserDatabase, for example, should almost always be read-only.
-#jspwiki.userdatabase.isSharedWithContainer = false
+jspwiki.userdatabase.readOnlyProfiles = false
# ACCESS CONTROL LISTS
# Last but not least, JSPWiki needs a way of reading and persisting page
Modified: incubator/jspwiki/trunk/etc/ldap/test.ldif
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/etc/ldap/test.ldif?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/etc/ldap/test.ldif (original)
+++ incubator/jspwiki/trunk/etc/ldap/test.ldif Sat Aug 15 11:53:37 2009
@@ -89,7 +89,7 @@
x500UniqueIdentifier: '110000111001011000011000011001111010111001011101101011011100101100001110010111001001011011101001100010110110110100101101110001011001011101101100101011011100101110001100001110101110001111000110100110000111000111100110110001110001'B
uid: Biff
sn: Biff
-cn: biff
+cn: Biff
mail: biff@example.com
userPassword: {SSHA}xKAIienaZZHhKTGCNv5Li6lzeemaSs6ZYXTHFQ==
Modified: incubator/jspwiki/trunk/src/WebContent/templates/default/AttachmentTab.jsp
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/templates/default/AttachmentTab.jsp?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/templates/default/AttachmentTab.jsp (original)
+++ incubator/jspwiki/trunk/src/WebContent/templates/default/AttachmentTab.jsp Sat Aug 15 11:53:37 2009
@@ -26,6 +26,7 @@
<%@ page import="org.apache.wiki.action.WikiContextFactory" %>
<%@ page import="org.apache.wiki.util.TextUtil" %>
<%@ page import="org.apache.wiki.api.WikiPage" %>
+<%@ page errorPage="/Error.jsp" %>
<%
int MAXATTACHNAMELENGTH = 30;
WikiContext c = WikiContextFactory.findContext( pageContext );
Modified: incubator/jspwiki/trunk/src/WebContent/templates/default/ProfileTab.jsp
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/WebContent/templates/default/ProfileTab.jsp?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/WebContent/templates/default/ProfileTab.jsp (original)
+++ incubator/jspwiki/trunk/src/WebContent/templates/default/ProfileTab.jsp Sat Aug 15 11:53:37 2009
@@ -82,8 +82,13 @@
<tr>
<td><s:label for="profile.fullname" /></td>
<td>
- <s:text name="profile.fullname" id="fullname" size="20"><wiki:UserProfile property="fullname" /></s:text>
- <s:errors field="profile.fullname" />
+ <wiki:UserProfile property="canChangeFullname">
+ <s:text name="profile.fullname" id="fullname" size="20"><wiki:UserProfile property="fullname" /></s:text>
+ <s:errors field="profile.fullname" />
+ </wiki:UserProfile>
+ <wiki:UserProfile property="!canChangeFullname">
+ <wiki:UserProfile property="fullname" />
+ </wiki:UserProfile>
<div class="formhelp"><fmt:message key="prefs.fullname.description" /></div>
</td>
</tr>
@@ -92,8 +97,13 @@
<tr>
<td><s:label for="profile.email" name="email" /></td>
<td>
- <s:text name="profile.email" id="email" size="20"><wiki:UserProfile property="email" /></s:text>
- <s:errors field="profile.email" />
+ <wiki:UserProfile property="canChangeEmail">
+ <s:text name="profile.email" id="email" size="20"><wiki:UserProfile property="email" /></s:text>
+ <s:errors field="profile.email" />
+ </wiki:UserProfile>
+ <wiki:UserProfile property="!canChangeEmail">
+ <wiki:UserProfile property="email" />
+ </wiki:UserProfile>
<div class="formhelp"><fmt:message key="prefs.email.description" /></div>
</td>
</tr>
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/Release.java Sat Aug 15 11:53:37 2009
@@ -77,7 +77,7 @@
* <p>
* If the build identifier is empty, it is not added.
*/
- public static final String BUILD = "139";
+ public static final String BUILD = "140";
/**
* This is the generic version string you should use
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/AuthenticationManager.java Sat Aug 15 11:53:37 2009
@@ -21,6 +21,7 @@
package org.apache.wiki.auth;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
@@ -109,13 +110,13 @@
protected static final Logger log = LoggerFactory.getLogger( AuthenticationManager.class );
/** Prefix for LoginModule options key/value pairs. */
- protected static final String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
+ public static final String PREFIX_LOGIN_MODULE_OPTIONS = "jspwiki.loginModule.options.";
/** If this jspwiki.properties property is <code>true</code>, allow cookies to be used to assert identities. */
protected static final String PROP_ALLOW_COOKIE_ASSERTIONS = "jspwiki.cookieAssertions";
/** The {@link javax.security.auth.spi.LoginModule} to use for custom authentication. */
- protected static final String PROP_LOGIN_MODULE = "jspwiki.loginModule.class";
+ public static final String PROP_LOGIN_MODULE = "jspwiki.loginModule.class";
/** Empty Map passed to JAAS {@link #doJAASLogin(Class, CallbackHandler, Map)} method. */
protected static final Map<String,String> EMPTY_MAP = Collections.unmodifiableMap( new HashMap<String,String>() );
@@ -211,6 +212,32 @@
public final void initialize( WikiEngine engine, Properties props ) throws WikiException
{
m_engine = engine;
+
+ // Initialize the keychain
+ m_keychain = new Keychain();
+ String path = props.getProperty( PROP_KEYCHAIN_PATH );
+ path = (path == null || path.trim().length() == 0) ? null : path.trim();
+ if( path == null )
+ {
+ path = "WEB-INF/" + DEFAULT_KEYCHAIN_PATH;
+ }
+ else
+ {
+ File filePath = new File( path );
+ if ( filePath.getParent() == null )
+ {
+ path = "WEB-INF/" + path;
+ }
+ }
+ m_keychainPath = path;
+
+ // Unlock the keychain if password was supplied.
+ String password = props.getProperty( PROP_KEYCHAIN_PASSWORD );
+ if( password != null )
+ {
+ initKeychain( password );
+ }
+
m_storeIPAddress = TextUtil.getBooleanProperty( props, PROP_STOREIPADDRESS, m_storeIPAddress );
// Should J2SE policies be used for authorization?
@@ -245,31 +272,6 @@
// Initialize the LoginModule options
initLoginModuleOptions( props );
-
- // Initialize the keychain
- m_keychain = new Keychain();
- String path = props.getProperty( PROP_KEYCHAIN_PATH );
- path = (path == null || path.trim().length() == 0) ? null : path.trim();
- if( path == null )
- {
- path = "WEB-INF/" + DEFAULT_KEYCHAIN_PATH;
- }
- else
- {
- File filePath = new File( path );
- if ( filePath.getParent() == null )
- {
- path = "WEB-INF/" + path;
- }
- }
- m_keychainPath = path;
-
- // Unlock the keychain if password was supplied.
- String password = props.getProperty( PROP_KEYCHAIN_PASSWORD );
- if( password != null )
- {
- initKeychain( password );
- }
}
/**
@@ -776,13 +778,24 @@
}
/**
- * Returns the first Principal in a set that isn't a {@link org.apache.wiki.auth.authorize.Role} or
+ * Returns the first Principal in a set of type
+ * {@link WikiPrincipal#LOGIN_NAME}, or failing that, the first one that
+ * isn't a {@link org.apache.wiki.auth.authorize.Role} or
* {@link org.apache.wiki.auth.GroupPrincipal}.
+ *
* @param principals the principal set
* @return the login principal
*/
protected Principal getLoginPrincipal(Set<Principal> principals)
{
+ for ( Principal principal : principals )
+ {
+ if ( principal instanceof WikiPrincipal
+ &&((WikiPrincipal)principal).getType() == WikiPrincipal.LOGIN_NAME )
+ {
+ return principal;
+ }
+ }
for (Principal principal: principals )
{
if ( isUserPrincipal( principal ) )
@@ -932,6 +945,21 @@
{
stream = m_engine.getServletContext().getResourceAsStream( "/" + m_keychainPath );
}
+
+ // If we can't get it from classloader, see if it's an absolute path
+ if ( stream == null )
+ {
+ File file = new File( m_keychainPath);
+ try
+ {
+ if ( file.isAbsolute() && file.exists() )
+ {
+ stream = new FileInputStream( file );
+ }
+ }
+ catch ( Exception e ) { }
+ }
+
if( stream == null )
{
throw new WikiSecurityException( "Unable to find keychain " + m_keychainPath + "." );
Added: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java?rev=804457&view=auto
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java (added)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/LdapConfig.java Sat Aug 15 11:53:37 2009
@@ -0,0 +1,381 @@
+package org.apache.wiki.auth;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.util.*;
+
+import javax.naming.Context;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.InitialLdapContext;
+
+import org.apache.wiki.util.TextUtil;
+import org.freshcookies.security.Keychain;
+
+/**
+ * Immutable holder for configuration of LDAP-related security modules.
+ */
+public class LdapConfig
+{
+ public static final Map<String,String> ACTIVE_DIRECTORY_CONFIG;
+
+ public static final Map<String,String> OPEN_LDAP_CONFIG;
+
+ public static final String KEYCHAIN_BIND_DN_ENTRY = "ldap.bindDNPassword";
+
+ /**
+ * Property that indicates what LDAP server configuration to use. Valid values include
+ * <code>ad</code> for Active Directory and <code>openldap</code> for OpenLDAP.
+ * If this value is set, the default settings for these configurations will be loaded.
+ */
+ public static final String PROPERTY_CONFIG = "ldap.config";
+
+ /**
+ * Property that indicates whether to use SSL for connecting to the LDAP
+ * server. This property is also used by
+ * {@link org.apache.wiki.auth.login.LdapLoginModule}.
+ */
+ public static final String PROPERTY_SSL = "ldap.ssl";
+
+ /**
+ * Property that supplies the pattern for finding roles within the role base, e.g.
+ * <code>(&(objectClass=groupOfUniqueNames)(cn={0}))</code>
+ * .
+ */
+ public static final String PROPERTY_ROLE_PATTERN = "ldap.rolePattern";
+
+ /**
+ * Property that supplies the pattern for finding users within the role base
+ * that possess a given role, e.g.
+ * <code>(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))</code>
+ * .
+ */
+ public static final String PROPERTY_IS_IN_ROLE_PATTERN = "ldap.isInRolePattern";
+
+ /**
+ * Property that supplies the DN used to bind to the directory when looking
+ * up users and roles.
+ */
+ public static final String PROPERTY_BIND_DN = "ldap.bindDN";
+
+ /**
+ * Property that supplies the connection URL for the LDAP server, e.g.
+ * <code>ldap://127.0.0.1:4890/</code>. This property is also used by
+ * {@link org.apache.wiki.auth.login.LdapLoginModule}.
+ */
+ public static final String PROPERTY_CONNECTION_URL = "ldap.connectionURL";
+
+ /**
+ * Property that specifies the JNDI authentication type. Valid values are
+ * the same as those for {@link Context#SECURITY_AUTHENTICATION}:
+ * <code>none</code>, <code>simple</code>, <code>strong</code> or
+ * <code>DIGEST-MD5</code>. The default is <code>simple</code> if SSL is
+ * specified, and <code>DIGEST-MD5</code> otherwise. This property is also
+ * used by {@link org.apache.wiki.auth.login.LdapLoginModule}.
+ */
+ public static final String PROPERTY_AUTHENTICATION = "ldap.authentication";
+
+ /**
+ * Property that specifies the login name attribute for user objects.
+ * By default, this is <code>uid</code>.
+ */
+ public static final String PROPERTY_USER_LOGIN_NAME_ATTRIBUTE = "ldap.user.loginName";
+
+ /**
+ * Property that specifies the base DN where users are contained,
+ * <em>e.g.,</em> <code>ou=people,dc=jspwiki,dc=org</code>. This DN is
+ * searched recursively.
+ */
+ public static final String PROPERTY_USER_BASE = "ldap.userBase";
+
+ /**
+ * Property that specifies the DN pattern for finding users within the
+ * user base, <em>e.g.,</em>
+ * <code>(&(objectClass=inetOrgPerson)(uid={0}))</code>
+ */
+ public static final String PROPERTY_USER_PATTERN = "ldap.userPattern";
+
+
+ /**
+ * Property that specifies the pattern for the username used to log in to the
+ * LDAP server. This pattern maps the username supplied at login time by the
+ * user to a username format the LDAP server can recognized. Usually this is
+ * a pattern that produces a full DN, for example
+ * <code>uid={0},ou=people,dc=jspwiki,dc=org</code>. However, sometimes (as
+ * with Active Directory 2003 and later) only the userid is used, in which
+ * case the principal will simply be <code>{0}</code>. The default value
+ * if not supplied is <code>{0}</code>.
+ */
+ public static final String PROPERTY_LOGIN_ID_PATTERN = "ldap.loginIdPattern";
+
+ public static final String PROPERTY_USER_OBJECT_CLASS = "ldap.user.objectClass";
+
+ /**
+ * Property that supplies the base DN where roles are contained, e.g.
+ * <code>ou=roles,dc=jspwiki,dc=org</code>.
+ */
+ public static final String PROPERTY_ROLE_BASE = "ldap.roleBase";
+
+ public final String connectionUrl;
+
+ public final String roleBase;
+
+ public final String rolePattern;
+
+ public final String isInRolePattern;
+
+ public final String bindDN;
+
+ public final String ssl;
+
+ public final String authentication;
+
+ public final String userBase;
+
+ public final String loginIdPattern;
+
+ public final String userPattern;
+
+ public final String userLoginNameAttribute;
+
+ public final String userObjectClass;
+
+ private final Set<String> m_configured = new HashSet<String>();
+
+ private Keychain m_keychain;
+
+ static
+ {
+ // Active Directory 2000+ defaults
+ Map<String,String> options = new HashMap<String,String>();
+ options.put( PROPERTY_IS_IN_ROLE_PATTERN, "(&(objectClass=group)(cn={0})(member={1}))" );
+ options.put( PROPERTY_LOGIN_ID_PATTERN, "{0}" );
+ options.put( PROPERTY_ROLE_PATTERN, "(&(objectClass=group)(cn={0}))" );
+ options.put( PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "sAMAccountName" );
+ options.put( PROPERTY_USER_OBJECT_CLASS, "person" );
+ options.put( PROPERTY_USER_PATTERN, "(&(objectClass=person)(sAMAccountName={0}))" );
+ ACTIVE_DIRECTORY_CONFIG = Collections.unmodifiableMap( options );
+
+ // OpenLDAP defaults
+ options = new HashMap<String,String>();
+ options.put( PROPERTY_IS_IN_ROLE_PATTERN, "(&(&(objectClass=groupOfUniqueNames)(cn={0}))(uniqueMember={1}))" );
+ options.put( PROPERTY_ROLE_PATTERN, "(&(objectClass=groupOfUniqueNames)(cn={0}))" );
+ options.put( PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "uid" );
+ options.put( PROPERTY_USER_OBJECT_CLASS, "inetOrgPerson" );
+ options.put( PROPERTY_USER_PATTERN, "(&(objectClass=inetOrgPerson)(uid={0}))" );
+ OPEN_LDAP_CONFIG = Collections.unmodifiableMap( options );
+ }
+
+ private String getProperty( Map<? extends Object,? extends Object> props, String property, String defaultValue )
+ {
+ String shortProperty = property;
+ String longProperty = AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + property;
+ String value = (String)props.get( property );
+ if ( value == null )
+ {
+ property = longProperty;
+ value = (String)props.get( property );
+ }
+ if( value != null && value.length() > 0 )
+ {
+ m_configured.add( shortProperty );
+ m_configured.add( longProperty );
+ return props.get( property ).toString().trim();
+ }
+ return defaultValue;
+ }
+
+ public String getBindDNPassword() throws KeyStoreException
+ {
+ if( m_keychain == null )
+ {
+ throw new KeyStoreException( "LdapConfig was initialized without a keychain!" );
+ }
+ KeyStore.Entry password = m_keychain.getEntry( LdapConfig.KEYCHAIN_BIND_DN_ENTRY );
+ if( password instanceof Keychain.Password )
+ {
+ return ((Keychain.Password) password).getPassword();
+ }
+ return null;
+ }
+
+ public static LdapConfig getInstance( Keychain keychain, Map<? extends Object,? extends Object> props, String[] requiredProperties )
+ {
+ return new LdapConfig( keychain, props, requiredProperties );
+ }
+
+ private LdapConfig( Keychain keychain, Map<? extends Object,? extends Object> props, String[] requiredProperties )
+ {
+ // Basic connection properties
+ connectionUrl = getProperty( props, PROPERTY_CONNECTION_URL, null );
+
+ // Binding DN properties
+ m_keychain = keychain;
+ bindDN = getProperty( props, PROPERTY_BIND_DN, null );
+
+ // User lookup properties
+ userBase = getProperty( props, PROPERTY_USER_BASE, null );
+ userPattern = getProperty( props, PROPERTY_USER_PATTERN, null );
+
+ // Role lookup properties
+ roleBase = getProperty( props, PROPERTY_ROLE_BASE, null );
+ rolePattern = getProperty( props, PROPERTY_ROLE_PATTERN, null );
+ isInRolePattern = getProperty( props, PROPERTY_IS_IN_ROLE_PATTERN, null );
+
+ // Optional security properties
+ String parsedSsl = getProperty( props, PROPERTY_SSL, null );
+ ssl = (parsedSsl != null && TextUtil.isPositive( parsedSsl )) ? "ssl" : "none";
+ String parsedAuthentication = getProperty( props, PROPERTY_AUTHENTICATION, null );
+ if( parsedAuthentication == null )
+ {
+ authentication = "ssl".equals( ssl ) ? "simple" : "DIGEST-MD5";
+ }
+ else
+ {
+ authentication = parsedAuthentication;
+ }
+ loginIdPattern = getProperty( props, PROPERTY_LOGIN_ID_PATTERN, "{0}" );
+
+ // Optional user object attributes
+ userObjectClass = getProperty( props, PROPERTY_USER_OBJECT_CLASS, "inetOrgPerson" );
+ userLoginNameAttribute = getProperty( props, PROPERTY_USER_LOGIN_NAME_ATTRIBUTE, "uid" );
+
+ // Validate everything
+ for( String property : requiredProperties )
+ {
+ if( !m_configured.contains( property ) )
+ {
+ throw new IllegalArgumentException( "Property " + property + " is required!" );
+ }
+ }
+ }
+
+ public Hashtable<String,String> newJndiEnvironment() throws NamingException
+ {
+ // If we need a Bind DN and Keychain is loaded, get the bind DN and password
+ String username = bindDN;
+ String password = null;
+ if( username != null )
+ {
+ try
+ {
+ password = getBindDNPassword();
+ }
+ catch( KeyStoreException e )
+ {
+ e.printStackTrace();
+ throw new NamingException( "Could not build JNDI environment: " + e.getMessage() );
+ }
+ }
+ return newJndiEnvironment( username, password );
+ }
+
+ /**
+ * Builds a JNDI environment hashtable for authenticating to the LDAP
+ * server. The hashtable is built using information extracted from the
+ * options map supplied to the LoginModule via
+ * {@link #initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, Map, Map)}
+ * . The username and password parameters supply the LDAP credentials.
+ *
+ * @param username the user's distinguished name (DN), used for
+ * authentication
+ * @param password the password
+ * @return the constructed hash table
+ */
+ public Hashtable<String,String> newJndiEnvironment( String username, String password )
+ {
+ Hashtable<String, String> env = new Hashtable<String, String>();
+ env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
+
+ // LDAP server to authenticate to
+ env.put( Context.PROVIDER_URL, connectionUrl );
+
+ // Add credentials if supplied
+ if ( username != null )
+ {
+ env.put( Context.SECURITY_PRINCIPAL, username );
+ env.put( Context.SECURITY_CREDENTIALS, password );
+ }
+ else
+ {
+
+ }
+
+ // Use SSL?
+ env.put( Context.SECURITY_PROTOCOL, ssl );
+
+ // Authentication type (simple, DIGEST-MD5, etc)
+ env.put( Context.SECURITY_AUTHENTICATION, authentication );
+
+ return env;
+ }
+
+ public static String getFullName( Attributes attributes ) throws NamingException
+ {
+ boolean hasCommonName = attributes.get( "cn" ) != null;
+ boolean hasSurname = attributes.get( "sn" ) != null;
+ boolean hasGivenName = attributes.get( "givenName" ) != null;
+
+ String fullName = null;
+ if( hasGivenName && hasSurname )
+ {
+ fullName = attributes.get( "givenName" ).get( 0 ) + " " + attributes.get( "sn" ).get( 0 );
+ }
+ else if( hasCommonName )
+ {
+ fullName = attributes.get( "cn" ).get( 0 ).toString();
+ }
+ else
+ {
+ throw new NamingException( "User did not have a givenName+sn or cn." );
+ }
+ return fullName;
+ }
+
+ private final Map<String, String> m_userDns = new HashMap<String, String>();
+
+ private static final SearchControls SEARCH_CONTROLS;
+
+ static
+ {
+ SEARCH_CONTROLS = new SearchControls();
+ SEARCH_CONTROLS.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ }
+
+ public String getUserDn( String loginName ) throws NamingException
+ {
+ String dn = m_userDns.get( loginName );
+ if( dn == null )
+ {
+ loginName = sanitizeDn( loginName );
+ String userFinder = userPattern.replace( "{0}", loginName );
+ Hashtable<String, String> env = newJndiEnvironment();
+
+ // Find the user
+ DirContext ctx = new InitialLdapContext( env, null );
+ NamingEnumeration<SearchResult> users = ctx.search( userBase, userFinder, SEARCH_CONTROLS );
+ if( users.hasMore() )
+ {
+ dn = users.next().getNameInNamespace();
+ m_userDns.put( loginName, dn );
+ }
+ }
+ return dn;
+ }
+
+ /**
+ * See http://blogs.sun.com/shankar/entry/what_is_ldap_injection
+ *
+ * @param index
+ * @return
+ */
+ private String sanitizeDn( String index )
+ {
+ index = index.replace( " ", " " );
+ return index.replace( "=", "\\3D" );
+ }
+}
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/UserManager.java Sat Aug 15 11:53:37 2009
@@ -52,6 +52,7 @@
import org.apache.wiki.ui.InputValidator;
import org.apache.wiki.util.ClassUtil;
import org.apache.wiki.util.MailUtil;
+import org.apache.wiki.util.TextUtil;
import org.apache.wiki.workflow.*;
@@ -63,6 +64,8 @@
*/
public final class UserManager
{
+ public static final String PROP_READ_ONLY_PROFILES = "jspwiki.userdatabase.readOnlyProfiles";
+
private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user";
private static final String SESSION_MESSAGES = "profile";
private static final String PARAM_EMAIL = "email";
@@ -73,6 +76,8 @@
private WikiEngine m_engine;
+ private boolean m_readOnlyProfiles = false;
+
private static Logger log = LoggerFactory.getLogger(UserManager.class);
/** Message key for the "save profile" message. */
@@ -86,8 +91,6 @@
protected static final String PREFS_FULL_NAME = "prefs.fullname";
protected static final String PREFS_EMAIL = "prefs.email";
- // private static final String PROP_ACLMANAGER = "jspwiki.aclManager";
-
/** Associates wiki sessions with profiles */
private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<WikiSession,UserProfile>();
@@ -115,8 +118,9 @@
m_useJAAS = AuthenticationManager.SECURITY_JAAS.equals( props.getProperty(AuthenticationManager.PROP_SECURITY, AuthenticationManager.SECURITY_JAAS ) );
- // Attach the PageManager as a listener
- // TODO: it would be better if we did this in PageManager directly
+ m_readOnlyProfiles = TextUtil.isPositive( props.getProperty( PROP_READ_ONLY_PROFILES ) );
+
+ // Attach the ContentManager as a listener
addWikiEventListener( engine.getContentManager() );
JSONRPCManager.registerGlobalObject( "users", new JSONUserModule(this), new AllPermission(null) );
@@ -249,6 +253,17 @@
}
/**
+ * Returns <code>true</code> if all user profiles returned by the back-end database are
+ * read-only. Read-only behavior can be forced by setting the <code>jspwiki.properties</code>
+ * property {@link #PROP_READ_ONLY_PROFILES}.
+ * @return the result
+ */
+ public boolean isReadOnly()
+ {
+ return m_readOnlyProfiles;
+ }
+
+ /**
* <p>
* Saves the {@link org.apache.wiki.auth.user.UserProfile}for the user in
* a wiki session. This method verifies that a user profile to be saved
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/authorize/LdapAuthorizer.java Sat Aug 15 11:53:37 2009
@@ -1,14 +1,11 @@
package org.apache.wiki.auth.authorize;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
import java.security.Principal;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Set;
-import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
@@ -19,39 +16,39 @@
import org.apache.wiki.WikiEngine;
import org.apache.wiki.WikiSession;
import org.apache.wiki.auth.Authorizer;
+import org.apache.wiki.auth.LdapConfig;
import org.apache.wiki.auth.WikiSecurityException;
-import org.apache.wiki.log.Logger;
-import org.apache.wiki.log.LoggerFactory;
-import org.apache.wiki.util.TextUtil;
-import org.freshcookies.security.Keychain;
+import org.apache.wiki.auth.login.LdapLoginModule;
/**
- * Authorizer whose Roles are supplied by LDAP groups.
+ * <p>
+ * Authorizer whose Roles are supplied by LDAP groups. This Authorizer requires
+ * that {@link LdapLoginModule} be used for authentication. This can be done
+ * either as part of the web container authentication configuration or (more
+ * likely) as part of JSPWiki's own native authentication configuration.
+ * </p>
+ * <p>
+ * When {@link #initialize(WikiEngine, Properties)} executes, a new instance of
+ * {@link org.apache.wiki.auth.LdapConfig} is created and configured based on
+ * the settings in <code>jspwiki.properties</code>. The properties that are
+ * required in order for LdapAuthorizer to function correctly are
+ * {@link LdapConfig#PROPERTY_CONNECTION_URL},
+ * {@link LdapConfig#PROPERTY_ROLE_BASE},
+ * {@link LdapConfig#PROPERTY_ROLE_PATTERN} and
+ * {@link LdapConfig#PROPERTY_IS_IN_ROLE_PATTERN}. Additional properties that
+ * can be set include {@link LdapConfig#PROPERTY_BIND_DN},
+ * {@link LdapConfig#PROPERTY_AUTHENTICATION} and
+ * {@link LdapConfig#PROPERTY_SSL}. See the documentation for that LdapConfig
+ * for more details.
+ * </p>
*/
public class LdapAuthorizer implements Authorizer
{
private Hashtable<String, String> m_jndiEnv;
- private String m_roleBase = null;
+ private LdapConfig m_cfg = null;
- /**
- * Finds all roles; based on m_rolePattern
- */
- private String m_roleFinder = null;
-
- private String m_authentication = null;
-
- private Keychain m_keychain = null;
-
- private String m_connectionUrl = null;
-
- private String m_ssl = null;
-
- private String m_rolePattern = null;
-
- private String m_userLoginIdPattern = null;
-
- private String m_bindDN = null;
+ private String m_allRoleFinder = null;
/**
* {@inheritDoc}
@@ -61,9 +58,9 @@
try
{
DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
- SearchControls searchControls = new SearchControls();
- searchControls.setReturningAttributes( new String[0] );
- NamingEnumeration<SearchResult> roles = ctx.search( m_roleBase, "(cn=" + role + ")", searchControls );
+ String roleFinder = m_cfg.rolePattern;
+ roleFinder = roleFinder.replace( "{0}", role );
+ NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, roleFinder, SEARCH_CONTROLS );
if( roles.hasMore() )
{
return new Role( role );
@@ -85,9 +82,7 @@
try
{
DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
- SearchControls searchControls = new SearchControls();
- searchControls.setReturningAttributes( new String[] { "cn" } );
- NamingEnumeration<SearchResult> roles = ctx.search( m_roleBase, m_roleFinder, searchControls );
+ NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, m_allRoleFinder, SEARCH_CONTROLS );
while ( roles.hasMore() )
{
SearchResult foundRole = roles.next();
@@ -102,111 +97,31 @@
return foundRoles.toArray( new Role[foundRoles.size()] );
}
- private static final Logger log = LoggerFactory.getLogger( LdapAuthorizer.class );
-
- /**
- * Property that specifies the JNDI authentication type. Valid values are
- * the same as those for {@link Context#SECURITY_AUTHENTICATION}:
- * <code>none</code>, <code>simple</code>, <code>strong</code> or
- * <code>DIGEST-MD5</code>. The default is <code>simple</code> if SSL is
- * specified, and <code>DIGEST-MD5</code> otherwise. This property is also
- * used by {@link org.apache.wiki.auth.login.LdapLoginModule}.
- */
- protected static final String PROPERTY_AUTHENTICATION = "jspwiki.loginModule.options.ldap.authentication";
-
- /**
- * Property that supplies the connection URL for the LDAP server, e.g.
- * <code>ldap://127.0.0.1:4890/</code>. This property is also used by
- * {@link org.apache.wiki.auth.login.LdapLoginModule}.
- */
- protected static final String PROPERTY_CONNECTION_URL = "jspwiki.loginModule.options.ldap.connectionURL";
-
- /**
- * Property that supplies the DN used to bind to the directory when looking
- * up users and roles.
- */
- protected static final String PROPERTY_BIND_DN = "jspwiki.ldap.bindDN";
-
- /**
- * Property that supplies the base DN where roles are contained, e.g.
- * <code>ou=roles,dc=jspwiki,dc=org</code>.
- */
- protected static final String PROPERTY_ROLE_BASE = "jspwiki.ldap.roleBase";
-
- /**
- * Property that supplies the pattern for finding users within the role
- * base, e.g.
- * <code>(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))</code>
- * .
- */
- protected static final String PROPERTY_ROLE_PATTERN = "jspwiki.ldap.rolePattern";
-
- /**
- * Property that indicates whether to use SSL for connecting to the LDAP
- * server. This property is also used by
- * {@link org.apache.wiki.auth.login.LdapLoginModule}.
- */
- protected static final String PROPERTY_SSL = "jspwiki.loginModule.options.ldap.ssl";
-
- /**
- * Property that specifies the pattern for the username used to log in to
- * the LDAP server. Usually this is a full DN, for example
- * <code>uid={0},ou=people,dc=jspwiki,dc=org</code>. However, sometimes (as
- * with Active Directory 2003 and later) only the userid is used, in which
- * case the principal will simply be <code>{0}</code>. This property is also
- * used by {@link org.apache.wiki.auth.login.LdapLoginModule}.
- */
- protected static final String PROPERTY_LOGIN_ID_PATTERN = "jspwiki.loginModule.options.ldap.userPattern";
+ private static final String[] REQUIRED_PROPERTIES = new String[] { LdapConfig.PROPERTY_CONNECTION_URL,
+ LdapConfig.PROPERTY_ROLE_BASE,
+ LdapConfig.PROPERTY_ROLE_PATTERN,
+ LdapConfig.PROPERTY_IS_IN_ROLE_PATTERN };
- private static final String[] REQUIRED_PROPERTIES = new String[] { PROPERTY_CONNECTION_URL, PROPERTY_LOGIN_ID_PATTERN,
- PROPERTY_ROLE_BASE, PROPERTY_ROLE_PATTERN };
+ private static final SearchControls SEARCH_CONTROLS;
- protected static final String KEYCHAIN_BIND_DN_ENTRY = "LdapAuthorizer.BindDN.Password";
+ static
+ {
+ SEARCH_CONTROLS = new SearchControls();
+ SEARCH_CONTROLS.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ }
/**
* {@inheritDoc}
*/
public void initialize( WikiEngine engine, Properties props ) throws WikiSecurityException
{
- // Make sure all required properties are here
- for( String prop : REQUIRED_PROPERTIES )
- {
- if( !props.containsKey( prop ) )
- {
- throw new WikiSecurityException( "Property " + prop + " is required!" );
- }
- }
-
- // Figure out LDAP environment settings
- m_connectionUrl = props.getProperty( PROPERTY_CONNECTION_URL ).trim();
- m_userLoginIdPattern = props.getProperty( PROPERTY_LOGIN_ID_PATTERN ).trim();
- m_roleBase = props.getProperty( PROPERTY_ROLE_BASE ).trim();
- m_rolePattern = props.getProperty( PROPERTY_ROLE_PATTERN ).trim();
- m_roleFinder = m_rolePattern.replaceAll( "\\{[0-1]\\}", "\\*" );
- m_keychain = engine.getAuthenticationManager().getKeychain();
-
- // Figure out optional properties
- String ssl = (String) props.get( PROPERTY_SSL );
- m_ssl = (ssl != null && TextUtil.isPositive( ssl )) ? "ssl" : "none";
- String authentication = (String) props.get( PROPERTY_AUTHENTICATION );
- if( authentication == null || authentication.length() == 0 )
- {
- m_authentication = "ssl".equals( m_ssl ) ? "simple" : "DIGEST-MD5";
- }
- else
- {
- m_authentication = authentication;
- }
- String bindDN = props.getProperty( PROPERTY_BIND_DN );
- if( bindDN != null && bindDN.length() > 0 )
- {
- m_bindDN = bindDN.trim();
- }
+ m_cfg = LdapConfig.getInstance( engine.getAuthenticationManager().getKeychain(), props, REQUIRED_PROPERTIES );
+ m_allRoleFinder = m_cfg.rolePattern.replace( "{0}", "*" );
// Do a quick connection test, and fail-fast if needed
- buildJndiEnvironment();
try
{
+ m_jndiEnv = m_cfg.newJndiEnvironment();
new InitialLdapContext( m_jndiEnv, null );
}
catch( NamingException e )
@@ -215,99 +130,52 @@
}
}
- private void buildJndiEnvironment() throws WikiSecurityException
- {
- Hashtable<String, String> env = new Hashtable<String, String>();
- env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
- env.put( Context.PROVIDER_URL, m_connectionUrl );
- env.put( Context.SECURITY_PROTOCOL, m_ssl );
- env.put( Context.SECURITY_AUTHENTICATION, m_authentication );
-
- // If we need and Bind DN and Keychain is loaded, get the bind DN and
- // password
- if( m_bindDN != null && m_keychain.isLoaded() )
- {
- try
- {
- KeyStore.Entry password = m_keychain.getEntry( KEYCHAIN_BIND_DN_ENTRY );
- if( password instanceof Keychain.Password )
- {
- env.put( Context.SECURITY_PRINCIPAL, m_bindDN );
- env.put( Context.SECURITY_CREDENTIALS, ((Keychain.Password) password).getPassword() );
- }
- }
- catch( KeyStoreException e )
- {
- e.printStackTrace();
- throw new WikiSecurityException( "Could not build JNDI environment. ", e );
- }
- }
-
- // Spill all the information if debugging
- if( log.isDebugEnabled() )
- {
- log.debug( "Built JNDI environment for LDAP login.", env );
- }
-
- m_jndiEnv = env;
- }
-
/**
* {@inheritDoc}
* <p>
- * This implementation returns <code>true</code> when at least one of the
- * user login Principals contained in the WikiSession's Subject belongs to
- * an LDAP group contained in the role-base DN. The user DN is constructed
- * from the user Principal, where the <code>uid</code> of the user's DN is
- * the Principal name, and the rest of the DN is determined by
- * {@link #PROPERTY_LOGIN_ID_PATTERN}.
- * </p>
- * <p>
- * To make an accurate search, Principals that are of type {@link Role},
- * {@link org.apache.wiki.auth.GroupPrincipal} are excluded from
- * consideration. So are {@link org.apache.wiki.auth.WikiPrincipal} whose
- * type are {@link org.apache.wiki.auth.WikiPrincipal#FULL_NAME} or
- * {@link org.apache.wiki.auth.WikiPrincipal#WIKI_NAME}.
+ * This implementation returns <code>true</code> when the user login
+ * Principal contained in the WikiSession's Subject belongs to an LDAP group
+ * found in the role-base DN. The login Principal is assumed to be a valid
+ * DN. The scope searched is provided by
+ * {@link LdapConfig#PROPERTY_ROLE_BASE}, and the filter to match roles is
+ * provided by {@link LdapConfig#PROPERTY_IS_IN_ROLE_PATTERN}.
* </p>
* <p>
- * For example, consider an LDAP user base of
- * <code>ou=people,dc=jspwiki,dc=org</code>, and a WikiSession whose subject
- * contains three user principals, the two built-in roles <code>ALL</code>
- * and <code>AUTHENTICATED</code>, and a group principal
- * <code>MyGroup</code>:
+ * For example, consider a WikiSession whose subject contains three user
+ * principals, the two built-in roles <code>ALL</code> and
+ * <code>AUTHENTICATED</code>, and a group principal <code>MyGroup</code>:
* </p>
- * <blockquote><code>WikiPrincipal.LOGIN_NAME "biggie.smalls"<br/>
+ * <blockquote>
+ * <code>WikiPrincipal.LOGIN_NAME "uid=biggie.smalls,ou=people,dc=jspwiki,dc=org"<br/>
* WikiPrincipal.FULL_NAME "Biggie Smalls"<br/>
* WikiPrincipal.WIKI_NAME "BiggieSmalls"<br/>
* Role.ALL
* Role.AUTHENTICATED
* GroupPrincipal "MyGroup"</code></blockquote>
* <p>
- * In this case, only WikiPrincipal.LOGIN_NAME "biggie.smalls" would be
- * examined. An LDAP search would be constructed that searched the LDAP role
- * base for an object whose <code>objectClass</code> was of type
- * <code>groupOfUniqueNames</code> and whose
- * <code>uniqueMember<code> attribute contained the value
+ * In this case, the DN
+ * <code>uid=biggie.smalls,ou=people,dc=jspwiki,dc=org</code> would be
+ * examined for membership in an LDAP group whose common name matches
+ * <code>role</code>. Given an is-in-role pattern of
+ * <code>(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))</code>
+ * , an LDAP search would be constructed to find objects whose
+ * <code>objectClass</code> was of type <code>groupOfUniqueNames</code> and
+ * whose <code>uniqueMember<code> attribute contained the value
* <code>uid=biggie.smalls,ou=people,dc=jspwiki,dc=org</code>.
* </p>
*/
public boolean isUserInRole( WikiSession session, Principal role )
{
- // Build DN
- String uid = session.getLoginPrincipal().getName();
- String dn = m_userLoginIdPattern.replace( "{0}", uid ).trim();
- dn = dn.replace( "=", "\\3D" );
-
+ String loginName = session.getLoginPrincipal().getName();
try
{
+ String dn = m_cfg.getUserDn( loginName );
DirContext ctx = new InitialLdapContext( m_jndiEnv, null );
- SearchControls searchControls = new SearchControls();
- searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
- searchControls.setReturningAttributes( new String[0] );
- String filter = m_rolePattern.replace( "{0}", role.getName() );
+ String filter = m_cfg.isInRolePattern.replace( "{0}", role.getName() );
filter = filter.replace( "{1}", dn );
- NamingEnumeration<SearchResult> roles = ctx.search( m_roleBase, filter, searchControls );
- return roles.hasMore();
+ NamingEnumeration<SearchResult> roles = ctx.search( m_cfg.roleBase, filter, SEARCH_CONTROLS );
+ boolean isMember = roles.hasMore();
+ return isMember;
}
catch( NamingException e )
{
@@ -315,5 +183,4 @@
}
return false;
}
-
}
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/login/LdapLoginModule.java Sat Aug 15 11:53:37 2009
@@ -23,16 +23,12 @@
import java.io.IOException;
import java.security.Principal;
import java.util.Hashtable;
-import java.util.Map;
import javax.naming.AuthenticationException;
import javax.naming.Context;
-import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
@@ -41,24 +37,37 @@
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
+import org.apache.wiki.auth.LdapConfig;
import org.apache.wiki.auth.WikiPrincipal;
import org.apache.wiki.i18n.InternationalizationManager;
import org.apache.wiki.log.Logger;
import org.apache.wiki.log.LoggerFactory;
-import org.apache.wiki.util.TextUtil;
/**
* <p>
* LoginModule that authenticates users against an LDAP server with the user's
* supplied credentials. The authentication is performed by binding to the LDAP
- * server using the user's credentials, with or without SSL. To use this
- * LoginModule, callers must initialize it with a JAAS options Map that supplies
- * the following key/value pairs:
+ * server using the user's credentials, with or without SSL. After the user
+ * successfully authenticates, the user's Subject will be populated with three
+ * WikiPrincipals:
+ * </p>
+ * <ul>
+ * <li>a WikiPrincipal of type {@link WikiPrincipal#LOGIN_NAME} whose value is
+ * the distinguished name (DN) of the user.</li>
+ * <li>a WikiPrincipal of type {@link WikiPrincipal#FULL_NAME} whose value is
+ * set to either (first name + surname) or common name, with the former
+ * preferred.</li>
+ * <li>a WikiPrincipal of type {@link WikiPrincipal#WIKI_NAME} whose value is
+ * identical to the full name, but from which all whitespace is removed.</li>
+ * </ul>
+ * <p>
+ * To use this LoginModule, callers must initialize it with a JAAS options Map
+ * that supplies the following key/value pairs:
* </p>
* <ul>
* <li>{@link #OPTION_CONNECTION_URL} - the connection string for the LDAP
* server, for example <code>ldap://ldap.jspwiki.org:389/</code>.</li>
- * <li>{@link #OPTION_LOGIN_ID_PATTERN} - a string pattern indicating how the
+ * <li>{@link #OPTION_LOGIN_ID_PATTERN} - optional string pattern indicating how the
* login id should be formatted into a credential the LDAP server will
* understand. The exact credential pattern varies by LDAP server. OpenLDAP
* expects login IDs that match a distinguished name. Active Directory, on the
@@ -70,7 +79,9 @@
* <li>{@link #OPTION_USER_BASE} - the distinguished name of the base location
* where user objects are located. This is generally an organizational unit (OU)
* DN, such as <code>ou=people,dc=jspwiki,dc=org</code>. The user base and all
- * of its subtrees will be searched.</li>
+ * of its subtrees will be searched. For directories that contain multiple OUs
+ * where users are located, use a higher-level base location (e.g.,
+ * <code>dc=jspwiki,dc=org</code>).</li>
* <li>{@link #OPTION_USER_PATTERN} - an RFC 2254 search filter string used for
* locating the actual user object within the user base. The user ID supplied
* during the login will be substituted into the <code>{0}</code> token in this
@@ -79,7 +90,9 @@
* pattern is <code>(&(objectClass=inetOrgPerson)(uid={0}))</code> and the user
* name supplied during login is <code>fflintstone</code>, the the first object
* within {@link #OPTION_USER_BASE} that matches the filter
- * <code>(&(objectClass=inetOrgPerson)(uid={0}))</code> will be selected.</li>
+ * <code>(&(objectClass=inetOrgPerson)(uid=fflintstone))</code> will be
+ * selected. A suitable value for this property that works with Active Directory
+ * 2000 and later is <code>(&(objectClass=person)(sAMAccountName={0}))</code>.</li>
* <li>{@link #OPTION_SSL} - Optional parameter that specifies whether to use
* SSL when connecting to the LDAP server. Values like <code>true</code> or
* <code>on</code> indicate that SSL should be used. If this parameter is not
@@ -120,39 +133,42 @@
* JAAS option that specifies the JNDI authentication type. Valid values are
* the same as those for {@link Context#SECURITY_AUTHENTICATION}:
* <code>none</code>, <code>simple</code>, <code>strong</code> or
- * <code>DIGEST-MD5</code>. The default is <code>simple</code> if SSL
- * is specified, and <code>DIGEST-MD5</code> otherwise.
+ * <code>DIGEST-MD5</code>. The default is <code>simple</code> if SSL is
+ * specified, and <code>DIGEST-MD5</code> otherwise.
*/
- protected static final String OPTION_AUTHENTICATION = "ldap.authentication";
+ public static final String OPTION_AUTHENTICATION = "ldap.authentication";
/**
* JAAS option that specifies the JNDI connection URL for the LDAP server,
* <em>e.g.,</em> <code>ldap://127.0.0.1:4890/</code>
*/
- protected static final String OPTION_CONNECTION_URL = "ldap.connectionURL";
+ public static final String OPTION_CONNECTION_URL = "ldap.connectionURL";
/**
* JAAS option that specifies the base DN where users are contained,
* <em>e.g.,</em> <code>ou=people,dc=jspwiki,dc=org</code>. This DN is
* searched recursively.
*/
- protected static final String OPTION_USER_BASE = "ldap.userBase";
+ public static final String OPTION_USER_BASE = LdapConfig.PROPERTY_USER_BASE;
/**
* JAAS option that specifies the DN pattern for finding users within the
* user base, <em>e.g.,</em>
* <code>(&(objectClass=inetOrgPerson)(uid={0}))</code>
*/
- protected static final String OPTION_USER_PATTERN = "ldap.userPattern";
+ public static final String OPTION_USER_PATTERN = LdapConfig.PROPERTY_USER_PATTERN;
/**
* JAAS option specifies the pattern for the username used to log in to the
- * LDAP server. Usually this is a full DN, for example
+ * LDAP server. This pattern maps the username supplied at login time by the
+ * user to a username format the LDAP server can recognized. Usually this is
+ * a pattern that produces a full DN, for example
* <code>uid={0},ou=people,dc=jspwiki,dc=org</code>. However, sometimes (as
* with Active Directory 2003 and later) only the userid is used, in which
- * case the principal will simply be <code>{0}</code>.
+ * case the principal will simply be <code>{0}</code>. The default value
+ * if not supplied is <code>{0}</code>.
*/
- protected static final String OPTION_LOGIN_ID_PATTERN = "ldap.loginIdPattern";
+ public static final String OPTION_LOGIN_ID_PATTERN = LdapConfig.PROPERTY_LOGIN_ID_PATTERN;
/**
* JAAS option specifies that indicates whether to use SSL for connecting to
@@ -161,22 +177,18 @@
protected static final String OPTION_SSL = "ldap.ssl";
private static final String[] REQUIRED_OPTIONS = new String[] { OPTION_AUTHENTICATION, OPTION_CONNECTION_URL,
- OPTION_LOGIN_ID_PATTERN, OPTION_USER_BASE,
- OPTION_USER_PATTERN };
+ OPTION_USER_BASE, OPTION_USER_PATTERN };
+
+ private LdapConfig m_cfg = null;
public boolean login() throws LoginException
{
- // Make sure all required properties are here
- for( String option : REQUIRED_OPTIONS )
- {
- if( !m_options.containsKey( option ) )
- {
- throw new LoginException( "Option " + option + " is required!" );
- }
- }
+ // Initialize the LDAP configuration
+ m_cfg = LdapConfig.getInstance( null, m_options, REQUIRED_OPTIONS );
// Retrieve the essential callbacks: username and password
String username;
+ String loginId;
String password;
NameCallback ncb = new NameCallback( "User name" );
PasswordCallback pcb = new PasswordCallback( "Password", false );
@@ -184,7 +196,9 @@
try
{
m_handler.handle( callbacks );
+ String userPattern = m_cfg.loginIdPattern;
username = ncb.getName();
+ loginId = userPattern.replace( "{0}", username );
password = new String( pcb.getPassword() );
}
catch( UnsupportedCallbackException e )
@@ -204,18 +218,16 @@
try
{
// Log in
- Hashtable<String, String> env = buildJndiEnvironment( username, password );
- String loginId = env.get( Context.SECURITY_PRINCIPAL );
+ Hashtable<String, String> env = m_cfg.newJndiEnvironment( loginId, password );
DirContext ctx = new InitialLdapContext( env, null );
-
- // If login succeeds, commit the login principal
- m_principals.add( new WikiPrincipal( username, WikiPrincipal.LOGIN_NAME ) );
if( log.isDebugEnabled() )
{
log.debug( "Logged in user " + username + " with LDAP DN " + loginId );
}
+ m_principals.add( new WikiPrincipal( username, WikiPrincipal.LOGIN_NAME ) );
- // Also look up the full name (and make the wiki name out of it)
+ // Go and get the DN and common name (and make the wiki name out of
+ // it)
Principal[] principals = extractNamePrincipals( ctx, username );
for( Principal principal : principals )
{
@@ -237,61 +249,13 @@
}
/**
- * Builds a JNDI environment hashtable for authenticating to the LDAP
- * server. The hashtable is built using information extracted from the
- * options map supplied to the LoginModule via
- * {@link #initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, Map, Map)}
- * . The username and password parameters supply the LDAP credentials.
- *
- * @param username the user's distinguished name (DN), used for
- * authentication
- * @param password the password
- * @return the constructed hash table
- */
- private Hashtable<String, String> buildJndiEnvironment( String username, String password )
- {
- Hashtable<String, String> env = new Hashtable<String, String>();
- env.put( Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory" );
-
- // LDAP server to authenticate to
- String option = (String) m_options.get( OPTION_CONNECTION_URL );
- env.put( Context.PROVIDER_URL, option );
-
- // Compose the username (credentials) and password
- option = (String) m_options.get( OPTION_LOGIN_ID_PATTERN );
- option = option.replace( "{0}", username );
- env.put( Context.SECURITY_PRINCIPAL, option );
- env.put( Context.SECURITY_CREDENTIALS, password );
-
- // Use SSL?
- option = (String) m_options.get( OPTION_SSL );
- boolean ssl = TextUtil.isPositive( option );
- env.put( Context.SECURITY_PROTOCOL, ssl ? "ssl" : "none" );
-
- // Authentication type (simple, DIGEST-MD5, etc)
- option = (String) m_options.get( OPTION_AUTHENTICATION );
- if( option == null )
- {
- option = ssl ? "simple" : "DIGEST-MD5";
- }
- env.put( Context.SECURITY_AUTHENTICATION, option );
-
- // Spill all the information if debugging
- if( log.isDebugEnabled() )
- {
- log.debug( "Built JNDI environment for LDAP login.", env );
- }
-
- return env;
- }
-
- /**
* Looks up the user in the user base and returns an array of WikiPrincipals
- * representing the full name and wiki name. The full name will be equal to
- * the user object's first name (givenName) + last name (sn) attributes,
- * separated by a space, <em>or</em> the user object's common name (cn)
- * attribute. The wiki name is simply the full name with all spaces removed.
- * If no user is found this method will return a zero-length array.
+ * representing the full name and wiki name. The full
+ * name will be equal to the user object's first name (givenName) + last
+ * name (sn) attributes, separated by a space, <em>or</em> the user object's
+ * common name (cn) attribute. The wiki name is simply the full name with
+ * all spaces removed. If no user is found this method will return a
+ * zero-length array.
*
* @param ctx the previously initialized LDAP context
* @param username the user name
@@ -300,44 +264,15 @@
*/
protected Principal[] extractNamePrincipals( DirContext ctx, String username ) throws NamingException
{
- // Create the search scope
- String userBase = (String) m_options.get( OPTION_USER_BASE );
- String userFinder = (String) m_options.get( OPTION_USER_PATTERN );
- userFinder = userFinder.replace( "{0}", username );
- SearchControls searchControls = new SearchControls();
- searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
- searchControls.setReturningAttributes( new String[] { "cn", "givenName", "sn" } );
-
// Find the user
- NamingEnumeration<SearchResult> users = ctx.search( userBase, userFinder, searchControls );
- if( users.hasMore() )
+ String dn = m_cfg.getUserDn( username );
+ Attributes attributes = ctx.getAttributes( dn );
+ if( attributes != null )
{
- // Look up the user in the current LDAP context
- Attributes attributes = users.next().getAttributes();
-
- // Figure out name to use. Prefer piecing together the first + last
- // names to CN
- boolean hasCommonName = attributes.get( "cn" ) != null;
- boolean hasSurname = attributes.get( "sn" ) != null;
- boolean hasGivenName = attributes.get( "givenName" ) != null;
-
- String fullName = null;
- if( hasGivenName && hasSurname )
- {
- fullName = attributes.get( "givenName" ).get( 0 ) + " " + attributes.get( "sn" ).get( 0 );
- }
- else if( hasCommonName )
- {
- fullName = attributes.get( "cn" ).get( 0 ).toString();
- }
- else
- {
- throw new NamingException( "User " + username + " did not have a givenName+sn or cn" );
- }
-
// Build the wiki principals
// FIXME: This should be sanitized better.
Principal[] principals = new Principal[2];
+ String fullName = LdapConfig.getFullName( attributes );
String wikiName = fullName.indexOf( ' ' ) == -1 ? fullName : fullName.replace( " ", "" );
principals[0] = new WikiPrincipal( fullName, WikiPrincipal.FULL_NAME );
principals[1] = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
Added: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java?rev=804457&view=auto
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java (added)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/LdapUserDatabase.java Sat Aug 15 11:53:37 2009
@@ -0,0 +1,214 @@
+package org.apache.wiki.auth.user;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.*;
+import javax.naming.ldap.InitialLdapContext;
+
+import org.apache.wiki.NoRequiredPropertyException;
+import org.apache.wiki.WikiEngine;
+import org.apache.wiki.auth.*;
+import org.apache.wiki.util.TextUtil;
+
+public class LdapUserDatabase extends AbstractUserDatabase
+{
+ /**
+ * LdapUserDatabase does not support this operation.
+ */
+ public void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException
+ {
+ throw new WikiSecurityException( "Operation not supported" );
+ }
+
+ public UserProfile findByEmail( String index ) throws NoSuchPrincipalException
+ {
+ index = sanitize( index );
+ return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(mail=" + index + "))" );
+ }
+
+ private UserProfile findLdapUser( String filter ) throws NoSuchPrincipalException
+ {
+ Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
+ try
+ {
+ DirContext ctx = new InitialLdapContext( env, null );
+ SearchControls searchControls = new SearchControls();
+ searchControls.setReturningAttributes( new String[] { m_cfg.userLoginNameAttribute, "cn", "givenName", "sn", "mail" } );
+ searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ NamingEnumeration<SearchResult> results = ctx.search( m_cfg.userBase, filter, searchControls );
+ if( results.hasMore() )
+ {
+ SearchResult user = results.next();
+ Attributes attributes = user.getAttributes();
+ Attribute loginName = attributes.get( m_cfg.userLoginNameAttribute );
+ Attribute cn = attributes.get( "cn" );
+ Attribute mail = attributes.get( "mail" );
+ if ( loginName == null || cn == null || mail == null )
+ {
+ throw new NamingException( "Malformed directory entry; missing cn, mail or uid." );
+ }
+ UserProfile p = newProfile();
+ p.setUid( user.getNameInNamespace() );
+ p.setLoginName( loginName.get( 0 ).toString() );
+ p.setFullname( LdapConfig.getFullName( user.getAttributes() ) );
+ p.setEmail( mail.get( 0 ).toString() );
+ return p;
+ }
+ }
+ catch( NamingException e )
+ {
+ throw new NoSuchPrincipalException( "Could not find object: " + e.getMessage() );
+ }
+ throw new NoSuchPrincipalException( "Could not find object." );
+ }
+
+ public UserProfile findByFullName( String index ) throws NoSuchPrincipalException
+ {
+ index = sanitize( index );
+ return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(cn=" + index + "))" );
+ }
+
+ public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException
+ {
+ index = sanitize( index );
+ return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(" + m_cfg.userLoginNameAttribute + "=" + index + "))" );
+ }
+
+ public UserProfile findByUid( String uid ) throws NoSuchPrincipalException
+ {
+ String filter = "(objectClass=" + m_cfg.userObjectClass + ")";
+ Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
+ try
+ {
+ DirContext ctx = new InitialLdapContext( env, null );
+ SearchControls searchControls = new SearchControls();
+ searchControls.setReturningAttributes( new String[] { m_cfg.userLoginNameAttribute, "cn", "givenName", "sn", "mail" } );
+ searchControls.setSearchScope( SearchControls.SUBTREE_SCOPE );
+ NamingEnumeration<SearchResult> results = ctx.search( uid, filter, searchControls );
+ if( results.hasMore() )
+ {
+ SearchResult user = results.next();
+ Attributes attributes = user.getAttributes();
+ Attribute loginName = attributes.get( m_cfg.userLoginNameAttribute );
+ Attribute cn = attributes.get( "cn" );
+ Attribute mail = attributes.get( "mail" );
+ if ( loginName == null || cn == null || mail == null )
+ {
+ throw new NamingException( "Malformed directory entry; missing cn, mail or uid." );
+ }
+ UserProfile p = newProfile();
+ p.setUid( user.getNameInNamespace() );
+ p.setLoginName( loginName.get( 0 ).toString() );
+ p.setFullname( LdapConfig.getFullName( user.getAttributes() ) );
+ p.setEmail( mail.get( 0 ).toString() );
+ return p;
+ }
+ }
+ catch( NamingException e )
+ {
+ throw new NoSuchPrincipalException( "Could not find object: " + e.getMessage() );
+ }
+ throw new NoSuchPrincipalException( "Could not find object." );
+ }
+
+ public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException
+ {
+ index = sanitize( index );
+ index = TextUtil.beautifyString( index );
+ return findLdapUser( "(&(objectClass=" + m_cfg.userObjectClass + ")(cn=" + index + "))" );
+ }
+
+ private String sanitize( String index )
+ {
+ index = index.replace( " ", " " );
+ return index.replace( "=", "\\3D" );
+ }
+
+ public Principal[] getWikiNames() throws WikiSecurityException
+ {
+ String filter = "(objectClass=" + m_cfg.userObjectClass + ")";
+ Hashtable<String, String> env = m_cfg.newJndiEnvironment( null, null );
+ Set<Principal> principals = new HashSet<Principal>();
+ try
+ {
+ DirContext ctx = new InitialLdapContext( env, null );
+ SearchControls searchControls = new SearchControls();
+ searchControls.setReturningAttributes( new String[]{ "cn", "givenName", "sn" } );
+ NamingEnumeration<SearchResult> results = ctx.search( m_cfg.userBase, filter, searchControls );
+ while( results.hasMore() )
+ {
+ SearchResult user = results.next();
+ String fullName = LdapConfig.getFullName( user.getAttributes() );
+ String wikiName = fullName.indexOf( ' ' ) == -1 ? fullName : fullName.replace( " ", "" );
+ principals.add( new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME ) );
+ }
+ }
+ catch( NamingException e )
+ {
+ throw new WikiSecurityException( "Could not find object: " + e.getMessage(), e );
+ }
+ return principals.toArray(new Principal[principals.size()]);
+ }
+
+ private LdapConfig m_cfg = null;
+
+ private static final String[] REQUIRED_PROPERTIES = new String[] { LdapConfig.PROPERTY_CONNECTION_URL,
+ LdapConfig.PROPERTY_USER_BASE };
+
+ public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
+ {
+ m_cfg = LdapConfig.getInstance( null, props, REQUIRED_PROPERTIES );
+ }
+
+ /**
+ * LdapUserDatabase does not support this operation.
+ */
+ public void rename( String loginName, String newName )
+ throws NoSuchPrincipalException,
+ DuplicateUserException,
+ WikiSecurityException
+ {
+ throw new WikiSecurityException( "Operation not supported" );
+ }
+
+ /**
+ * LdapUserDatabase does not support this operation.
+ */
+ public void save( UserProfile profile ) throws WikiSecurityException
+ {
+ throw new WikiSecurityException( "Operation not supported" );
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * This implementation validates the password by binding to the LDAP server
+ * as the user. The value of <code>loginName</code> is substituted into the
+ * <code>{0}</code> pattern specified by property
+ * {@link LdapConfig#PROPERTY_LOGIN_ID_PATTERN}.
+ * </p>
+ */
+ public boolean validatePassword( String loginName, String password )
+ {
+ String userPattern = m_cfg.loginIdPattern;
+ String username = userPattern.replace( "{0}", loginName );
+
+ Hashtable<String, String> env = m_cfg.newJndiEnvironment( username, password );
+ try
+ {
+ new InitialLdapContext( env, null );
+ return true;
+ }
+ catch( NamingException e )
+ {
+ }
+ return false;
+ }
+
+}
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/UserDatabase.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/UserDatabase.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/UserDatabase.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/auth/user/UserDatabase.java Sat Aug 15 11:53:37 2009
@@ -75,7 +75,7 @@
* this Principal using
* {@link org.apache.wiki.auth.WikiPrincipal#WikiPrincipal(String, String)}
* with the <code>type</code> parameter set to
- * {@link org.apache.wiki.auth.WikiPrincipal#WIKI_NAME}. The method
+ * {@link org.apache.wiki.auth.WikiPrincipal#FULL_NAME}. The method
* {@link org.apache.wiki.WikiSession#getUserPrincipal()} will return this
* principal as the "primary" principal. Note that this method can also be
* used to mark a WikiPrincipal as a login name or a wiki name.
Modified: incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/UserProfileTag.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/UserProfileTag.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/UserProfileTag.java (original)
+++ incubator/jspwiki/trunk/src/java/org/apache/wiki/tags/UserProfileTag.java Sat Aug 15 11:53:37 2009
@@ -64,10 +64,17 @@
* in the user database
* <li><code>new</code> - evaluates the body of the tag if user's profile does not
* exist in the user database
- * <li><code>canChangeLoginName</code> - always true if custom auth used; also true for container auth
- * and current UserDatabase.isSharedWithContainer() is true.</li>
- * <li><code>canChangePassword</code> - always true if custom auth used; also true for container auth
- * and current UserDatabase.isSharedWithContainer() is true.</li>
+ * <li><code>canChangeLoginName</code> - true if custom auth used and
+ * {@link UserManager#PROP_READ_ONLY_PROFILES} is not set; always
+ * false for container auth
+ * is set to <code>true</code></li>
+ * <li><code>canChangePassword</code> - true if custom auth used and
+ * {@link UserManager#PROP_READ_ONLY_PROFILES} is not set; always
+ * false for container auth</li>
+ * <li><code>canChangeEmail</code> - always true unless {@link UserManager#PROP_READ_ONLY_PROFILES}
+ * is set</li>
+ * <li><code>canChangeFullname</code> - always true unless {@link UserManager#PROP_READ_ONLY_PROFILES}
+ * is set</li>
* </ul>
* <p>In addition, the values <code>exists</code>, <code>new</code>, <code>canChangeLoginName</code>
* and <code>canChangeLoginName</code> can also be prefixed with <code>!</code> to indicate the
@@ -113,6 +120,14 @@
private static final String NOT_CHANGE_PASSWORD = "!canchangepassword";
+ private static final String CHANGE_FULL_NAME = "canchangefullname";
+
+ private static final String NOT_CHANGE_FULL_NAME = "!canchangefullname";
+
+ private static final String CHANGE_EMAIL = "canchangeemail";
+
+ private static final String NOT_CHANGE_EMAIL = "!canchangeemail";
+
private String m_prop;
public void initTag()
@@ -199,6 +214,20 @@
return EVAL_BODY_INCLUDE;
}
}
+ else if ( CHANGE_EMAIL.equals( m_prop ) || CHANGE_FULL_NAME.equals( m_prop ) )
+ {
+ if ( !m_wikiContext.getEngine().getUserManager().isReadOnly() )
+ {
+ return EVAL_BODY_INCLUDE;
+ }
+ }
+ else if ( NOT_CHANGE_EMAIL.equals( m_prop ) || NOT_CHANGE_FULL_NAME.equals( m_prop ) );
+ {
+ if ( m_wikiContext.getEngine().getUserManager().isReadOnly() )
+ {
+ return EVAL_BODY_INCLUDE;
+ }
+ }
if ( result != null )
{
Modified: incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java
URL: http://svn.apache.org/viewvc/incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java?rev=804457&r1=804456&r2=804457&view=diff
==============================================================================
--- incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java (original)
+++ incubator/jspwiki/trunk/tests/java/org/apache/wiki/auth/authorize/LdapAuthorizerTest.java Sat Aug 15 11:53:37 2009
@@ -24,17 +24,14 @@
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.Principal;
-import java.util.HashMap;
-import java.util.Map;
import java.util.Properties;
import junit.framework.TestCase;
import org.apache.wiki.TestEngine;
import org.apache.wiki.WikiSession;
-import org.apache.wiki.auth.AuthenticationManager;
-import org.apache.wiki.auth.AuthorizationManager;
-import org.apache.wiki.auth.Authorizer;
+import org.apache.wiki.auth.*;
+import org.apache.wiki.auth.login.LdapLoginModule;
import org.freshcookies.security.Keychain;
/**
@@ -42,47 +39,57 @@
*/
public class LdapAuthorizerTest extends TestCase
{
- private Map<String, String> m_options;
+ private TestEngine m_engine;
protected void setUp() throws Exception
{
- m_options = new HashMap<String, String>();
- m_options.put( LdapAuthorizer.PROPERTY_CONNECTION_URL, "ldap://127.0.0.1:4890" );
- m_options.put( LdapAuthorizer.PROPERTY_LOGIN_ID_PATTERN, "uid={0},ou=people,dc=jspwiki,dc=org" );
- m_options.put( LdapAuthorizer.PROPERTY_ROLE_BASE, "ou=roles,dc=jspwiki,dc=org" );
- m_options.put( LdapAuthorizer.PROPERTY_ROLE_PATTERN, "(&(objectClass=groupOfUniqueNames)(cn={0})(uniqueMember={1}))" );
- m_options.put( LdapAuthorizer.PROPERTY_SSL, "false" );
- m_options.put( LdapAuthorizer.PROPERTY_AUTHENTICATION, "simple" );
- m_options.put( LdapAuthorizer.PROPERTY_BIND_DN, "uid=Fred,ou=people,dc=jspwiki,dc=org" );
-
// Create the Keychain
Keychain keychain = new Keychain();
keychain.load( null, "keychain-password".toCharArray() );
Keychain.Password password = new Keychain.Password( "password" );
- keychain.setEntry( LdapAuthorizer.KEYCHAIN_BIND_DN_ENTRY, password );
+ keychain.setEntry( LdapConfig.KEYCHAIN_BIND_DN_ENTRY, password );
File file = new File("tests/etc/WEB-INF/test-keychain" );
OutputStream stream = new FileOutputStream( file );
keychain.store( stream, "keychain-password".toCharArray() );
- }
- /**
- * @see junit.framework.TestCase#setUp()
- */
- protected TestEngine createEngine( Map<String, String> config ) throws Exception
- {
+ // Create the TestEngine properties
Properties props = new Properties();
props.load( TestEngine.findTestProperties() );
- props.putAll( config );
+
+ // Set the LoginModule options
+ props.put( UserManager.PROP_READ_ONLY_PROFILES, "true" );
+ props.put( AuthenticationManager.PROP_LOGIN_MODULE, LdapLoginModule.class.getName() );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapLoginModule.OPTION_CONNECTION_URL, "ldap://127.0.0.1:4890" );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapLoginModule.OPTION_LOGIN_ID_PATTERN, "uid={0},ou=people,dc=jspwiki,dc=org" );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapLoginModule.OPTION_USER_BASE, "dc=jspwiki,dc=org" );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapLoginModule.OPTION_USER_PATTERN, "(&(objectClass=inetOrgPerson)(uid={0}))" );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapLoginModule.OPTION_AUTHENTICATION, "simple" );
+ props.put( AuthenticationManager.PREFIX_LOGIN_MODULE_OPTIONS + LdapConfig.PROPERTY_SSL, "false" );
+
+ // Set the Authorizer properties
props.put( AuthorizationManager.PROP_AUTHORIZER, LdapAuthorizer.class.getCanonicalName() );
+ props.put( LdapConfig.PROPERTY_ROLE_BASE, "ou=roles,dc=jspwiki,dc=org" );
+ props.put( LdapConfig.PROPERTY_ROLE_PATTERN, "(&(objectClass=groupOfUniqueNames)(cn={0}))" );
+ props.put( LdapConfig.PROPERTY_IS_IN_ROLE_PATTERN, "(&(&(objectClass=groupOfUniqueNames)(cn={0}))(uniqueMember={1}))" );
+ props.put( LdapConfig.PROPERTY_BIND_DN, "uid=Fred,ou=people,dc=jspwiki,dc=org" );
props.put( AuthenticationManager.PROP_KEYCHAIN_PATH, "test-keychain" );
props.put( AuthenticationManager.PROP_KEYCHAIN_PASSWORD, "keychain-password" );
- TestEngine engine = new TestEngine( props );
- return engine;
+
+ m_engine = new TestEngine( props );
+ }
+
+ protected void tearDown() throws Exception
+ {
+ File file = new File("tests/etc/WEB-INF/test-keychain" );
+ if ( file.exists() )
+ {
+ file.delete();
+ }
}
public void testGetRoles() throws Exception
{
- Authorizer authorizer = createEngine( m_options ).getAuthorizationManager().getAuthorizer();
+ Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
// LDAP should return just 2 roles, Admin and Role1
Principal[] roles = authorizer.getRoles();
@@ -95,7 +102,7 @@
public void testFindRole() throws Exception
{
- Authorizer authorizer = createEngine( m_options ).getAuthorizationManager().getAuthorizer();
+ Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
// We should be able to find roles Admin and Role1
assertEquals( new Role("Admin"), authorizer.findRole( "Admin" ) );
@@ -107,18 +114,18 @@
public void testIsUserInRole() throws Exception
{
- TestEngine engine = createEngine( m_options );
- Authorizer authorizer = engine.getAuthorizationManager().getAuthorizer();
+ assertTrue( m_engine.getUserManager().isReadOnly() );
+ Authorizer authorizer = m_engine.getAuthorizationManager().getAuthorizer();
Role admin = new Role( "Admin" );
Role role1 = new Role( "Role1" );
// Janne does not belong to any roles
- WikiSession session = engine.janneSession();
+ WikiSession session = m_engine.janneSession();
assertFalse( authorizer.isUserInRole( session, admin ) );
assertFalse( authorizer.isUserInRole( session, role1 ) );
// The Admin belongs to just the Admin role
- session = engine.adminSession();
+ session = m_engine.adminSession();
assertTrue( authorizer.isUserInRole( session, admin ) );
assertFalse( authorizer.isUserInRole( session, role1 ) );
}