/* * Copyright 2013 Eediom Inc. * * 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.araqne.logdb.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Provides; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.araqne.api.PrimitiveConverter; import org.araqne.confdb.Config; import org.araqne.confdb.ConfigCollection; import org.araqne.confdb.ConfigDatabase; import org.araqne.confdb.ConfigIterator; import org.araqne.confdb.ConfigService; import org.araqne.confdb.ConfigTransaction; import org.araqne.confdb.Predicates; import org.araqne.logdb.Account; import org.araqne.logdb.AccountEventListener; import org.araqne.logdb.AccountService; import org.araqne.logdb.AuthServiceNotLoadedException; import org.araqne.logdb.ExternalAuthService; import org.araqne.logdb.Permission; import org.araqne.logdb.Privilege; import org.araqne.logdb.SecurityGroup; import org.araqne.logdb.Session; import org.araqne.logdb.SessionEventListener; import org.araqne.logstorage.TableEventListener; import org.araqne.logstorage.LogTableRegistry; import org.araqne.logstorage.TableSchema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(name = "logdb-account") @Provides(specifications = { AccountService.class }) public class AccountServiceImpl implements AccountService, TableEventListener { private final Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class); private static final String DB_NAME = "araqne-logdb"; private static final String DEFAULT_MASTER_ACCOUNT = "araqne"; private static final char[] SALT_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); @Requires private ConfigService conf; @Requires private LogTableRegistry tableRegistry; private ConcurrentMap<String, Session> sessions; private ConcurrentMap<String, Account> localAccounts; private ConcurrentMap<String, SecurityGroup> securityGroups; private ConcurrentMap<String, ExternalAuthService> authServices; private String selectedExternalAuth; private CopyOnWriteArraySet<SessionEventListener> sessionListeners; private CopyOnWriteArraySet<AccountEventListener> accountListeners; private String instanceGuid; private Object updateLock = new Object(); public AccountServiceImpl() { sessions = new ConcurrentHashMap<String, Session>(); localAccounts = new ConcurrentHashMap<String, Account>(); securityGroups = new ConcurrentHashMap<String, SecurityGroup>(); authServices = new ConcurrentHashMap<String, ExternalAuthService>(); sessionListeners = new CopyOnWriteArraySet<SessionEventListener>(); accountListeners = new CopyOnWriteArraySet<AccountEventListener>(); } @Validate public void start() { tableRegistry.addListener(this); sessions.clear(); localAccounts.clear(); authServices.clear(); ConfigDatabase db = conf.ensureDatabase(DB_NAME); removeDuplicateAccounts(db); // load accounts for (Account account : db.findAll(Account.class).getDocuments(Account.class)) { localAccounts.put(account.getLoginName(), account); } // load security groups for (SecurityGroup group : db.findAll(SecurityGroup.class).getDocuments(SecurityGroup.class)) { securityGroups.put(group.getGuid(), group); } // generate default 'araqne' account if not exists if (!localAccounts.containsKey(DEFAULT_MASTER_ACCOUNT)) { String salt = randomSalt(10); Account account = new Account(DEFAULT_MASTER_ACCOUNT, salt, HashUtils.hash(salt, Account.DEFAULT_HASH_ALGORITHM)); db.add(account); localAccounts.put(DEFAULT_MASTER_ACCOUNT, account); } // load external auth service config ConfigCollection col = db.ensureCollection("global_config"); Config c = col.findOne(null); if (c != null) { @SuppressWarnings("unchecked") Map<String, Object> m = (Map<String, Object>) c.getDocument(); selectedExternalAuth = (String) m.get("external_auth"); } instanceGuid = UUID.randomUUID().toString(); } private void removeDuplicateAccounts(ConfigDatabase db) { ConfigIterator it = null; List<Config> targets = new ArrayList<Config>(); try { it = db.findAll(Account.class); Set<String> loginNames = new HashSet<String>(); while (it.hasNext()) { Config c = it.next(); String loginName = c.getDocument(Account.class).getLoginName(); if (loginNames.contains(loginName)) targets.add(c); else loginNames.add(loginName); } } finally { if (it != null) it.close(); } if (targets.isEmpty()) return; ConfigTransaction xact = null; try { xact = db.beginTransaction(); for (Config c : targets) db.remove(xact, c, false); xact.commit("araqne-logdb", "remove duplicate accounts"); } catch (Throwable t) { if (xact != null) xact.rollback(); } } @Invalidate public void stop() { if (tableRegistry != null) tableRegistry.removeListener(this); } @Override public List<Session> getSessions() { return new ArrayList<Session>(sessions.values()); } @Override public Session getSession(String guid) { return sessions.get(guid); } @Override public boolean isAdmin(String loginName) { Account account = ensureAccount(loginName); return account.isAdmin(); } @Override public void grantAdmin(Session session, String loginName) { if (!session.isAdmin()) throw new IllegalStateException("no permission"); Account account = ensureAccount(loginName); account.setAdmin(true); updateAccount(account); for (AccountEventListener listener : accountListeners) { try { listener.onGrantAdmin(session, account); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public void revokeAdmin(Session session, String loginName) { if (!session.isAdmin()) throw new IllegalStateException("no permission"); if (session.getLoginName().equals(loginName)) throw new IllegalStateException("cannot revoke current admin session"); Account account = ensureAccount(loginName); account.setAdmin(false); updateAccount(account); for (AccountEventListener listener : accountListeners) { try { listener.onRevokeAdmin(session, account); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public List<Privilege> getPrivileges(Session session, String loginName) { verifyNotNull(session, "session"); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); // allow own info check or master admin only if (!checkOwner(session, loginName)) throw new IllegalStateException("no permission"); List<Privilege> privileges = new ArrayList<Privilege>(); if (loginName != null) { checkAccountIncludingExternal(loginName); Account account = ensureAccount(loginName); for (String tableName : account.getReadableTables()) { privileges.add(new Privilege(loginName, tableName, Arrays.asList(Permission.READ))); } } else { ConfigDatabase db = conf.ensureDatabase(DB_NAME); for (Account account : db.findAll(Account.class).getDocuments(Account.class)) for (String tableName : account.getReadableTables()) privileges.add(new Privilege(account.getLoginName(), tableName, Arrays.asList(Permission.READ))); } return privileges; } @Override public void resetPrivileges(Session session, List<Privilege> privileges) { verifyNotNull(privileges, "privileges"); verifyAdminSession(session); Map<String, Account> accounts = new HashMap<String, Account>(); for (Account account : localAccounts.values()) { account = account.clone(); account.getReadableTables().clear(); for (Privilege privilege : privileges) { if (privilege.getLoginName().equals(account.getLoginName())) account.getReadableTables().add(privilege.getTableName()); } accounts.put(account.getLoginName(), account); } ConfigDatabase db = conf.ensureDatabase(DB_NAME); ConfigIterator it = null; Map<String, Config> configs = new HashMap<String, Config>(); try { it = db.find(Account.class, Predicates.in("login_name", accounts.keySet())); while (it.hasNext()) { Config c = it.next(); String loginName = c.getDocument(Account.class).getLoginName(); configs.put(loginName, c); } } finally { if (it != null) it.close(); } ConfigTransaction xact = null; try { xact = db.beginTransaction(); for (String loginName : accounts.keySet()) { Config c = configs.get(loginName); Account account = accounts.get(loginName); if (c == null) db.add(xact, account); else db.update(xact, c, account, false); } xact.commit("araqne-logdb", "set accounts"); for (Account account : accounts.values()) localAccounts.put(account.getLoginName(), account); } catch (Throwable t) { if (xact != null) xact.rollback(); throw new IllegalStateException(t); } } @Override public void setPrivileges(Session session, String loginName, List<Privilege> privileges) { verifyNotNull(loginName, "loginName"); verifyNotNull(privileges, "privileges"); verifyAdminSession(session); Account account = ensureAccount(loginName); List<String> tables = account.getReadableTables(); tables.clear(); for (Privilege privilege : privileges) if (privilege.getLoginName() != null && privilege.getLoginName().equals(loginName)) tables.add(privilege.getTableName()); updateAccount(account); } private void verifyAdminSession(Session session) { verifyNotNull(session, "session"); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); // master admin only if (!session.isAdmin()) throw new IllegalStateException("no permission"); } private boolean checkOwner(Session session, String loginName) { Account account = ensureAccount(session.getLoginName()); if (account.isAdmin()) return true; if (loginName == null) return false; return loginName.equals(session.getLoginName()); } @Override public Set<String> getAccountNames() { return new HashSet<String>(localAccounts.keySet()); } @Override public Account getAccount(String name) { Account account = localAccounts.get(name); if (account == null) return null; return account.clone(); } @Override public boolean verifyPassword(String loginName, String password) { verifyNotNull(loginName, "login name"); verifyNotNull(password, "password"); Account account = localAccounts.get(loginName); // try local login first if (account != null && account.getAuthServiceName() == null) { // salted hash String hash = account.getPassword(); String salt = account.getSalt(); return hash.equals(HashUtils.hash(password + salt, account.getHashAlgorithm())); } else if (selectedExternalAuth != null) { // try external login ExternalAuthService auth = authServices.get(selectedExternalAuth); if (auth == null) throw new AuthServiceNotLoadedException(selectedExternalAuth); return auth.verifyPassword(loginName, password); } else throw new IllegalStateException("account not found"); } @Override public Session newSession(String loginName) { verifyNotNull(loginName, "login name"); Account account = ensureAccount(loginName); return registerSession(account); } @Override public Session login(String loginName, String hash, String nonce) { verifyNotNull(loginName, "login name"); verifyNotNull(hash, "hash"); verifyNotNull(nonce, "nonce"); Account account = localAccounts.get(loginName); if (account == null) throw new IllegalStateException("account-not-found"); String password = account.getPassword(); if (!hash.equals(HashUtils.hash(password + nonce, account.getHashAlgorithm()))) { throw new IllegalStateException("invalid-password"); } return registerSession(account); } @Override public Session login(String loginName, String password) { if (!verifyPassword(loginName, password)) throw new IllegalStateException("invalid password"); Account account = ensureAccount(loginName); return registerSession(account); } private Session registerSession(Account account) { String guid = UUID.randomUUID().toString(); Session session = new SessionImpl(guid, account.getLoginName(), account.isAdmin()); sessions.put(guid, session); // invoke callbacks for (SessionEventListener listener : sessionListeners) { try { listener.onLogin(session); } catch (Throwable t) { logger.warn("araqne logdb: session event listener should not throw any exception", t); } } return session; } @Override public void logout(Session session) { if (sessions.remove(session.getGuid()) == null) throw new IllegalStateException("session not found: " + session.getGuid()); // invoke callbacks for (SessionEventListener listener : sessionListeners) { try { listener.onLogout(session); } catch (Throwable t) { logger.warn("araqne logdb: session event listener should not throw any exception", t); } } } @Override public void createAccount(Session session, String loginName, String password) { verifyNotNull(session, "session"); verifyNotNull(loginName, "login name"); verifyNotNull(password, "password"); if (localAccounts.containsKey(loginName)) throw new IllegalStateException("duplicated login name"); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); if (!session.isAdmin()) throw new IllegalStateException("no permission"); // check database ConfigDatabase db = conf.ensureDatabase(DB_NAME); Config c = db.findOne(Account.class, Predicates.field("login_name", loginName)); if (c != null) throw new IllegalStateException("duplicated login name"); String salt = randomSalt(10); String hash = HashUtils.hash(password + salt, Account.DEFAULT_HASH_ALGORITHM); Account account = new Account(loginName, salt, hash); Account old = localAccounts.putIfAbsent(account.getLoginName(), account); if (old != null && old.getAuthServiceName() == null) throw new IllegalStateException("duplicated login name"); db.add(account); for (AccountEventListener listener : accountListeners) { try { listener.onCreateAccount(session, account); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public void changePassword(Session session, String loginName, String password) { verifyNotNull(session, "session"); verifyNotNull(loginName, "login name"); verifyNotNull(password, "password"); if (!localAccounts.containsKey(loginName)) throw new IllegalStateException("account not found"); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); // check if owner or master if (!checkOwner(session, loginName)) throw new IllegalStateException("no permission"); Account account = localAccounts.get(loginName); String hash = HashUtils.hash(password + account.getSalt(), account.getHashAlgorithm()); account.setPassword(hash); updateAccount(account); } private void updateAccount(Account account) { synchronized (updateLock) { ConfigDatabase db = conf.ensureDatabase(DB_NAME); Config c = db.findOne(Account.class, Predicates.field("login_name", account.getLoginName())); if (c != null) { c.setDocument(PrimitiveConverter.serialize(account)); c.update(); } else { db.add(account); logger.info("araqne-logdb: update account [{}]", account.getLoginName()); } localAccounts.put(account.getLoginName(), account); } } @Override public void removeAccount(Session session, String loginName) { verifyNotNull(loginName, "login name"); if (!localAccounts.containsKey(loginName)) throw new IllegalStateException("account not found"); if (session != null) { if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); if (session.getLoginName().equals(loginName)) throw new IllegalStateException("cannot delete your own account"); // master admin only if (!session.isAdmin()) throw new IllegalStateException("no permission"); } Account account = localAccounts.remove(loginName); // drop all sessions for (Session s : new ArrayList<Session>(sessions.values())) { if (s.getLoginName().equals(loginName)) sessions.remove(s.getGuid()); } // delete from database ConfigDatabase db = conf.ensureDatabase(DB_NAME); Config c = db.findOne(Account.class, Predicates.field("login_name", loginName)); if (c != null) c.remove(); // delete from security groups for (SecurityGroup group : securityGroups.values()) { if (group.getAccounts().contains(loginName)) { group.getAccounts().remove(loginName); updateSecurityGroup(null, group); } } for (AccountEventListener listener : accountListeners) { try { listener.onRemoveAccount(session, account); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public void removeAccounts(Session session, Set<String> loginNames) { verifyNotNull(loginNames, "login names"); if (session != null) verifyAdminSession(session); List<Account> accounts = new ArrayList<Account>(); for (String loginName : loginNames) { Account account = localAccounts.remove(loginName); if (account != null) accounts.add(account); } for (Session s : new ArrayList<Session>(sessions.values())) { if (loginNames.contains(s.getLoginName())) sessions.remove(s.getGuid()); } ConfigDatabase db = conf.ensureDatabase(DB_NAME); ConfigIterator it = null; List<Config> configs = null; try { it = db.find(Account.class, Predicates.in("login_name", loginNames)); configs = it.getConfigs(0, Integer.MAX_VALUE); } finally { if (it != null) it.close(); } if (configs == null || configs.isEmpty()) return; ConfigTransaction xact = null; try { xact = db.beginTransaction(); for (Config c : configs) db.remove(xact, c, false); xact.commit("araqne-logdb", "remove accounts"); } catch (Throwable t) { if (xact != null) xact.rollback(); throw new IllegalStateException(t); } Set<SecurityGroup> groups = new HashSet<SecurityGroup>(securityGroups.values()); List<SecurityGroup> updateGroups = new ArrayList<SecurityGroup>(); for (SecurityGroup group : groups) { boolean isUpdate = false; for (String loginName : loginNames) { if (group.getAccounts().contains(loginName)) { group.getAccounts().remove(loginName); isUpdate = true; } } if (isUpdate) updateGroups.add(group); } if (!updateGroups.isEmpty()) { updateSecurityGroups(session, updateGroups); } for (AccountEventListener listener : accountListeners) { try { listener.onRemoveAccounts(session, accounts); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public Set<String> getSecurityGroupGuids() { return new HashSet<String>(securityGroups.keySet()); } @Override public List<SecurityGroup> getSecurityGroups() { List<SecurityGroup> l = new ArrayList<SecurityGroup>(); for (SecurityGroup g : securityGroups.values()) l.add(g.clone()); return l; } @Override public SecurityGroup getSecurityGroup(String guid) { SecurityGroup old = securityGroups.get(guid); if (old == null) return null; return old.clone(); } @Override public void createSecurityGroup(Session session, SecurityGroup group) { if (session != null && !session.isAdmin()) { throw new IllegalStateException("no permission"); } verifySecurityGroup(group); synchronized (securityGroups) { for (SecurityGroup o : securityGroups.values()) { // check duplicated name if (o.getName().equals(group.getName())) throw new IllegalStateException("duplicated security group name: " + group.getName()); // check duplicated guid if (o.getGuid().equals(group.getGuid())) throw new IllegalStateException("duplicated security group guid: " + group.getGuid()); } ConfigDatabase db = conf.ensureDatabase("araqne-logdb"); db.add(group, "araqne-logdb", "added security group [guid: " + group.getGuid() + ", name: " + group.getName() + "]"); securityGroups.put(group.getGuid(), group); } // invoke callbacks for (AccountEventListener listener : accountListeners) { try { listener.onCreateSecurityGroup(session, group.clone()); } catch (Throwable t) { logger.warn("araqne logdb: account listener should not throw any exception", t); } } } @Override public void updateSecurityGroups(Session session, List<SecurityGroup> groups) { Map<String, SecurityGroup> securityGroups = new HashMap<String, SecurityGroup>(); for (SecurityGroup group : groups) { verifySecurityGroup(group); securityGroups.put(group.getGuid(), group); } synchronized (this.securityGroups) { ConfigDatabase db = conf.ensureDatabase("araqne-logdb"); ConfigIterator it = null; ConfigTransaction xact = null; try { it = db.find(SecurityGroup.class, Predicates.in("guid", securityGroups.keySet())); List<SecurityGroup> updatedGroups = new ArrayList<SecurityGroup>(); xact = db.beginTransaction(); while (it.hasNext()) { Config c = it.next(); SecurityGroup old = c.getDocument(SecurityGroup.class); SecurityGroup newGroup = securityGroups.get(old.getGuid()); if (newGroup == null) continue; db.update(xact, c, newGroup, false); updatedGroups.add(newGroup); } xact.commit("araqne-logdb", "update security groups"); for (SecurityGroup group : updatedGroups) this.securityGroups.put(group.getGuid(), group); } catch (Throwable t) { if (xact != null) xact.rollback(); throw new RuntimeException(t); } finally { if (it != null) it.close(); } } } @Override public void updateSecurityGroup(Session session, SecurityGroup group) { verifySecurityGroup(group); SecurityGroup old = null; synchronized (securityGroups) { old = securityGroups.get(group.getGuid()); if (old == null) throw new IllegalStateException("security group not found: " + group.getGuid()); for (SecurityGroup o : securityGroups.values()) { if (!o.getGuid().equals(group.getGuid()) && o.getName().equals(group.getName())) throw new IllegalStateException("duplicated security group name: " + group.getName()); } old.setName(group.getName()); old.setDescription(group.getDescription()); old.setAccounts(new HashSet<String>(group.getAccounts())); old.setReadableTables(new HashSet<String>(group.getReadableTables())); old.setUpdated(new Date()); ConfigDatabase db = conf.ensureDatabase("araqne-logdb"); Config c = db.findOne(SecurityGroup.class, Predicates.field("guid", group.getGuid())); if (c == null) throw new IllegalStateException("security group not found at confdb: " + group.getGuid()); db.update(c, group, false, "araqne-logdb", "updated security group [guid: " + group.getGuid() + ", name: " + group.getName() + "]"); } // invoke callbacks for (AccountEventListener listener : accountListeners) { try { listener.onUpdateSecurityGroup(session, old.clone()); } catch (Throwable t) { logger.warn("araqne logdb: account listener should not throw any exception", t); } } } private void verifySecurityGroup(SecurityGroup group) { if (group.getName() == null) throw new IllegalArgumentException("security group name should not be null"); if (group.getGuid() == null) throw new IllegalArgumentException("security group guid should not be null"); // check if table exists for (String tableName : group.getReadableTables()) { if (!tableRegistry.exists(tableName)) throw new IllegalStateException("table not found: " + tableName); } syncAccounts(group.getAccounts()); } private void syncAccounts(Set<String> loginNames) { Set<String> targetLoginNames = new HashSet<String>(); for (String loginName : loginNames) { if (localAccounts.containsKey(loginName)) continue; targetLoginNames.add(loginName); } List<Account> accounts = new ArrayList<Account>(); if (selectedExternalAuth != null) { ExternalAuthService auth = authServices.get(selectedExternalAuth); if (auth != null) accounts = auth.findAccounts(targetLoginNames); } if (accounts.isEmpty()) return; ConfigDatabase db = conf.ensureDatabase(DB_NAME); ConfigTransaction xact = null; try { xact = db.beginTransaction(); for (Account account : accounts) { db.add(xact, account); } xact.commit("araqne-logdb", "sync accounts with external auth [" + selectedExternalAuth + "]"); for (Account account : accounts) localAccounts.putIfAbsent(account.getLoginName(), account); } catch (Throwable t) { if (xact != null) xact.rollback(); throw new IllegalStateException(t); } } @Override public void removeSecurityGroup(Session session, String guid) { SecurityGroup old = null; synchronized (securityGroups) { old = securityGroups.remove(guid); if (old == null) throw new IllegalStateException("security group not found: " + guid); ConfigDatabase db = conf.ensureDatabase("araqne-logdb"); Config c = db.findOne(SecurityGroup.class, Predicates.field("guid", guid)); if (c != null) db.remove(c, false, "araqne-logdb", "removed security group [guid: " + old.getGuid() + ", name: " + old.getName() + "]"); } // invoke callbacks for (AccountEventListener listener : accountListeners) { try { listener.onRemoveSecurityGroup(session, old.clone()); } catch (Throwable t) { logger.warn("araqne logdb: account listener should not throw any exception", t); } } } @Override public boolean checkPermission(Session session, String tableName, Permission permission) { verifyNotNull(session, "session"); verifyNotNull(tableName, "table name"); verifyNotNull(permission, "permission"); if (permission != Permission.READ) throw new UnsupportedOperationException(); // allow dummy login if (session.isAdmin()) return true; String loginName = session.getLoginName(); Account account = ensureAccount(loginName); boolean b = account.getReadableTables().contains(tableName); if (b) return true; // check security groups for (SecurityGroup g : securityGroups.values()) { if (!g.getAccounts().contains(loginName)) continue; if (g.getReadableTables().contains(tableName)) return true; } return false; } @Override public void grantPrivilege(Session session, String loginName, String tableName, Permission... permissions) { verifyNotNull(session, "session"); verifyNotNull(loginName, "login name"); verifyNotNull(tableName, "table name"); if (permissions.length == 0) return; checkAccountIncludingExternal(loginName); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); // master admin only if (!session.isAdmin()) throw new IllegalStateException("no permission"); if (!tableRegistry.exists(tableName)) throw new IllegalStateException("table not found"); Account account = ensureAccount(loginName); if (account.getReadableTables().contains(tableName)) return; account.getReadableTables().add(tableName); updateAccount(account); for (AccountEventListener listener : accountListeners) { try { listener.onGrantPrivilege(session, loginName, tableName, permissions); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } @Override public void revokePrivilege(Session session, String loginName, String tableName, Permission... permissions) { verifyNotNull(session, "session"); verifyNotNull(loginName, "login name"); verifyNotNull(tableName, "table name"); if (permissions.length == 0) return; checkAccountIncludingExternal(loginName); if (!sessions.containsKey(session.getGuid())) throw new IllegalStateException("invalid session"); // master admin only if (!session.isAdmin()) throw new IllegalStateException("no permission"); if (!tableRegistry.exists(tableName)) throw new IllegalStateException("table not found"); Account account = ensureAccount(loginName); if (!account.getReadableTables().contains(tableName)) return; account.getReadableTables().remove(tableName); updateAccount(account); for (AccountEventListener listener : accountListeners) { try { listener.onRevokePrivilege(session, loginName, tableName, permissions); } catch (Throwable t) { logger.warn("araqne logdb: account event listener should not throw any exception", t); } } } private Account ensureAccount(String loginName) { Account account = localAccounts.get(loginName); if (account != null) return account; if (selectedExternalAuth != null) { ExternalAuthService auth = authServices.get(selectedExternalAuth); if (auth != null && auth.verifyUser(loginName)) { account = new Account(); account.setLoginName(loginName); account.setAuthServiceName(selectedExternalAuth); localAccounts.put(loginName, account); return account; } } throw new IllegalStateException("account not found: " + loginName); } private void checkAccountIncludingExternal(String loginName) { if (!localAccounts.containsKey(loginName)) { if (selectedExternalAuth != null) { ExternalAuthService auth = authServices.get(selectedExternalAuth); if (auth != null && auth.verifyUser(loginName)) return; } throw new IllegalStateException("account not found"); } } private void verifyNotNull(Object o, String name) { if (o == null) throw new IllegalArgumentException(name + " should not be null"); } private String randomSalt(int saltLength) { StringBuilder salt = new StringBuilder(saltLength); Random rand = new Random(); for (int i = 0; i < saltLength; i++) salt.append(SALT_CHARS[rand.nextInt(SALT_CHARS.length)]); return salt.toString(); } @Override public ExternalAuthService getUsingAuthService() { if (selectedExternalAuth == null) return null; return authServices.get(selectedExternalAuth); } @Override public void useAuthService(String name) { if (name != null && !authServices.containsKey(name)) throw new IllegalStateException("external auth service not found: " + name); ConfigDatabase db = conf.ensureDatabase(DB_NAME); ConfigCollection col = db.ensureCollection("global_config"); Config c = col.findOne(null); if (c != null) { @SuppressWarnings("unchecked") Map<String, Object> doc = (Map<String, Object>) c.getDocument(); doc.put("external_auth", name); c.setDocument(doc); c.update(); } else { Map<String, Object> doc = new HashMap<String, Object>(); doc.put("external_auth", name); col.add(doc); } selectedExternalAuth = name; } @Override public List<ExternalAuthService> getAuthServices() { return new ArrayList<ExternalAuthService>(authServices.values()); } @Override public ExternalAuthService getAuthService(String name) { return authServices.get(name); } @Override public void registerAuthService(ExternalAuthService auth) { ExternalAuthService old = authServices.putIfAbsent(auth.getName(), auth); if (old != null) throw new IllegalStateException("duplicated logdb auth service name: " + auth.getName()); } @Override public void unregisterAuthService(ExternalAuthService auth) { authServices.remove(auth.getName(), auth); } @Override public void addListener(AccountEventListener listener) { accountListeners.add(listener); } @Override public void removeListener(AccountEventListener listener) { accountListeners.remove(listener); } @Override public void addListener(SessionEventListener listener) { sessionListeners.add(listener); } @Override public void removeListener(SessionEventListener listener) { sessionListeners.remove(listener); } @Override public void onCreate(TableSchema schema) { } @Override public void onAlter(TableSchema oldSchema, TableSchema newSchema) { } @Override public void onDrop(TableSchema schema) { String tableName = schema.getName(); // remove all granted permissions for this table for (Account account : localAccounts.values()) { if (account.getReadableTables().contains(tableName)) { ArrayList<String> copy = new ArrayList<String>(account.getReadableTables()); copy.remove(tableName); account.setReadableTables(copy); updateAccount(account); } } for (SecurityGroup group : securityGroups.values()) { if (group.getReadableTables().contains(tableName)) { group.getReadableTables().remove(tableName); updateSecurityGroup(null, group); } } } @Override public String getInstanceGuid() { return instanceGuid; } @Override public void setInstanceGuid(String guid) { ConfigDatabase db = conf.ensureDatabase(DB_NAME); ConfigCollection col = db.ensureCollection("global_config"); Config c = col.findOne(null); if (c != null) { @SuppressWarnings("unchecked") Map<String, Object> doc = (Map<String, Object>) c.getDocument(); doc.put("instance_guid", guid); c.setDocument(doc); c.update(); } else { Map<String, Object> doc = new HashMap<String, Object>(); doc.put("instance_guid", guid); col.add(doc); } this.instanceGuid = guid; } }