Commit a7d40b6e authored by Jay Kline's avatar Jay Kline Committed by jay

!!This will require config changes to all SSO users!!

Large restructure of SASL authorization and some SASL authentication changes:

 * Implemented PLAIN SASL Server 
 * Moved PLAIN auth to using SASL Server object
 * Allow case for client EXTERNAL auth
 * Created AuthorizationMappings (allow for default usernames different from 
      principal used)
 * More robust handling of LDAP authorization (allows JID != principal)
 * Fixes case sensitivy issue with default authorization policy
 * Removed UnixK5LoginPorivder, since it will likely never be used, has never
      been tested, and would be difficult to maintain in the long run.

The Loose, Lazy, and Strict policies have been removed, and folded into a 
single Default policy that now resides in 
org.jivesoftware.openfire.auth.DefaultAuthorizationPolicy

Issues: JM-1079 JM-1086



git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@8583 b35dd754-fafc-0310-a699-88a17e54d16e
parent bab6f4cf
......@@ -9,7 +9,7 @@
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
package org.jivesoftware.openfire.auth;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals;
......@@ -42,8 +42,8 @@ import java.util.StringTokenizer;
*/
public class AuthorizationManager {
private static ArrayList<AuthorizationProvider> providers =
new ArrayList<AuthorizationProvider>();
private static ArrayList<AuthorizationPolicy> authorizationPolicies = new ArrayList<AuthorizationPolicy>();
private static ArrayList<AuthorizationMapping> authorizationMapping = new ArrayList<AuthorizationMapping>();
private static AuthorizationManager instance = new AuthorizationManager();
static {
......@@ -54,18 +54,46 @@ public class AuthorizationManager {
String s_provider = st.nextToken();
try {
Class c_provider = ClassUtils.forName(s_provider);
AuthorizationProvider provider =
(AuthorizationProvider) (c_provider.newInstance());
AuthorizationPolicy provider =
(AuthorizationPolicy)(c_provider.newInstance());
Log.debug("AuthorizationManager: Loaded " + s_provider);
providers.add(provider);
authorizationPolicies.add(provider);
}
catch (Exception e) {
Log.error("AuthorizationManager: Error loading AuthorizationProvider: " + s_provider + "\n" + e);
}
}
}
if (authorizationPolicies.isEmpty()) {
Log.debug("AuthorizationManager: No AuthorizationProvider's found. Loading DefaultAuthorizationPolicy");
authorizationPolicies.add(new DefaultAuthorizationPolicy());
}
classList = null;
classList = JiveGlobals.getXMLProperty("provider.authorizationMapping.classList");
if (classList != null) {
StringTokenizer st = new StringTokenizer(classList, " ,\t\n\r\f");
while (st.hasMoreTokens()) {
String s_provider = st.nextToken();
try {
Class c_provider = ClassUtils.forName(s_provider);
Object o_provider = c_provider.newInstance();
if(o_provider instanceof AuthorizationMapping) {
AuthorizationMapping provider = (AuthorizationMapping)(o_provider);
Log.debug("AuthorizationManager: Loaded " + s_provider);
authorizationMapping.add(provider);
} else {
Log.debug("AuthorizationManager: Unknown class type.");
}
} catch (Exception e) {
Log.error("Error loading AuthorizationProvider: " + s_provider + "\n" + e);
Log.error("AuthorizationManager: Error loading AuthorizationMapping: " + s_provider + "\n" + e);
}
}
}
if (providers.isEmpty()) {
Log.debug("No AuthorizationProvider's found. Loading DefaultAuthorizationPolicy");
providers.add(new DefaultAuthorizationPolicy());
if (authorizationMapping.isEmpty()) {
Log.debug("AuthorizationManager: No AuthorizationMapping's found. Loading DefaultAuthorizationMapping");
authorizationMapping.add((AuthorizationMapping)new DefaultAuthorizationMapping());
}
}
......@@ -81,8 +109,8 @@ public class AuthorizationManager {
*
* @return the current AuthorizationProvider.
*/
public static Collection<AuthorizationProvider> getAuthorizationProviders() {
return providers;
public static Collection<AuthorizationPolicy> getAuthorizationPolicies() {
return authorizationPolicies;
}
/**
......@@ -98,15 +126,37 @@ public class AuthorizationManager {
* Authorize the authenticated used to the requested username. This uses the
* selected the selected AuthenticationProviders.
*
* @param username The requested username.
* @param principal The authenticated principal.
* @return true if the user is authorized.
*/
public static boolean authorize(String authorId, String authenId) {
for (AuthorizationProvider ap : providers) {
if (ap.authorize(authorId, authenId)) {
public static boolean authorize(String username, String principal) {
for (AuthorizationPolicy ap : authorizationPolicies) {
Log.debug("AuthorizationManager: Trying "+ap.name()+".authorize("+username+","+principal+")");
if (ap.authorize(username, principal)) {
return true;
}
}
return false;
}
/**
* Map the authenticated principal to the default username. If the authenticated
* principal did not supply a username, determine the default to use.
*
* @param principal The authentiated principal to determine the default username.
* @return The default username for the authentiated principal.
*/
public static String map(String principal) {
for (AuthorizationMapping am : authorizationMapping) {
Log.debug("AuthorizationManager: Trying "+am.name()+".map("+principal+")");
String username = am.map(principal);
if( ! username.equals(principal) ) {
return username;
}
}
return principal;
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
......@@ -9,40 +9,35 @@
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
package org.jivesoftware.openfire.auth;
/**
* Provider for authorization policies. Policy decisions are
* not based on any storage or specific options. They are
* ment to be broad sweeping policies, and are often implemented
* with a simple pattern matching algorithm. For a large
* majority of sites, a policy will be all that is required.
*
* Users that wish to integrate with their own authorization
* system must extend this class and implement the
* AuthorizationProvider interface then register the class
* with Openfire in the <tt>openfire.xml</tt> file. An entry
* in that file would look like the following:
*
* This is the interface the used to provide default defualt authorization
* ID's when none was selected by the client.
* <p/>
* Users that wish to integrate with their own authorization
* system must implement this interface.
* Register the class with Openfire in the <tt>openfire.xml</tt>
* file. An entry in that file would look like the following:
* <p/>
* <pre>
* &lt;provider&gt;
* &lt;authorizationpolicy&gt;
* &lt;classlist&gt;com.foo.auth.CustomPolicyProvider&lt;/classlist&gt;
* &lt;/authorizationpolicy&gt;
* &lt;authorizationMapping&gt;
* &lt;classlist&gt;com.foo.auth.CustomProvider&lt;/classlist&gt;
* &lt;/authorizationMapping&gt;
* &lt;/provider&gt;</pre>
*
* @author Jay Kline
*/
public abstract class AbstractAuthorizationPolicy implements AuthorizationProvider {
public interface AuthorizationMapping {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
* @param principal The autheticated principal requesting authorization.
* @return The name of the default username to use.
*/
public abstract boolean authorize(String username, String principal);
public String map(String principal);
/**
* Returns the short name of the Policy
......@@ -57,5 +52,4 @@ public abstract class AbstractAuthorizationPolicy implements AuthorizationProvid
* @return The description of the Policy.
*/
public abstract String description();
}
\ No newline at end of file
......@@ -9,38 +9,51 @@
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
package org.jivesoftware.openfire.auth;
/**
* This is the interface the AuthorizationManager uses to
* conduct authorizations.
*
* Users that wish to integrate with their own authorization
* system must implement this interface, and are strongly
* This is the interface the AuthorizationManager uses to
* conduct authorizations.
* <p/>
* Users that wish to integrate with their own authorization
* system must implement this interface, and are strongly
* encouraged to extend either the AbstractAuthoriationPolicy
* or the AbstractAuthorizationProvider classes which allow
* the admin console manage the classes more effectively.
* Register the class with Openfire in the <tt>openfire.xml</tt>
* file. An entry in that file would look like the following:
*
* <p/>
* <pre>
* &lt;provider&gt;
* &lt;authorizationpolicy&gt;
* &lt;authorization&gt;
* &lt;classlist&gt;com.foo.auth.CustomPolicyProvider&lt;/classlist&gt;
* &lt;/authorizationpolicy&gt;
* &lt;/authorization&gt;
* &lt;/provider&gt;</pre>
*
* @author Jay Kline
*/
public interface AuthorizationProvider {
public interface AuthorizationPolicy {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal);
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public abstract String name();
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public abstract String description();
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.auth;
import java.util.Vector;
import java.util.StringTokenizer;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
/**
* This is the interface the used to provide default defualt authorization
* ID's when none was selected by the client.
* This class simply removes the realm (if any) from the principal if and only if
* the realm matches the server's realm, the server's xmpp domain name, or
*
* @author Jay Kline
*/
public class DefaultAuthorizationMapping implements AuthorizationMapping {
private Vector<String> approvedRealms;
public DefaultAuthorizationMapping() {
approvedRealms = new Vector<String>();
String realmList = JiveGlobals.getXMLProperty("sasl.approvedRealms");
if(realmList != null) {
StringTokenizer st = new StringTokenizer(realmList, " ,\t\n\r\f");
while(st.hasMoreTokens()) {
approvedRealms.add(st.nextToken());
}
}
}
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param principal The autheticated principal requesting authorization.
* @return The name of the default username to use.
*/
public String map(String principal) {
if(principal.contains("@")) {
String realm = principal.substring(principal.lastIndexOf('@')+1);
String username = principal.substring(0,principal.lastIndexOf('@'));
if(realm.length() > 0) {
if(realm.equals(JiveGlobals.getProperty("xmpp.domain"))) {
Log.debug("DefaultAuthorizationMapping: realm = xmpp.domain");
return username;
} else if(realm.equals(JiveGlobals.getXMLProperty("sasl.realm"))) {
Log.debug("DefaultAuthorizationMapping: ream = sasl.realm");
return username;
} else {
for(String approvedRealm : approvedRealms) {
if(realm.equals(approvedRealm)) {
Log.debug("DefaultAuthorizationMapping: realm ("+realm+") = "+approvedRealm+" which is approved");
return username;
} else {
Log.debug("DefaultAuthorizationPolicy: realm ("+realm+") != "+approvedRealm+" which is approved");
}
}
}
Log.debug("DefaultAuthorizationMapping: No approved mappings found.");
return principal;
} else {
Log.debug("DefaultAuthorizationMapping: Realm has no length");
}
} else {
Log.debug("DefaultAuthorizationMapping: No realm found");
}
return principal;
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Default Mapping";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Simply remove's the realm of the requesting principal if and only if "+
"the realm matches the server's realm or the server's xmpp domain name. "+
"Otherwise the principal is used as the username.";
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.auth;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.Log;
import java.util.Vector;
import java.util.StringTokenizer;
/**
* Different clients perform authentication differently, so this policy
* will authorize any principal to a requested user that match specific
* conditions that are considered secure defaults for most installations.
*
* Keep in mind if a client does not request any username Java copies the
* authenticated ID to the requested username.
*
* <ul>
* <li>If the authenticated ID is in the form of a plain username, and the
* requested user is in the form of a plain username, then the two must
* be exactly the same.
* <li>If the authenticated ID contains an '@', then the portion before the
* '@' must match exactly the requested username and the portion after
* the '@' must match at least one of the following:
* <ul>
* <li>The XMPP domain of the server
* <li>The SASL realm of the server
* <li>Be in the list of acceptable realms
* </ul>
* <li>If the requested username contains an '@' then the porotion before the
* '@' will be considered the requested username only if the portion after
* the '@' matches the XMPP domain of the server or the portion after the
* '@' in the authenticated ID, if any.
* </ul>
*
*
* @see AuthorizationManager
* @author Jay Kline
*/
public class DefaultAuthorizationPolicy implements AuthorizationPolicy {
private Vector<String> approvedRealms;
public DefaultAuthorizationPolicy() {
approvedRealms = new Vector<String>();
String realmList = JiveGlobals.getXMLProperty("sasl.approvedRealms");
if(realmList != null) {
StringTokenizer st = new StringTokenizer(realmList, " ,\t\n\r\f");
while(st.hasMoreTokens()) {
approvedRealms.add(st.nextToken());
}
}
}
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param authenID The authenticated ID requesting the username.
* @return true if the authenticated ID is authorized to the requested user.
*/
public boolean authorize(String username, String authenID) {
boolean authorized = false;
String userUser = username; //I know, I know, dumb variable name...
String userRealm = null;
String authenUser = authenID;
String authenRealm = null;
if(username.contains("@")) {
userUser = username.substring(0,username.lastIndexOf("@"));
userRealm = username.substring((username.lastIndexOf("@")+1));
}
if(authenID.contains("@")){
authenUser = authenID.substring(0,(authenID.lastIndexOf("@")));
authenRealm = authenID.substring((authenID.lastIndexOf("@")+1));
}
if(!userUser.equals(authenUser)) {
//for this policy the user portion of both must match, so lets short circut here if we can
if(JiveGlobals.getBooleanProperty("xmpp.auth.ignorecase",true)) {
if(!userUser.toLowerCase().equals(authenUser.toLowerCase())){
Log.debug("DefaultAuthorizationPolicy: usernames don't match ("+userUser+" "+authenUser+")");
return false;
}
} else {
Log.debug("DefaultAuthorizationPolicy: usernames don't match ("+userUser+" "+authenUser+")");
return false;
}
}
Log.debug("DefaultAuthorizationPolicy: Checking authenID realm");
// Next up, check if the authenID realm is acceptable.
if(authenRealm != null) {
if(authenRealm.equals(JiveGlobals.getProperty("xmpp.domain"))) {
Log.debug("DefaultAuthorizationPolicy: authenRealm = xmpp.domain");
authorized = true;
} else if(authenRealm.equals(JiveGlobals.getXMLProperty("sasl.realm"))) {
Log.debug("DefaultAuthorizationPolicy: authenRealm = sasl.realm");
authorized = true;
} else {
for(String realm : approvedRealms) {
if(authenRealm.equals(realm)) {
Log.debug("DefaultAuthorizationPolicy: authenRealm = "+realm+" which is approved");
authorized = true;
} else {
Log.debug("DefaultAuthorizationPolicy: authenRealm != "+realm+" which is approved");
}
}
}
} else {
//no realm in the authenID
authorized = true;
}
if(!authorized) {
return false;
} else {
//reset for next round of tests
authorized = false;
}
//Next up, check if the username realm is acceptable.
if(userRealm != null) {
if(userRealm.equals(JiveGlobals.getProperty("xmpp.domain"))) {
Log.debug("DefaultAuthorizationPolicy: userRealm = xmpp.domain");
authorized = true;
} else {
if(authenRealm != null && authenRealm.equals(userRealm)) {
//authen and username are identical
Log.debug("DefaultAuthorizationPolicy: userRealm = "+authenRealm+" which is approved");
authorized = true;
}
}
} else {
authorized = true;
}
//no more checks
return authorized;
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Default Policy";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Different clients perform authentication differently, so this policy "+
"will authorize any principal to a requested user that match specific "+
"conditions that are considered secure defaults for most installations.";
}
}
......@@ -3,8 +3,13 @@
<head>
</head>
<body>
<p>Authentication service interfaces and classes. Custom
authentication implementations can be created by extending the
{@link org.jivesoftware.openfire.auth.AuthProvider} interface.
<p>Authentication and Authorization service interfaces and classes. There are three compoents:
<ul>
<li><b>Authentication</b>. Authentication is the process of verifying a user. Custom authentication implementations can be created by extending the {@link org.jivesoftware.openfire.auth.AuthProvider} interface.</li>
<li><b>Authorization</b>. Authorization is the process of allowing an authenticated identity to choose a username. Default authorization will authorize an authenticated username to the same username only. Custom authorization implementations can be created by extending the {@link org.jivesoftware.openfire.auth.AuthorizationPolicy} interface.</li>
<li><b>Authorization Mapping</b>. Mapping occurs when the client did not request any specific username. This provides a method of giving a default username in these situations. Custom authorization mappings can be created by extending the {@link org.jivesoftware.auth.AuthorizationMapping} interface.</li>
</ul>
</body>
</html>
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.ldap;
import org.jivesoftware.openfire.ldap.LdapManager;
import org.jivesoftware.openfire.auth.AuthorizationMapping;
import org.jivesoftware.util.Log;
import org.jivesoftware.util.JiveGlobals;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.NamingEnumeration;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
/**
* Provider for authorization mapping using LDAP. If the authenticated
* principal did not request a username, provide one via LDAP. Specify the
* lookup field in the <tt>openfire.xml</tt> file. An entry in that file would
* look like the following:
* <p/>
* <pre>
* &lt;ldap&gt;
* &lt;princField&gt; k5login &lt;/princField&gt;
* &lt;princSearchFilter&gt; princField={0} &lt;/princSearchFilter&gt;
* &lt;/ldap&gt;</pre>
* <p/>
* Each ldap object that represents a user is expcted to have exactly one of
* ldap.usernameField and ldap.princField, and they are both expected to be unique
* over the search base. A search will be performed over all objects where
* princField = principal, and the usernameField will be returned.
* Note that it is expected this search return exactly one object. (There can
* only be one default) If more than one is returned, the first entry
* encountered will be used, and no sorting is performed or requested.
* If more control over the search is needed, you can specify the mapSearchFilter
* used to perform the LDAP query.
* This implementation requires that LDAP be configured, obviously.
*
*
* @author Jay Kline
*/
public class LdapAuthorizationMapping implements AuthorizationMapping {
private LdapManager manager;
private String usernameField;
private String princField;
private String princSearchFilter;
public LdapAuthorizationMapping() {
manager = LdapManager.getInstance();
usernameField = manager.getUsernameField();
princField = JiveGlobals.getXMLProperty("ldap.princField", "k5login");
princSearchFilter = JiveGlobals.getXMLProperty("ldap.princSearchFilter");
StringBuilder filter = new StringBuilder();
if(princSearchFilter == null) {
filter.append("(").append(princField).append("={0})");
} else {
filter.append("(&(").append(princField).append("={0})(");
filter.append(princSearchFilter).append("))");
}
princSearchFilter = filter.toString();
}
public String map(String principal) {
String username = principal;
DirContext ctx = null;
try {
Log.debug("Starting LDAP search...");
String usernameField = manager.getUsernameField();
String baseDN = manager.getBaseDN();
boolean subTreeSearch = manager.isSubTreeSearch();
ctx = manager.getContext();
SearchControls constraints = new SearchControls();
if (subTreeSearch) {
constraints.setSearchScope
(SearchControls.SUBTREE_SCOPE);
}
// Otherwise, only search a single level.
else {
constraints.setSearchScope(SearchControls.ONELEVEL_SCOPE);
}
constraints.setReturningAttributes(new String[] { usernameField });
NamingEnumeration answer = ctx.search("", princSearchFilter, new String[] {principal},
constraints);
Log.debug("... search finished");
if (answer == null || !answer.hasMoreElements()) {
Log.debug("Username based on principal '" + principal + "' not found.");
return principal;
}
Attributes atrs = ((SearchResult)answer.next()).getAttributes();
Attribute usernameAttribute = atrs.get(usernameField);
username = (String) usernameAttribute.get();
}
catch (Exception e) {
// Ignore.
}
finally {
try {
if (ctx != null) {
ctx.close();
}
}
catch (Exception ignored) {
// Ignore.
}
}
return username;
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "LDAP Authorization Mapping";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Provider for authorization using LDAP. Returns the principals default username using the attribute specified in ldap.princField.";
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.ldap;
import org.jivesoftware.openfire.ldap.LdapManager;
import org.jivesoftware.openfire.auth.AuthorizationPolicy;
import org.jivesoftware.util.JiveGlobals;
import org.xmpp.packet.JID;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
/**
* Provider for authorization using LDAP. Checks if the authenticated
* principal is in the user's LDAP object using the authorizeField
* from the <tt>openfire.xml</tt> file. An entry in that file would
* look like the following:
* <p/>
* <pre>
* &lt;ldap&gt;
* &lt;authorizeField&gt; k5login &lt;/authorizeField&gt;
* &lt;/ldap&gt;</pre>
* <p/>
* This implementation requires that LDAP be configured, obviously.
*
* @author Jay Kline
*/
public class LdapAuthorizationPolicy implements AuthorizationPolicy {
private LdapManager manager;
private String usernameField;
private String authorizeField;
public LdapAuthorizationPolicy() {
manager = LdapManager.getInstance();
usernameField = manager.getUsernameField();
authorizeField = JiveGlobals.getXMLProperty("ldap.authorizeField", "k5login");
}
/**
* Returns if the principal is explicity authorized to the JID, throws
* an UnauthorizedException otherwise
*
* @param username The username requested.import org.jivesoftware.openfire.ldap.*;
* @param principal The principal requesting the username.
*/
public boolean authorize(String username, String principal) {
return getAuthorized(username).contains(principal);
}
/**
* Returns a String Collection of principals that are authorized to use
* the named user.
*
* @param username the username.
* @return A String Collection of principals that are authorized.
*/
private Collection<String> getAuthorized(String username) {
// Un-escape Node
username = JID.unescapeNode(username);
Collection<String> authorized = new ArrayList<String>();
DirContext ctx = null;
try {
String userDN = manager.findUserDN(username);
// Load record.
String[] attributes = new String[]{
usernameField,
authorizeField
};
ctx = manager.getContext();
Attributes attrs = ctx.getAttributes(userDN, attributes);
Attribute authorizeField_a = attrs.get(manager.getNameField());
if (authorizeField_a != null) {
for (Enumeration e = authorizeField_a.getAll(); e.hasMoreElements();) {
authorized.add((String)e.nextElement());
}
}
return authorized;
}
catch (Exception e) {
// Ignore.
}
finally {
try {
if (ctx != null) {
ctx.close();
}
}
catch (Exception ignored) {
// Ignore.
}
}
return authorized;
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "LDAP Authorization Policy";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Provider for authorization using LDAP. Checks if the authenticated principal is in the user's LDAP object using the authorizeField property.";
}
}
......@@ -16,6 +16,7 @@ import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException;
......@@ -31,6 +32,7 @@ import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.*;
......@@ -188,11 +190,7 @@ public class SASLAuthentication {
// Store the requested SASL mechanism by the client
session.setSessionData("SaslMechanism", mechanism);
//Log.debug("SASLAuthentication.doHandshake() AUTH entered: "+mechanism);
if (mechanism.equalsIgnoreCase("PLAIN") &&
mechanisms.contains("PLAIN")) {
status = doPlainAuthentication(session, doc);
}
else if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
mechanisms.contains("ANONYMOUS")) {
status = doAnonymousAuthentication(session);
}
......@@ -221,11 +219,17 @@ public class SASLAuthentication {
}
}
byte[] challenge = ss.evaluateResponse(token);
// Send the challenge
sendChallenge(session, challenge);
if (ss.isComplete()) {
authenticationSuccessful(session, ss.getAuthorizationID(),
challenge);
status = Status.authenticated;
}
else {
// Send the challenge
sendChallenge(session, challenge);
status = Status.needResponse;
}
session.setSessionData("SaslServer", ss);
status = Status.needResponse;
}
catch (SaslException e) {
Log.warn("SaslException", e);
......@@ -243,11 +247,7 @@ public class SASLAuthentication {
case RESPONSE:
// Store the requested SASL mechanism by the client
mechanism = (String) session.getSessionData("SaslMechanism");
if (mechanism.equalsIgnoreCase("PLAIN") &&
mechanisms.contains("PLAIN")) {
status = doPlainAuthentication(session, doc);
}
else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
if (mechanism.equalsIgnoreCase("EXTERNAL")) {
status = doExternalAuthentication(session, doc);
}
else if (mechanism.equalsIgnoreCase("JIVE-SHAREDSECRET")) {
......@@ -397,88 +397,88 @@ public class SASLAuthentication {
}
}
private static Status doPlainAuthentication(LocalSession session, Element doc)
private static Status doExternalAuthentication(LocalSession session, Element doc)
throws UnsupportedEncodingException {
String username;
String password;
String response = doc.getTextTrim();
if (response == null || response.length() == 0) {
// No info was provided so send a challenge to get it
sendChallenge(session, new byte[0]);
return Status.needResponse;
}
// At this point the connection has already been secured using TLS
// Parse data and obtain username & password
String data = new String(StringUtils.decodeBase64(response), CHARSET);
StringTokenizer tokens = new StringTokenizer(data, "\0");
if (tokens.countTokens() > 2) {
// Skip the "authorization identity"
tokens.nextToken();
}
username = tokens.nextToken();
if (username != null && username.contains("@")) {
// Check that the specified domain matches the server's domain
int index = username.indexOf("@");
String domain = username.substring(index + 1);
if (domain.equals(XMPPServer.getInstance().getServerInfo().getName())) {
// Domains match. Store in username just the username
username = username.substring(0, index);
if (session instanceof IncomingServerSession) {
String hostname = doc.getTextTrim();
if (hostname == null || hostname.length() == 0) {
// No hostname was provided so send a challenge to get it
sendChallenge(session, new byte[0]);
return Status.needResponse;
}
else {
// Unknown domain. Return authentication failed
authenticationFailed(session);
return Status.failed;
hostname = new String(StringUtils.decodeBase64(hostname), CHARSET);
// Check if cerificate validation is disabled for s2s
// Flag that indicates if certificates of the remote server should be validated.
// Disabling certificate validation is not recommended for production environments.
boolean verify =
JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify", true);
if (!verify) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
}
}
password = tokens.nextToken();
try {
AuthToken token = AuthFactory.authenticate(username, password);
authenticationSuccessful(session, token.getUsername(), null);
return Status.authenticated;
}
catch (UnauthorizedException e) {
authenticationFailed(session);
return Status.failed;
}
}
private static Status doExternalAuthentication(LocalSession session, Element doc)
throws UnsupportedEncodingException {
// Only accept EXTERNAL SASL for s2s. At this point the connection has already
// been secured using TLS
if (!(session instanceof IncomingServerSession)) {
return Status.failed;
}
String hostname = doc.getTextTrim();
if (hostname == null || hostname.length() == 0) {
// No hostname was provided so send a challenge to get it
sendChallenge(session, new byte[0]);
return Status.needResponse;
}
hostname = new String(StringUtils.decodeBase64(hostname), CHARSET);
// Check if cerificate validation is disabled for s2s
// Flag that indicates if certificates of the remote server should be validated.
// Disabling certificate validation is not recommended for production environments.
boolean verify =
JiveGlobals.getBooleanProperty("xmpp.server.certificate.verify", true);
if (!verify) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
}
// Check that hostname matches the one provided in a certificate
SocketConnection connection = (SocketConnection) session.getConnection();
try {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
if (CertificateManager.getPeerIdentities((X509Certificate) certificate)
.contains(hostname)) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
// Check that hostname matches the one provided in a certificate
SocketConnection connection = (SocketConnection) session.getConnection();
try {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
if (CertificateManager.getPeerIdentities((X509Certificate) certificate)
.contains(hostname)) {
authenticationSuccessful(session, hostname, null);
return Status.authenticated;
}
}
}
catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
}
}
catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
else {
// Client EXTERNALL login
Log.debug("SASLAuthentication: EXTERNAL authentication via SSL certs for c2s connection");
// This may be null, we will deal with that later
String username = doc.getTextTrim();
String principal = "";
ArrayList<String> principals = new ArrayList<String>();
SocketConnection connection = (SocketConnection)session.getConnection();
try {
for (Certificate certificate : connection.getSSLSession().getPeerCertificates()) {
principals.addAll(CertificateManager.getPeerIdentities((X509Certificate)certificate));
}
}
catch (SSLPeerUnverifiedException e) {
Log.warn("Error retrieving client certificates of: " + session, e);
}
if (username == null || username.length() == 0) {
// No username was provided, according to XEP-0178 we need to:
// * attempt to get it from the cert first
// * have the server assign one
// There shouldn't be more than a few principals in here. One ideally
// We set principal to the first one in the list to have a sane default
// If this list is empty, then the cert had no identity at all, which
// will cause an authorization failure
principal = principals.get(0);
for(String princ : principals) {
String u = AuthorizationManager.map(princ);
if(!u.equals(princ)) {
username = u;
principal = princ;
break;
}
}
Log.debug("SASLAuthentication: no username requested, using "+username);
}
//Its possible that either/both username and principal are null here
//The providers should not allow a null authorization
if (AuthorizationManager.authorize(username,principal)) {
Log.debug("SASLAuthentication: "+principal+" authorized to "+username);
return Status.authenticated;
}
}
authenticationFailed(session);
return Status.failed;
......@@ -650,6 +650,7 @@ public class SASLAuthentication {
mech.equals("DIGEST-MD5") ||
mech.equals("CRAM-MD5") ||
mech.equals("GSSAPI") ||
mech.equals("EXTERNAL") ||
mech.equals("JIVE-SHAREDSECRET"))
{
Log.debug("SASLAuthentication: Added " + mech + " to mech list");
......@@ -673,5 +674,7 @@ public class SASLAuthentication {
}
}
}
//Add our providers to the Security class
Security.addProvider(new org.jivesoftware.openfire.sasl.SaslProvider());
}
}
\ No newline at end of file
......@@ -12,7 +12,7 @@ package org.jivesoftware.openfire.net;
import org.jivesoftware.util.Log;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.sasl.AuthorizationManager;
import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import javax.security.auth.callback.*;
......@@ -44,20 +44,19 @@ public class XMPPCallbackHandler implements CallbackHandler {
String name = null;
for (int i = 0; i < callbacks.length; i++) {
// Log.info("Callback: " + callbacks[i].getClass().getSimpleName());
if (callbacks[i] instanceof RealmCallback) {
realm = ((RealmCallback) callbacks[i]).getText();
if (realm == null) {
realm = ((RealmCallback) callbacks[i]).getDefaultText();
}
//Log.info("RealmCallback: " + realm);
Log.debug("XMPPCallbackHandler: RealmCallback: "+realm);
}
else if (callbacks[i] instanceof NameCallback) {
name = ((NameCallback) callbacks[i]).getName();
if (name == null) {
name = ((NameCallback) callbacks[i]).getDefaultName();
}
//Log.info("NameCallback: " + name);
Log.debug("XMPPCallbackHandler: NameCallback: "+name);
}
else if (callbacks[i] instanceof PasswordCallback) {
try {
......@@ -66,29 +65,35 @@ public class XMPPCallbackHandler implements CallbackHandler {
((PasswordCallback) callbacks[i])
.setPassword(AuthFactory.getPassword(name).toCharArray());
//Log.info("PasswordCallback: "
//+ new String(((PasswordCallback) callbacks[i]).getPassword()));
Log.debug("XMPPCallbackHandler: PasswordCallback");
}
catch (UserNotFoundException e) {
throw new IOException(e.toString());
}
}
else if (callbacks[i] instanceof AuthorizeCallback) {
Log.debug("XMPPCallbackHandler: AuthorizeCallback");
AuthorizeCallback authCallback = ((AuthorizeCallback) callbacks[i]);
String authenId =
String principal =
authCallback.getAuthenticationID(); // Principal that authenticated
String authorId =
String username =
authCallback.getAuthorizationID(); // Username requested (not full JID)
if (AuthorizationManager.authorize(authorId, authenId)) {
if(principal.equals(username)) {
//client perhaps made no request, get default username
username = AuthorizationManager.map(principal);
Log.debug("XMPPCallbackHandler: no username requested, using "+username);
}
if (AuthorizationManager.authorize(username, principal)) {
Log.debug("XMPPCallbackHandler: "+ principal + " authorized to " + username);
authCallback.setAuthorized(true);
authCallback.setAuthorizedID(authorId);
Log.debug(authenId + " authorized to " + authorId);
authCallback.setAuthorizedID(username);
}
else {
Log.debug(authenId + " not authorized to " + authorId);
Log.debug("XMPPCallbackHandler: "+principal + " not authorized to " + username);
}
}
else {
Log.debug("XMPPCallbackHandler: Callback: " + callbacks[i].getClass().getSimpleName());
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
......
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import java.util.Collection;
/**
* Provider for authorization. Unlike the AbstractAuthorizationPolicy
* class, this is intended for classes that need a more "heavyweight"
* solution, often that requires consulting some storage or external
* entity about each specific case. This class allows individual mappings
* between authenticated principals and usernames, and if the storage
* mechanism allows it, management of those mappings.
*
* Users that wish to integrate with their own authorization
* system must extend this class and implement the
* AuthorizationProvider interface then register the class
* with Openfire in the <tt>openfire.xml</tt> file. An entry
* in that file would look like the following:
*
* <pre>
* &lt;provider&gt;
* &lt;authorizationpolicy&gt;
* &lt;classlist&gt;com.foo.auth.CustomPolicyProvider&lt;/classlist&gt;
* &lt;/authorizationpolicy&gt;
* &lt;/provider&gt;</pre>
*
* @author Jay Kline
*/
public abstract class AbstractAuthorizationProvider implements AuthorizationProvider {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public abstract boolean authorize(String username, String principal);
/**
* Returns a String Collection of principals that are authorized to use
* the named user.
*
* @param username The username.
* @return A String Collection of principals that are authorized.
*/
public abstract Collection<String> getAuthorized(String username);
/**
* Returns true if this AuthorizationProvider supports changing the
* list of authorized principals for users.
*
* @return true if updating the list of authorized principals is
* supported by this AuthorizationProvider.
*/
public abstract boolean isWritable();
/**
* Add a single authorized principal to use the named user.
*
* @param username The username.
* @param principal The principal authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public abstract void addAuthorized(String username, String principal) throws UnsupportedOperationException;
/**
* Add a Collection of users authorized to use the named user.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public abstract void addAuthorized(String username, Collection<String> principals) throws UnsupportedOperationException;
/**
* Set the users authorized to use the named user. All existing principals listed
* will be removed.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public abstract void setAuthorized(String username, Collection<String> principals) throws UnsupportedOperationException;
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public abstract String name();
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public abstract String description();
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.auth.UnauthorizedException;
/**
* Provider interface for authorization policy. Users that wish to integrate with
* their own authorization system must implement this class and then register
* the implementation with Openfire in the <tt>openfire.xml</tt>
* file. An entry in that file would look like the following:
*
* <pre>
* &lt;provider&gt;
* &lt;authorizationpolicy&gt;
* &lt;className&gt;com.foo.auth.CustomPolicyProvider&lt;/className&gt;
* &lt;/authorizationpolicy&gt;
* &lt;/provider&gt;</pre>
*
* @author Jay Kline
*/
public interface AuthorizationPolicyProvider {
/**
* Returns if the principal is explicity authorized to the JID, throws
* an UnauthorizedException otherwise
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @throws UnauthorizedException
*/
public void authorize(String username, String principal) throws UnauthorizedException;
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.openfire.XMPPServer;
/**
* This policy will authorize any principal that matches exactly the full
* JID (REALM and server name must be the same if using GSSAPI) or any
* principal that matches exactly the username (without REALM or server
* name). This does exactly what users expect if not supplying a seperate
* principal for authentication.
*
* @author Jay Kline
*/
public class DefaultAuthorizationPolicy extends AbstractAuthorizationPolicy
implements AuthorizationProvider {
private String serverName;
public DefaultAuthorizationPolicy() {
serverName = XMPPServer.getInstance().getServerInfo().getName();
}
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal) {
return (principal.equals(username) || principal.equals(username + "@" + serverName));
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Default Policy";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "This policy will authorize any principal that matches exactly the full " +
"JID (REALM and server name must be the same if using GSSAPI) or any principal " +
"that matches exactly the username (without REALM or server name). This does " +
"exactly what users expect if not supplying a seperate principal for authentication.";
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.Log;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
/**
* Provider for authorization using the default storage database. Checks
* if the authenticated principal is in the user's list of authorized
* principals.
*
* @author Jay Kline
*/
public class DefaultAuthorizationProvider extends AbstractAuthorizationProvider
implements AuthorizationProvider {
private static final String MATCH_AUTHORIZED =
"SELECT username FROM jiveSASLAuthorized WHERE username=? AND authorized=?";
private static final String GET_AUTHORIZED =
"SELECT authorized FROM jiveSASLAuthorized WHERE username=?";
private static final String INSERT_AUTHORIZED =
"INSERT into jiveSASLAuthorized (username,authorized) VALUES (?,?)";
private static final String DELETE_AUTHORIZED =
"DELETE FROM jiveSASLAuthorized WHERE username=? AND authorized=?";
private static final String DELETE_USER = "DELETE FROM jiveSASLAuthorized WHERE username=?";
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(MATCH_AUTHORIZED);
pstmt.setString(1, username);
pstmt.setString(2, principal);
ResultSet rs = pstmt.executeQuery();
return rs.next();
}
catch (Exception e) {
return false;
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
// not reachable
//return false;
}
/**
* Returns a String Collection of principals that are authorized to use
* the named user.
*
* @param username The username.
* @return A String Collection of principals that are authorized.
*/
public Collection<String> getAuthorized(String username) {
Connection con = null;
PreparedStatement pstmt = null;
Collection<String> authorized = new ArrayList<String>();
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(GET_AUTHORIZED);
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
authorized.add(rs.getString("authorized"));
}
return authorized;
} catch (Exception e) {
return new ArrayList<String>();
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
/**
* Returns true.
*
* @return true
*/
public boolean isWritable() {
return true;
}
/**
* Add a single authorized principal to use the named user.
*
* @param username The username.
* @param principal The principal authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public void addAuthorized(String username, String principal)
throws UnsupportedOperationException {
if (authorize(username, principal)) {
// Already exists
return;
}
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(INSERT_AUTHORIZED);
pstmt.setString(1, username);
pstmt.setString(2, principal);
pstmt.execute();
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
} finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
/**
* Add a Collection of users authorized to use the named user.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public void addAuthorized(String username, Collection<String> principals)
throws UnsupportedOperationException {
for (String principal : principals) {
addAuthorized(username, principal);
}
}
/**
* Set the users authorized to use the named user. All existing principals listed
* will be removed.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public void setAuthorized(String username, Collection<String> principals)
throws UnsupportedOperationException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_USER);
pstmt.setString(1, username);
pstmt.execute();
addAuthorized(username, principals);
}
catch (Exception e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Default Provider";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Provider for authorization using the default storage database.";
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
/**
* This policy will authorize any principal who's username matches exactly
* the username of the JID. This means when cross realm authentication is
* allowed, user@REALM_A.COM and user@REALM_B.COM could both authorize as
* user@servername, so there is some risk here. But if usernames across the
*
* @author Jay Kline
*/
public class LazyAuthorizationPolicy extends AbstractAuthorizationPolicy implements AuthorizationProvider {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal) {
return (principal.startsWith(username+"@"));
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Lazy";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "This policy will authorize any principal who's username matches exactly the username of the JID. This means when cross realm authentication is allowed, user@REALM_A.COM and user@REALM_B.COM could both authorize as user@servername, so there is some risk here. But if usernames across the realms are unique, this can be very helpful.";
}
}
\ No newline at end of file
package org.jivesoftware.openfire.sasl;
import java.security.Provider;
public class SaslProvider extends Provider {
public SaslProvider() {
super("JiveSoftware", 1.0, "JiveSoftware SASL provider v1.0, implementing server mechanisms for: PLAIN");
put("SaslServerFactory.PLAIN", "org.jivesoftware.openfire.sasl.SaslServerFactoryImpl");
}
}
\ No newline at end of file
package org.jivesoftware.openfire.sasl;
import java.util.Map;
import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.SaslServerFactory;
import javax.security.sasl.SaslServer;
import javax.security.sasl.SaslException;
import com.sun.security.sasl.util.PolicyUtils;
public class SaslServerFactoryImpl implements SaslServerFactory {
private static final String myMechs[] = { "PLAIN" };
private static final int mechPolicies[] = { PolicyUtils.NOANONYMOUS };
private static final int PLAIN = 0;
public SaslServerFactoryImpl() {
}
public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh) throws SaslException {
if (mechanism.equals(myMechs[PLAIN]) && PolicyUtils.checkPolicy(mechPolicies[PLAIN], props)) {
if (cbh == null) {
throw new SaslException("CallbackHandler with support for Password, Name, and AuthorizeCallback required");
}
return new SaslServerPlainImpl(protocol, serverName, props, cbh);
}
return null;
}
public String[] getMechanismNames(Map<String, ?> props) {
return PolicyUtils.filterMechs(myMechs, mechPolicies, props);
}
}
\ No newline at end of file
package org.jivesoftware.openfire.sasl;
import java.util.Map;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslServer;
import javax.security.sasl.SaslException;
import javax.security.sasl.AuthorizeCallback;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
public class SaslServerPlainImpl implements SaslServer {
private String principal;
private String username; //requested authorization identity
private String password;
private CallbackHandler cbh;
private boolean completed;
private boolean aborted;
public SaslServerPlainImpl(String protocol, String serverFqdn, Map props, CallbackHandler cbh) throws SaslException {
this.cbh = cbh;
this.completed = false;
}
/**
* Returns the IANA-registered mechanism name of this SASL server.
* ("PLAIN").
* @return A non-null string representing the IANA-registered mechanism name.
*/
public String getMechanismName() {
return "PLAIN";
}
/**
* Evaluates the response data and generates a challenge.
*
* If a response is received from the client during the authentication
* process, this method is called to prepare an appropriate next
* challenge to submit to the client. The challenge is null if the
* authentication has succeeded and no more challenge data is to be sent
* to the client. It is non-null if the authentication must be continued
* by sending a challenge to the client, or if the authentication has
* succeeded but challenge data needs to be processed by the client.
* <tt>isComplete()</tt> should be called
* after each call to <tt>evaluateResponse()</tt>,to determine if any further
* response is needed from the client.
*
* @param response The non-null (but possibly empty) response sent
* by the client.
*
* @return The possibly null challenge to send to the client.
* It is null if the authentication has succeeded and there is
* no more challenge data to be sent to the client.
* @exception SaslException If an error occurred while processing
* the response or generating a challenge.
*/
public byte[] evaluateResponse(byte[] response)
throws SaslException {
if (completed) {
throw new IllegalStateException("PLAIN authentication already completed");
}
if (aborted) {
throw new IllegalStateException("PLAIN authentication previously aborted due to error");
}
try {
if(response.length != 0) {
String data = new String(response, "UTF8");
StringTokenizer tokens = new StringTokenizer(data, "\0");
if (tokens.countTokens() > 2) {
username = tokens.nextToken();
principal = tokens.nextToken();
} else {
username = tokens.nextToken();
principal = username;
}
password = tokens.nextToken();
NameCallback ncb = new NameCallback("PLAIN authentication ID: ",principal);
PasswordCallback pcb = new PasswordCallback("PLAIN password: ",false);
cbh.handle(new Callback[]{ncb,pcb});
char correctPassword[] = pcb.getPassword();
if (correctPassword == null || correctPassword.length == 0) {
aborted = true;
throw new SaslException("PLAIN: username not found: "+principal);
}
pcb.clearPassword();
String s_correctPassword = new String(correctPassword);
if (s_correctPassword.equals(password) ) {
AuthorizeCallback acb = new AuthorizeCallback(principal,username);
cbh.handle(new Callback[]{acb});
username = acb.getAuthorizationID();
completed = true;
} else {
throw new SaslException("PLAIN: user not authorized: "+principal);
}
} else {
//throw new SaslException("PLAIN expects an initial response");
//Client gave no initial response, give a null (possible infinate loop?)
return null;
}
} catch (UnsupportedEncodingException e) {
aborted = true;
throw new SaslException("UTF8 not available on platform", e);
} catch (UnsupportedCallbackException e) {
aborted = true;
throw new SaslException("PLAIN authentication failed", e);
} catch (IOException e) {
aborted = true;
throw new SaslException("PLAIN authentication failed", e);
}
return null;
}
/**
* Determines whether the authentication exchange has completed.
* This method is typically called after each invocation of
* <tt>evaluateResponse()</tt> to determine whether the
* authentication has completed successfully or should be continued.
* @return true if the authentication exchange has completed; false otherwise.
*/
public boolean isComplete() {
return completed;
}
/**
* Reports the authorization ID in effect for the client of this
* session.
* This method can only be called if isComplete() returns true.
* @return The authorization ID of the client.
* @exception IllegalStateException if this authentication session has not completed
*/
public String getAuthorizationID() {
if(completed) {
return username;
} else {
throw new IllegalStateException("PLAIN authentication not completed");
}
}
/**
* Unwraps a byte array received from the client. PLAIN supports no security layer.
*
* @throws SaslException if attempted to use this method.
*/
public byte[] unwrap(byte[] incoming, int offset, int len)
throws SaslException {
if(completed) {
throw new IllegalStateException("PLAIN does not support integrity or privacy");
} else {
throw new IllegalStateException("PLAIN authentication not completed");
}
}
/**
* Wraps a byte array to be sent to the client. PLAIN supports no security layer.
*
* @throws SaslException if attempted to use this method.
*/
public byte[] wrap(byte[] outgoing, int offset, int len)
throws SaslException {
if(completed) {
throw new IllegalStateException("PLAIN does not support integrity or privacy");
} else {
throw new IllegalStateException("PLAIN authentication not completed");
}
}
/**
* Retrieves the negotiated property.
* This method can be called only after the authentication exchange has
* completed (i.e., when <tt>isComplete()</tt> returns true); otherwise, an
* <tt>IllegalStateException</tt> is thrown.
*
* @param propName the property
* @return The value of the negotiated property. If null, the property was
* not negotiated or is not applicable to this mechanism.
* @exception IllegalStateException if this authentication exchange has not completed
*/
public Object getNegotiatedProperty(String propName) {
if (completed) {
if (propName.equals(Sasl.QOP)) {
return "auth";
} else {
return null;
}
} else {
throw new IllegalStateException("PLAIN authentication not completed");
}
}
/**
* Disposes of any system resources or security-sensitive information
* the SaslServer might be using. Invoking this method invalidates
* the SaslServer instance. This method is idempotent.
* @throws SaslException If a problem was encountered while disposing
* the resources.
*/
public void dispose() throws SaslException {
password = null;
username = null;
principal = null;
completed = false;
}
}
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-20 10:46:24 -0500 (Thu, 20 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.util.JiveGlobals;
/**
* This policy will authorize any principal who:
*
* <li> Username of principal matches exactly the username of the JID </li>
* <li> The user principal's realm matches exactly the realm of the server.</li>
* Note that the realm may not match the servername, and in fact for this
* policy to be useful it will not match the servername. RFC3920 Section
* 6.1, item 7 states that if the principal (authorization entity) is the
* same as the JID (initiating entity), its MUST NOT provide an authorization
* identity. In practice however, GSSAPI will provide both. (Note: Ive
* not done extensive testing on this)
*
* @author Jay Kline
*/
public class StrictAuthorizationPolicy extends AbstractAuthorizationPolicy implements AuthorizationProvider {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal) {
return (principal.equals(username+"@"+JiveGlobals.getXMLProperty("sasl.realm")));
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Strict Policy";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "This policy will authorize any principal whos username matches exactly the username of the JID and whos realm matches exactly the realm of the server.";
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: $
* $Date: 2006-04-07 09:28:54 -0500 (Fri, 07 Apr 2006) $
*
* Copyright (C) 2004 Jive Software. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package org.jivesoftware.openfire.sasl;
import org.jivesoftware.util.JiveGlobals;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
/**
* Provider for authorization. Checks if the authenticated principal is in
* the user's .k5login file. A traditional Unix Kerberos methodology. The
* location of this file can be configured in the <tt>openfire.xml</tt>
* file. An entry in that file would look like the following:
*
* <pre>
* &lt;unix&gt;
* &lt;k5login&gt; /home/{0}/.k5login &lt;/k5login&gt;
* &lt;/unix&gt;</pre>
*
* The string <tt>{0}</tt> will be replaced with the username.
*
* @author Jay Kline
*/
public class UnixK5LoginProvider extends AbstractAuthorizationProvider implements AuthorizationProvider {
/**
* Returns true if the principal is explicity authorized to the JID
*
* @param username The username requested.
* @param principal The principal requesting the username.
* @return true is the user is authorized to be principal
*/
public boolean authorize(String username, String principal) {
return getAuthorized(username).contains(principal);
}
/**
* Returns a String Collection of principals that are authorized to use
* the named user.
*
* @param username The username.
* @return A String Collection of principals that are authorized.
*/
public Collection<String> getAuthorized(String username) {
Collection<String> authorized = new ArrayList<String>();
try {
String filename = JiveGlobals.getXMLProperty("unix.k5login","/home/{0}/.k5login");
filename = filename.replace("{0}",username);
File k5login = new File(filename);
FileInputStream fis = new FileInputStream(k5login);
DataInputStream dis = new DataInputStream(fis);
String line;
while ( (line = dis.readLine() ) != null) {
authorized.add(line);
}
} catch (IOException e) {
//??
}
return authorized;
}
/**
* Returns false, this implementation is not writeable.
*
* @return False.
*/
public boolean isWritable() {
return false;
}
/**
* Always throws UnsupportedOperationException.
*
* @param username The username.
* @param principal The principal authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public void addAuthorized(String username, String principal) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Always throws UnsupportedOperationException.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
*/
public void addAuthorized(String username, Collection<String> principals) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Always throws UnsupportedOperationException.
*
* @param username The username.
* @param principals The Collection of principals authorized to use the named user.
* @throws UnsupportedOperationException If this AuthorizationProvider cannot be updated.
*/
public void setAuthorized(String username, Collection<String> principals) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* Returns the short name of the Policy
*
* @return The short name of the Policy
*/
public String name() {
return "Unix .k5login";
}
/**
* Returns a description of the Policy
*
* @return The description of the Policy.
*/
public String description() {
return "Checks if the authenticated principal is in the user's .k5login file. A traditional Unix Kerberos methodology.";
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment