Kinesis Recorderを使ってiOSアプリからAmazon KinesisへPutRecord


これは個人のメモ/意見であり、所属する組織を代表するものではありません。

## はじめに

このポストでは、以下のようなストーリーのiOSアプリを取り扱う

1. Facebookログイン
2. CognitoのFacebook認証連携を使ってAWSのTemporary Credentialを取得
3. Kinesis Recorderを使ってAmazon Kinesisにデータを投げ込む

## Cognito?

正式には[Amazon Cognito](http://aws.amazon.com/jp/cognito/)。大きく分けてCognito IdentityとCognito Syncという機能がある。Cognito IdentityはAWSの各種APIへアクセスするために、ユーザーの認証と権限の認可を行ってくれる。このときにユーザーに対してCognito IDというユニークな識別子を払い出す。Cognito SyncはこのCognito IDに基いてユーザーの情報を保存し、デバイス間同期をしてくれる機能。例えばiphoneとipadで同じゲームをするときに、それぞれのデバイスのデータを同期してくれる、みたいなのがユースケースのイメージ。

### Cognitoで払い出す権限の種類

今回はCognito Identityを取り扱う。Cognito Identityで払い出す権限は大きく分けて2つに分けられる。

1. Unauth
 - 認証されていないユーザーに対して払い出す権限。
 - 例えばDynamoDBのRead権限だけ渡す、とかKinesisの特定のStreamへの書き込み権限だけわたすとか。ある程度限られた権限を払い出すのが利用イメージ。
 - 匿名で使えるサービスや、ユーザー登録が必要なサービスにおける未登録ユーザーなどに利用させるイメージ。
 - 実際にはこのUnauth用に設定したIAM Roleをひもづけることによって利用可能な操作が決定される。
2. Auth
 - 何かしらの認証バックエンド（自前のDBやFacebookアプリなど）と連携して認証されたユーザーに対して払い出す権限。
 - 必要に応じて権限を渡してあげて利用する。
 - こちらも同様にこのAuth用に設定したIAM Roleをひもづけることによって利用可能な操作が決定される。

### Authの認証バックエンド

前項で触れたAuth権限だが、この認証バックエンドは更に3種類に別れる。

1. ソーシャルアプリケーション
 - Facebook, Google, Amazonがサポートされている。 **今回はFacebookと認証連携するサンプルを解説する。**
2. OpenID Connect Provider
 - 上記以外のサービスでもOpenID Connectに対応していれば連携可能。[mixiとかいけそう。](http://developer.mixi.co.jp/connect/mixi_graph_api/api_auth/)
3. 自前ユーザーデータベース
 - 上記以外の方法として、自前のユーザーデータベースで認証してOKだったらCognitoのAuth権限を認可するという方法もとれる。

## Kinesis Recorder?

正式には[AWSKinesisRecorder](http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSKinesisRecorder.html)。[Amazon Kinesis](http://aws.amazon.com/jp/kinesis/)をiOSデバイスから取り扱うために提供されている[AWS SDK for iOS](http://aws.amazon.com/jp/sdkforios/)の中のクラスで、Kinesisを扱うための抽象度の高いクライアントクラス。[AWSKinesis](http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSKinesis.html)というプリミティブなクラスも提供されている。Kinesis Recorderが提供する付加価値としてはKinesisへ送信するデータをローカルにバッファしておき、デバイスがオンラインになったときに送信してくれるということが一番のポイント。

## 実際にやってみる

### KinesisのStreamを作成

-  何はともあれKinesisのStreamを作成する。特に迷うような話はないと思うのでキャプチャはナシ。
- このあと、ここで作ったStreamのARN(Amazon Resource Name。AWSの各種リソースを一意に識別するためのID）を利用するのだが、例えばus-east-1(ヴァージニア)にsampleという名前でStreamを作った場合には以下のようなARNになる。
 - arn:aws:kinesis:us-east-1:123456789012:stream/sample
 
汎化するとこんな感じ arn:aws:(サービス名):(リージョン名):(AWSアカウントID):(サービスごとのリソース識別子)

### Facebookアプリを作成

1. つぎにhttps://developers.facebook.com/ で適当にアプリをつくる
2. 作成したアプリの情報をXcodeのプロジェクトに設定する。Facebookアプリを作成したあとに出てくるQuick Start for iOSにそのまま従えばOK。

### CognitoのIdentity Poolを作成

1. Facebookアプリが出来上がったのでこれを認証バックエンドにするCognitoの設定(Identity Pool)を作成する。CognitoのCreate Identity Poolボタンからウィザードを起動して最初の画面はこんな感じ。他にもOpenID Connectどうするとか、Unauthどうするとか聞かれるけど、今回はFacebookアプリのIDだけ書いてあればOK。
![Cursor_and_New_Identity_Pool.png](https://qiita-image-store.s3.amazonaws.com/0/10292/f5575306-d768-a532-9852-7efb89a36f19.png "Cursor_and_New_Identity_Pool.png")
2. 次にIAM Roleについて訊かれるのでこんな感じに。
![Cursor_and_Access_Management_-_Amazon_Cognito_2-_Amazon_Web_Services.png](https://qiita-image-store.s3.amazonaws.com/0/10292/511dbedc-51e0-edf1-1d1e-579c33eeace7.png "Cursor_and_Access_Management_-_Amazon_Cognito_2-_Amazon_Web_Services.png")
3. これでCognitoの設定は完了。

### IAM Roleに権限を付ける

1. CognitoのIdentity Poolを作った時にAuth用のIAM Role（上の例ではCognito_sampleAuth_DefaultRole)が作成されたが、これにはほとんど権限が付いていないので、KinesisのPutRecord APIを呼べるように権限を付与する。
2. 対象のIAM Roleを開き、Attach Policyでウィザードを起動
![Cursor_and_IAM_Management_Console1.png](https://qiita-image-store.s3.amazonaws.com/0/10292/2f678739-fef2-fa91-bb3e-dbac6d517077.png "Cursor_and_IAM_Management_Console1.png")
![Cursor_and_IAM_Management_Console2.png](https://qiita-image-store.s3.amazonaws.com/0/10292/486aef78-3e12-5f88-2af1-f278340dde03.png "Cursor_and_IAM_Management_Console2.png")

3. Policy Generatorを選択
![Cursor_and_IAM_Management_Console3.png](https://qiita-image-store.s3.amazonaws.com/0/10292/ec410584-b7f7-8c3b-1331-63c8c94f1d50.png "Cursor_and_IAM_Management_Console3.png")
4. 以下のようになるようなPolicyを作る。KinesisのARNは先ほど自分が作ったものを当てはめる。ActionはPutRecordとPutRecordsを許可する。

```json:policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1420887713000",
      "Effect": "Allow",
      "Action": [
        "kinesis:PutRecord",
        "kinesis:PutRecords"
      ],
      "Resource": [
        "arn:aws:kinesis:YOUR_REGION:YOUR_ACCOUNT:stream/YOUR_STREAM"
      ]
    }
  ]
}
```

### いまどこ？

だんだんわかりずらくなってきたが、今までの作業で下記の状況になっている。
**FacebookアプリをバックエンドとしてCognitoで認証されたユーザーは、Cognito_sampleAuth_DefaultRoleというIAM Roleに付与されたKinesisへのPutRecord権限を認可される**

ここからはXCodeでの実装フェーズ。

### Facebookの認証

とりあえずFacebookアプリつくったときに出てきたQuick Start for iOSそのまま。

```objective-c:AppDelegate.h
#import <FacebookSDK/FacebookSDK.h>
```

```objective-c:AppDelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [FBAppEvents activateApp];
}

- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
    // attempt to extract a token from the url
    return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
}

```


### AWSのCredential Providerの初期化

これでFBSession.activeSession.accessTokenData.accessTokenがとれるようになるので、これを使ってCognitoの認証をする。コード的にはAWSCognitoCredentialsProviderというクラスを初期化してやる感じ。


Credential Providerの初期化、この部分はCognitoでIdentitiy Poolを作成するとコピペ用のスニペットが出力されるのでそれを使うのが楽。

```objective-c
AWSCognitoCredentialsProvider *credentialsProvider = [AWSCognitoCredentialsProvider
                                                          credentialsWithRegionType:AWSRegionUSEast1
                                                          accountId:@"YOUR_AWS_ACCOUNT_ID"
                                                          identityPoolId:@"YOUR_IDENTITY_POOL_ID"
                                                          authRoleArn:@"YOUR_IAM_ROLE_ARN"];
```
   
次にFacebookのアクセストークンをCredential Providerに設定する。
更にそこからAWSServiceConfigurationをCredential Providerから作成。ここはおまじないのようなものだと思って。

```objective-c

NSString *token = FBSession.activeSession.accessTokenData.accessToken;
credentialsProvider.logins = @{ @(AWSCognitoLoginProviderKeyFacebook): token };
   
AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1
credentialsProvider:credentialsProvider];
    
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
```

ここまでくれば、あとはKinesis Recorderを初期化してデータを投入できる！

```objective-c

AWSKinesisRecorder *kinesisRecorder = [AWSKinesisRecorder defaultKinesisRecorder];
    
NSMutableArray *tasks = [NSMutableArray new];

for (int32_t i = 0; i < 10; i++) {
        [tasks addObject:[kinesisRecorder 
        saveRecord:[[NSString stringWithFormat:@"TestString-%02d", i] 
        dataUsingEncoding:NSUTF8StringEncoding] streamName:@"YOUR_STREAM_NAME"]];
}

[[[BFTask taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(BFTask *task) {
    return [kinesisRecorder submitAllRecords];
}] continueWithBlock:^id(BFTask *task) {
    if (task.error) {
        NSLog(@"Error: [%@]", task.error);
    }
    return nil;
}];

```