package org.rakam.aws.dynamodb.apikey;
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.Condition;
import com.amazonaws.services.dynamodbv2.model.CreateTableRequest;
import com.amazonaws.services.dynamodbv2.model.DeleteItemRequest;
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.QueryRequest;
import com.amazonaws.services.dynamodbv2.model.ResourceNotFoundException;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
import com.amazonaws.services.dynamodbv2.model.ScanRequest;
import com.amazonaws.services.dynamodbv2.model.Select;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.rakam.analysis.ApiKeyService;
import org.rakam.aws.AWSConfig;
import org.rakam.util.CryptUtil;
import org.rakam.util.RakamException;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.amazonaws.services.dynamodbv2.model.KeyType.HASH;
import static java.lang.String.format;
public class DynamodbApiKeyService
implements ApiKeyService
{
private final AmazonDynamoDBClient dynamoDBClient;
private static final List<KeySchemaElement> PROJECT_KEYSCHEMA = ImmutableList.of(
new KeySchemaElement().withKeyType(HASH).withAttributeName("project")
);
private static final Set<AttributeDefinition> ATTRIBUTES = ImmutableSet.of(
new AttributeDefinition().withAttributeName("project").withAttributeType(ScalarAttributeType.S)
);
private final DynamodbApiKeyConfig apiKeyConfig;
@Inject
public DynamodbApiKeyService(AWSConfig config, DynamodbApiKeyConfig apiKeyConfig)
{
dynamoDBClient = new AmazonDynamoDBClient(config.getCredentials());
dynamoDBClient.setRegion(config.getAWSRegion());
if (config.getDynamodbEndpoint() != null) {
dynamoDBClient.setEndpoint(config.getDynamodbEndpoint());
}
this.apiKeyConfig = apiKeyConfig;
}
@PostConstruct
public void setup()
{
try {
DescribeTableResult table = dynamoDBClient.describeTable(apiKeyConfig.getTableName());
if (!table.getTable().getKeySchema().equals(PROJECT_KEYSCHEMA)) {
throw new IllegalStateException("Dynamodb table for api key service has invalid key schema");
}
if (!ImmutableSet.copyOf(table.getTable().getAttributeDefinitions()).equals(ATTRIBUTES)) {
throw new IllegalStateException("Dynamodb table for api key service has invalid attribute schema");
}
}
catch (ResourceNotFoundException e) {
createTable();
}
}
private void createTable()
{
dynamoDBClient.createTable(new CreateTableRequest()
.withTableName(apiKeyConfig.getTableName()).withKeySchema(PROJECT_KEYSCHEMA)
.withAttributeDefinitions(ATTRIBUTES)
.withProvisionedThroughput(new ProvisionedThroughput()
.withReadCapacityUnits(1L)
.withWriteCapacityUnits(1L)));
}
@Override
public ProjectApiKeys createApiKeys(String project)
{
String masterKey = CryptUtil.generateRandomKey(64);
String readKey = CryptUtil.generateRandomKey(64);
String writeKey = CryptUtil.generateRandomKey(64);
dynamoDBClient.putItem(new PutItemRequest().withTableName(apiKeyConfig.getTableName())
.withItem(ImmutableMap.of("project", new AttributeValue(project), "keys",
new AttributeValue()
.addMEntry("master_key", new AttributeValue(masterKey))
.addMEntry("read_key", new AttributeValue(readKey))
.addMEntry("write_key", new AttributeValue(writeKey))
)));
return ProjectApiKeys.create(masterKey, readKey, writeKey);
}
@Override
public String getProjectOfApiKey(String apiKey, AccessKeyType type)
{
List<Map<String, AttributeValue>> project = dynamoDBClient.scan(new ScanRequest()
.withTableName(apiKeyConfig.getTableName())
.withConsistentRead(true)
.withProjectionExpression("#P")
.withExpressionAttributeNames(ImmutableMap.of("#K", "keys", "#P", "project"))
.withExpressionAttributeValues(ImmutableMap.of(":apiKey", new AttributeValue(apiKey)))
.withFilterExpression(String.format("#K.%s = :apiKey", type.getKey()))).getItems();
if (project.isEmpty()) {
throw new RakamException(HttpResponseStatus.FORBIDDEN);
}
return project.get(0).get("project").getS();
}
@Override
public void revokeApiKeys(String project, String masterKey)
{
dynamoDBClient.deleteItem(new DeleteItemRequest()
.withTableName(apiKeyConfig.getTableName())
.withKey(ImmutableMap.of("project", new AttributeValue(project)))
.withExpressionAttributeNames(ImmutableMap.of("#K", format("keys")))
.withExpressionAttributeValues(ImmutableMap.of(":V", new AttributeValue(masterKey)))
.withConditionExpression(format("#K.master_key = :V", masterKey)));
}
@Override
public void revokeAllKeys(String project)
{
dynamoDBClient.deleteItem(new DeleteItemRequest().withTableName(apiKeyConfig.getTableName())
.withKey(ImmutableMap.of("project", new AttributeValue(project))));
}
}