first try of a HBase storage for Vysper
Project: http://git-wip-us.apache.org/repos/asf/mina-vysper/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-vysper/commit/843a6d6c Tree: http://git-wip-us.apache.org/repos/asf/mina-vysper/tree/843a6d6c Diff: http://git-wip-us.apache.org/repos/asf/mina-vysper/diff/843a6d6c Branch: refs/heads/master Commit: 843a6d6c1ee63a08d5830722e9c8f4607eb122c9 Parents: b055a22 Author: Bernd Fondermann <[email protected]> Authored: Fri Jul 5 22:11:45 2013 +0200 Committer: Bernd Fondermann <[email protected]> Committed: Fri Jul 5 22:11:45 2013 +0200 ---------------------------------------------------------------------- server/storage/hbase/pom.xml | 6 + .../vysper/storage/hbase/HBaseStorage.java | 145 +++++++++++++++ .../storage/hbase/HBaseStorageException.java | 21 +++ .../hbase/HBaseStorageProviderRegistry.java | 37 ++++ .../apache/vysper/storage/hbase/HBaseUtils.java | 34 ++++ .../hbase/roster/HBaseRosterManager.java | 177 +++++++++++++++++++ .../storage/hbase/user/HBaseUserManagement.java | 144 +++++++++++++++ 7 files changed, 564 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/pom.xml ---------------------------------------------------------------------- diff --git a/server/storage/hbase/pom.xml b/server/storage/hbase/pom.xml index 0a9ba92..480bd57 100644 --- a/server/storage/hbase/pom.xml +++ b/server/storage/hbase/pom.xml @@ -42,6 +42,12 @@ </dependency> <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-core</artifactId> + <version>1.0.4</version> + </dependency> + + <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase</artifactId> <version>0.94.7</version> http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java new file mode 100644 index 0000000..b2faeb9 --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorage.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.vysper.storage.hbase; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.MasterNotRunningException; +import org.apache.hadoop.hbase.ZooKeeperConnectionException; +import org.apache.hadoop.hbase.client.Get; +import org.apache.hadoop.hbase.client.HBaseAdmin; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.client.HTablePool; +import org.apache.hadoop.hbase.client.Result; +import org.apache.vysper.xmpp.addressing.Entity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import static org.apache.vysper.storage.hbase.HBaseUtils.*; + +/** + * back-end adaptor for HBase + * @author The Apache MINA Project ([email protected]) + */ +public class HBaseStorage { + + final Logger LOG = LoggerFactory.getLogger(HBaseStorage.class); + + public static final String TABLE_NAME_USER = "vysper_user"; + public static final String COLUMN_FAMILY_NAME_BASIC = "bsc"; + public static final String COLUMN_FAMILY_NAME_CONTACT = "cct"; + public static final byte[] COLUMN_FAMILY_NAME_CONTACT_BYTES = COLUMN_FAMILY_NAME_CONTACT.getBytes(); + public static final String COLUMN_FAMILY_NAME_ROSTER = "rst"; + public static final byte[] COLUMN_FAMILY_NAME_ROSTER_BYTES = COLUMN_FAMILY_NAME_ROSTER.getBytes(); + + protected static HBaseStorage hbaseStorageSingleton; + + protected HBaseStorage() { + super(); + // protected + } + + public static HBaseStorage getInstance() throws HBaseStorageException { + if (hbaseStorageSingleton != null) return hbaseStorageSingleton; + synchronized (HBaseStorage.class) { + if (hbaseStorageSingleton == null) hbaseStorageSingleton = new HBaseStorage(); + hbaseStorageSingleton.init(); + return hbaseStorageSingleton; + } + } + + protected Configuration hbaseConfiguration = null; + protected HBaseAdmin hbaseAdmin; + protected HTablePool tablePool; + + public void init() throws HBaseStorageException { + try { + hbaseConfiguration = HBaseConfiguration.create(); + } catch (Exception e) { + throw new HBaseStorageException("failed to load HBase configuration from file hbase-site.xml"); + } + final int size = hbaseConfiguration.size(); + if (size == 0) throw new HBaseStorageException("HBase configuration is empty"); + + try { + connectHBase(); + } catch (HBaseStorageException e) { + LOG.error("connection to HBase failed", e); + throw e; + } + } + + protected void connectHBase() throws HBaseStorageException { + try { + LOG.info("connecting to HBase..."); + hbaseAdmin = new HBaseAdmin(hbaseConfiguration); + tablePool = new HTablePool(hbaseConfiguration, Integer.MAX_VALUE); + LOG.info("HBase connected."); + } catch (MasterNotRunningException e) { + throw new HBaseStorageException("failed connecting to HBase Master Server", e); + } catch (ZooKeeperConnectionException e) { + throw new HBaseStorageException("failed connecting to HBase Zookeeper Cluster", e); + } + } + + public HTableInterface getTable(String tableName) { + return tablePool.getTable(tableName); + } + + public Result getEntityRow(Entity entity, String... columnFamilyNames) { + if (columnFamilyNames == null || columnFamilyNames.length == 0) { + columnFamilyNames = new String[]{COLUMN_FAMILY_NAME_CONTACT}; + } + + final HTableInterface userTable = getTable(TABLE_NAME_USER); + try { + final Get get = new Get(entityAsBytes(entity.getBareJID())); + for (String columnFamilyName : columnFamilyNames) { + get.addFamily(asBytes(columnFamilyName)); + } + final Result result = userTable.get(get); + return result; + } catch (IOException e) { + e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. + return null; + } finally { + putTable(userTable); + } + } + + public void putTable(HTableInterface userTable) { + if (userTable == null) return; + try { + userTable.close(); + } catch (IOException e) { + String tableName = "unknown"; + try { + tableName = new String(userTable.getTableName(), "UTF-8"); + } catch (UnsupportedEncodingException e1) { + e1.printStackTrace(); // encoding exceptions are killing me + } + LOG.warn("failed to return table " + tableName + " to pool"); + } + } + +} http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java new file mode 100644 index 0000000..86ad141 --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageException.java @@ -0,0 +1,21 @@ +package org.apache.vysper.storage.hbase; + +/** + */ +public class HBaseStorageException extends Exception { + public HBaseStorageException() { + super(); + } + + public HBaseStorageException(String s) { + super(s); + } + + public HBaseStorageException(String s, Throwable throwable) { + super(s, throwable); + } + + public HBaseStorageException(Throwable throwable) { + super(throwable); + } +} http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java new file mode 100644 index 0000000..32ad50a --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseStorageProviderRegistry.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.vysper.storage.hbase; + +import org.apache.vysper.storage.OpenStorageProviderRegistry; +import org.apache.vysper.storage.hbase.roster.HBaseRosterManager; +import org.apache.vysper.storage.hbase.user.HBaseUserManagement; + +/** + * + * @author The Apache MINA Project ([email protected]) + */ +public class HBaseStorageProviderRegistry extends OpenStorageProviderRegistry { + + public HBaseStorageProviderRegistry() throws HBaseStorageException { + add(new HBaseUserManagement(HBaseStorage.getInstance())); + add(new HBaseRosterManager(HBaseStorage.getInstance())); + } + +} http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java new file mode 100644 index 0000000..a0d8f49 --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/HBaseUtils.java @@ -0,0 +1,34 @@ +package org.apache.vysper.storage.hbase; + +import org.apache.vysper.xmpp.addressing.Entity; + +import java.io.UnsupportedEncodingException; + +/** + */ +public class HBaseUtils { + + public static byte[] asBytes(String str) { + if (str == null) return null; + try { + return str.getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); // won't happen! UTF-8 is supported + return null; + } + } + + public static byte[] entityAsBytes(Entity entity) { + if (entity == null) return null; + return asBytes(entity.getFullQualifiedName()); + } + + public static String toStr(byte[] bytes) { + if (bytes == null) return null; + try { + return new String(bytes, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return null; // will not happen for UTF-8 + } + } +} http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java new file mode 100644 index 0000000..a7417d6 --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/roster/HBaseRosterManager.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.vysper.storage.hbase.roster; + +import org.apache.hadoop.hbase.client.Delete; +import org.apache.hadoop.hbase.client.HTableInterface; +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.vysper.storage.hbase.HBaseStorage; +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.addressing.EntityFormatException; +import org.apache.vysper.xmpp.addressing.EntityImpl; +import org.apache.vysper.xmpp.modules.roster.AskSubscriptionType; +import org.apache.vysper.xmpp.modules.roster.MutableRoster; +import org.apache.vysper.xmpp.modules.roster.Roster; +import org.apache.vysper.xmpp.modules.roster.RosterException; +import org.apache.vysper.xmpp.modules.roster.RosterGroup; +import org.apache.vysper.xmpp.modules.roster.RosterItem; +import org.apache.vysper.xmpp.modules.roster.SubscriptionType; +import org.apache.vysper.xmpp.modules.roster.persistence.AbstractRosterManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableMap; + +import static org.apache.vysper.storage.hbase.HBaseStorage.*; +import static org.apache.vysper.storage.hbase.HBaseUtils.asBytes; +import static org.apache.vysper.storage.hbase.HBaseUtils.entityAsBytes; +import static org.apache.vysper.storage.hbase.HBaseUtils.toStr; + +/** + * @author The Apache MINA Project ([email protected]) + */ +public class HBaseRosterManager extends AbstractRosterManager { + + final Logger LOG = LoggerFactory.getLogger(HBaseRosterManager.class); + + public static final String COLUMN_PREFIX_NAME = "n:"; + public static final String COLUMN_PREFIX_TYPE = "t:"; + public static final String COLUMN_PREFIX_ASKTYPE = "a:"; + + protected HBaseStorage hBaseStorage; + + public HBaseRosterManager(HBaseStorage hBaseStorage) { + this.hBaseStorage = hBaseStorage; + } + + @Override + protected Roster retrieveRosterInternal(Entity bareJid) { + final Result entityRow = hBaseStorage.getEntityRow(bareJid); + + MutableRoster roster = new MutableRoster(); + + final NavigableMap<byte[],byte[]> contacts = entityRow.getFamilyMap(COLUMN_FAMILY_NAME_CONTACT_BYTES); + for (byte[] contactBytes : contacts.keySet()) { + String contactAsString = null; + EntityImpl contactJID = null; + try { + contactAsString = new String(contactBytes, "UTF-8"); + contactJID = EntityImpl.parse(contactAsString); + } catch (Exception e) { + LOG.warn("failed to read contact identified by '{}' for user {}", bareJid, contactAsString); + continue; + } + + final NavigableMap<byte[],byte[]> contactDetails = entityRow.getFamilyMap(COLUMN_FAMILY_NAME_ROSTER_BYTES); + String name = toStr(contactDetails.get(asBytes(COLUMN_PREFIX_NAME + contactAsString))); + String typeString = toStr(contactDetails.get(asBytes(COLUMN_PREFIX_TYPE + contactAsString))); + String askTypeString = toStr(contactDetails.get(asBytes(COLUMN_PREFIX_ASKTYPE + contactAsString))); + + SubscriptionType subscriptionType = null; + try { + subscriptionType = SubscriptionType.valueOf(typeString == null ? "NONE" : typeString.toUpperCase()); + } catch (IllegalArgumentException e) { + LOG.warn("when loading roster for user " + bareJid + ", contact " + contactJID + " misses a subscription type"); + } + + AskSubscriptionType askSubscriptionType = AskSubscriptionType.NOT_SET; + try { + if (askTypeString != null) + askSubscriptionType = AskSubscriptionType.valueOf(askTypeString); + } catch (IllegalArgumentException e) { + LOG.warn("when loading roster for user " + bareJid.getFullQualifiedName() + ", contact " + + contactJID.getFullQualifiedName() + ", the ask subscription type is unparsable. skipping!"); + continue; // don't return it, don't set a default! + } + + List<RosterGroup> groups = new ArrayList<RosterGroup>(); + // TODO read groups + + RosterItem item = new RosterItem(contactJID, name, subscriptionType, askSubscriptionType, groups); + LOG.info("item loaded for " + bareJid.getFullQualifiedName() + ": " + item.toString()); + roster.addItem(item); + } + return roster; + } + + @Override + protected Roster addNewRosterInternal(Entity jid) { + return new MutableRoster(); + } + + @Override + public void addContact(Entity jid, RosterItem rosterItem) throws RosterException { + if (jid == null) + throw new RosterException("jid not provided"); + if (rosterItem.getJid() == null) + throw new RosterException("contact jid not provided"); + + Entity contactJid = rosterItem.getJid().getBareJID(); + final String contactIdentifier = contactJid.getFullQualifiedName(); + + // prepare contact entries + final Put put = new Put(entityAsBytes(jid.getBareJID())); + put.add(COLUMN_FAMILY_NAME_CONTACT_BYTES, asBytes(contactIdentifier), asBytes(rosterItem.getSubscriptionType().value())); + put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_NAME + contactIdentifier), asBytes(rosterItem.getName())); + put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_TYPE + contactIdentifier), asBytes(rosterItem.getSubscriptionType().value())); + put.add(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_ASKTYPE + contactIdentifier), asBytes(rosterItem.getAskSubscriptionType().value())); + + final HTableInterface userTable = hBaseStorage.getTable(TABLE_NAME_USER); + try { + userTable.put(put); + LOG.info("contact {} saved to HBase for user {}", rosterItem.getJid(), jid); + } catch (IOException e) { + throw new RosterException("failed to add contact node to roster for user = " + jid.getFullQualifiedName() + + " and contact jid = " + rosterItem.getJid().getFullQualifiedName(), e); + } finally { + hBaseStorage.putTable(userTable); + } + } + + @Override + public void removeContact(Entity jidUser, Entity jidContact) throws RosterException { + if (jidUser == null) + throw new RosterException("jid not provided"); + if (jidContact == null) + throw new RosterException("contact jid not provided"); + + final String contactIdentifier = jidContact.getFullQualifiedName(); + final Delete delete = new Delete(entityAsBytes(jidUser.getBareJID())); + delete.deleteColumns(COLUMN_FAMILY_NAME_CONTACT_BYTES, asBytes(contactIdentifier)); + delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_NAME + contactIdentifier)); + delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_TYPE + contactIdentifier)); + delete.deleteColumns(COLUMN_FAMILY_NAME_ROSTER_BYTES, asBytes(COLUMN_PREFIX_ASKTYPE + contactIdentifier)); + + final HTableInterface userTable = hBaseStorage.getTable(TABLE_NAME_USER); + try { + userTable.delete(delete); + LOG.info("contact {} removed from HBase for user {}", jidContact, jidUser); + } catch (IOException e) { + throw new RosterException("failed to add contact node to roster for user = " + jidUser.getFullQualifiedName() + + " and contact jid = " + jidContact.getFullQualifiedName(), e); + } finally { + hBaseStorage.putTable(userTable); + } + } +} http://git-wip-us.apache.org/repos/asf/mina-vysper/blob/843a6d6c/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java ---------------------------------------------------------------------- diff --git a/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java new file mode 100644 index 0000000..72a2b0e --- /dev/null +++ b/server/storage/hbase/src/main/java/org/apache/vysper/storage/hbase/user/HBaseUserManagement.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.vysper.storage.hbase.user; + +import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.Result; +import org.apache.vysper.storage.hbase.HBaseStorage; +import org.apache.vysper.xmpp.addressing.Entity; +import org.apache.vysper.xmpp.addressing.EntityFormatException; +import org.apache.vysper.xmpp.addressing.EntityImpl; +import org.apache.vysper.xmpp.authentication.AccountCreationException; +import org.apache.vysper.xmpp.authentication.AccountManagement; +import org.apache.vysper.xmpp.authentication.UserAuthentication; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.security.MessageDigest; + +import static org.apache.vysper.storage.hbase.HBaseStorage.COLUMN_FAMILY_NAME_BASIC; +import static org.apache.vysper.storage.hbase.HBaseUtils.entityAsBytes; + +/** + * + * @author The Apache MINA Project ([email protected]) + */ +public class HBaseUserManagement implements UserAuthentication, AccountManagement { + + final Logger logger = LoggerFactory.getLogger(HBaseUserManagement.class); + + public static final byte[] PASSWORD_COLUMN = "pwd".getBytes(); + + protected HBaseStorage hBaseStorage; + + /** + * the salt before encrypting all passwords + * change once before creating the first account + */ + private String encryptionSalt = "saltetForVysper"; + + /** + * the number of hashing rounds for encrypting all passwords + * change once before creating the first account + */ + private int hashingRounds = 5; + + public HBaseUserManagement(HBaseStorage hBaseStorage) { + this.hBaseStorage = hBaseStorage; + } + + public boolean verifyCredentials(Entity jid, String passwordCleartext, Object credentials) { + if (passwordCleartext == null) + return false; + try { + final Result entityRow = hBaseStorage.getEntityRow(jid, COLUMN_FAMILY_NAME_BASIC); + if (entityRow == null) return false; + + final String encryptedGivenPassword = encryptPassword(passwordCleartext); + final byte[] passwordSavedBytes = entityRow.getValue(COLUMN_FAMILY_NAME_BASIC.getBytes(), PASSWORD_COLUMN); + return new String(passwordSavedBytes, "UTF-8").equals(encryptedGivenPassword); + } catch (Exception e) { + return false; + } + } + + protected String encryptPassword(String passwordCleartext) { + if (passwordCleartext == null) passwordCleartext = ""; + try { + passwordCleartext = passwordCleartext + encryptionSalt; + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + int rounds = Math.max(1, hashingRounds); + byte[] pwdBytes = passwordCleartext.getBytes("UTF-8"); + for (int i = 0; i < rounds; i++) { + pwdBytes = digest.digest(pwdBytes); + } + return new String(pwdBytes, "UTF-8"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public boolean verifyCredentials(String username, String passwordCleartext, Object credentials) { + try { + return verifyCredentials(EntityImpl.parse(username), passwordCleartext, credentials); + } catch (EntityFormatException e) { + return false; + } + } + + public boolean verifyAccountExists(Entity jid) { + final Result entityRow = hBaseStorage.getEntityRow(jid, COLUMN_FAMILY_NAME_BASIC); + return !entityRow.isEmpty(); + } + + public void addUser(Entity username, String password) throws AccountCreationException { + // if already existent, don't create, throw error + if (verifyAccountExists(username)) { + throw new AccountCreationException("account already exists: " + username.getFullQualifiedName()); + } + + // now, finally, create + try { + // row is created when first column for it is created. + setPasswordInHBase(username, password); + logger.info("account created in HBase for " + username); + } catch (Exception e) { + throw new AccountCreationException("failed to creating in HBase account " + username, e); + } + + } + + private void setPasswordInHBase(Entity username, String password) throws IOException { + final Put put = new Put(entityAsBytes(username)); + put.add(COLUMN_FAMILY_NAME_BASIC.getBytes(), PASSWORD_COLUMN, encryptPassword(password).getBytes("UTF-8")); + hBaseStorage.getTable(HBaseStorage.TABLE_NAME_USER).put(put); + } + + public void changePassword(Entity username, String password) throws AccountCreationException { + try { + setPasswordInHBase(username, password); + logger.info("password changed for " + username); + } catch (Exception e) { + throw new AccountCreationException("failed to change password for " + username, e); + } + } +}
