セキュリティルールについて

セキュリティルールとは、データストアごとのアクセス制御を行うためのルールです。データストアごとに「誰が(どういう条件で)どのAPIを使えるか」を定めます(セキュリティルールを設定しても、アプリへの接続自体は誰でも出来ます。アプリへの接続自体の制御については認証のドキュメントをご覧ください)。

セキュリティルールは、意図しないデータの保存やデータの破壊を防ぐために重要な機能となります。

セキュリティルールの記述法

セキュリティルールは、管理画面の「セキュリティルール」にあるエディタで編集できます。

セキュリティルールの記述形式

JSONのように、データストア名ごとに使用可能なAPI(permit)とその条件(rule)を定めます(データストア名のあとは半角空白が必要なので、ご注意下さい)。

データストア名 {
  permit : 使用可能なAPI;
  rule : 条件式;
}
データストア名

hogeのようにデータストア名を指定すれば、そのデータストアにルールが適用されます。階層を持つときはhoge/hugaのように/区切りで指定します。

hoge {
  // ...
}
hoge/huga {
  // ...
}

その他にも、以下のような指定方法があります。

*ワイルドカードを使うことが出来ます。前方一致(test/*)・後方一致(*/test)・部分一致(*/test/*)が使用可能です(test*のようなデータストア名自体の一致には使えません)。
[]ワイルドカードの代わりにtest/[userID]のように指定すると、test以下のパス名がuserIDに代入されて、rule内で使用できます。「データストア名とログイン中のユーザー名が一致していたら」といった条件に使用できます。
permit
permitで使用できる記述は以下で、複数指定する場合は,(カンマ)で区切って下さい。
allすべてのAPIを許可
pushpush()を許可
setset()を許可
querystream(), history()を許可
getget()を許可
removeremove()を許可
sendsend()を許可
on(push)on('push')を許可
on(set)on('set')を許可
on(remove)on('remove')を許可
on(send)on('send')を許可
rule

ruleには条件式を記述します。rule上では、以下のようなものが使用できます。複数の条件を&&||でつなぐこともできます。

・account

あなたのアプリで認証したユーザーの情報を指します。認証方法にはJSON Web Tokenを使った「トークン認証」と、API KeyとAPI Secretを使った「API Key認証」があり、認証の方法によって制御で使用するプロパティが異なります。

account.sub「トークン認証」を使った場合に、ユーザーのIDにあたるものです。こちらはJSON Web Tokenの仕様で決められているものです。
account.keyこちらは「API Key認証」を使った場合の、API Keyにあたります。
・newData

push()set()で新しく保存されるデータを指します。newDataにデータ形式を指定して、バリデーションをかけることができます。

newData.hasKey(key)データがkeyを持っていたらtrueを返します
newData.key.isNumber()/isString()isNumber()の場合、データにkeyが存在して、さらにそのkeyの値がNumber型であればtrueを返します。
newData.key.match(RegExp)match()を使えば、正規表現で文字列の形式を指定できます。match("[a]")のように使います。
・データストア関連
データストア名で指定された[変数]データストア名で変数を大括弧[]で囲むと、rule上で使うことが出来ます。「account.subと同じデータストア名であれば許可」のような使い方が出来ます。
dataStore(ds).exists(id)データストアdsidというidを持ったデータが存在していればtrueを返します。「オーナーが許可した人だけこの部屋を使える」のような用途に使えます。

なお、等価演算子はイコール2つ(==)を使用下さい。

※文字列に日本語は使用できないので、使用したい場合はクライアント側でエンコードする等の対応をお願い致します。

セキュリティルールの適用順序

ルールを複数書いたときの適用順序については、「その条件が1回でもtrueになっていれば許可」されます(OR演算子的)。

例えば、以下のように、一度「すべてのパスにすべてのAPIを許可」すると、その後に何を書いても「すべてのパスにすべてのAPIが許可」されます。

* {
  permit : all;
  rule : true;
}
hoge {
  permit : push;
  rule : true;
}
// hogeはpush以外も発行できる。

使用例

セキュリティルールの使用例です。セキュリティルールの書き方は限られているので、用途に合わせてこちらの例をコピペするかたちで利用頂くことをお勧めします。

すべてのパスにすべてのAPIを許可する

以下はすべてのパスに対して、すべてのAPIを許可する例です(デフォルトではこのルールが設定されています)。

* {
  permit : all;
  rule : true;
}

特定のパスだけ許可する

以下の例ではlogというパスに対してのみAPIを許可します。

log/* {
  permit : all;
  rule : true;
}
// 'log'にはpushできます
milkcocoa.dataStore("log").push({content : "log content"});

// 'message'にはpushできません。
milkcocoa.dataStore("message").push({content : "message content"}, function() {}, function(err) {
  console.log(err);  // -> permission denied
});

特定のAPIだけ許可する

以下のルールスクリプトではlogというパスに対して、push APIのみを許可します。

log/* {
  permit : push;
  rule : true;
}
// pushはできます
milkcocoa.dataStore("log").push({content : "log content"});

// sendはできません
milkcocoa.dataStore("log").send({content : "log content"}, function() {}, function (err) {
  console.log(err);  // -> permission denied
});

データ形式によるバリデーション

データの形式を制限することで、意図しないデータの保存を未然に防ぐことが出来ます

以下では、データがindexcolorというkeyを持っていて、かつ、indexが数字で、colorが文字列の場合のみpushを許可します。

dots {
    permit : query, on(push);
    rule : true;
}

dots {
    permit : push;
    rule : newData.index.isNumber() && newData.color.isString();
}

正規表現によって、文字列の形式を限定することも出来ます。

dots {
    permit : push;
    rule : newData.index.isNumber() && newData.color.match("#[0-9a-fA-F]*");
}
// pushできます
milkcocoa.dataStore("dots").push({index : 10});
milkcocoa.dataStore("dots").push({color : '#fff'});

// pushできません
milkcocoa.dataStore("dots").push({index : '10'}); // 型違い
milkcocoa.dataStore("dots").push({color : 'fff'}); // 文字列の形式違い
milkcocoa.dataStore("dots").push({hoge : 10}); // key違い

特定のデバイスだけデータストアへの操作を許可する

デバイスでAPI Key認証を行えばそのAPI Keyからのみpushを受け付ける、といったことが可能です。

Milkcocoaトップページのグラフを例にとると、Web側ではデータの取得だけできて、特定のRaspberry Piからのみpushを許可、といったことができます。

light {
    permit : query, on(push);
    rule : true;
}
light {
    permit : push;
    rule : account.key == 'デバイスのAPI_Key';
}
// pushできます
var milkcocoa = MilkCocoa.connectWithApiKey('app-id.mlkcca.com', 'API_Key', 'API_Secret');
milkcocoa.dataStore("light").push({v : 1});

// pushできません
var milkcocoa = new MilkCocoa('app-id.mlkcca.com');
milkcocoa.dataStore("light").push({v : 1});

ログインユーザーに対して、それぞれのユーザーが持つデータストアのみの操作を許可する

Webアプリ等でトークン認証を行って、「ユーザーの情報はそのユーザー自身しか操作できない」といったことを実現したいときに使うセキュリティルールです。

以下の例では、例えば/memo/000001のパスは、ユーザーID(トークン認証を行った場合、user.subが ユーザーのIDにあたります)が"000001"の人しか操作できません。

memo/[userID] {
  permit : all;
  rule : account.sub == userID;
}

このルールを適用している場合、"memo/自分のユーザID"というパスに対してのみ、APIを発行できます。

var ds = milkcocoa.dataStore("memo");

milkcocoa.user(function(err, user) {
  if(user) {
    // pushできます
    ds.child(user.sub).push({content : "Hello!!"});
  }
});

// pushできません
ds.child("適当なuserid").push({content : "Hello!!"});

ログインユーザーが、ある条件を満たした場合だけにデータストアへの操作を許可する

Webアプリでトークン認証を行った際の、もう少し複雑な例です。「あるオーナーが許可したユーザーのみが、そのデータストアを操作できる」といった、ルーム機能を実装したいときに使います。

以下のルールスクリプトでは、例えば"/rooms/room1"のパスへは、"/rooms/room1/allows"にidが自分のアカウントIDの要素が存在する場合のみ操作できます。

rooms/[roomID] {
	permit : all;
	rule : dataStore("rooms/" + roomID + "/allows").exists(account.sub);
}

詳しい実装方法は、こちらのブログ記事をご覧下さい。