/*
* 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.IOException;
import java.net.URI;
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.client.XMLRepresentationParser;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ChallengeScheme;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.representation.StringRepresentation;
/**
*
* @author alex
*/
public class AuthProtocolService implements AuthService
{
static UUID ROOT_ROLE = UUID.fromString("1434b5c6-5e52-4096-9312-f1060b1a38b5");
static UUID REALM_ROOT_ROLE = UUID.fromString("497d7f39-f9d2-468b-91c8-34ceca927f78");
static URI NAMESPACE = URI.create("http://www.atomojo.org/Vocabulary/Auth/2007/1/0");
static Name SESSION = Name.create(NAMESPACE,"session");
static Name USER = Name.create(NAMESPACE,"user");
static Name NAME = Name.create(NAMESPACE,"name");
static Name EMAIL = Name.create(NAMESPACE,"email");
static Name GROUP = Name.create(NAMESPACE,"group");
static Name ROLE = Name.create(NAMESPACE,"role");
static Logger LOG = Logger.getLogger(AuthProtocolService.class.getName());
URI serviceBase;
String authURL;
String sessionURL;
Client client;
Cache<String,User> userCache;
Cache<String,User> sessionCache;
Map<String,String> passwords;
XMLRepresentationParser parser;
public AuthProtocolService() {
}
public URI getServiceURI() {
return serviceBase;
}
public void init(Properties props)
throws AuthException
{
String sbaseURI = props.getProperty("base-uri");
URI baseURI = sbaseURI==null ? null : URI.create(sbaseURI);
String href = props.getProperty("href");
if (href==null) {
throw new AuthException("Missing 'href' property.");
}
serviceBase = baseURI==null ? URI.create(href) : baseURI.resolve(href);
authURL = serviceBase.resolve("./auth?session=false").toString();
sessionURL = serviceBase.resolve("./auth/").toString();
Protocol protocol = Protocol.valueOf(serviceBase.getScheme());
client = new Client(new Context(LOG),protocol);
client.getContext().getAttributes().put("hostnameVerifier", org.apache.commons.ssl.HostnameVerifier.DEFAULT);
parser = new XMLRepresentationParser();
passwords = new TreeMap<String,String>();
userCache = new Cache<String,User>(100,2*60*1000) {
public User fetch(AuthCredentials cred,String alias)
throws AuthException
{
if (cred==null) {
return null;
}
return fetchUser(cred,alias);
}
public User remove(String alias) {
passwords.remove(alias);
return super.remove(alias);
}
public String getFacet(User instance) {
return instance.getAlias();
}
};
sessionCache = new Cache<String,User>(100,2*60*1000) {
public User fetch(AuthCredentials cred,String alias)
throws AuthException
{
if (cred==null) {
return null;
}
return fetchUser(cred,alias);
}
public User remove(String alias) {
passwords.remove(alias);
return super.remove(alias);
}
public String getFacet(User instance) {
return instance.getAlias();
}
};
}
public boolean createGroup(AuthCredentials cred,String name)
throws AuthException
{
String groupsURL = serviceBase.resolve("./groups").toString();
Request request = new Request(Method.POST,groupsURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
request.setEntity(new StringRepresentation("<group xmlns='"+NAMESPACE+"' alias='"+name+"'/>",MediaType.APPLICATION_XML));
Response response = client.handle(request);
return response.getStatus().isSuccess();
}
public boolean deleteGroup(AuthCredentials cred,String name)
throws AuthException
{
String groupURL = serviceBase.resolve("./groups/a/"+name).toString();
Request request = new Request(Method.DELETE,groupURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
return response.getStatus().isSuccess();
}
public boolean addUserToGroup(AuthCredentials cred,String alias,String name)
throws AuthException
{
String groupURL = serviceBase.resolve("./groups/a/"+name+"/users").toString();
Request request = new Request(Method.POST,groupURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
request.setEntity(new StringRepresentation("<user xmlns='"+NAMESPACE+"' alias='"+alias+"'/>",MediaType.APPLICATION_XML));
Response response = client.handle(request);
boolean success = response.getStatus().isSuccess();
if (!success && response.getStatus().getCode()!=404) {
throw new AuthException("Failed to add "+alias+" to group "+name+", status="+response.getStatus().getCode());
}
return success;
}
public boolean removeUserFromGroup(AuthCredentials cred,String alias,String name)
throws AuthException
{
String groupURL = serviceBase.resolve("./groups/a/"+name+"/users").toString();
Request request = new Request(Method.DELETE,groupURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
boolean success = response.getStatus().isSuccess();
if (!success && response.getStatus().getCode()!=404) {
throw new AuthException("Failed to remove "+alias+" from group "+name+", status="+response.getStatus().getCode());
}
return success;
}
public User createUser(AuthCredentials cred,String alias,String name,String email,String password)
throws AuthException
{
String usersURL = serviceBase.resolve("./users").toString();
Request request = new Request(Method.POST,usersURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
String xml = "<user xmlns='"+NAMESPACE+"' alias='"+alias+"' password='"+password+"'>" +
(name==null ? "" : "<name>"+name+"</name>") +
(email==null ? "" : "<email>"+name+"</email>") +
"</user>";
request.setEntity(new StringRepresentation(xml,MediaType.APPLICATION_XML));
Response response = client.handle(request);
if (!response.getStatus().isSuccess()) {
throw new AuthException("Failed to create user "+alias+", status="+response.getStatus().getCode());
}
try {
Document userDoc = parser.load(response.getEntity());
User user = createUser(cred,userDoc);
userCache.put(alias,user);
passwords.put(alias,password);
return user;
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
}
public boolean deleteUser(AuthCredentials cred,String alias)
throws AuthException
{
throw new AuthException("Not implemented.");
}
User createUser(AuthCredentials cred,Document userDoc)
throws AuthException,IOException,XMLException
{
Element top = userDoc.getDocumentElement();
if (top.getName().equals(USER)) {
String userAlias = top.getAttributeValue("alias");
String groupsURL = serviceBase.resolve("./users/a/"+userAlias+"/groups").toString();
// get groups
Request request = new Request(Method.GET,groupsURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
Document groupsDoc = parser.load(response.getEntity());
UUID uuid = UUID.fromString(top.getAttributeValue("id"));
Element nameE = top.getFirstElementNamed(NAME);
String name = nameE==null ? null : nameE.getText();
Element emailE = top.getFirstElementNamed(EMAIL);
String email = emailE==null ? null : emailE.getText();
List<String> groups = new ArrayList<String>();
Iterator<Element> groupElements = groupsDoc.getDocumentElement().getElementsByName(GROUP);
while (groupElements.hasNext()) {
groups.add(groupElements.next().getAttributeValue("alias"));
}
return new User(userAlias,uuid,name,email,groups);
} else {
try {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} catch (IOException ex) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
} else {
throw new AuthException("Unexpected element "+top.getName()+" from auth service.");
}
}
User fetchUser(AuthCredentials cred,String alias)
throws AuthException
{
String userURL = serviceBase.resolve("./users/a/"+alias).toString();
Request request = new Request(Method.GET,userURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
try {
Document userDoc = parser.load(response.getEntity());
User user = createUser(cred,userDoc);
return user;
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
} else if (response.getStatus().getCode()!=404) {
try {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} catch (IOException ex) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
return null;
}
public User getUser(AuthCredentials cred,String alias)
throws AuthException
{
return userCache.get(cred,alias);
}
public Iterator<User> getUsers(final AuthCredentials cred)
throws AuthException
{
String userURL = serviceBase.resolve("./users/").toString();
Request request = new Request(Method.GET,userURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
try {
// TODO: this should stream...
Document usersDoc = parser.load(response.getEntity());
final Iterator<Element> users = usersDoc.getDocumentElement().getElementsByName(USER);
return new Iterator<User>() {
public boolean hasNext() {
return users.hasNext();
}
public void remove() {
throw new UnsupportedOperationException("remove() is not supported.");
}
public User next() {
Element userE = users.next();
String alias = userE.getAttributeValue("alias");
try {
User user = userCache.get(null,alias);
if (user==null) {
// get groups
String groupsURL = serviceBase.resolve("./users/a/"+alias+"/groups").toString();
Request request = new Request(Method.GET,groupsURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
try {
Document groupsDoc = parser.load(response.getEntity());
UUID uuid = UUID.fromString(userE.getAttributeValue("id"));
Element nameE = userE.getFirstElementNamed(NAME);
String name = nameE==null ? null : nameE.getText();
Element emailE = userE.getFirstElementNamed(EMAIL);
String email = emailE==null ? null : emailE.getText();
List<String> groups = new ArrayList<String>();
Iterator<Element> groupElements = groupsDoc.getDocumentElement().getElementsByName(GROUP);
while (groupElements.hasNext()) {
groups.add(groupElements.next().getAttributeValue("alias"));
}
// we won't cache this
return new User(alias,uuid,name,email,groups);
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
} else {
try {
throw new RuntimeException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} catch (IOException ex) {
throw new RuntimeException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
}
return user;
} catch (AuthException ex) {
throw new RuntimeException("Cannot get user "+alias,ex);
}
}
};
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
} else {
try {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} catch (IOException ex) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
}
public boolean updateUser(AuthCredentials cred,String alias,String name,String email)
throws AuthException
{
String userURL = serviceBase.resolve("./users/a/"+alias).toString();
Request request = new Request(Method.PUT,userURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
String xml = "<user xmlns='"+NAMESPACE+"' alias='"+alias+"'>" +
(name==null ? "" : "<name>"+name+"</name>") +
(email==null ? "" : "<email>"+name+"</email>") +
"</user>";
request.setEntity(new StringRepresentation(xml,MediaType.APPLICATION_XML));
Response response = client.handle(request);
return response.getStatus().isSuccess();
}
public boolean setPassword(AuthCredentials cred,String alias,String password)
throws AuthException
{
String userURL = serviceBase.resolve("./users/a/"+alias).toString();
Request request = new Request(Method.POST,userURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,cred.getName(),cred.getPassword()));
String xml = "<password xmlns='"+NAMESPACE+"'>" + password + "</password>";
request.setEntity(new StringRepresentation(xml,MediaType.APPLICATION_XML));
Response response = client.handle(request);
boolean success = response.getStatus().isSuccess();
if (!success && response.getStatus().getCode()!=404) {
try {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} catch (IOException ex) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
return success;
}
public User authenticate(String alias,String password)
throws AuthException
{
User user = null;
String checkPassword = passwords.get(alias);
if (checkPassword!=null && checkPassword.equals(password)) {
user = userCache.get(null,alias);
if (user!=null) {
return user;
}
}
Request request = new Request(Method.GET,authURL);
request.setChallengeResponse(new ChallengeResponse(ChallengeScheme.HTTP_BASIC,alias,password));
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
try {
Document doc = parser.load(response.getEntity());
user = parseUser(doc);
User cachedUser = userCache.get(null, alias);
if (cachedUser!=null) {
user = cachedUser;
passwords.put(user.getAlias(),password);
} else {
LOG.fine("Caching user "+user.getAlias());
passwords.put(user.getAlias(),password);
userCache.put(user.getAlias(),user);
}
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
} else if (response.getStatus().getCode()!=401) {
try {
if (response.getEntity()!=null) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode()+", message="+response.getEntity().getText());
} else {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
} catch (IOException ex) {
throw new AuthException("Cannot communicate with auth service, status="+response.getStatus().getCode());
}
}
return user;
}
protected User parseUser(Document doc)
throws AuthException
{
Element top = doc.getDocumentElement();
if (top.getName().equals(SESSION)) {
UUID uuid = UUID.fromString(top.getAttributeValue("user-id"));
String userAlias = top.getAttributeValue("user-alias");
Element nameE = top.getFirstElementNamed(NAME);
String name = nameE==null ? null : nameE.getText();
Element emailE = top.getFirstElementNamed(EMAIL);
String email = emailE==null ? null : emailE.getText();
// TODO: group is outdated. It needs to be replaced
List<String> groups = new ArrayList<String>();
Iterator<Element> groupElements = top.getElementsByName(GROUP);
while (groupElements.hasNext()) {
groups.add(groupElements.next().getAttributeValue("alias"));
}
boolean makeAdmin = false;
Iterator<Element> roleElements = top.getElementsByName(ROLE);
while (roleElements.hasNext()) {
UUID role = UUID.fromString(roleElements.next().getAttributeValue("id"));
if (role.equals(ROOT_ROLE) || role.equals(REALM_ROOT_ROLE)) {
makeAdmin = true;
}
}
if (makeAdmin) {
groups.add(AuthService.ADMIN_GROUP);
}
return new User(userAlias,uuid,name,email,groups);
} else {
throw new AuthException("Unexpected element "+top.getName()+" from auth service.");
}
}
public User verifySession(String session)
throws AuthException
{
Request request = new Request(Method.GET,sessionURL+session);
Response response = client.handle(request);
if (response.getStatus().isSuccess()) {
try {
Document doc = parser.load(response.getEntity());
User user = parseUser(doc);
LOG.fine("Caching user "+user.getAlias());
sessionCache.put(session,user);
userCache.put(user.getAlias(),user);
return user;
} catch (IOException ex) {
throw new AuthException("I/O exception while communicating with auth service.",ex);
} catch (XMLException ex) {
throw new AuthException("XML Error while parsing result form auth service.",ex);
}
}
return null;
}
}