読者です 読者をやめる 読者になる 読者になる

Murayama blog.

プログラミングと、その次の話

AndroidでParse入門 - Users -

元ネタ

こちらを参考にUsersについてまとめます。
https://parse.com/docs/android_guide#users

前回の記事はこちら。
AndroidでParse入門 - Analytics - - Murayama blog.

Users

多くのアプリは、セキュアな方法でユーザアカウント管理する必要があります。ParseはParseUserという特別なユーザクラスを提供しています。これにより、ユーザーアカウント管理に必要な多くの機能が自動的に施されることになります。

このクラスを使用することで、あなたのアプリにユーザアカウント管理機能を追加することができます。

ParseUserクラスはParseObjectクラスのサブクラスです。そのため、フレキシブルなスキーマ、自動永続化、Key-ValueインタフェースといったParseObjectの機能を継承しています。ParseObjectクラスのすべてのメソッドはParseUserクラスにも存在しています。ParseUserクラスには、ユーザアカウント管理のための特別な機能が追加されているのです。

Properties

ParseUserクラスにはいくつかのプロパティが追加されています。

  • username: ユーザの名前です(必須)
  • password: ユーザのパスワードです(必須)
  • email: ユーザのメールアドレスです(任意)

これらは、ユーザ管理の様々なユースケースで必要になるものです。usernameやemailはsetterメソッドでセットします。putメソッドを使う必要がないことに気をつけてください。

Signing Up

アプリで最初にやることはサインアップ処理でしょう。次のコードは典型的なサインアップ処理を示しています。

ParseUser user = new ParseUser();
user.setUsername("my name");
user.setPassword("my pass");
user.setEmail("email@example.com");
 
// other fields can be set just like with ParseObject
user.put("phone", "650-253-0000");
 
user.signUpInBackground(new SignUpCallback() {
  public void done(ParseException e) {
    if (e == null) {
      // Hooray! Let them use the app now.
    } else {
      // Sign up didn't succeed. Look at the ParseException
      // to figure out what went wrong
    }
  }
});

この呼び出しは、非同期でParseアプリ内に新しいユーザを生成します。また、usernameとemailがユニークであるかというチェックも働きます。クラウド上にはパスワードのハッシュ値のみが格納されます。パスワードを平文でやりとりすることはありませんし、パスワードを平文でクライアントに送ることもありません。

signUpInBackgroundメソッドを使用したことに注目してください。saveInBackgroundメソッドは使用していません。新規のParseUserは、signUpInBackground(あるいはsignUp)メソッドによって生成されます。その後の更新処理には、saveメソッドを利用することができます。

サインアップ処理は、signUpInBackgroundメソッド以外にも種類があります。エラーハンドリングを受け取ったり、同期処理したりといったものです。通常は、可能な限り非同期処理で行うことを推奨しています。なぜなら、アプリのUIスレッドをロックしないからです。メソッドの使い方についての詳細を知りたい場合は、API docsを参照してください。

サインアップ処理に失敗した場合は、エラーオブジェクトを調べます。よくあるケースは、usernameやemailが他のユーザと重複するケースです。このような場合はユーザに異なるユーザ名の入力を促すようにします。

メールアドレスをユーザ名として使うことも可能です。ユーザにはメールアドレスの入力を依頼します。その際、usernameプロパティを利用するようにします。このようなケースでもParseUserは問題なく動作します。リセットパスワードのセクションで、どのように処理されるかがわかるでしょう。

Logging In

サインアップしたユーザは、自身のアカウントを使ってログインすることができます。次のようにlogInInBackgroundメソッドを使用します。

ParseUser.logInInBackground("Jerry", "showmethemoney", new LogInCallback() {
  public void done(ParseUser user, ParseException e) {
    if (user != null) {
      // Hooray! The user is logged in.
    } else {
      // Signup failed. Look at the ParseException to see what happened.
    }
  }
});

Verifying Emails

アプリケーションの設定でメールアドレスの検証を有効にすると、メールアドレスの検証処理を追加できます。メールアドレスを検証するには、ParseUserオブジェクトにemailVerifiedキーを追加します。ParseUserのメールアドレスが変更されると、emailVerifiedにはfalseが設定されます。Parseは、メールアドレスとユーザがリンクするとemailVerifiedにtrueを設定します。

emailVerifiedには3つの状態が存在します。

  • true - Parseから送信されるメールによって、メールアドレスを確認できた場合。 ParseUserオブジェクトはアカウント生成後すぐ、trueとなるわけではありません。
  • false - 最後にフェッチされた時に、メールアドレスの確認がされていない場合。emailVerifiedがfalseの場合、ParseUserオブジェクトのfetch()

呼び出しを考えてみてください。

  • missing - メールアドレスの検証がOFFのときに生成されたParseUserか、メールアドレスを保持しないPerseUserです。

Current User

アプリケーションの至る所でユーザーのログを残すのは面倒です。キャッシュされたcurrentUserオブジェクトを使うことでこの問題を解消できます。

signupやloginメソッドを利用すると、ユーザはディスクにキャッシュされます。これにより、セッションとしてこのキャッシュを利用するができますし、ユーザがログインしていると見なすこともできます。

ParseUser currentUser = ParseUser.getCurrentUser();
if (currentUser != null) {
  // do stuff with the user
} else {
  // show the signup or login screen
}

ログアウトすれば、現行ユーザはクリアされます。

ParseUser.logOut();
ParseUser currentUser = ParseUser.getCurrentUser(); // this will now be null

Anonymous Users

個々のユーザにデータやオブジェクトを関連づけることは大事ですが、時には、ユーザ名やパスワードを使用せずにユーザを扱いたいこともあるでしょう。Anonymousユーザは、ユーザ名やパスワードなしで生成できるユーザです。他のParseUserと同じように振る舞うことができます。Anonymousユーザはログアウトする破棄されるので、そのデータにはアクセスできなくなります。ParseAnonymousUtilsを使うとAnonymousユーザを生成できます。

ParseAnonymousUtils.logIn(new LogInCallback() {
  @Override
  public void done(ParseUser user, ParseException e) {
    if (e != null) {
      Log.d("MyApp", "Anonymous login failed.");
    } else {
      Log.d("MyApp", "Anonymous user logged in.");
    }
  }
});

Anonymousユーザは、signUpメソッドを使用してユーザ名とパスワードを設定することで、通常のユーザに変更することもできます。他にもFacebookTwitterのようなサービスと関連づけることもできます。変更後のユーザーであっても、それまで保持していたデータを継続して利用することができます。

if (ParseAnonymousUtils.isLinked(ParseUser.getCurrentUser())) {
  enableSignUpButton();
} else {
  enableLogOutButton();
}

Anonymousユーザは、ネットワーク要求なしで生成できるので、アプリケーションを起動してすぐに利用することができます。アプリケーション起動時に、自動Anonymousユーザ生成を有効にすると、ParseUser.getCurrentUser()メソッドはnullを返さないようになります。ユーザは、ユーザオブジェクトや関連のあるオブジェクトが保存されたときに、初めてクラウド上に生成されます。それまでobjectIdはnullとなっています。自動Anonymousユーザ生成を有効にすれば、簡単にデータを関連づけることができます。たとえば、Application.onCreate()メソッドの中で次のように実装します。

ParseUser.enableAutomaticUser();
ParseUser.getCurrentUser().increment("RunCount");
ParseUser.getCurrentUser().saveInBackground();

Security For User Objects

ParseUserクラスはデフォルトでセキュアな実装となっています。ParseUser上に保存されたデータは、ユーザ本人にしか更新できません。また、デフォルトでは他のユーザから読み取ることもできません。そして、認証済みのParseUserオブジェクトの場合は、他のユーザからは読み込みのみ許可しますが、本人であれば更新することも可能です。

具体的には、loginやsignUpメソッドのような認証メソッドによって取得したParseUserオブジェクトの場合を除いて、saveメソッドやdeleteメソッドを呼び出すことはできません。これにより、ユーザ本人だけが自身のデータを更新できるようになっています。

以下はこのセキュリティポリシーを示しています。

ParseUser user = ParseUser.logIn("my_username", "my_password");
user.setUsername("my_new_username"); // attempt to change username
user.saveInBackground(); // This succeeds, since the user was authenticated on the device
 
// Get the user from a non-authenticated manner
ParseQuery<ParseUser> query = ParseUser.getQuery();
query.getInBackground(user.getObjectId(), new GetCallback<ParseUser>() {
  public void done(ParseUser object, ParseException e) {
    object.setUsername("another_username");
 
    // This will throw an exception, since the ParseUser is not authenticated
    object.saveInBackground();
  }
});

ParseUserが認証済みかどうか確認したいなら、isAuthenticated()メソッドを利用します。メソッドによって取得したParseUserは、常に認証済みのものが返ります。認証メソッドを通じて取得したParseUserオブジェクトについては、isAuthenticated()メソッドを呼び出す必要はありません。

Security for Other Objects

ParseUserと同等のセキュリティモデルが他のオブジェクトにも適用されています。あるオブジェクトに対して、特定のユーザだけに読み込みを許可したり、書き込みを許可したり制御することができます。このようなセキュリティをサポートするために個々のオブジェクトはACL(Access Control List)を保持しています。ACLはParseACLクラスによって実装されています。

ParseACLを使う最もシンプルな方法は、あるオブジェクトの読み込み・書き込みをシングルユーザに限定する方法です。このようなオブジェクトを作成するには、まずParseUserとしてログインしておく必要があります。そして、new ParseACL(user)によってParseACLオブジェクトを生成します。結果として、このオブジェクトは特定のユーザだけによる限定的なアクセスが可能となります。オブジェクトのACLは、他のプロパティと同様にオブジェクトの保存時に更新されます。たとえば、プライベートなメモ(PraivateNote)へのアクセスを限定する場合は次のようになります。

ParseObject privateNote = new ParseObject("Note");
privateNote.put("content", "This note is private!");
privateNote.setACL(new ParseACL(ParseUser.getCurrentUser()));
privateNote.saveInBackground();

このノートは、現在のユーザのみアクセスできます。また、他のデバイスでサインインしてもアクセス可能となります。これは複数のデバイスで横断的にユーザ情報を扱う事ができるので役に立ちます。たとえば、TODOリストの管理などに適しているでしょう。

パーミッションは、ユーザ単位で付与することができます。パーミッションを指定するにはParseACLのsetReadAccess、setWriteAccessを使用します。たとえば、あなたは伝えたいメッセージを、何人かのユーザグループに送りたい場合、対象のユーザ一人ひとりに読み込み・削除権限を付与します。

arseObject groupMessage = new ParseObject("Message");
ParseACL groupACL = new ParseACL();
     
// userList is an Iterable<ParseUser> with the users we are sending this message to.
for (ParseUser user : userList) {
  groupACL.setReadAccess(user, true);
  groupACL.setWriteAccess(user, true);  
}
 
groupMessage.setACL(groupACL);
groupMessage.saveInBackground();

すべてのユーザに一括でパーミッションを与えたいなら、setPublicReadAccess、setPublicWriteAccessメソッドを使用します。これは、メッセージボードへのコメントを投稿するようなパターンを可能とします。たとえば、投稿データの更新は本人のみに限定し、他のユーザには閲覧のみを許可する場合は次のようになります。

ParseObject publicPost = new ParseObject("Post");
ParseACL postACL = new ParseACL(ParseUser.getCurrentUser());
postACL.setPublicReadAccess(true);
publicPost.setACL(postACL);
publicPost.saveInBackground();

ユーザのデータがデフォルトでセキュアなものとするために、生成されたすべてのParseObjectに対してACLを施すこともできます。

ParseACL.setDefaultACL(defaultACL, true);

上記のコードの第2引数は、現在のユーザに対して、オブジェクト生成時の読み書きを許可するかどうかを設定します。この設定をしなければ、ユーザがログインしたり、ログアウトしたりするたびに、デフォルトACLをリセットする必要があります。そうすることで現在のユーザに対して適切なアクセス権が付与されます。この設定をすれば、ユーザが明示的に異なるアクセス権を付与するまで現在のユーザの変更を無視することができます。*1

デフォルトACLは、アクセスパターンに従うことでアプリを作成することが簡単になります。Twitterのようなアプリケーションの場合、ユーザのコンテンツは読み込みを公開します。次の様なACLになるでしょう。

ParseACL defaultACL = new ParseACL();
defaultACL.setPublicReadAccess(true);
ParseACL.setDefaultACL(defaultACL, true);

Dropboxのようなアプリケーションでは、ユーザが明示的に権限を設定をしない限り、ユーザ本人にのみアクセスを許可すべきでしょう。デフォルトACLを使用することで、現在のユーザにのみアクセスを許可することができます。

ParseACL.setDefaultACL(new ParseACL(), true);

Parseにデータを記録するが、ユーザにそのデータへのアクセスを提供しないアプリケーションでは、限定的なACLを提供することで、現在のユーザのアクセスを拒否することができます。

ParseACL.setDefaultACL(new ParseACL(), false);

書き込み権限を持たずにオブジェクトを削除するような禁止される操作は、ParseException.OBJECT_NOT_FOUNDという結果になります。
セキュリティの観点では、IDが存在しないのか、それともアクセス権がないのか、ユーザにエラーの原因を特定させないことは大切です。

Resetting Passwords

システムに登録したパスワードを忘れてしまうのはよくあることでしょう。Parseのライブラリは、パスワードをリセットするセキュアな方法を提供しています。

パスワードをリセットする手順を見てみましょう。まずはユーザのメールアドレスを確認します。次のようになるでしょう。

ParseUser.requestPasswordResetInBackground("myemail@example.com",
                                           new RequestPasswordResetCallback() {
  public void done(ParseException e) {
    if (e == null) {
      // An email was successfully sent with reset instructions.
    } else {
      // Something went wrong. Look at the ParseException to see what's up.
    }
  }
});

入力されたメールアドレスと、emailフィールド、あるいはusernameフィールドにマッチするユーザを探します。該当したユーザには、パスワードリセット通知がメールで届きます。このように、ユーザのユーザ名(username)としてメールアドレスを使うのか、メールアドレスはemailフィールドとして別で管理するのかを選択することができます。

パスワードリセットの手順は以下のとおりです。

  1. ユーザは、パスワードのリセット通知をメールで要求します。
  2. Parseは、指定されたアドレスにメールを送ります。そのメールにはリセット用のリンクが記載されています。
  3. ユーザがリセットリンクをクリックすると、Parseの特別なページにつながります。そこで新しいパスワードを設定することができます。
  4. ユーザは新しいパスワードを入力します。パスワードは入力された値でリセットされます。

注意:メールの本文の中では、アプリケーション生成時に指定したアプリケーション名が引用されます。

Querying

ユーザ用のクエリは、専用のクエリを使う必要があります。

ParseQuery<ParseUser> query = ParseUser.getQuery();
query.whereEqualTo("gender", "female");
query.findInBackground(new FindCallback<ParseUser>() {
  public void done(List<ParseUser> objects, ParseException e) {
    if (e == null) {
        // The query was successful.
    } else {
        // Something went wrong.
    }
  }
});

ID指定でParseUserを取得したい場合はget()メソッドを使用します。

Associations

ParseUserに関連を持たせることができます。たとえば、ブログアプリケーションを作る場合を見てみましょう。ユーザの新しい投稿を保存し、すべての記事を取得する場合は次のようになります。

ParseUser user = ParseUser.getCurrentUser();
 
// Make a new post
ParseObject post = new ParseObject("Post");
post.put("title", "My New Post");
post.put("body", "This is some great content.");
post.put("user", user);
post.saveInBackground();
 
// Find all posts by the current user
ParseQuery<ParseObject> query = ParseQuery.getQuery("Post");
query.whereEqualTo("user", user);
query.findInBackground(new FindCallback<ParseObject>() { ... });

Users in the Data Browser

ユーザクラスはParseUserオブジェクトを格納する専用のクラスです。データブラウザで確認すると、小さな人形のアイコンが表示されるようになっています。

*1:この辺は訳が怪しいので原文を読まれた方が良いです。。