Cognitoを使って公開されていないS3バケットからファイルを取得する

## やりたいこと

* S3のバケットをWebホスティング用に設定することなく、モバイルアプリケーションにバケット内のオブジェクトをダウンロードさせたい。
* ユーザー登録やログインのようなユーザー管理は行わない

モバイルアプリでCognitoを使って匿名ユーザーとして認証を行い、一時的なキーを作成する。このキーにはS3からのダウンロード権限のみを与え、モバイルアプリはキーを使うことでS3からオブジェクトをダウンロードすることができるようになる。

## 準備

### Identity pool を作成する

Identity poolとはCognitoが作成する認証キーが保存されるところです。ここで何個のキーが発行されたかを確認したり、個別のキーを削除したりすることが可能です。

Identity poolにはIAMロールを関連付けます。Cognitoで作成された認証キーはこのIAMロールによってアクセス可能なAWSリソースを制限されます。
関連付けるIAMロールはUnauthenticated roleとAuthenticated roleの2種類があり、匿名認証時とID認証時で必要な権限を分けることができます。両方ともに同じロールを指定することも可能です。

### ロールを作成する

Identity poolを作成する時に関連付けるIAMロールを設定します。先にIAMロールを作っておいて設定することもできますし、Identity poolを作成するプロセスの中でIAMロールを作成することもできます。

どちらの場合でも適切に必要最低限の機能のみを与えるようにすることが重要です。
今回の例として`my-test-bucket`というバケット内の**全てのオブジェクトについて取得のみの権限**を与えることを考えます。このようなロールは以下のように設定します。
なお、`Statement`の一つ目の要素は最初から設定されている権限で、モバイルアナリティクスやCognito同期機能で使用する権限なので、そのまま残してあります。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::my-test-bucket/*"
            ]
        }
    ]
}
```

Identity poolが作成できたら、次はアプリケーション側の実装です。
以下はAndroid Studioでの作業手順です。

### Dependencyを追加

app/build.gradleの`dependencies`ブロックに以下の記述を追加します。

```groovy:app/build.gradle
dependencies {
    compile 'com.amazonaws:aws-android-sdk-core:2.2.+'
    compile 'com.amazonaws:aws-android-sdk-s3:2.2.+'
}
```

### パーミッションを追加

S3と通信をするためにもちろんインターネットパーミッションが必要です。ドキュメントには書いてなかったのですが`android.permission.ACCESS_NETWORK_STATE`もないとエラーになりました。

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
```

### Java

ここからはJavaの実装になります。
Cognitoを使用して認証キーを取得するためにはまず`CognitoCachingCredentialsProvider`インスタンスを作成します。`IDENTITY_POOL_ID`は実際のIdentity poolのIDに置き換えてください。この値はAWSコンソールでCognito→Manage Federated Identities→Identity pool名をクリック→Edit identity poolと辿ると確認できます。

```java
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
    getApplicationContext(),
    IDENTITY_POOL_ID,
    Regions.AP_NORTHEAST_1 // Region
);
```

次に、作成した`CognitoCachingCredentialsProvider`を使って`AmazonS3Client`インスタンスを作成します。

```java
AmazonS3Client s3 = new AmazonS3Client(credentialsProvider);
```

S3からファイルをダウンロードするには、`TransferUtility`を使います。

```java
TransferUtility transferUtility = new TransferUtility(s3, getApplicationContext());
TransferObserver observer = transferUtility.download("bucketName", "objectName", targetFile);
```

まとめると以下のような感じになります。

```java
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
        getApplicationContext(),
        IDENTITY_POOL_ID, // 実際のIdentity poolに置き換える
        Regions.AP_NORTHEAST_1);

AmazonS3Client s3 = new AmazonS3Client(credentialsProvider);
TransferUtility transferUtility = new TransferUtility(s3, getApplicationContext());

String bucketName = "my-test-bucket";
String fileName = "test.jpg";
File file = new File(getFilesDir(), fileName);

TransferObserver observer = transferUtility.download(bucketName, fileName, file);
observer.setTransferListener(new TransferListener() {
    @Override
    public void onStateChanged(int id, TransferState state) {
        switch (state) {
            case COMPLETED:
                // ダウンロード完了時
                break;
        }
    }

    @Override
    public void onProgressChanged(int id, long bytesCurrent, long bytesTotal) {
    }

    @Override
    public void onError(int id, Exception ex) {
    }
});
```