SimpleRealm.java
/***************************************************************************
Copyright 2008 Emily Estes
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 net.metanotion.authident;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicLong;
import net.metanotion.util.SecureString;
/** This is an incredibly basic in-memory implementation of a realm. This implementation is mostly thread safe, but
under moderate loads there are quite likely to be update anomalies. <b>You should NOT use this implementation in
production under any circumstances. This implementation is provided for testing and API clarification only.</b> */
public final class SimpleRealm implements AuthPasswordRealm<SimpleRealm>, AuthPassword<SimpleRealm> {
private static final String USER_CREATION_ERROR_STRING = "User not created.";
private static final String BAD_TOKEN = "This user token did not come from this realm.";
private final AtomicLong nextID = new AtomicLong();
private final ConcurrentMap<Long,UT> tokens = new ConcurrentSkipListMap<>();
private final ConcurrentMap<String,Long> usernames = new ConcurrentSkipListMap<>();
private static final class UT implements UserToken {
private final SimpleRealm realm;
private final long id;
private final Set<String> users = new ConcurrentSkipListSet<>();
private byte[] password = null;
public UT(final SimpleRealm realm, final long id) {
this.realm = realm;
this.id = id;
}
// UserToken methods
@Override public Realm realm() { return realm; }
@Override public long getID() { return this.id; }
@Override public void delete() {
this.password = null;
for(String username: users) { realm.usernames.remove(username); }
this.users.clear();
this.realm.tokens.remove(this.id);
}
// Object methods.
@Override public int hashCode() {
return this.realm.hashCode() + Long.valueOf(this.id).hashCode();
}
@Override public boolean equals(final Object o) {
if(o instanceof UserToken) {
final UT u = getToken((UserToken) o);
if(u==null) { return false; }
return this.realm.equals(u.realm) && (u.id == this.id);
}
return false;
}
}
@Override public UserToken getUser(final long id) { return this.tokens.get(id); }
@Override public UserToken createUser() {
final UT u = new UT(this, nextID.getAndIncrement());
tokens.put(u.getID(), u);
return u;
}
@Override public AuthPassword<SimpleRealm> getAuthPassword() { return this; }
@Override public CredentialedUserToken<SimpleRealm, String> authenticate(final String username, final SecureString password) {
if((username == null) || (password == null)) { return null; }
final Long uid = usernames.get(username);
if(uid == null) { return null; }
final UT u = tokens.get(uid);
if((u.password != null) && (Arrays.equals(u.password, password.normalize().toByteArray()))) {
password.close();
return new CredentialedUserTokenImpl<SimpleRealm>(u, username, this);
} else {
password.close();
return null;
}
}
@Override public CredentialedUserToken<SimpleRealm, String> getIdentity(final String username) {
if(username == null) { return null; }
final Long uid = usernames.get(username);
if(uid == null) { return null; }
return new CredentialedUserTokenImpl<SimpleRealm>(tokens.get(uid), username, this);
}
private static UT getToken(final UserToken u) {
if(u instanceof UT) {
return (UT) u;
} else if(u != null) {
final Realm r = u.realm();
if(r instanceof SimpleRealm) {
return (UT) r.getUser(u.getID());
}
}
return null;
}
@Override public Iterable<String> listUsernames(final UserToken u) {
if(u == null) { return Collections.emptyList(); }
if(u.realm() != this) { return Collections.emptyList(); }
final UT u1 = getToken(u);
if(u1 == null) { return Collections.emptyList(); }
return new ArrayList<String>(u1.users);
}
@Override public CredentialedUserToken<SimpleRealm, String> createAuthentication(final String username,
final SecureString password) {
if(username == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
if(password == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
final Long uid = usernames.get(username);
if(uid != null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
final UT u = (UT) this.createUser();
final byte[] bytes = password.normalize().toByteArray();
u.password = Arrays.copyOf(bytes, bytes.length);
password.close();
this.addAuthentication(u, username);
return new CredentialedUserTokenImpl<SimpleRealm>(u, username, this);
}
@Override public void addAuthentication(final UserToken u, final String username) {
if(u.realm() != this) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
if(username == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
final Long uid = usernames.get(username);
if(uid != null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
usernames.put(username, u.getID());
getToken(u).users.add(username);
}
@Override public void setPassword(final String username, final SecureString password) {
if(username == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
if(password == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
final Long uid = usernames.get(username);
if(uid == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
final UT u = (UT) this.tokens.get(uid);
final byte[] bytes = password.normalize().toByteArray();
u.password = Arrays.copyOf(bytes, bytes.length);
password.close();
}
@Override public void deleteAuthentication(final UserToken u, final String username) {
if(username == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
if(u.realm() != this) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
if(usernames.remove(username) == null) { throw new RuntimeException(USER_CREATION_ERROR_STRING); }
getToken(u).users.remove(username);
}
/** Use the {@link net.metanotion.authident.ApiTest} suite to test this implementation.
@param args The command line arguments.
@throws Exception if the test fails.
*/
public static void main(final String[] args) throws Exception {
final SimpleRealm realm = new SimpleRealm();
ApiTest.testRealm(realm);
final SimpleRealm pw = new SimpleRealm();
ApiTest.testAuthPassword(pw, pw.getAuthPassword());
}
}