/* * Copyright 2010-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file 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 com.amazonaws.tvm.identity; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import com.amazonaws.AmazonClientException; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClient; import com.amazonaws.services.dynamodbv2.model.AttributeDefinition; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.CreateTableRequest; import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest; import com.amazonaws.services.dynamodbv2.model.DescribeTableRequest; import com.amazonaws.services.dynamodbv2.model.DescribeTableResult; import com.amazonaws.services.dynamodbv2.model.GetItemRequest; import com.amazonaws.services.dynamodbv2.model.KeySchemaElement; import com.amazonaws.services.dynamodbv2.model.KeyType; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException; import com.amazonaws.services.dynamodbv2.model.ScanRequest; import com.amazonaws.services.dynamodbv2.model.ScanResult; import com.amazonaws.tvm.Utilities; import com.amazonaws.tvm.Configuration; import com.amazonaws.tvm.identity.exception.DataAccessException; import com.amazonaws.tvm.TokenVendingMachineLogger; /** * This class is used store and authenticate users. All users and there * username/password information is stored in a DynamoDB table. */ public class UserAuthentication { private static final Logger log = TokenVendingMachineLogger.getLogger(); /** * Constant for the table name used to store the identities. */ private static final String USER_TABLE = Configuration.USERS_TABLE; /** * Constant for the username attribute */ private static final String ATTRIBUTE_USERNAME = "username"; /** * Constant for the hash of password attribute */ private static final String ATTRIBUTE_HASH_SALTED_PASSWORD = "hash_salted_password"; /** * Constant for the enabled attribute */ private static final String ATTRIBUTE_ENABLED = "enabled"; private final AmazonDynamoDBClient ddb; /** * Looks up table name and creates one if it does not exist */ public UserAuthentication() { ddb = new AmazonDynamoDBClient(new BasicAWSCredentials(Configuration.AWS_ACCESS_KEY_ID, Configuration.AWS_SECRET_KEY)); ddb.setEndpoint(Configuration.DYNAMODB_ENDPOINT); try { if (!doesTableExist(USER_TABLE)) { createIdentityTable(); } } catch (DataAccessException e) { throw new RuntimeException("Failed to create device table.", e); } } /** * Returns the list of usernames stored in the identity table. * * @return list of existing usernames in DynamoDB table */ public List<String> listUsers() { List<String> users = new ArrayList<String>(1000); ScanResult result = ddb.scan(new ScanRequest().withTableName(USER_TABLE).withLimit(1000)); for (Map<String, AttributeValue> item : result.getItems()) { String s = ""; for (Entry<String, AttributeValue> entry : item.entrySet()) { s += " ** " + entry.getKey() + " = " + entry.getValue().getS(); } users.add(s); } return users; } /** * Attempts to register the username, password combination. Checks if * username not already exist. Returns true if successful, false otherwise. * * @param username * Unique user identifier * @param password * user password * @param uri * endpoint URI * @return true if successful, false otherwise. * @throws DataAccessException */ public boolean registerUser(String username, String password, String uri) throws DataAccessException { if (checkUsernameExists(username)) { return false; } storeUser(username, password, uri); return true; } /** * Deletes the specified username from the identity table. * * @param username * Unique user identifier * @throws DataAccessException */ public void deleteUser(String username) throws DataAccessException { HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>(); key.put(ATTRIBUTE_USERNAME, new AttributeValue().withS(username)); DeleteItemRequest deleteItemRequest = new DeleteItemRequest() .withTableName(USER_TABLE) .withKey(key); try { ddb.deleteItem(deleteItemRequest); } catch (AmazonClientException e) { throw new DataAccessException("Failed to delete user: " + username, e); } } /** * Authenticates the given username, password combination. Hash of password * is matched against the hash value stored for password field * * @param username * Unique user identifier * @param password * user password * @param uri * endpoint URI * @return true if authentication was successful, false otherwise * @throws DataAccessException */ public boolean authenticateUser(String username, String password, String uri) throws DataAccessException { if (null == username || null == password) { return false; } UserInfo user = getUserInfo(username); if (user == null) { return false; } String hashedSaltedPassword = Utilities.getSaltedPassword(username, uri, password); return hashedSaltedPassword.equals(user.getHashedPassword()); } /** * Authenticates the given username, signature combination. A signature is * generated and matched against the given signature. If they match then * returns true. * * @param username * Unique user identifier * @param timestamp * Timestamp of the request * @param signature * Signature of the request * @return true if authentication was successful, false otherwise * @throws DataAccessException */ public boolean authenticateUserSignature(String username, String timestamp, String signature) throws DataAccessException { UserInfo user = getUserInfo(username); if (user == null) { return false; } String computedSignature = Utilities.identitySign(timestamp, user.getHashedPassword()); return Utilities.slowStringComparison(signature, computedSignature); } /** * Store the username, password combination in the Identity table. The * username will represent the item name and the item will contain a * attributes password and userid. * * @param username * Unique user identifier * @param password * user password * @param uri * endpoint URI */ protected void storeUser(String username, String password, String uri) throws DataAccessException { if (null == username || null == password) { return; } String hashedSaltedPassword = Utilities.getSaltedPassword(username, uri, password); Map<String, AttributeValue> item = new HashMap<String, AttributeValue>(); item.put(ATTRIBUTE_USERNAME, new AttributeValue().withS(username)); item.put(ATTRIBUTE_HASH_SALTED_PASSWORD, new AttributeValue().withS(hashedSaltedPassword)); item.put(ATTRIBUTE_ENABLED, new AttributeValue().withS("true")); PutItemRequest putItemRequest = new PutItemRequest() .withTableName(USER_TABLE) .withItem(item); try { ddb.putItem(putItemRequest); } catch (AmazonClientException e) { throw new DataAccessException("Failed to store user: " + username, e); } } /** * Used to create the Identity Table. This function only needs to be called * once. */ protected void createIdentityTable() throws DataAccessException { ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput() .withReadCapacityUnits(10L) .withWriteCapacityUnits(5L); ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>(); attributeDefinitions .add(new AttributeDefinition().withAttributeName(ATTRIBUTE_USERNAME).withAttributeType("S")); ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); tableKeySchema.add(new KeySchemaElement().withAttributeName(ATTRIBUTE_USERNAME).withKeyType(KeyType.HASH)); CreateTableRequest createTableRequest = new CreateTableRequest() .withTableName(USER_TABLE) .withProvisionedThroughput(provisionedThroughput) .withAttributeDefinitions(attributeDefinitions) .withKeySchema(tableKeySchema); try { ddb.createTable(createTableRequest); } catch (AmazonClientException e) { throw new DataAccessException("Failed to create table: " + USER_TABLE, e); } } /** * Checks to see if given tableName exist * * @param tableName * The table name to check * @return true if tableName exist, false otherwise */ protected boolean doesTableExist(String tableName) throws DataAccessException { try { DescribeTableRequest request = new DescribeTableRequest().withTableName(USER_TABLE); DescribeTableResult result = ddb.describeTable(request); return "ACTIVE".equals(result.getTable().getTableStatus()); } catch (ResourceNotFoundException e) { return false; } catch (AmazonClientException e) { throw new DataAccessException("Failed to get status of table: " + tableName, e); } } /** * Get user info for the username * * @param username * Unique user identifier * @return UserInfo for the user, null otherwise */ public UserInfo getUserInfo(String username) throws DataAccessException { HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>(); key.put(ATTRIBUTE_USERNAME, new AttributeValue().withS(username)); GetItemRequest getItemRequest = new GetItemRequest() .withTableName(USER_TABLE) .withKey(key); try { return UserInfo.fromData(ddb.getItem(getItemRequest).getItem()); } catch (AmazonClientException e) { throw new DataAccessException("Failed to get item username: " + username, e); } } /** * Checks to see if the username already exist in the user table * * @param username * Unique user identifier * @return true if username already exist, false otherwise * @throws DataAccessException */ public boolean checkUsernameExists(String username) throws DataAccessException { return getUserInfo(username) != null; } /** * A class that represents the item stored in user table. */ public static class UserInfo { private final String username; private final String hashedPassword; private final String enabled; public UserInfo(String username, String hashedPassword, String enabled) { this.username = username; this.hashedPassword = hashedPassword; this.enabled = enabled; } public String getUsername() { return username; } public String getHashedPassword() { return hashedPassword; } public String getEnabled() { return enabled; } public static final UserInfo fromData(Map<String, AttributeValue> data) { if (data == null || data.isEmpty()) { return null; } return new UserInfo(data.get(ATTRIBUTE_USERNAME).getS(), data.get(ATTRIBUTE_HASH_SALTED_PASSWORD).getS(), data.get(ATTRIBUTE_ENABLED).getS()); } } }