/* * Copyright 2010 NCHOVY * * 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 org.krakenapps.account; import java.security.MessageDigest; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.krakenapps.api.AccountManager; import org.krakenapps.api.PrimitiveConverter; import org.krakenapps.auth.api.AuthCallback; import org.krakenapps.auth.api.AuthProvider; import org.krakenapps.auth.api.UserCredentials; import org.krakenapps.auth.api.UserPrincipal; import org.krakenapps.confdb.Config; import org.krakenapps.confdb.ConfigCollection; import org.krakenapps.confdb.ConfigDatabase; import org.krakenapps.confdb.ConfigIterator; import org.krakenapps.confdb.ConfigService; import org.krakenapps.confdb.ConfigTransaction; import org.krakenapps.confdb.Predicates; public class AccountManagerImpl implements AccountManager, AuthProvider { private static final String SALT = "kraken"; private ConfigService conf; public AccountManagerImpl(ConfigService conf) { this.conf = conf; createDefaultAccount(); } @Override public String getName() { return "local"; } private void createDefaultAccount() { try { createAccount("root", SALT); } catch (Exception e) { // ignore if already exists } } @Override public void authenticate(UserPrincipal principal, UserCredentials credentials, AuthCallback callback) { System.out.println(principal + ", " + credentials); String password = (String) credentials.get("password"); boolean result = verifyPassword(principal.getName(), password); if (result) callback.onSuccess(this, principal, credentials); else callback.onFail(this, principal, credentials); } @Override public Collection<String> getAccounts() { ConfigDatabase db = conf.getDatabase("kraken-core"); ConfigCollection principals = db.ensureCollection("principal"); List<String> accounts = new ArrayList<String>(); ConfigIterator it = principals.findAll(); while (it.hasNext()) { Config c = it.next(); UserPrincipal p = c.getDocument(UserPrincipal.class); accounts.add(p.getName()); } return accounts; } @Override public void createAccount(String name, String password) { ConfigDatabase db = conf.getDatabase("kraken-core"); ConfigCollection principals = db.ensureCollection("principal"); ConfigCollection credentials = db.ensureCollection("credential"); Config c = principals.findOne(Predicates.field("login_name", name)); if (c != null) throw new IllegalStateException("duplicated principal name: " + name); ConfigTransaction xact = db.beginTransaction(5000); try { UserPrincipal principal = new UserPrincipal(name); principals.add(xact, PrimitiveConverter.serialize(principal)); String hash = hashPassword(SALT, password); Map<String, Object> m = new HashMap<String, Object>(); m.put("domain", principal.getDomain()); m.put("login_name", principal.getName()); m.put("salt", SALT); m.put("password", hash); credentials.add(xact, m); xact.commit("kraken-core", "created account: " + name); } catch (Throwable e) { xact.rollback(); } } @Override public void removeAccount(String name) { if (name.equals("root")) throw new IllegalArgumentException("cannot remove root account"); ConfigDatabase db = conf.getDatabase("kraken-core"); ConfigCollection principals = db.ensureCollection("principal"); ConfigCollection credentials = db.ensureCollection("credential"); ConfigTransaction xact = db.beginTransaction(5000); try { Config c1 = principals.findOne(Predicates.field("login_name", name)); if (c1 == null) return; db.remove(c1); Config c2 = credentials.findOne(Predicates.field("login_name", name)); if (c2 == null) return; db.remove(c2); xact.commit("kraken-core", "removed account: " + name); } catch (Throwable e) { xact.rollback(); } } @Override public void changePassword(String name, String currentPassword, String newPassword) { ConfigDatabase db = conf.getDatabase("kraken-core"); ConfigCollection credentials = db.ensureCollection("credential"); Config c = credentials.findOne(Predicates.field("login_name", name)); if (c == null) throw new IllegalStateException("account not found"); @SuppressWarnings("unchecked") Map<String, Object> m = (Map<String, Object>) c.getDocument(); String salt = (String) m.get("salt"); String hash = (String) m.get("password"); String currentPasswordHash = hashPassword(salt, currentPassword); String newPasswordHash = hashPassword(salt, newPassword); if (hash == null || !currentPasswordHash.equals(hash)) throw new IllegalStateException("invalid current password"); m.put("password", newPasswordHash); c.setDocument(m); credentials.update(c, false, "kraken-core", "changed password for " + name); } @Override public boolean verifyPassword(String name, String password) { if (password == null) return false; ConfigDatabase db = conf.getDatabase("kraken-core"); ConfigCollection credentials = db.ensureCollection("credential"); Config c = credentials.findOne(Predicates.field("login_name", name)); if (c == null) return false; @SuppressWarnings("unchecked") Map<String, Object> m = (Map<String, Object>) c.getDocument(); String salt = (String) m.get("salt"); String hash = (String) m.get("password"); if (hash == null) return false; String computed = hashPassword(salt, password); if (computed == null) return false; if (computed.equals(hash)) return true; return false; } private static String hashPassword(String salt, String text) { try { text = salt + text; MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] sha1hash = new byte[40]; md.update(text.getBytes("iso-8859-1"), 0, text.length()); sha1hash = md.digest(); return convertToHex(sha1hash); } catch (Exception e) { return null; } } private static String convertToHex(byte[] data) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < data.length; i++) { int halfbyte = (data[i] >>> 4) & 0x0F; int two_halfs = 0; do { if ((0 <= halfbyte) && (halfbyte <= 9)) buf.append((char) ('0' + halfbyte)); else buf.append((char) ('a' + (halfbyte - 10))); halfbyte = data[i] & 0x0F; } while (two_halfs++ < 1); } return buf.toString(); } }