/*
* $Header: /home/cvs/jakarta-slide/src/jaas/org/apache/slide/jaas/spi/SlideLoginModule.java,v 1.1.2.1 2004/10/06 16:12:40 ozeigermann Exp $
* $Revision: 1.1.2.1 $
* $Date: 2004/10/06 16:12:40 $
*
* ====================================================================
*
* Copyright 1999-2002 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.slide.jaas.spi;
import java.io.IOException;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.apache.slide.authenticate.CredentialsToken;
import org.apache.slide.authenticate.SecurityToken;
import org.apache.slide.common.Domain;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.SlideException;
import org.apache.slide.common.SlideToken;
import org.apache.slide.common.SlideTokenImpl;
import org.apache.slide.content.Content;
import org.apache.slide.content.NodeProperty;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.NodeRevisionDescriptors;
import org.apache.slide.security.ACLSecurityImpl;
import org.apache.slide.security.Security;
import org.apache.slide.structure.ObjectNotFoundException;
import org.apache.slide.structure.SubjectNode;
/**
* JAAS LoginModule for authenticating against users in a Slide namespace.
*
* <p>
* The implementation assumes the ACLSecurityImpl is used.
* </p>
*
* Options include (to be specified in JAAS login configuration file):
* <ul>
* <li><code>namespace</code>: the namespace to load users from.
* Defaults to the default namespace from the Slide domain configuration.
* </li>
* </ul>
*/
public class SlideLoginModule implements LoginModule {
// Slide helpers
protected Content m_content;
protected Security m_security;
// Slide configuration
protected String m_usersPath;
protected String m_rolesPath;
// authentication state information
protected boolean m_authenticated = false;
protected Subject m_subject;
protected Principal m_principal;
protected Principal[] m_roles;
protected Group m_group;
protected boolean m_committed = false;
protected CallbackHandler m_callbackHandler;
protected Map m_sharedState;
public SlideLoginModule() {
}
/**
* Initialize this <code>LoginModule</code> with the specified
* configuration information.
*
* @param subject The <code>Subject</code> to be authenticated
* @param callbackHandler A <code>CallbackHandler</code> for communicating
* with the end user as necessary
* @param sharedState State information shared with other
* <code>LoginModule</code> instances
* @param options Configuration information for this specific
* <code>LoginModule</code> instance
*/
public void initialize(
Subject subject,
CallbackHandler callbackHandler,
Map sharedState,
Map options) {
m_subject = subject;
m_callbackHandler = callbackHandler;
m_sharedState = sharedState;
// namespace option
String namespace = (String) options.get("namespace");
if (namespace == null) {
namespace = Domain.getDefaultNamespace();
}
// access the namespace
NamespaceAccessToken nat = Domain.accessNamespace(new SecurityToken(this),namespace);
m_content = nat.getContentHelper();
m_security = nat.getSecurityHelper();
m_usersPath = nat.getNamespaceConfig().getUsersPath();
m_rolesPath = nat.getNamespaceConfig().getRolesPath();
}
/**
* Phase 1 of authenticating a <code>Subject</code>.
*
* @return <code>true</code> if the authentication succeeded, or
* <code>false</code> if this <code>LoginModule</code> should be ignored
*
* @exception LoginException if the authentication fails
*/
public boolean login() throws LoginException {
if (m_callbackHandler == null) {
// we need a CallbackHandler to do our work
throw new LoginException ("No callback handler");
}
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("Username: ");
callbacks[1] = new PasswordCallback("Password: ",false);
try {
// prompt for username and password
m_callbackHandler.handle(callbacks);
String username = ((NameCallback) callbacks[0]).getName();
char[] password = ((PasswordCallback) callbacks[1]).getPassword();
if (username == null) {
throw new LoginException("No user name entered");
}
if (password == null) {
throw new LoginException("No password entered");
}
// share username and password with other LoginModules
m_sharedState.put("javax.security.auth.login.name",username);
m_sharedState.put("javax.security.auth.login.password",password);
// use the user's own credentials to retrieve its info
SlideToken slideToken = new SlideTokenImpl(new CredentialsToken(username));
// retrieve user object
SubjectNode userNode;
try {
userNode = (SubjectNode) m_security.getPrincipal(slideToken);
}
catch (ObjectNotFoundException e) {
final String msg = "No such user";
if (Domain.isDebugEnabled()) {
Domain.debug("[SlideLoginModule] - " + msg);
}
throw new LoginException(msg);
}
catch (SlideException e) {
final String msg = "Failure loading user object";
Domain.error("[SlideLoginModule] - " + msg,e);
throw new LoginException(msg);
}
m_principal = new SlidePrincipal(username);
m_group = new SlideGroup();
// get the user's password property
NodeRevisionDescriptors revisions = m_content.retrieve(slideToken, m_usersPath + "/" + username);
NodeRevisionDescriptor revision = m_content.retrieve(slideToken, revisions);
NodeProperty property = revision.getProperty("password", NodeProperty.SLIDE_NAMESPACE);
if (property != null) {
m_authenticated = new String(password).equals(property.getValue());
if (!m_authenticated) {
final String msg = "Authentication failed";
if (Domain.isDebugEnabled()) {
Domain.debug("[SlideLoginModule] - " + msg + " for user " + username
+ ": wrong password.");
}
throw new LoginException(msg);
}
else if (Domain.isDebugEnabled()) {
Domain.debug("[SlideLoginModule] - user " + username
+ " successfully authenticated");
}
// find the roles the user is a member of
ArrayList list = new ArrayList();
Enumeration roles = ((ACLSecurityImpl) m_security).
getGroupMembership(slideToken, userNode);
while (roles.hasMoreElements()) {
String role = ((String) roles.nextElement()).substring(m_rolesPath.length()+1);
if (Domain.isDebugEnabled()) {
Domain.debug("[SlideLoginModule] - adding role " + role
+ " for user " + username);
}
SlideRole slideRole = new SlideRole(role);
// apparently Jetty and Tomcat expect the roles to be
// at different locations:
// the Group is to satisfy Jetty, the list is to satisfy Tomcat
m_group.addMember(slideRole);
list.add(slideRole);
}
m_roles = (Principal[]) list.toArray(new Principal[list.size()]);
return true;
}
else {
final String msg = "User " + username + " doesn't have his password " +
"property set: can't authenticate.";
Domain.warn("[SlideLoginModule] - " + msg);
throw new LoginException(msg);
}
}
catch (IOException e) {
final String msg = "Failure during login()";
Domain.error("[SlideLoginModule] - " + msg,e);
throw new LoginException(msg);
}
catch (UnsupportedCallbackException e) {
final String msg = "Failure during login()";
Domain.error("[SlideLoginModule] - " + msg,e);
throw new LoginException(msg);
}
catch (SlideException e) {
final String msg = "Failure during login()";
Domain.error("[SlideLoginModule] - " + msg,e);
throw new LoginException(msg);
}
}
/**
* Phase 2 of authenticating a <code>Subject</code> when Phase 1
* was successful. This method is called if the <code>LoginContext</code>
* succeeded in the overall authentication chain.
*
* @return <code>true</code> if the authentication succeeded, or
* <code>false</code> if this <code>LoginModule</code> should be ignored
*
* @exception LoginException if the commit fails
*/
public boolean commit() throws LoginException {
if (m_authenticated) {
m_subject.getPrincipals().add(m_principal);
m_subject.getPrincipals().add(m_group);
for (int i = 0; i < m_roles.length; i++) {
m_subject.getPrincipals().add(m_roles[i]);
}
}
m_committed = true;
return m_authenticated;
}
/**
* Phase 2 of authenticating a <code>Subject</code> when Phase 1
* fails. This method is called if the <code>LoginContext</code>
* failed somewhere in the overall authentication chain.
*
* @return <code>true</code> if this method succeeded, or
* <code>false</code> if this <code>LoginModule</code> should be ignored
*
* @exception LoginException if the abort fails
*/
public boolean abort() throws LoginException {
m_principal = null;
m_group = null;
m_roles = null;
return m_authenticated;
}
/**
* Log out this user.
*
* @return <code>true</code> in all cases because this
* <code>LoginModule</code> should not be ignored.
*
* @exception LoginException if logging out failed
*/
public boolean logout() throws LoginException {
m_subject.getPrincipals().remove(m_principal);
m_subject.getPrincipals().remove(m_group);
for (int i = 0; i < m_roles.length; i++) {
m_subject.getPrincipals().remove(m_roles[i]);
}
m_committed = false;
m_principal = null;
m_group = null;
m_roles = null;
return true;
}
}