diff --git a/CHANGES.txt b/CHANGES.txt index fec387aba6a..d8bcc08a306 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -411,6 +411,7 @@ Release 0.21.0 - Unreleased HBASE-2070 Collect HLogs and delete them after a period of time HBASE-2221 MR to copy a table HBASE-2257 [stargate] multiuser mode + HBASE-2263 [stargate] multiuser mode: authenticator for zookeeper OPTIMIZATIONS HBASE-410 [testing] Speed up the test suite diff --git a/contrib/stargate/src/main/java/org/apache/hadoop/hbase/stargate/auth/ZooKeeperAuthenticator.java b/contrib/stargate/src/main/java/org/apache/hadoop/hbase/stargate/auth/ZooKeeperAuthenticator.java new file mode 100644 index 00000000000..abf209bdd74 --- /dev/null +++ b/contrib/stargate/src/main/java/org/apache/hadoop/hbase/stargate/auth/ZooKeeperAuthenticator.java @@ -0,0 +1,127 @@ +/* + * Copyright 2010 The Apache Software Foundation + * + * 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.hadoop.hbase.stargate.auth; + +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.zookeeper.ZooKeeperWrapper; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.WatchedEvent; +import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.data.Stat; + +import org.json.JSONObject; + +/** + * A simple authenticator module for ZooKeeper. + *
+ *   /stargate/
+ *     users/
+ *       <token>
+ * Where <token> is a JSON formatted user record with the keys + * 'name' (String, required), 'token' (String, optional), 'admin' (boolean, + * optional), and 'disabled' (boolean, optional). + */ +public class ZooKeeperAuthenticator extends Authenticator { + + static final String USERS_ROOT_ZNODE = "/stargate/users"; + + ZooKeeperWrapper wrapper; + + private boolean ensureParentExists(final String znode) { + int index = znode.lastIndexOf("/"); + if (index <= 0) { // Parent is root, which always exists. + return true; + } + return ensureExists(znode.substring(0, index)); + } + + private boolean ensureExists(final String znode) { + ZooKeeper zk = wrapper.getZooKeeper(); + try { + Stat stat = zk.exists(znode, false); + if (stat != null) { + return true; + } + zk.create(znode, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + return true; + } catch (KeeperException.NodeExistsException e) { + return true; // ok, move on. + } catch (KeeperException.NoNodeException e) { + return ensureParentExists(znode) && ensureExists(znode); + } catch (KeeperException e) { + } catch (InterruptedException e) { + } + return false; + } + + /** + * Constructor + * @param conf + * @throws IOException + */ + public ZooKeeperAuthenticator(Configuration conf) throws IOException { + this(new ZooKeeperWrapper(conf, new Watcher() { + public void process(WatchedEvent event) { } + })); + ensureExists(USERS_ROOT_ZNODE); + } + + /** + * Constructor + * @param wrapper + */ + public ZooKeeperAuthenticator(ZooKeeperWrapper wrapper) { + this.wrapper = wrapper; + } + + @Override + public User getUserForToken(String token) throws IOException { + ZooKeeper zk = wrapper.getZooKeeper(); + try { + byte[] data = zk.getData(USERS_ROOT_ZNODE + "/" + token, null, null); + if (data == null) { + return null; + } + JSONObject o = new JSONObject(Bytes.toString(data)); + if (!o.has("name")) { + throw new IOException("invalid record, missing 'name'"); + } + String name = o.getString("name"); + boolean admin = false; + if (o.has("admin")) { admin = o.getBoolean("admin"); } + boolean disabled = false; + if (o.has("disabled")) { disabled = o.getBoolean("disabled"); } + return new User(name, token, admin, disabled); + } catch (KeeperException.NoNodeException e) { + return null; + } catch (Exception e) { + throw new IOException(e); + } + } + +} diff --git a/contrib/stargate/src/test/java/org/apache/hadoop/hbase/stargate/auth/TestZooKeeperAuthenticator.java b/contrib/stargate/src/test/java/org/apache/hadoop/hbase/stargate/auth/TestZooKeeperAuthenticator.java new file mode 100644 index 00000000000..195d8e433da --- /dev/null +++ b/contrib/stargate/src/test/java/org/apache/hadoop/hbase/stargate/auth/TestZooKeeperAuthenticator.java @@ -0,0 +1,110 @@ +/* + * Copyright 2010 The Apache Software Foundation + * + * 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.hadoop.hbase.stargate.auth; + +import org.apache.hadoop.hbase.stargate.MiniClusterTestBase; +import org.apache.hadoop.hbase.util.Bytes; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.ZooDefs.Ids; + +import org.json.JSONStringer; + +public class TestZooKeeperAuthenticator extends MiniClusterTestBase { + + static final String UNKNOWN_TOKEN = "00000000000000000000000000000000"; + static final String ADMIN_TOKEN = "e998efffc67c49c6e14921229a51b7b3"; + static final String ADMIN_USERNAME = "testAdmin"; + static final String USER_TOKEN = "da4829144e3a2febd909a6e1b4ed7cfa"; + static final String USER_USERNAME = "testUser"; + static final String DISABLED_TOKEN = "17de5b5db0fd3de0847bd95396f36d92"; + static final String DISABLED_USERNAME = "disabledUser"; + + ZooKeeperAuthenticator authenticator; + + @Override + public void setUp() throws Exception { + authenticator = new ZooKeeperAuthenticator(conf); + ZooKeeper zk = authenticator.wrapper.getZooKeeper(); + if (zk.exists(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" + + ADMIN_TOKEN, null) == null) { + zk.create(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" + ADMIN_TOKEN, + Bytes.toBytes(new JSONStringer() + .object() + .key("name").value(ADMIN_USERNAME) + .key("admin").value(true) + .endObject().toString()), + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + if (zk.exists(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" + + USER_TOKEN, null) == null) { + zk.create(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" + USER_TOKEN, + Bytes.toBytes(new JSONStringer() + .object() + .key("name").value(USER_USERNAME) + .key("disabled").value(false) + .endObject().toString()), + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + if (zk.exists(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" + + DISABLED_TOKEN, null) == null) { + zk.create(ZooKeeperAuthenticator.USERS_ROOT_ZNODE + "/" +DISABLED_TOKEN, + Bytes.toBytes(new JSONStringer() + .object() + .key("name").value(DISABLED_USERNAME) + .key("admin").value(false) + .key("disabled").value(true) + .endObject().toString()), + Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + } + } + + public void testGetUserUnknown() throws Exception { + User user = authenticator.getUserForToken(UNKNOWN_TOKEN); + assertNull(user); + } + + public void testGetAdminUser() throws Exception { + User user = authenticator.getUserForToken(ADMIN_TOKEN); + assertNotNull(user); + assertEquals(user.getName(), ADMIN_USERNAME); + assertTrue(user.isAdmin()); + assertFalse(user.isDisabled()); + } + + public void testGetPlainUser() throws Exception { + User user = authenticator.getUserForToken(USER_TOKEN); + assertNotNull(user); + assertEquals(user.getName(), USER_USERNAME); + assertFalse(user.isAdmin()); + assertFalse(user.isDisabled()); + } + + public void testGetDisabledUser() throws Exception { + User user = authenticator.getUserForToken(DISABLED_TOKEN); + assertNotNull(user); + assertEquals(user.getName(), DISABLED_USERNAME); + assertFalse(user.isAdmin()); + assertTrue(user.isDisabled()); + } + +}