SwitchBotのスマートロックを、指紋認証つきのキーパッドと一緒に購入しました。

ちゃんと設置できるか不安はあったものの、特に迷うことなく取り付けることができました。ハブとAPI連携できるという情報も聞いていたので、ついでにハブの方も購入。

IFTTT連携も簡単にできそうだということで、設置後にさっそく検証してみました。IFTTTの「IF」にSwitchBotを選択すると、ロック関連の項目が2個見つかります。

英語、日本語という違いだけでおそらくどちらを選んでもOKです。デバイスに関しては自分のロックのデバイスを選択します(自分の場合「ロックFA」)。

どのようなときにトリガーするか、というところで「ロック状態」が選択できます。選択できる状態は以下4つ:

  • 施錠済み
  • 解錠済み
  • ドアが開けられた
  • ドアが閉められた

今回は解錠のときのAPI連携をしてみました。IFTTTの「THAT」の部分にはとりあえずSlackを設定してみます。

例えばMessageのところに埋め込むこのとできる情報は CreatedAtContentでした。 Content には何が入るのでしょうね?

SwitchBot のアプリの方を覗いてみると、履歴のところで以下のように「どの認証方法で解錠されたか」がわかるようになっています。

「これは期待できる!」と思ってさっそく検証してみたところ

残念ながら {{Content}} には何も値が入っておらず undefined でした。。。

下記ドキュメントを参照しても、そのような情報は現時点では渡って来ることは無さそうですね。

github.com

{
    "eventType": "changeReport",
    "eventVersion": "1",
    "context": {
        "deviceType": "WoLock",
        "deviceMac": DEVICE_MAC_ADDR,
        "lockState": "LOCKED",
        "timeOfSample": 123456789
    }
}

「どの認証方法で解錠されたか」の情報があれば、例えば子どもたちに登録した指紋でフィルタリングしてSlackに通知するようにできそうですね。 例えば留守にしないといけない状況で子どもが先に家に帰ってきたときなど、通知が飛んできてくれたら少し安心できそうです。

今後に期待したいですね。

ということでスマートロックを購入したのでAPI連携の検証をしてみた話でした。

Stripe Checkoutを使ってみたのでTipsを残しておく

Stripeを仕事で使うことになり、いろいろと調べたことを忘れないうちにアウトプットしておこうと思います。尚、これは執筆時点の仕様に関するもので、今後変わる可能性は大いにあります。

2022/02/17更新

とてもありがたいことにStripeの中の人にアドバイスをいただくことができたので、ところどころ記載内容をアップデートしています。

Stripe Checkoutはフロントエンドだけで使える?

一応Stripe CheckoutにはClient-only integrationというものがあります。

stripe.com

ただし、Stripeからも非推奨と言われています。以下の制限が主な理由です。

  • カード支払いのみがサポートされています。
  • Coupons、Discounts、Promotion Codes、Tax Rates の各 API には対応していません。
  • 既存の Customer オブジェクトを使用した 1 回限りの支払いの作成はサポートされていません。
  • 請求前のカードの売上の保留はサポートされていません。
  • 1 回限りの支払いと継続支払いを 1 回の取引で行うことはサポートされていません。
  • Connect プラットフォームは、連結アカウントに代わってこの組み込みを使用することはできません。
  • Stripe Tax はサポートされていません。

全部痛いのですが、特にクーポンが使えないことと、Tax ratesやStripe Taxが使えないのが致命的だったのでClient-only integrationは僕はあきらめました。

使い方については以下が参考になります。

www.youtube.com

Payment Linksについて

フロントエンドだけでどうにかできないかと思い、Payment Linksについても調べてみました。こちらはクーポンやStripe Taxも使えていい感じです。

stripe.com

ただし、Stripe Checkoutで扱える情報以外にも保存しておきたいものがあったため(お客様の会社名とか備考とか)、フロントエンドだけで使うのはどのみちあきらめました。

特に細かい要件が無ければPayment Linksでいろいろと足りそうです。

www.youtube.com

www.youtube.com

Stripe Checkout client-server integration

フロントエンドだけでは無理そうだとなったら、client-server integrationを使います。こちらはサーバーサイドでセッションを作って、もうちょっといろいろとごにょごにょできるようになります。

stripe.com

Stripe Checkoutのページにいい感じに消費税は表示できる?

作りたてのアカウントではいい感じには表示されません。商品に消費税を設定しましょう。手数料が許容できるのであればStripe Taxも検討した方が良いですし、推奨されています。

qiita.com

Dynamic Tax Ratesというものあるのですが、残念ながら日本はまだサポートされていません。

stripe.com

サブスクリプションな商品の場合、途中でプラン変更した場合などの支払いはどうなる?

スタンダードの1年プランみたいなものの2ヶ月目とかでプレミアムプランに切り替えたとき、お金の扱いがどうなるか。あるいはプレミアムからスタンダードに切り替えたときはどうなるのか。

このあたりは「比例配分」の話で、Stripeが「いい感じにやってくれる」仕組みがあります。

stripe.com

qiita.com

サブスクリプションの期間の調整はStripeの管理画面からできる?

サブスクリプションの周期を変えたくなるときが出てくるかもしれません。そんなときはTrial期間をうまく使うみたいです。

stripe.com

Stripe Checkoutで買った商品(サブスク)の支払い方法の変更はどうやってやる?

1度買ったら終わりな商品はあまり関係ないのですが、サブスクリプションだとカードの期限が切れたりします。その場合に支払い方法をどうやって変えれば良いのか。

そこで使うのがCustomer Portalです。

stripe.com

お客様用の専用ページを作って、そこでお客様に自分自身で変更してもらいます。

注意点として、Customer PortalはStripeのテスト環境だと、 管理画面からサクッと見ることができる のですが、これはあくまでプレビュー用&テスト用のものです。

本番用のCustomer PortalのURLはAPIを使って発行する必要があります。セキュリティの観点から、URLの有効期限もシビアなので気をつけましょう。

新しいポータルセッションは 5 分後に期限切れとなります。顧客がその期限内にセッションを使用すると、そのセッションは最新のアクティビティの 1 時間後に期限切れとなります。各ポータルセッションは、最大 2 時間まで有効です。

Stripe Checkoutで申し込んだお客様に飛ぶメールの言語はどうなる?

Stripe Checkoutで商品を購入すると、例えば領収書がメールでお客様に送信されます。このメールの言語はどのように決まるのか。

まず、Checkoutのページには locale を設定することができます。ここで en とすれば英語のページなりますし、 ja にしたら日本語のページです。 auto にすると、ブラウザの言語で自動判定してくれます。

stripe.com

で、Checkoutのページの言語でメールも飛んでくれるかと思うかもしれませんが、そうはなりません。

何も考えずに実装すると、Stripeで設定している「顧客のメール」の「デフォルトの言語」で送信されます。

これをどうにかいい感じに切り替わるようにしたければ、Customerオブジェクトをあらかじめ作って、 preferred_locales を設定する必要があります。

stackoverflow.com

stripe.com

ただし、Checkoutのページから顧客が「キャンセル」した場合にCustomerオブジェクトのゴミが作られちゃう問題などがあるため、よく考慮して実装する必要があります。

2022/02/17追記:

直接この問題とは関係が無いのですが、Customerオブジェクトのデータがたくさんできちゃう問題に対処するには、ゲスト顧客を検討するのが良さそうです。メールアドレス、電話番号、カード番号から、顧客をグルーピングしてくれるいい感じな機能です。

t.co

決済が発生したときにお客様とStripe User双方にメールは飛ぶ?

設定によって飛ばすことができます。注意点として、Stripe User側は、ユーザー毎に設定が必要です(全体設定ではない)

Stripe Userとしてのメール通知設定について

support.stripe.com

お客様に送るメールの設定

stripe.com

テスト環境では自動メールは飛ばない

注意点として、領収書などの自動メールはテスト環境では飛びません。領収書に関してはStripeのダッシュボードから送信できるので特に問題は無いのですが、Stripe Userにどういうメールが飛ぶのかというのが本番でしか確認できないのはちょっとつらいです(特に自分が誰かのためにシステムを作っている場合)。

この場合は本番でしか確認する方法がありません。領収書付きで確認したい場合、日本では最低価格50円のサブスクリプションを作って、実際に決済するしかありません(現時点では。サポートに確認済みです)。自分のクレジットカードを使うのに抵抗があるかもしれませんが、ひとまずあきらめましょう(実はいい方法があれば知りたいです)。

いろいろと事情があるようで、メールのサンプルについてもサポートから開示してもらえなかったので、決済してみてのお楽しみです。

Stripeのメタデータに設定できる量に制限はある?

  • 50 keys(40 characters long)
  • 500 characters long values

Stripeが標準で保存する情報の他に、プラスαで保存したくなる情報があります(会社名とか備考とか)。それにはStripeのメタデータが使えるのですが、これにも一応制限があるので注意が必要です。

stripe.com

priceIdはテスト環境から本番環境に引き継がれる?

Stripeはあらかじめテスト環境と本番環境を用意してくれていて、非常にテストがしやすいDevExになってます。ただ、ちょっとつらかったのが、テスト環境で使っていたものをそのままシームレスに本番環境で使えるわけではない点です。

テスト環境の商品を本番環境にコピーするのはとても簡単です。「本番環境へコピー」というボタンをポチッと押すだけで本番にコピーされます。ただし、このときに priceId などのIDの値は変わります。

ドキュメント に以下のような記載があったので、IDも引き継げるのかと期待したのですが、どうやらこれはPlanIdに特化したもので、基本的にはIDは変わるものとして設計する必要があります。

f:id:kenev:20220214115226p:plain

今は非推奨(?)なPlanに関しては、確かにIDを設定するAPIがあります。

stripe.com

~IDが引き継げるとめちゃくちゃDevExは向上しそうなのですが、このあたりはあきらめてツールを駆使してあまり意識しなくても良いようにしましょう。~

2022/02/17追記:

この問題は検索キーを使うことで解消できるようです。検索キーとpriceIdを関連付けて置くことで、フロント側が意識するキーをテスト環境も本番環境も同じものにしておくことができそうです。

t.co

TemporalというWorkflow Engineについて

temporal.io

Podcastを聴いていてたまたまTemporalというWorkflow Engineを見つけて、気になったので情報を集めてみました。

Temporalという会社

Temporalというプロダクト

  • Workflow Engine
    • AWS Step FunctionsとかAzure Durable Functionsと同じような機能を持っている
    • Single Stateful Service in your whole cloud that makes everything in your cloud stateless
  • 解決してくれる課題
    • Cloud Native Architectureで、スケールしやすくするためにはなるべく作るものはStatelessにしていきたい
    • でもWorkflowを組むにはどこかしらにStateは必要になる
      • Stateの管理は難しいし複雑になりやすい
    • そこでまるっとおまかせできるのがTemporal
      • TemporalがOrchestratorとしてStateをハンドリングしてくれる
  • アーキテクチャ
  • 現時点でサポートしてる言語

Workflowとは

例えばフードデリバリーの例

  1. マッチング
  2. ドライバーをレストランに行かせる
  3. レストランからお客さんところにドライバーを行かせる
  4. お客さんに評価させる

という一連の流れがWorkflowで、これはすべてうまく言った場合の流れ。この他にも失敗した場合のリトライだったり、タイムアウトをどのくらいにするか、とかの考慮お必要になる。このあたりのリトライ、タイムアウト、ヘルスチェック周りもTemporalがハンドリングしてくれる。

Temporal使ってる人たち

所感

  • Step Functionsだとローカルでテストしづらいから、どこででも動かせそうなTemporalに期待したい
  • ↑のことからローカルやCIでのテストができるのに期待
  • Saga Patternについてもいい感じに実現できそう

参考

Udemyなどオンライン講座を宅録するときの諸注意

f:id:kenev:20211021140131p:plain

Udemyの講座をレコーディングするにあたって、自分なりに工夫した点を記録しておきたかったので記事にします。随時気づいた点があれば追記します。

正直、日本で綺麗に宅録するのは鬼門です。

前準備

動画のバックアップ先

  • レコーディングした動画をどこにバックアップしておくか、というのを早い段階で決めておく
  • 今回は元々契約してるOneDriveをひとまず選択
    • 落ち着いたタイミングでHDDに移すかも?(OneDriveの容量を逼迫するので)

レコーディングする画面の解像度

  • 1920x1080の解像度が最低限あること
  • Macbook標準の画面サイズだと比率が合わないので要注意
  • 必ずレコーディングテストすること
    • ここを甘く見ると、全部のレコーディングがパーになるかもしれないので、入念にチェック!

レコーディング用のOSユーザー

  • レコーディング用のOSユーザーを用意しておいたほうが良い
  • 普段のユーザーでやると、変なものが画面に写るかもしれないし、開発環境が世間の人々と大きく異なっている可能性がある
  • なので、なるべくクリーンな環境にするためにも専用のユーザーを用意しておく方がおそらく良い

ソフトウェア

  • レコーディングにはCamtasiaを使用 www.techsmith.com
    • 特段使い勝手が言いわけではないんですけど、昔から使ってるので
  • ノイズ軽減にKrispを使用 krisp.ai
  • レコーディングのバックアップにはOneDriveを使用

機材

その他

スケジューリング

レコーディングのスケジュールをたてておくのは結構大事です。

  • 近隣のスケジュール確認
    • 学校の長期休暇期間かどうか(家の付近で子供たちが騒ぎそうな期間かどうか)
    • 工事が行われる予定があるかどうか(道路工事とか建物の工事がある日はレコーディング厳しいです)
  • 夏はセミがうるさいので要注意
  • 一日にレコーディングできる量は、多くても40分程度
    • 編集とかを考えると、それ以上やるとメンタルもフィジカルも持たない

レコーディング

レコーディング前の確認

  • OneDriveアプリがOFFかチェック
    • OneDriveのクライアントがONだと、動画と音声がずれる傾向がありました
    • 動画の保存先がOneDriveのフォルダだったので、CPU負荷が影響していたのかもしれません
    • なので、レコーディング前には必ずOneDriveがOFFになってることをチェック
  • 外付けWebカメラをはずす
    • Webカメラを接続しているときに妙なノイズ(クリック音みたいな)が乗ることが多く、音質に影響があるレベルだったため、外付けのWebカメラをいつも物理的にはずしていました(たぶん相性の問題)
  • マイクが入っているかチェック
    • これ結構侮れないです。ちゃんとマイク入力があるか確認が必要です
    • レコーディング終わったあと、音声が一つも入っていないのを見ると泣けます
  • KrispがONかチェック
  • デスクをスタンディングモードにする
    • 立って発声するほうが明らかに良い声が出ます

編集

  • 2倍速で編集
    • 1倍速で編集していると時間がかかりすぎるので、動画を2倍速にして編集作業を行う f:id:kenev:20211021123140p:plain
    • Camtasiaならこんなメニューから変更可能
  • ノイズ除去を適用
    • 適宜、デフォルトのレコーディング状態でノイズが入るようだったら、ノイズ除去のエフェクトを適用 f:id:kenev:20211021133021p:plain
    • Camtasiaだとわかりづらいんですけど、ノイズ除去っていうエフェクトがあります
  • UHKのショートカット割り当てで編集作業効率化 f:id:kenev:20211021134614p:plain
    • 「トラックの分割」、「削除」、「再生位置を戻す」をキーに割り当てておけばものすごく効率上がります(指の負担も)

動画のエキスポート

  • 普通にmp4で出力すればOK

その他注意点

  • 夏のセミが想像以上にうるさいんですけど、Beta58Aのおかげか、レコーディングに影響はありませんでした
  • カラスが大声で近くで鳴いてると結構きつい
  • 救急車、選挙カーなどの大音量車両が通るときは要注意
  • 坂道を登るときの大型車両のエンジン音も、結構離れてていてもマイクが拾ってしまう

OsoでRBACを実装してみる

はじめに

久しぶりに権限周りに関するエントリです。 Osoと呼ばれるライブラリを最近ふとしたタイミングで見つけたのでいろいろと実験してます。

www.osohq.com

Osoについて詳しく書ける時間ができたら書きたいのですが、この記事では軽く紹介するだけにとどめます。

  • Osoは認可(Authorization)に特化したライブラリ
  • コアな部分はRustで作られてますが、Node.js/Python/Go/Ruby/Rust/Java用のライブラリが提供されてる
  • PolicyにはPolarと呼ばれる言語が使われている(OPAで言うところのRego)

権限や認可についてあまり馴染みが無い方は、以下の記事を読んでみると雰囲気が少しつかめるかもしれません。

kenfdev.hateblo.jp

また、英語に抵抗が無い方であれば、Osoが書いてるAuthorization Academyっていう神ドキュメントがあるので、そちらをぜひ読んでみてください。

www.osohq.com

使ってみた

公式ドキュメントはかなり頑張って書かれてはいるものの、自分のコード(JSとか)とPolarがどのように関わってるのかが、実際に手を動かしてみないとわかりづらかったので、サンプルアプリでRBACを実装してみました。

ベースとなってるのは以下のトピックです。

docs.osohq.com

RBAC構成

以下のような権限を実現したいと思ってます。

f:id:kenev:20210922212912p:plain

「誰が(Actor)」、「何に(Resource)」、「何を(Action)」して良いのか。

という観点でこの図を表現すると、

  • contributerはRepositoryをreadすることができる
  • maintainerはRepositoryにpushすることができる

上記は結構単純です。Role(contributor/maintainer)にAction(read/push)が紐付いてて、対象がRepositoryになってます。

ここに、さらにちょっと複雑な要件も増やします。

  • contributerができることは、maintainerもできる
    • なので、maintainerはRepositoryをreadすることもできる
  • Repositoryを所有しているOrganizationのownerであれば、maintainerと同等なことができる
    • なので、単純にRoleにActionが紐付いてるわけではなく、Resourceの関連(Relation)も考慮している

上記を満たすためのPolicyを、Osoを使って書くことができます。

そこで登場するのがOsoのDSLであるPolarです。

Polar

Polarについても詳しいことは割愛してしまいますが、Policyを定義するためのOsoが作った専用の言語です。気になる人は以下のドキュメントを読んでみましょう。

docs.osohq.com

先程の要件を満たすPolarを書くと、(公式ドキュメントそのまんまですけど)以下のようになります。(シンタックスハイライトが無いので、キャプチャを貼り付けます)

f:id:kenev:20210922214912p:plain

全部で30行ほどのPolicyですが、結構Osoの裏側でこれだけでもいろいろなことが起きてます。

allowとhas_permission

エントリポイントとなってるのが1行目のallowです。自分のコード(JSなど)からOsoを呼び出すときの入り口となる部分です。 このallowがtrueかfalseなのか、という判断をPolicyに基づいてOsoが判断してくれます。allowはPolarに最初から組み込まれているRule Typeになります。

最初から組み込まれているRule Typeについては以下参照。

docs.osohq.com

そして、allowの中身は以下のように定義しています。

allow(actor, action, resource) if
  has_permission(actor, action, resource);

has_permissionがもし(if)trueだったら、allowもtrueという意味になってます。このhas_permissionも、Polarに最初から組み込まれている型になります。このhas_permissionの定義は、後ほどShorthand Ruleを書くときに重要になってきます。

has_roleとhas_relationに関してはいったん置いておいて、次はactorとresourceについて見ていきましょう。

actorとresource

actorとresourceの定義を見てみましょう。書き方は下のような感じです。

actor User {}

resource Organization {
# ...
}

resource Repository {
# ...
}

actorは「誰が」に対応する存在となります。で、resourceが「何に」の「何」に該当します。ここで宣言したactorとresourceに関しては、自分のコードと紐付けるときにそれぞれ、コード側のどの型に対応するのか、というのをマッピングする必要があります。

具体的にはJavaScriptであれば以下のようなコードで、OsoにUser, Organization, Repositoryを登録します。

class Organization {}
class Repository {}
class User {}

oso = new Oso();
oso.registerClass(Organization);
oso.registerClass(Repository);
oso.registerClass(User);

これで、JS側のエンティティと、Polarの中のエンティティを紐付けられているということです。

では、resourceの中身を見ていきましょう。まずはOrganizationから。

resource Organization {
  roles = ["owner"];
}

rolesという定義があります。これはRBAC(Role-Based Access Control)をする上で便利で、対象となるResourceに紐づくことのできるRoleを定義することができます。なので、ここではOrganizationというResourceに、ownerというRoleが紐づくことができると言ってます。

次にRepositoryを見てみましょう。こっちはもっと複雑です。

resource Repository {
  permissions = ["read", "push"];
  roles = ["contributor", "maintainer"];
  relations = { parent: Organization };

  # An actor has the "read" permission if they have the "contributor" role.
  "read" if "contributor";
  # An actor has the "push" permission if they have the "maintainer" role.
  "push" if "maintainer";

  # An actor has the "contributor" role if they have the "maintainer" role.
  "contributor" if "maintainer";

  # An actor has the "maintainer" role if they have the "owner" role on the "parent" Organization.
  "maintainer" if "owner" on "parent";
}

permissionsという定義があります。これは、対象となるResourceに対して、「何ができるか」というのを定義します。なので、ここではRepositoryに対して「readとpushを行うことができる」と定義してます。

rolesを見てみると、Repositoryに紐づくRoleはcontributorとmaintainerだと定義しています。

次に登場するのがrelationsの定義ですが、いったん飛ばしましょう。

まずは、3つほど文字列とifで構成されてるRuleを見てみましょう

"read" if "contributor";
"push" if "maintainer";
"contributor" if "maintainer";

読みやすいといえば読みやすいと思います。「contributerならreadできる」、「maintainerならpushできる」、「maintainerであればcontributerでもある」という感じです。

これは何なのかというと、Shorthand Ruleというものになります。Shorthandと言ってるように、要は省略記法です。

通常のRuleで表現すると以下のとおり。

$x if $y;
=> rule1(actor: Actor, $x, resource: $Type) if rule2(actor, $y, resource);

resource: $Type$Type に関しては、どのresource内で書かれていたかによって変動するということです。今回はresource Repositoryで使ってるので以下のようになります。

$x if $y;
=> rule1(actor: Actor, $x, resource: Resource) if rule2(actor, $y, resource);

で、$x がもしpermissionであれば、rule1has_permissionに置き換わりますし、 roleであれば、has_roleに置き換わります。同様なことが$yでも言えます。

なので、

"read" if "contributor";
↓
has_permission(actor: Actor, "read", resource: Repository) if has_role(actor, "contributor", resource);

というように読み換えることができます。ここでシレッとhas_roleを出しましたけど、これもまたPolarに最初から組み込まれているRule Typeの一つです。

先程は飛ばしましたが、あらためてhas_roleの定義を見ると、以下の通りです。

has_role(user: User, name: String, resource: Resource) if
  role in user.roles and
  role.name = name and
  role.resource = resource;

これを見たときにuser.rolesとかrole.nameってどこから現れたんだって思うかもしれません。ここが自分のコード(JSとか)とマッピングした型の情報になります。

class Role {
  constructor(name, resource) {
    this.name = name;
    this.resource = resource;
  }
}

class User {
  constructor(name) {
    this.name = name;
    this.roles = new Set();
  }

  assignRoleForResource(name, resource) {
    this.roles.add(new Role(name, resource));
  }
}

なので、has_roleのRuleが言っていることは、「もしuserのrolesの中で、role.nameが、引数で渡されたnameと一致して、role.resourceもresourceと一致するなら、確かにnameというRoleを持っているね」ということです。

もう一例みておくと

"contributor" if "maintainer";
↓
has_role(actor: Actor, "contributor", resource: Repository) if has_role(actor, "maintainer", resource);

というRuleもありました。これは「「maintainer」Roleを持っているのであれば、「contributor」Roleも持っていることにする」という意味があります。Roleの継承っぽいことができるイメージです。

では、最後に残っているのが以下のルールです。

"maintainer" if "owner" on "parent";

Shorthand Ruleなのですが、今回はonがついてます。これはRelationを使ったShorthand Ruleになります。

$x if $y on $z;
=> rule1(actor: Actor, $x, resource: $Type) if rule2(actor, $y, related) and has_relation(related, $z, resource);

今回の場合であれば

"maintainer" if "owner" on "parent";
↓
has_role(actor: Actor, "maintainer", resource: Repository) if has_role(actor, "owner", related) and has_relation(related, "parent", resource);

になります。「親Resourceにowner Roleを持っていて、Repositoryが、親Resourceとparentのリレーションをもっているのであれば、Repositoryに対してmaintainer Roleを持っているのと同等とする」という意味になります。

ここで登場しているのがhas_relationのRuleです。そして先程飛ばしていたresourceに定義しているrelationsも深く関係しています。

relations = { parent: Organization };

これは、Resource同士の「関連」を定義するときに使えます。ここで言っていることは、「RepositoryはOrganizationとparentリレーションを通して関連づいている」ということです。「parentリレーションってなに?」ってところで登場するのが、has_relationの定義です。

has_relationもまた最初から組み込まれてるPolarのRule Typeです。この中身に、Resource同士の「関連」を定義します。今回の定義を見てみると以下のとおりです。

has_relation(organization: Organization, "parent", repository: Repository) if
  organization = repository.organization;

これは、「もしorganizationが、repositoryに定義されているorganizationと一致するなら、OrganizationとRepositoryはparentリレーションを持っている」と定義しています。なので、Resourceの親子関係を持たせることができているのです。ちなみにここで登場しているorganizationやrepositoryは、自分のコード(JSとか)で定義した型のことです。それぞれ以下のように定義しています。

class Organization {
  constructor(name) {
    this.name = name;
  }
}

class Repository {
  constructor(name, organization) {
    this.name = name;
    this.organization = organization;
  }
}

これでPolarによるPolicyの定義が完了します。

テスト

では、これらの情報を使って実際にテストしてみましょう。まずはOsoに、Polara側で使うJavaScriptのclassを登録しておきます。そして、PolicyをOsoのloadFilesを使って読み込みます。

oso = new Oso();
oso.registerClass(Organization);
oso.registerClass(Repository);
oso.registerClass(User);

await oso.loadFiles([`${__dirname}/../policies/main.polar`]);

あとは、セットアップとして登場人物たちを用意します。

再掲しますが、以下のような関係になるようにします。

f:id:kenev:20210922212912p:plain

コード的には以下のような感じです。

org = new Organization('ACME');
repo = new Repository('ACME App', org);

alice = new User('Alice');
alice.assignRoleForResource('contributor', repo);

bob = new User('Bob');
bob.assignRoleForResource('maintainer', repo);

tom = new User('Tom');
tom.assignRoleForResource('owner', org);

jane = new User('Jane');
jane.assignRoleForResource('guest', repo);

あとは、Osoを使って、permissionが許可されるかどうかを判断してもらうだけです。今回はOsoのisAllowedメソッドを使うことにしました。メソッドにわたすのは、Actor, Permission, Resourceで、Polarのallowに定義されているパラメータと同様です。

// contributorはreadだけできる
expect(await oso.isAllowed(alice, 'read', repo)).toBe(true);
expect(await oso.isAllowed(alice, 'push', repo)).toBe(false);

// maintainerはreadもpushもできる
expect(await oso.isAllowed(bob, 'read', repo)).toBe(true);
expect(await oso.isAllowed(bob, 'push', repo)).toBe(true);

// Organizationのownerはmaintainer同様ではreadもpushもできる
expect(await oso.isAllowed(tom, 'read', repo)).toBe(true);
expect(await oso.isAllowed(tom, 'push', repo)).toBe(true);

// それ以外の人はreadもpushもできない
expect(await oso.isAllowed(jane, 'read', repo)).toBe(false);
expect(await oso.isAllowed(jane, 'push', repo)).toBe(false);

というように、意図した結果を確認することができました。

以下のリポジトリに実際に動作する例を置いているので、興味がある人は見てみてください。

github.com

まとめ

なぜtrueになったのか、なぜfalseになったのか、についてはまた時間のあるときに書きます。

また、この記事はOsoのほんの一部しか紹介できてません。真に強力なのは0.20.0から追加されたData Filterです。これは長年僕が悩んでいた、権限をデータ取得のときにどう適用すれば良いのか、ということに関する答えの一つになってます。興味がある人はぜひ使ってみましょう。

docs.osohq.com

まとめというほとまとめられませんが、OsoでのRBAC実装を実際にやってみた記録を残してみました。Polarの暗黙的な組み込みRule Typeが最初は「なにこれ?」って感じになります。ドキュメントをしっかり読まないとこれが最初の鬼門になりそうです。あと、自分のコード(JSとか)と連携するために、classをOsoに定義してマッピングするあたりも、最初は関係性を理解するのに苦労しそうです。

と、難しそうな感想を言ってますが、期待はめちゃくちゃしてます。そもそも認可とか権限が難しすぎるんです。オレオレ認可フレームワークでいつも苦しむよりは、ようやく登場したかもしれないベストプラクティスを凝縮したOsoというライブラリを使ってみて、このあたりのドメインもあまり悩みすぎることなく作っていけると良いですね。

WindowsでMongoDBをインストールする

Windowsで開発用にさくっとMongoDBを使えるようにする方法について記録を残しておきます。

概要

  • キャプチャを貼ってますが、ウェブサイトの見た目が変わる可能性は高いです
  • MongoDBをWindowsのサービスとしてインストールすることもできますが、この記事ではサービスとしてはインストールしません
    • シンプルに、使いたいときに exe を起動するようにします
  • MongoDBのバージョンは4系をインストールします
  • GUIの管理ツールであるMongoDB Compassはインストールしません
  • DBのデータを格納する場所を明示的に指定します(~/webdevbc/data/db)

ダウンロードとインストール

How to Guidesに行きます f:id:kenev:20210803222331p:plain

Install MongoDBをクリックしましょう f:id:kenev:20210803222445p:plain

URL的には以下になります。 docs.mongodb.com

「MongoDB Download Center」でダウンロードって言われるのでダウンロードセンターへ行きます。 f:id:kenev:20211013225049p:plain

f:id:kenev:20210803223738p:plain ↑On-premisesを選択します

f:id:kenev:20210803223932p:plain

  • MongoDB Community Serverを選択
  • バージョンは4系を使いたいので、執筆時点での最新版の4.4.7を選択
  • ダウンロードします

ダウンロードしたインストーラを起動します。

f:id:kenev:20210803224116p:plain

Next f:id:kenev:20210803224213p:plain

AcceptしてNext f:id:kenev:20210803224301p:plain

こだわりが無ければComplete f:id:kenev:20210803224341p:plain

サービスとしてインストールしないので、「Install MongoD as a Service」のチェックをはずしてNext (サービスとしてインストールしたい人はどうぞ) f:id:kenev:20210803224414p:plain

MongoDB Compassはインストールしないので「Install MongoDB Compass」のチェックをはずしてNext (インストールしたい人はチェックをつけたままNext) f:id:kenev:20210803224527p:plain

Install f:id:kenev:20210803224639p:plain

Finish f:id:kenev:20210803224931p:plain

MongoDBの起動

Git Bashを起動してまずはMongoDBのデータを保存する場所を用意します。

$ mkdir -p ~/webdevbc/data/db

MongoDBのデーモンを起動します。

$ /c/Program\ Files/MongoDB/Server/4.4/bin/mongod.exe --dbpath ~/webdevbc/data/db

ログがたくさんでてきて、特にエラーらしきログが出て無ければ成功です。 f:id:kenev:20210803230532p:plain

mongoコマンドでMongoDBのシェルを起動

Git Bashだと微妙にmongoのシェルがうまく動作しないので、コマンドプロンプトを起動します。

f:id:kenev:20210803232038p:plain

mongoコマンドをコマンドプロンプトから実行

"c:\Program Files\MongoDB\Server\4.4\bin\mongo.exe"

下のような結果になったら接続成功!

f:id:kenev:20210803232218p:plain

シェルが動作しているか確認

  • db と入力して test
  • 1 + 2 と入力して 3
  • show dbs と入力して admin, config, local などのデータベースの名前

が返ってこれば動作OKです!

f:id:kenev:20210803232319p:plain

WindowsでGit Bashが使えるようにする

GitのエディタとしてVisual Studio Codeを使う手順にしています。 そのため、Visual Studio Codeをあらかじめインストールしておく必要があります。

本記事の執筆時点ではバージョンは2.32.0.2でした。

gitforwindows.org

ダウンロード f:id:kenev:20210803213602p:plain

msi起動 f:id:kenev:20210803213659p:plain

Next f:id:kenev:20210803213818p:plain

Next f:id:kenev:20210803213846p:plain

特にこだわりが無ければデフォルトでOK f:id:kenev:20210803214023p:plain

Next f:id:kenev:20210803214100p:plain

デフォルトが「Vim」になっている場合は、 「User Visual Studio Code as Git's default editor」を選択 f:id:kenev:20210803214959p:plain

こだわりが無ければNext f:id:kenev:20210803215304p:plain

こだわりが無ければNext f:id:kenev:20210803215340p:plain

Next f:id:kenev:20210803215417p:plain

Next f:id:kenev:20210803215436p:plain

Next f:id:kenev:20210803215503p:plain

Next f:id:kenev:20210803215525p:plain

Next f:id:kenev:20210803215547p:plain

Next f:id:kenev:20210803215604p:plain

Install f:id:kenev:20210803215633p:plain

すぐに起動したければ「Launch Git Bashf:id:kenev:20210803215816p:plain

スタートアップメニューなどから「Git Bash」が起動できるようになります f:id:kenev:20210803215933p:plain

下のような画面が出たらインストール成功です! f:id:kenev:20210803220015p:plain