/*
* AuthService.java
*
* Created on July 1, 2007, 1:22 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org.atomojo.app.auth;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.UUID;
import java.util.logging.Logger;
import org.atomojo.app.admin.AdminXML;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.infoset.xml.util.WriterItemDestination;
/**
*
* @author alex
*/
public class FileAuthService implements AuthService
{
static Logger LOG = Logger.getLogger(FileAuthService.class.getName());
long lastModified;
File authFile;
Map<String,String> groups;
Map<String,User> users;
Map<String,String> passwords;
DocumentLoader loader;
public FileAuthService() {
}
public void init(Properties props)
throws AuthException
{
loader = new SAXDocumentLoader();
groups = new TreeMap<String,String>();
users = new TreeMap<String,User>();
passwords = new TreeMap<String,String>();
URI baseURI = URI.create(props.getProperty("base-uri"));
String href = props.getProperty("href");
if (href==null) {
throw new AuthException("Missing 'href' property.");
}
URI fileURI = baseURI.resolve(href);
if (!fileURI.getScheme().equals("file")) {
throw new AuthException("Unsupported scheme "+fileURI.getScheme());
}
authFile = new File(fileURI.getSchemeSpecificPart());
if (!authFile.exists() && "true".equals(props.getProperty("create"))) {
LOG.info("Creating auth file "+authFile);
try {
create();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file creation for "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("XML error on auth file creation for "+authFile,ex);
}
}
if (!authFile.exists()) {
throw new AuthException("Auth file "+authFile+" does not exist.");
}
if (!authFile.canRead()) {
throw new AuthException("Cannot read auth file "+authFile+" does not exist.");
}
try {
load();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
}
private void create()
throws AuthException,IOException,XMLException
{
createGroup(null,AuthService.ADMIN_GROUP);
createUser(null,AuthService.ADMIN_USER,AuthService.ADMIN_USER,null,AuthService.ADMIN_USER);
addUserToGroup(null,AuthService.ADMIN_USER,AuthService.ADMIN_GROUP);
store();
}
private void load()
throws IOException,XMLException
{
LOG.info("Loading auth from "+authFile);
lastModified = authFile.lastModified();
FileInputStream is = new FileInputStream(authFile);
Reader reader = new InputStreamReader(is,"UTF-8");
loader.generate(reader,authFile.toURI(),new ItemDestination() {
public void send(Item item)
throws XMLException
{
if (item.getType()==Item.ItemType.ElementItem) {
Element e = (Element)item;
if (e.getName().equals(AdminXML.NM_USER)) {
String alias = e.getAttributeValue("alias");
if (alias==null) {
LOG.warning("Missing 'alias' attribute on user.");
return;
}
String uuid = e.getAttributeValue("id");
if (uuid==null) {
LOG.warning("Missing 'id' attribute on user.");
return;
}
String password = e.getAttributeValue("password");
if (password==null) {
password = e.getAttributeValue("md5-password");
} else {
try {
password = User.md5Password(password);
} catch (NoSuchAlgorithmException ex) {
throw new XMLException("Cannot MD5 password.",ex);
}
}
if (password==null) {
LOG.warning("Missing 'md5-password' or 'password' attribute on user.");
return;
}
String name = e.getAttributeValue("name");
if (name==null) {
name = alias;
}
String email = e.getAttributeValue("email");
LOG.fine(alias+","+uuid+","+name+","+email);
String groupsSpec = e.getAttributeValue("groups");
List<String> groupList = new ArrayList<String>();
if (groupsSpec!=null) {
String [] groupStrings = groupsSpec.split(",");
for (int i=0; i<groupStrings.length; i++) {
String group = groupStrings[i].trim();
if (group.length()!=0 && groups.get(group)!=null) {
LOG.fine(alias+": "+group);
groupList.add(group);
}
}
}
User u = new User(alias,UUID.fromString(uuid),name,email,groupList);
users.put(alias,u);
passwords.put(alias,password);
} else if (e.getName().equals(AdminXML.NM_GROUP)) {
String name = e.getAttributeValue("name");
if (name!=null) {
groups.put(name,name);
}
}
}
}
});
reader.close();
}
private void store()
throws IOException,XMLException
{
File backupFile = new File(authFile.getAbsoluteFile().getParentFile(),authFile.getName()+".save");
if (authFile.exists()) {
if (!authFile.renameTo(backupFile)) {
throw new IOException("Cannot backup file to "+backupFile);
}
}
OutputStream os = new FileOutputStream(authFile);
Writer w = new OutputStreamWriter(os,"UTF-8");
generate(new WriterItemDestination(w,"UTF-8"));
w.flush();
w.close();
lastModified = authFile.lastModified();
if (backupFile.exists()) {
backupFile.delete();
}
}
private void generate(ItemDestination dest)
throws XMLException
{
ItemConstructor constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
dest.send(constructor.createDocument());
dest.send(constructor.createElement(AdminXML.NM_USERS));
dest.send(constructor.createCharacters("\n"));
for (String group : groups.values()) {
Element groupE = constructor.createElement(AdminXML.NM_GROUP);
groupE.setAttributeValue("name",group);
dest.send(groupE);
dest.send(constructor.createElementEnd(AdminXML.NM_GROUP));
dest.send(constructor.createCharacters("\n"));
}
for (User user : users.values()) {
Element userE = constructor.createElement(AdminXML.NM_USER);
userE.setAttributeValue("alias",user.getAlias());
userE.setAttributeValue("id",user.getId().toString());
userE.setAttributeValue("md5-password",passwords.get(user.getAlias()));
userE.setAttributeValue("name",user.getName());
if (user.getEmail()!=null) {
userE.setAttributeValue("email",user.getName());
}
String groups = "";
for (String group : user.getGroups()) {
if (groups.length()>0) {
groups += ",";
groups += group;
} else {
groups = group;
}
}
userE.setAttributeValue("groups",groups);
dest.send(userE);
dest.send(constructor.createElementEnd(AdminXML.NM_USER));
dest.send(constructor.createCharacters("\n"));
}
dest.send(constructor.createElementEnd(AdminXML.NM_USERS));
dest.send(constructor.createDocumentEnd());
}
private void check()
throws IOException,XMLException
{
if (authFile.lastModified()>lastModified) {
load();
}
}
public boolean createGroup(AuthCredentials cred,String name)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
if (groups.get(name)!=null) {
throw new AuthException("Group "+name+" already exists.");
}
synchronized (groups) {
groups.put(name,name);
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return true;
}
public boolean deleteGroup(AuthCredentials cred,String name)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
synchronized (groups) {
boolean retval = groups.remove(name)!=null;
if (retval) {
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return retval;
}
}
public boolean addUserToGroup(AuthCredentials cred,String alias,String name)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
User user = users.get(alias);
if (user==null) {
throw new AuthException("User "+alias+" does not exist.");
}
List<String> newGroups = new ArrayList<String>();
newGroups.addAll(user.getGroups());
newGroups.add(name);
User newUser = new User(user.getAlias(),user.getId(),user.getAlias(),user.getEmail(),newGroups);
synchronized (users) {
users.put(alias,newUser);
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return true;
}
public boolean removeUserFromGroup(AuthCredentials cred,String alias,String name)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
User user = users.get(alias);
if (user==null) {
return false;
}
List<String> newGroups = new ArrayList<String>();
newGroups.addAll(user.getGroups());
newGroups.remove(name);
User newUser = new User(user.getAlias(),user.getId(),user.getName(),user.getEmail(),newGroups);
synchronized (users) {
users.put(alias,newUser);
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return true;
}
public User createUser(AuthCredentials cred,String alias,String name,String email,String password)
throws AuthException
{
UUID uid = UUID.randomUUID();
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
User user = users.get(alias);
if (user!=null) {
throw new AuthException("User "+alias+" does not exist.");
}
User newUser = new User(alias,uid,name,email,new ArrayList<String>());
synchronized (users) {
users.put(alias,newUser);
try {
passwords.put(alias,User.md5Password(password));
} catch (NoSuchAlgorithmException ex) {
throw new AuthException("Cannot MD5 password.",ex);
}
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return newUser;
}
public boolean deleteUser(AuthCredentials cred,String alias)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
synchronized (users) {
boolean retval = users.remove(alias)!=null;
if (retval) {
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return retval;
}
}
public User getUser(AuthCredentials cred,String alias)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
return users.get(alias);
}
public Iterator<User> getUsers(AuthCredentials cred)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
return users.values().iterator();
}
public boolean updateUser(AuthCredentials cred,String alias,String name,String email)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
User user = users.get(alias);
if (user==null) {
throw new AuthException("User "+alias+" does not exist.");
}
User newUser = new User(user.getAlias(),user.getId(),name,email,user.getGroups());
synchronized (users) {
users.put(alias,newUser);
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
}
return true;
}
public boolean setPassword(AuthCredentials cred,String alias,String password)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
synchronized (users) {
try {
passwords.put(alias,User.md5Password(password));
try {
store();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot store auth file "+authFile,ex);
}
} catch (NoSuchAlgorithmException ex) {
throw new AuthException("Unable to MD5 password.",ex);
}
}
return true;
}
public User authenticate(String alias,String password)
throws AuthException
{
try {
check();
} catch (IOException ex) {
throw new AuthException("I/O error on auth file "+authFile,ex);
} catch (XMLException ex) {
throw new AuthException("Cannot parse auth file "+authFile,ex);
}
try {
String md5 = User.md5Password(password);
String checkMd5 = passwords.get(alias);
if (md5.equals(checkMd5)) {
return users.get(alias);
} else {
return null;
}
} catch (NoSuchAlgorithmException ex) {
throw new AuthException("Unable to MD5 password.",ex);
}
}
public User verifySession(String session)
throws AuthException
{
return null;
}
}