/* * 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.anonymous; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; 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.anonymous.exception.DataAccessException; import com.amazonaws.tvm.Configuration; /** * This class is used to store and authenticate devices. All devices and their * information is stored in a DynamoDB table. */ public class DeviceAuthentication { /** * Constant for the table name used to store the devices. */ private final static String DEVICE_TABLE = Configuration.DEVICE_TABLE; /** * Constant for the uid (device id) attribute */ private static final String ATTRIBUTE_UID = "uid"; /** * Constant for the key attribute */ private static final String ATTRIBUTE_KEY = "key"; private final AmazonDynamoDBClient ddb; /** * Looks up table name and creates one if it doesnot exist */ public DeviceAuthentication() { ddb = new AmazonDynamoDBClient(new BasicAWSCredentials(Configuration.AWS_ACCESS_KEY_ID, Configuration.AWS_SECRET_KEY)); ddb.setEndpoint(Configuration.DYNAMODB_ENDPOINT); try { if (!doesTableExist(DEVICE_TABLE)) { createDeviceTable(); } } catch (DataAccessException e) { throw new RuntimeException("Failed to create device table.", e); } } /** * @return the list of device ID (UID) stored in the identity table. */ public List<String> listDevices() { List<String> devices = new ArrayList<String>(1000); ScanResult result = ddb.scan(new ScanRequest().withTableName(DEVICE_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(); } devices.add(s); } return devices; } /** * Returns device info for given device ID (UID) * * @param uid * Unique device identifier * @return device info for the given uid * @throws DataAccessException */ public DeviceInfo getDeviceInfo(String uid) throws DataAccessException { HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>(); key.put(ATTRIBUTE_UID, new AttributeValue().withS(uid)); GetItemRequest getItemRequest = new GetItemRequest() .withTableName(DEVICE_TABLE) .withKey(key); try { return DeviceInfo.fromData(ddb.getItem(getItemRequest).getItem()); } catch (AmazonClientException e) { throw new DataAccessException("Failed to get device: " + uid, e); } } /** * Attempts to register the UID, Key combination. This method is used to * register device using anonymous userid. Returns true if successful, false * otherwise. Useful in Anonymous mode * * @param uid * Unique device identifier * @param key * encryption key associated with UID * @return true if device registration was successful, false otherwise */ public boolean registerDevice(String uid, String key) throws DataAccessException { if (checkUidExists(uid)) { return false; } storeDevice(uid, key); return true; } /** * Deletes the specified UID from the identity table. * * @param uid * Unique device identifier */ public void deleteDevice(String uid) throws DataAccessException { HashMap<String, AttributeValue> key = new HashMap<String, AttributeValue>(); key.put(ATTRIBUTE_UID, new AttributeValue().withS(uid)); DeleteItemRequest deleteItemRequest = new DeleteItemRequest() .withTableName(DEVICE_TABLE) .withKey(key); try { ddb.deleteItem(deleteItemRequest); } catch (AmazonClientException e) { throw new DataAccessException("Failed to delete device: " + uid, e); } } /** * Authenticates the given UID, Key combination. If the password in the item * identified by the item name 'UID' matches the Key given then true is * returned, false otherwise. * * @param uid * Unique device identifier * @param key * encryption key associated with UID * @return true if authentication was successful, false otherwise * @throws DataAccessException */ public boolean authenticateDevice(String uid, String key) throws DataAccessException { DeviceInfo device = getDeviceInfo(uid); return device != null && key.equals(device.getKey()); } /** * Store the UID, Key combination in the device table. The UID will * represent the item name and the item will contain attributes key. * * @param uid * Unique device identifier * @param key * encryption key associated with UID */ protected void storeDevice(String uid, String key) throws DataAccessException { Map<String, AttributeValue> item = new HashMap<String, AttributeValue>(); item.put(ATTRIBUTE_UID, new AttributeValue().withS(uid)); item.put(ATTRIBUTE_KEY, new AttributeValue().withS(key)); PutItemRequest putItemRequest = new PutItemRequest() .withTableName(DEVICE_TABLE) .withItem(item); try { ddb.putItem(putItemRequest); } catch (AmazonClientException e) { throw new DataAccessException(String.format("Failed to store device uid: %s; key: %s", uid, key), e); } } /** * Used to create the device table. This function only needs to be called * once. */ protected void createDeviceTable() throws DataAccessException { ProvisionedThroughput provisionedThroughput = new ProvisionedThroughput() .withReadCapacityUnits(10L) .withWriteCapacityUnits(5L); ArrayList<AttributeDefinition> attributeDefinitions = new ArrayList<AttributeDefinition>(); attributeDefinitions.add(new AttributeDefinition().withAttributeName( ATTRIBUTE_UID).withAttributeType("S")); ArrayList<KeySchemaElement> tableKeySchema = new ArrayList<KeySchemaElement>(); tableKeySchema.add(new KeySchemaElement().withAttributeName(ATTRIBUTE_UID) .withKeyType(KeyType.HASH)); CreateTableRequest createTableRequest = new CreateTableRequest() .withTableName(DEVICE_TABLE) .withProvisionedThroughput(provisionedThroughput) .withAttributeDefinitions(attributeDefinitions) .withKeySchema(tableKeySchema); try { ddb.createTable(createTableRequest); } catch (AmazonClientException e) { throw new DataAccessException("Failed to create table: " + DEVICE_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(tableName); 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); } } /** * Checks to see if the device id (UID) already exist in the device table * * @param uid * Unique device identifier * @return true if the given UID already exist, false otherwise * @throws DataAccessException */ private boolean checkUidExists(String uid) throws DataAccessException { return getDeviceInfo(uid) != null; } public static class DeviceInfo { private final String uid; private final String key; public DeviceInfo(String uid, String key) { this.uid = uid; this.key = key; } public String getUid() { return uid; } public String getKey() { return key; } private static DeviceInfo fromData(Map<String, AttributeValue> data) { if (data == null || data.isEmpty()) { return null; } return new DeviceInfo(data.get(ATTRIBUTE_UID).getS(), data.get(ATTRIBUTE_KEY).getS()); } } }