/*
* User.java
*
* Created on July 1, 2007, 1:25 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/
package org.atomojo.auth.service.db;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;
import java.util.Iterator;
import java.util.UUID;
import org.infoset.xml.Element;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;
import org.milowski.db.DB;
import org.milowski.db.DBConnection;
import org.milowski.db.DBIterator;
import org.milowski.db.DBObject;
import org.milowski.db.DBQueryHandler;
import org.milowski.db.DBResultConstructor;
import org.milowski.db.DBUpdateHandler;
import org.milowski.db.Slot;
/**
*
* @author alex
*/
public class User extends DBObject<AuthDB> implements XMLObject
{
static public class Authenticated {
AuthDB db;
int dbid;
User user;
Date expiration;
Date created;
UUID session;
Realm realm;
public Authenticated(AuthDB db,int id,UUID session,Date expiration,Date created,User user,Realm realm) {
this.db = db;
this.dbid = id;
this.session = session;
this.expiration = expiration;
this.created = created;
this.user = user;
this.realm = realm;
}
public User getUser() {
return user;
}
public Realm getRealm() {
return realm;
}
public UUID getSession() {
return session;
}
public Date getExpiration() {
return expiration;
}
public Date getCreated() {
return created;
}
public boolean isExpired() {
return expiration!=null && expiration.getTime()<System.currentTimeMillis();
}
public void delete()
throws SQLException
{
DBConnection connection = db.getConnection();
try {
connection.deleteById(AuthDB.DELETE_AUTHENTICATED, dbid);
} finally {
db.release(connection);
}
}
}
String alias;
UUID uuid;
String name;
String email;
/** Creates a new instance of User */
public User(AuthDB db,int id,UUID uuid,String alias,String name,String email)
{
super(db,id);
this.alias = alias;
this.uuid = uuid;
this.name = name;
this.email = email;
}
public void delete()
throws SQLException
{
DBConnection connection = db.getConnection();
try {
connection.deleteById(AuthDB.DELETE_USER_ROLES_BY_USER, id);
connection.deleteById(AuthDB.DELETE_AUTHENTICATION_BY_USER, id);
connection.deleteById(AuthDB.DELETE_USER_ALIAS_BY_USER, id);
connection.query(AuthDB.REALM_USERS_BY_USER, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1, id);
}
public void onResults(ResultSet set)
throws SQLException
{
while (set.next()) {
Realm realm = db.realmCache.get(set.getInt(2));
RealmUser user = db.realmUserCaches.get(realm).get(set.getInt(1));
user.delete();
db.realmUserCaches.get(realm).remove(user.getId());
}
}
});
connection.deleteById(AuthDB.DELETE_USER, id);
db.userCache.remove(id);
} finally {
db.release(connection);
}
}
public void setPassword(String password)
throws SQLException,NoSuchAlgorithmException
{
String md5 = md5Password(password);
setEncryptedPassword("md5",md5);
}
public void setEncryptedPassword(String algorithm,final String value)
throws SQLException,NoSuchAlgorithmException
{
if (!algorithm.equals("md5")) {
throw new NoSuchAlgorithmException("Algorithm "+algorithm+" is not supported.");
}
DBConnection connection = db.getConnection();
try {
connection.deleteById(AuthDB.DELETE_AUTHENTICATION_BY_USER, id);
connection.create(AuthDB.CREATE_AUTHENTICATION, -1,new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setString(2,value);
}
});
} finally {
db.release(connection);
}
}
public void addRole(final Role role)
throws SQLException
{
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_USER_ROLE, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,role.getId());
}
});
connection.update(AuthDB.ADD_USER_ROLE, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,role.getId());
}
});
} finally {
db.release(connection);
}
}
public boolean hasRole(final Role role)
throws SQLException
{
final Slot<Boolean> hasRole = new Slot<Boolean>(false);
DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.USER_HAS_ROLE, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,role.getId());
}
public void onResults(ResultSet set)
throws SQLException
{
hasRole.set(set.next());
}
});
} finally {
db.release(connection);
}
return hasRole.get();
}
public boolean hasPermission(Permission permission)
throws SQLException
{
boolean found = false;
Iterator<Role> roles = getRoles();
while (roles.hasNext()) {
if (roles.next().hasPermission(permission)) {
found = true;
}
}
return found;
}
public boolean removeRole(final Role role)
throws SQLException
{
DBConnection connection = db.getConnection();
try {
return connection.update(AuthDB.USER_HAS_ROLE, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,role.getId());
}
})>0;
} finally {
db.release(connection);
}
}
public Iterator<Role> getRoles()
throws SQLException
{
final Slot<Iterator<Role>> result = new Slot<Iterator<Role>>();
final DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.USER_ROLES, new DBQueryHandler() {
public boolean shouldClose() { return false; }
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
}
public void onResults(ResultSet set)
{
result.set(new DBIterator<Role>(set,new DBResultConstructor<Role>() {
public Role newInstance(ResultSet set)
throws SQLException
{
return db.roleCache.get(set.getInt(1));
}
},db,connection));
}
});
} catch (SQLException ex) {
db.release(connection);
throw ex;
}
return result.get();
}
public String getAlias()
{
return alias;
}
public boolean changeAlias(final String alias)
throws SQLException
{
if (alias==null && this.alias!=null) {
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_USER_ALIAS, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
}
});
} finally {
db.release(connection);
}
this.alias = null;
db.userCache.removeByName(this.alias);
return true;
}
if (alias.equals(this.alias)) {
return true;
}
if (!db.isUserAliasAvailable(alias)) {
return false;
}
if (this.alias!=null) {
db.userCache.removeByName(this.alias);
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.CHANGE_USER_ALIAS, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setString(1,alias);
s.setString(2,id+"-"+alias);
s.setInt(3,id);
}
});
} finally {
db.release(connection);
}
} else {
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.CREATE_USER_ALIAS, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setString(2,alias);
s.setString(3,id+"-"+alias);
}
});
} finally {
db.release(connection);
}
}
this.alias = alias;
return true;
}
public UUID getUUID()
{
return uuid;
}
public String getName()
{
return name;
}
public void setName(final String name)
throws SQLException
{
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.CHANGE_USER_NAME, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
if (name==null) {
s.setNull(1,Types.VARCHAR);
} else {
s.setString(1,name);
}
s.setInt(2,id);
}
});
} finally {
db.release(connection);
}
this.name = name;
}
public String getEmail()
{
return email;
}
public void setEmail(final String email)
throws SQLException
{
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.CHANGE_USER_NAME, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
if (email==null) {
s.setNull(1,Types.VARCHAR);
} else {
s.setString(1,email);
}
s.setInt(2,id);
}
});
} finally {
db.release(connection);
}
this.email = email;
}
public boolean equals(Object obj) {
return obj instanceof User && ((User)obj).getUUID().equals(uuid);
}
public Authenticated isAuthenticated(UUID session)
throws SQLException
{
return isAuthenticated(null,session);
}
public Authenticated isAuthenticated(final Realm realm,final UUID session)
throws SQLException
{
final Slot<Authenticated> retval = new Slot<Authenticated>();
if (realm==null) {
DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.USER_AUTHENTICATED, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setString(2,session.toString());
}
public void onResults(ResultSet set)
throws SQLException
{
if (set.next()) {
Timestamp created = set.getTimestamp(2);
Timestamp expiration = set.getTimestamp(3);
Authenticated auth = new Authenticated(db,set.getInt(1),session,created,expiration,User.this,realm);
retval.set(auth);
}
}
});
if (retval.get().isExpired()) {
// the session has expired
retval.get().delete();
retval.set(null);
}
} finally {
db.release(connection);
}
} else {
DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.REALM_USER_AUTHENTICATED, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,realm.getId());
s.setString(3,session.toString());
}
public void onResults(ResultSet set)
throws SQLException
{
if (set.next()) {
Timestamp created = set.getTimestamp(2);
Timestamp expiration = set.getTimestamp(3);
Authenticated auth = new Authenticated(db,set.getInt(1),session,created,expiration,User.this,realm);
retval.set(auth);
}
}
});
if (retval.get().isExpired()) {
// the session has expired
retval.get().delete();
retval.set(null);
}
} finally {
db.release(connection);
}
}
return retval.get();
}
public Authenticated authenticate(String password,long expires)
throws SQLException,NoSuchAlgorithmException
{
return authenticate(null,null,password,expires);
}
public Authenticated authenticate(Realm realm,String password,long expires)
throws SQLException,NoSuchAlgorithmException
{
return authenticate(realm,null,password,expires);
}
public Authenticated authenticate(final Realm realm,final UUID session,String password,long expires)
throws SQLException,NoSuchAlgorithmException
{
if (session!=null) {
// delete any session authentication information first
if (realm==null) {
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_USER_AUTHENTICATION, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setString(2,session.toString());
}
});
} finally {
db.release(connection);
}
} else {
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_REALM_USER_AUTHENTICATION, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setInt(2,realm.getId());
s.setString(3,session.toString());
}
});
} finally {
db.release(connection);
}
}
}
// delete any expired sessions for the user
final Timestamp created = new Timestamp(System.currentTimeMillis());
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_EXPIRED_SESSIONS, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setTimestamp(2,created);
}
});
} finally {
db.release(connection);
}
// check the internal password
if (checkPassword(password)) {
// We have the correct password
int dbid = -1;
if (expires>0) {
//construct an auth record with a session id
final UUID theSession = session==null ? UUID.randomUUID() : session;
final Timestamp expiration = new Timestamp(created.getTime()+expires);
connection = db.getConnection();
try {
connection.update(AuthDB.CREATE_AUTHENTICATED, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
if (realm==null) {
s.setNull(2,Types.INTEGER);
} else {
s.setInt(2,realm.getId());
}
s.setString(3,theSession.toString());
s.setTimestamp(4,created);
s.setTimestamp(5,expiration);
}
});
} finally {
db.release(connection);
}
return new Authenticated(db,dbid,theSession,created,expiration,this,realm);
} else {
// the auth expires now
return new Authenticated(db,dbid,session,created,created,this,realm);
}
}
return null;
}
public Authenticated selfAuthenticate(final Realm realm,long expires)
throws SQLException,NoSuchAlgorithmException
{
// delete any expired sessions for the user
final Timestamp created = new Timestamp(System.currentTimeMillis());
DBConnection connection = db.getConnection();
try {
connection.update(AuthDB.DELETE_EXPIRED_SESSIONS, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setTimestamp(2,created);
}
});
} finally {
db.release(connection);
}
int dbid = -1;
if (expires>0) {
//construct an auth record with a session id
final UUID session = UUID.randomUUID();
final Timestamp expiration = new Timestamp(created.getTime()+expires);
connection = db.getConnection();
try {
connection.update(AuthDB.CREATE_AUTHENTICATED, new DBUpdateHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
if (realm==null) {
s.setNull(2,Types.INTEGER);
} else {
s.setInt(2,realm.getId());
}
s.setString(3,session.toString());
s.setTimestamp(4,created);
s.setTimestamp(5,expiration);
}
});
} finally {
db.release(connection);
}
return new Authenticated(db,dbid,session,created,expiration,this,realm);
} else {
return null;
}
}
public Authenticated recover(Realm realm)
throws SQLException,NoSuchAlgorithmException
{
// expires in one hour
long expires = (60*60*1000);
return selfAuthenticate(realm,expires);
}
public boolean checkPassword(String password)
throws SQLException,NoSuchAlgorithmException
{
final Slot<Boolean> found = new Slot<Boolean>(false);
final String encrypted = md5Password(password);
DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.CHECK_PASSWORD, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
s.setString(2,encrypted);
}
public void onResults(ResultSet set)
throws SQLException
{
found.set(set.next());
}
});
} finally {
db.release(connection);
}
return found.get();
}
public String getEncryptedPassword()
throws SQLException
{
final Slot<String> found = new Slot<String>();
DBConnection connection = db.getConnection();
try {
connection.query(AuthDB.ENCRYPTED_PASSWORD, new DBQueryHandler() {
public void prepare(PreparedStatement s)
throws SQLException
{
s.setInt(1,id);
}
public void onResults(ResultSet set)
throws SQLException
{
if (set.next()) {
found.set(set.getString(1));
}
}
});
} finally {
db.release(connection);
}
return found.get();
}
public void generate(ItemConstructor constructor,ItemDestination dest)
throws XMLException
{
generate(constructor,dest,false,true);
}
public void generate(ItemConstructor constructor,ItemDestination dest,boolean contents)
throws XMLException
{
generate(constructor,dest,false,contents);
}
public void generate(ItemConstructor constructor,ItemDestination dest,boolean showPassword,boolean showRoles)
throws XMLException
{
Element user = constructor.createElement(XML.USER_NAME);
user.setAttributeValue("id",uuid.toString());
if (alias!=null) {
user.setAttributeValue("alias",alias);
}
if (showPassword) {
try {
String md5 = getEncryptedPassword();
if (md5!=null) {
user.setAttributeValue("password-md5",md5);
}
} catch (SQLException ex) {
throw new XMLException("Cannot get password from database for user "+uuid,ex);
}
}
dest.send(user);
if (name!=null) {
dest.send(constructor.createElement(XML.NAME_NAME));
dest.send(constructor.createCharacters(name));
dest.send(constructor.createElementEnd(XML.NAME_NAME));
}
if (email!=null) {
dest.send(constructor.createElement(XML.EMAIL_NAME));
dest.send(constructor.createCharacters(email));
dest.send(constructor.createElementEnd(XML.EMAIL_NAME));
}
if (showRoles) {
try {
dest.send(constructor.createElement(XML.ROLES_NAME));
Iterator<Role> roles = getRoles();
boolean first = true;
while (roles.hasNext()) {
if (first) {
constructor.createCharacters("\n");
first = false;
}
roles.next().generate(constructor,dest,false);
constructor.createCharacters("\n");
}
dest.send(constructor.createElementEnd(XML.ROLES_NAME));
} catch (SQLException ex) {
throw new XMLException("Cannot get user roles.",ex);
}
}
dest.send(constructor.createElementEnd(XML.USER_NAME));
}
public static boolean isAlias(String alias)
{
int len = alias.length();
for (int i=0; i<len; i++) {
char ch = alias.charAt(i);
if (!Character.isLetterOrDigit(ch) && ch!='.' && ch!='-') {
return false;
}
}
return true;
}
private static String[] hex = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
public static String md5Password(String password)
throws java.security.NoSuchAlgorithmException
{
MessageDigest md5 = MessageDigest.getInstance( "MD5" );
md5.update( password.getBytes() );
byte[] b = md5.digest();
StringBuilder buf = new StringBuilder( b.length * 2 );
for ( int i = 0; i < b.length; i++ ) {
int n = b[i];
if ( n < 0 ) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
buf.append( hex[d1] );
buf.append( hex[d2] );
}
return buf.toString();
}
public static void main(String [] args)
{
try {
for (int i=0; i<args.length; i++) {
System.out.println(md5Password(args[i]));
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
}