/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.webgate.api.auth;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import de.innovationgate.utils.WGUtils;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.WGFactory;
/**
* This authentication module takes an XML file as base where users and groups are defined:
*
* <!-- This is a demostration file for a definition xml for users and groups. The attribute "allowanonymous" controls, if anonymous access to databases will be allowed -->
* <users allowanonymous="true">
* <!--
* The user definition:
* name: Full distinguished and unique name of this user
* password: Her password to sign on
* mail: Mail address used for workflow notifications
* aliases: name aliases divided by comma. She can also logon using these. You should check for uniqueness of them too
* -->
* <user name="John Doe" password="mypass" mail="john.doe@mycompany.com" aliases="John,jd"/>
* <user name="Jane Doe" password="anotherpass" mail="jane.doe@mycompany.com" aliases="Jane,jad"/>
*
* <!--
* The group definition:
* name: Full distinguished (and only) name of this group.
* members: Name of member users. Name aliases will do but you should use the fully distinguished name
* for lookup speed and readability. You cannot use names of other groups in here.
* -->
* <group name="admins" members="John Doe,Jane Doe"/>
* </users>
*/
public class FileAuthenticationModule implements AuthenticationModule, PasswordCachingAuthenticationModule {
public static final String AUTHMODULE_KEY = "file";
public static final Logger LOG = Logger.getLogger("wga.api.auth.file");
/**
* Sysproperty to specify a base path for XML config files.
*/
public static final String SYSPROPERTY_AUTH_FOLDER = "de.innovationgate.wga.auth.folder";
private boolean _allowAnonymous;
private long _lastModified;
private Map _users = new HashMap();
private File _file = null;
private String _fileName;
private List _authenticationSourceListeners = new ArrayList();
/**
* Creation option to specify the XML config file location.
* If sysproperty de.innovationgate.wga.auth.folder is set, this is evaluated relative to it.
*/
public static final String AUTH_FILE = "auth.file";
class User {
private String _mail;
private String _pwd;
private Set _aliases;
private Set _groups = new HashSet();
private String _name;
public User(String name, String pwd, String mail, Set aliases) {
_name = name;
_pwd = pwd;
_mail = mail;
_aliases = aliases;
}
/**
* @return
*/
public Set getAliases() {
return _aliases;
}
/**
* @return
*/
public String getName() {
return _name;
}
public void addGroup(String name) {
_groups.add(name);
}
/**
* @return
*/
public String getPwd() {
return _pwd;
}
/**
* @return
*/
public Set getGroups() {
return _groups;
}
/**
* @return
*/
public String getMail() {
return _mail;
}
}
class Group {
private String _name;
private List _members;
public Group(String name, List members) {
_name = name;
_members = members;
}
/**
* @return
*/
public List getMembers() {
return _members;
}
/**
* @return
*/
public String getName() {
return _name;
}
}
class Session implements AuthenticationSession {
private Set _names;
private boolean _valid = true;
private User _user;
public Session(User user) {
_user = user;
_names = _user.getAliases();
_names.add(_user.getName());
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#getDistinguishedName()
*/
public String getDistinguishedName() {
return _user.getName();
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#getMailAddress()
*/
public String getMailAddress() {
return _user.getMail();
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#getNames()
*/
public Set getNames() {
return _names;
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#getGroups()
*/
public Set getGroups() {
return _user.getGroups();
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#logout()
*/
public void logout() {
_valid = false;
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationSession#isValid()
*/
public boolean isValid() {
return _valid;
}
public String getSessionToken() {
return null;
}
}
class AnonymousSession extends Session {
public AnonymousSession() {
super(new User(WGDatabase.ANONYMOUS_USER, null, null, new HashSet()));
}
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#init(java.util.Map)
*/
public void init(Map params, WGDatabase db) throws ConfigurationException {
_fileName = (String) params.get(AUTH_FILE);
if (_fileName == null) {
throw new ConfigurationException("Authentication file not specified");
}
String authFolder = System.getProperty(SYSPROPERTY_AUTH_FOLDER, null);
if (authFolder != null) {
_file = new File(new File(authFolder), _fileName);
}
if (_file == null || !_file.exists()) {
_file = new File(_fileName);
}
if (!_file.exists()) {
throw new ConfigurationException("XML authentication file does not exist: " + _fileName);
}
update(_file);
}
/**
* @param _file
*/
private synchronized void update(File _file) {
try {
// Parse doc
SAXReader reader = new SAXReader();
reader.setIncludeExternalDTDDeclarations(false);
reader.setIncludeInternalDTDDeclarations(false);
reader.setValidation(false);
Document doc = reader.read(_file);
// Flags
_allowAnonymous = Boolean.valueOf(doc.getRootElement().attributeValue("allowanonymous")).booleanValue();
// Read users
Map users = new HashMap();
Iterator userNodes = doc.selectNodes("/users/user").iterator();
Element userNode;
while (userNodes.hasNext()) {
userNode = (Element) userNodes.next();
String name = userNode.attributeValue("name");
String password = userNode.attributeValue("password");
String mail = userNode.attributeValue("mail");
String aliasesStr = userNode.attributeValue("aliases");
Set aliases = new HashSet();
if (aliasesStr != null) {
aliases.addAll(WGUtils.deserializeCollection(aliasesStr, ",", true));
}
users.put(name, new User(name, password, mail, aliases));
}
_users = users;
// Read Groups
Map groups = new HashMap();
Iterator groupNodes = doc.selectNodes("/users/group").iterator();
Element groupNode;
while (groupNodes.hasNext()) {
groupNode = (Element) groupNodes.next();
String name = groupNode.attributeValue("name");
List members = WGUtils.deserializeCollection(groupNode.attributeValue("members"), ",", true);
Group group = new Group(name, members);
groups.put(name, group);
notifyMembers(group);
}
// Call listeners
synchronized (_authenticationSourceListeners) {
Iterator listeners = _authenticationSourceListeners.iterator();
while (listeners.hasNext()) {
((AuthenticationSourceListener) listeners.next()).authenticationDataChanged();
}
}
}
catch (DocumentException e) {
WGFactory.getLogger().error("Error parsing authentication file '" + getAuthenticationSource() + "': Error in XML: " + e.getMessage());
}
}
/**
* @param group
*/
private void notifyMembers(Group group) {
Iterator members = group.getMembers().iterator();
String memberName;
User user;
while (members.hasNext()) {
memberName = (String) members.next();
user = findUser(memberName);
if (user != null) {
user.addGroup(group.getName());
}
else {
WGFactory.getLogger().error("Error parsing authentication file '" + getAuthenticationSource() + "': User '" + memberName + "' not defined");
}
}
}
/**
* @param memberName
* @return
*/
private User findUser(String memberName) {
// First try to find by name - fastest
User user = (User) _users.get(memberName);
if (user != null) {
return user;
}
// Then try to find by alias - slow
Iterator users = _users.values().iterator();
while (users.hasNext()) {
user = (User) users.next();
if (user.getAliases().contains(memberName)) {
return user;
}
}
return null;
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#login(java.lang.String, java.lang.String)
*/
public AuthenticationSession login(String user, Object credentials) throws AuthenticationException {
String password = (String) credentials;
if (user.equals(WGDatabase.ANONYMOUS_USER)) {
if (_allowAnonymous) {
return new AnonymousSession();
}
else {
LOG.warn("Failed login from anonymous user: Anonymous login not allowed (" + getAuthenticationSource() + ")");
return null;
}
}
if (_file.lastModified() > _lastModified) {
synchronized (this) {
_lastModified = _file.lastModified();
update(_file);
}
}
User userObj = findUser(user);
if (userObj == null) {
LOG.warn("Failed login for '" + user + "': Unknown user (" + getAuthenticationSource() + ")");
return null;
}
if (!userObj.getPwd().equals(password)) {
LOG.warn("Failed login for '" + user + "': Wrong password (" + getAuthenticationSource() + ")");
return null;
}
return new Session(userObj);
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#clearCache()
*/
public void clearCache() {
update(_file);
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#getAuthenticationSource()
*/
public String getAuthenticationSource() {
return "File-Authentication - " + _file.getAbsolutePath();
}
/* (Kein Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#getEMailAddress(java.lang.String)
*/
public String getEMailAddress(String user) {
User userObj = findUser(user);
if (userObj != null) {
return userObj.getMail();
}
else {
return null;
}
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#isPoolable()
*/
public boolean isPoolable() {
return true;
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#addAuthenticationSourceListener(de.innovationgate.webgate.api.auth.AuthenticationSourceListener)
*/
public void addAuthenticationSourceListener(AuthenticationSourceListener listener) {
synchronized (_authenticationSourceListeners) {
_authenticationSourceListeners.add(listener);
}
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#removeAuthenticationSourceListener(de.innovationgate.webgate.api.auth.AuthenticationSourceListener)
*/
public void removeAuthenticationSourceListener(AuthenticationSourceListener listener) {
synchronized (_authenticationSourceListeners) {
_authenticationSourceListeners.remove(listener);
}
}
/* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#getAllowedCredentialClasses()
*/
public Class[] getAllowedCredentialClasses() {
return new Class[] { String.class };
}
/*
* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#isQueryable(java.lang.String)
*/
public boolean isQueryable(String queryType) {
return false;
}
/*
* (non-Javadoc)
* @see de.innovationgate.webgate.api.auth.AuthenticationModule#query(java.lang.Object, java.lang.String)
*/
public Object query(Object query, String queryType) {
return null;
}
public void destroy() {
}
public boolean isGeneratesSessionToken() {
return false;
}
public void dropPasswordCache(String loginName) {
clearCache();
}
}