athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

クリスマス

クリスマスが近づき、街も華やぐ今日この頃、

情報システム部のフロアの一角がクリスマス仕様になりました。

 

f:id:akhr1219:20171207131942j:plain

 

こんにちは 情報システム部の赤堀です。

当記事では、この一角のことやクリスマス準備の風景についてご紹介していきます。

 

 

リフレッシュコーナーができた!

先月フロアのレイアウト変更が行われ、仕事中の息抜きの場となるリフレッシュコーナーができました。

 

ですが!

 

f:id:akhr1219:20171207113337j:plain

机と椅子だけでちょっと寂しい……?

 

f:id:akhr1219:20171207130356j:plain

当部署いやし役のペッパーも、こんな日記を書いちゃう。(※休日だから)

 

 

リフレッシュコーナーを良い感じにしていこう!

リフレッシュコーナーを快適にするため、リフレッシュしやすい環境を作るため、

リフレッシュコーナー運営委員会が発足されました。

 

そして紆余曲折ありまして、委員会活動 第一弾、

リフレッシュコーナーをクリスマス仕様に飾り付けようの会が開かれることになったのです。

 

 

f:id:akhr1219:20171207113249j:plain

委員会メンバーと有志で飾り付けていきます。

 

f:id:akhr1219:20171207113325j:plain

ペッパーも嬉しそう。

 

f:id:akhr1219:20171207113024j:plain

どんどん飾りついていきます。

 

 

 

さて、クリスマスの飾り付けの中にはみんなで手作りした箇所があります。

それは靴下です。

 

f:id:akhr1219:20171207130722j:plain

 

こちらアドベントカレンダー(※日ごとの記事を書かないほう)を模していまして、

赤い靴下がどんどん緑に裏返されていくことでクリスマスまでのカウントダウンを演出しています。

 

靴下の中にはちゃんとお菓子も入ってます。

 

 

ただ靴下のサイズの都合上、お菓子は横から取り出します。

本当に靴下かな?

f:id:akhr1219:20171207113037j:plain

 

 

この靴下は、アドベントカレンダーをみんなで手作りしたい!お菓子を食べたい!

などなどのお話をきっかけに作られました。

普段パソコンばかり眺めているので布を切ったり貼ったりするのは良い気分転換です。

まさにリフレッシュ……?

 

 

でもこれでリフレッシュできたのはまだまだ一部です!

 

より多くの人のリフレッシュを目指して、リフレッシュコーナー運営委員会、今後どんどん活動していきます!

 

 

それではこのあたりで記事を終えたいと思います。

 

 

おわり

RHEL7.4でASP.NET Coreを動かす

こんにちは、情報システム部の高野です。
以前、下記の記事を書きまして今度はRHELだって環境構築したら
またもやハマったので手順を書いておきます。*1
dblog.athome.co.jp

環境

Red Hat Enterprise Linux Server release 7.4 (Maipo)
.Net Core2.0.0 (rhel.7-x64)

.Net Coreのインストール

ここを見れば分かりますが、一応

> sudo subscription-manager repos --enable=rhel-7-server-dotnet-rpms
> sudo yum install scl-utils
> sudo yum install rh-dotnet20
> scl enable rh-dotnet20 bash

※上記は、サーバ版のRHELのコマンドになります。ワークステーション版などはコマンドが違うので注意が必要です。

確認してみる。下記のように表示されればOKです。

> dotnet --info
.NET Command Line Tools (2.0.0)

Product Information:
 Version:            2.0.0
 Commit SHA-1 hash:  cdcd1928c9

Runtime Environment:
 OS Name:     rhel
 OS Version:  7
 OS Platform: Linux
 RID:         rhel.7-x64
 Base Path:   /opt/rh/rh-dotnet20/root/usr/lib64/dotnet/sdk/2.0.0/

Microsoft .NET Core Shared Framework Host

  Version  : 2.0.0
  Build    : N/A


注意が必要なのが、下記のコマンドです。

> scl enable rh-dotnet20 bash

これで「dotnet」コマンドがどこでも起動できるようになるのですが
ターミナルを起動するたびに入力が必要になります。

永続的にコマンドを使いたければ
/etc/profile.d/enabledotnet20.shを作成します。
中身は下記

source scl_source enable rh-dotnet20


ターミナルを再起動せずに利用したい場合は、下記コマンドを実行します。

source /etc/profile.d/enabledotnet20.sh

Nginxのインストール

Nginxのインストールと設定は、以前のCentOSの記事と同様です。

Kestrelをバックグラウンドで実行する

CentOSとパスが違うので記載しておきます。

/etc/systemd/systemに○○.serviceというファイルを作成します。(○○は、なんでもいいです)

[Unit]
  Description=kestrel server
  After=syslog.target network.target
 
[Service]
  ExecStart=/opt/rh/rh-dotnet20/root/bin/dotnet /home/user/www/hoge/sample.dll #dotnetのパスが異なる
  WorkingDirectory=/home/user/www/hoge
  Restart=always
  RestartSec=10
  SyslogIdentifier=sampleapp
  User=user
  Environment=ASPNETCORE_ENVIRONMENT=Staging #サイト毎に環境を変えたい時などに使える
 
[Install]
WantedBy=multi-user.target


起動します。

> sudo systemctl start ○○.service


サーバー再起動時にKestrelも起動したい場合

> sudo systemctl enable ○○.service

デプロイする

dotnet publishコマンドでデプロイ用のモジュールを作成します。

dotnet publish -o /home/www/hoge/ -c Release -r rhel.7-x64

「-r」 オプションで「rhel.7-x64」を付けないとダメです。

まとめ

RHELASP.NET Core環境の作り方を説明しました。
CentOSとは、微妙に違う部分があるので注意が必要です。


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
athome-inc.jp

*1:いつもこのパターンですいません。

バージョン管理システムの比較(という名のポエム)

情報システム部の中嶋です。弊社では以前よりバージョン管理システムVCS)としてGitを採用していますが、Subversion歴が長い*1おっさんとしては、ここで一度ちゃんと比較をしておきたいと思い、4つの論点で比較してみました。(まぁ、控えめに言ってポエムです。)
なお、「チェンジセット記録型」とか「タグ型ブランチ」とかの用語はこの文書のために作った造語なので、あまり気にせずに読み飛ばしていただけると幸いです。

TL;DR

悲観的排他かつ集中リポジトリ 楽観的排他かつ集中リポジトリ 楽観的排他かつ分散リポジトリ
チェンジセット記録型かつコピー型ブランチ VSSなど Subversion、TFVCなど (たぶんない)
スナップショット記録型かつタグ型ブランチ (たぶんない) (たぶんない) Gitなど

「SubversinよりGit」と言う人は、超巨大プロジェクトを扱う機会がなく、マージ先を柔軟に選択できるからGitを選んでいるのではないかと思う。

論点1:悲観的排他と楽観的排他

悲観的排他とは

悲観的排他とは、ファイルの更新をそのファイルの更新権(ロック)を取得してから行うことにより、同時に複数人が同じファイルを更新してしまうことを抑止する方法である。ロックを取得することを「チェックアウト」、更新内容をリポジトリに格納しロックを手放すことを「チェックイン」と呼ぶことが多い。有名なVCSとしては、Visual SourceSafe(VSS)などがある。

楽観的排他とは

楽観的排他とは、「同時に複数人が同じファイルを更新することは稀なので、コンフリクトしたら(自分が修正したファイルを他人も修正してすでにコミットしていたら)その時に考える」という方法である。通常、コンフリクトした場合には、マージを行なってから再度コミットする。一般的に、更新内容をリポジトリに格納することを「コミット」と呼ぶが、リポジトリに格納した更新内容自体をコミットと呼ぶVCSもある。また「チェックアウト」という用語も「作業場の内容を最新に更新する」的な意味で用いられるが、詳細はVCSによって異なる。
近年の楽観的排他を用いたVCSは、コンフリクトが発生した場合にも自動的にマージすることができるようになってきたため、編集者がコンフリクトを解決する場面は少なくなってきている。有名なVCSには、Subversion*2やTeam Foundation Version Control(TFVC)、Gitなどがある。
なお、Subversion*3やTFVCは悲観的排他もサポートしている。

楽観的排他に対する悲観的排他の利点

絶対にコンフリクトが起きないので、マージの必要がない。よって、マージミスは絶対に発生しない。また、Excelファイルなどテキスト形式でないファイルはマージが行えない(もしくは難しい)ため、初めからコンフリクトが起きないようにしておくことは有効な方法である。

悲観的排他に対する楽観的排他の利点

ロックの取得が不要であるため、「他人が更新中であるため、自分が更新できない」という事態が発生しない。また、ロックを取得したまま長期間放置する不心得者への対処が不要である。

論点2:集中と分散

集中リポジトリ型とは

集中リポジトリ型とは、中央にある1つのリポジトリを複数の編集者が共有する方法である。有名なVCSには、VSS、Subversion、TFVCなどがある。

分散リポジトリ型とは

分散リポジトリ型とは、編集者個々がそれぞれリポジトリを所有し、必要に応じてリポジトリ同士を同期させる方法である。一般的には、中央リポジトリを1つ用意し、各編集者はそのリポジトリのコピー(クローン)を手元に作成して使用する。「プッシュ」や「プル」などクローン元のリポジトリと同期するための操作がある。有名なVCSには、Git、Mercurial、BitKeeperなどがある。

分散リポジトリ型に対する集中リポジトリ型の利点

リポジトリをクローンする必要がない。極端に大規模なプロジェクトでなければ問題となることはないが、実際にWindows OSやOfficeなどはリポジトリが巨大であるために分散リポジトリ型への移行が行えずにいた。Microsoftはこの問題を解決するためにGit専用のファイルシステムまで開発することになった*4。また、原理的に悲観的排他は集中リポジトリ型でなければ実現できない*5

集中リポジトリ型に対する分散リポジトリ型の利点

リポジトリは必ず編集者の手元にあるため、オフラインな環境であってもリポジトリにアクセスできる。また、試行錯誤の過程をリポジトリに格納しながら他人には公開しないといったことも行える。
ファイルの更新履歴の表示なども、リポジトリが手元にあるため高速に行うことができる。ただし、ここで表示されるファイルの更新履歴は編集者の手元のリポジトリに記録されているものであるため、必ずしも中央リポジトリと同じとは限らない。

論点3:チェンジセットとスナップショット

チェンジセット記録型とは

チェンジセット記録型とは、コミットの際、リポジトリに現在の状態と直前の状態との変更差分を記録する方法である。リポリトリに格納する操作を「コミットする」、コミットする対象を「チェンジセット」と呼ぶ場合が多い。有名なVCSに、SubversionやTFVCなどがある。
なおVSSも変更差分を記録しているが、同時に行われた複数のファイルに対する変更を1つのものとしては認識せず、ファイル単位で管理している。

スナップショット記録型とは

スナップショット記録型とは、コミットの際、リポジトリに管理対象ファイル全ての現在の状態を記録する方法である。リポジトリに格納する操作も格納する対象も「コミット(する)」と呼ぶ。この方式を採用している代表的なVCSにはGit*6がある。

スナップショット記録型に対するチェンジセット記録型の利点

スナップショット記録型のVCSではファイル名の変更を記録することができない*7。Gitではこの問題を回避するために、「削除されたファイルと追加されたファイルの内容が非常に似ている場合、それはファイル名が変更されたものだ」と判断しUIに表示している。
また、スナップショット記録型は必ず管理対象ファイル全てを同時に扱うため管理対象ファイルの一部だけをチェックアウトすることができないが、チェンジセット記録型では1つのリポジトリのうち必要な部分だけをチェックアウトし作業を行うことができる。このため、チェンジセット記録型はスナップショット記録型よりも大規模プロジェクトに向いていると言える。
さらにスナップショット記録型は各時点でのファイルのスナップショットを保持しているため、チェンジセット記録型よりも格納しているデータ量が多く、特に大きなファイルが存在する場合、リポジトリの肥大化が顕著である。

チェンジセット記録型に対するスナップショット記録型の利点

チェンジセット記録型のVCSでは1つ1つのコミットが変更差分しか持っていないため、1つのコミットだけを独立して操作することはできない。しかしスナップショット記録型のVCSは、そのリポジトリが管理対象としている全てのファイルを1つのコミットの中に保持しているため、1つのコミットだけを独立して操作することができる。これによりコミット順序の変更やコミット単位の調整、分岐元/分岐先以外へのマージが行えるようになっている。

論点4:コピーとタグ

コピー型ブランチとは

コピー型ブランチとは、ブランチの作成を「リポジトリ内でのファイルコピー」として扱い記録する方法である。有名なVCSに、Subversion*8やTFVCなどがある。
なおVSSもコピー型ブランチを採用していると言えるが、ブランチを作成するという行為自体が悲観的排他の利点を損なう行為であるため、利用場面は限られる*9

タグ型ブランチとは

タグ型ブランチとは、ブランチを「コミット指し示すポインタ(ここではタグと表現する)」として扱い記録する方法である。この方式を採用している代表的なVCSには、Git*10がある。

タグ型ブランチに対するコピー型ブランチの利点

コピー型ブランチでは、1つの作業領域に1度に複数のブランチを展開することができる。このため、あるブランチで作業中に他のブランチの作業を行う際、作業中の修正をコミット/退避する必要がない。
また、試験的な実装を行なっている複数ブランチのプログラムを同時に実行するなど、柔軟な対応が行える。これに対し、タグ型ブランチでは1つのブランチしか展開することができない。1つのファイルシステム上に複数のブランチを同時に展開するためには、リポジトリ自体をクローンする必要がある。

コピー型ブランチに対するタグ型ブランチの利点

タグ型ブランチを採用しているVCSにおいて「ブランチの作成」とは、すなわち「タグの作成」であるため、その処理は非常に高速である*11。また、ブランチの内容が作成時に固定化されないため、ブランチの分岐位置を作成後に容易に変更することができる。さらにマージに関しても、マージ先のブランチが指すコミットがマージ元のコミット履歴に含まれる場合はマージ先ブランチが指すコミットを変更するだけで高速かつ確実にマージを行うことができる。


ということで、一通りまとめてみました。後発のシステムほどそれまでのシステムが抱えている問題を解決した良いものになっているわけですが、「人は何かの犠牲なしに何も得ることはできない。」ので多かれ少なかれトレードオフがあったりします。
「じゃぁ、どれを採るか」ということですが、コードレビューをしっかりやるとどうしても変更履歴が無駄に増えてしまうので、ここを変更できるGitが今の所一番使いやすいのかなぁと思っています。


2017年9月8日
公開当初、Mercurialも比較対象に含めておりましたが、内容に誤りがございましたので削除いたしました。

*1:自分は2017年4月にアットホームにJoinしましたが、それまではSubversionやTFVCを使用していました。

*2:http://jtdan.com/vcs/svn/svn-book/svn.basic.vsn-models.html#svn.basic.vsn-models.copy-merge

*3:http://jtdan.com/vcs/svn/svn-book/svn.advanced.locking.html

*4:https://www.infoq.com/jp/news/2017/02/GVFS

*5:Mercurialにはその名もズバリ「Lock Extension」という分散なのに悲観的排他が実現できるエクステンションがあるそうなので、「原理的に〜実現できない」は大げさな表現かもしれません。

*6:https://git-scm.com/book/ja/v1/使い始める-Gitの基本

*7:Subversionも、リポジトリがVer.1.8になるまでは変更が追跡できないことが多かった記憶があります。Ver.1.8になってから、変更の追跡もマージも一気に優秀になりました。

*8:http://jtdan.com/vcs/svn/svn-book/svn.branchmerge.using.html

*9:VSSでも「ファイルの共有」を使用することでバージョン別のブランチを悲観的排他で運用することが可能ですが、マージが前提となるトピックブランチのような運用は想定されていないと思われます。

*10:https://git-scm.com/book/ja/v2/Git-のブランチ機能-ブランチとは

*11:実際にはSubversionにおけるブランチ作成も「このリビジョンからこのブランチを作成した」という情報だけをコミットしているので、そこまでの速度差はないと思われます。

画像システムのリプレイスについて基調講演を行いました

情報システム部の河野です。 入社・開発2年目で、現在はRuby on Rails を使った開発を担当しています。

本日は2017年7月26日(水)に行われました、

ZDNet Japan × TechRepublic Japan & AWS Partner Network 5週間連続セミナーシリーズ データベース編 導入事例から考える–なぜ今、クラウドDBが注目されるのか? の内容について書きたいと思います。

今回このイベントにて、弊社の取り組みについて弊社の鈴木が基調講演をさせて頂きました! 「OracleからAuroraへの移行とオンプレミスとの連携」と題しまして、2016年に行った画像システムのOracle DBのオンプレミス環境からAWSへの移行を行った際の経緯や検討事項、本稼働後の課題についてご紹介しました。

ご存じの方も多いとは思いますが、弊社アットホームは不動産会社と物件を探す方をつなぐ不動産情報サービスを展開しています。 今では物件を探し検討する際、物件の画像がついているのは当たり前とも言える時代です。 弊社の各種サービスでも画像を利用したものは多くあり、その物件画像の配信サービスを支えているシステムは弊社のサービスを支える柱の一つとも言えます。

画像システムの移行と結果

さて、前述にもあります通り画像の需要が増えたことで、画像配信システムのリクエストは1億数千万件/日に上り、キャッシュを利用してもリクエストの件数は増加の傾向にありました。

それにより弊社のシステムには、運用・保守費用の増加や、24時間365日の稼働を行うための安定性確保の課題が浮上してきました。

具体的には

  • Oracleが接続するSANのIO性能の限界
  • ストレージの容量追加やDBサーバのメモリ追加の度に作業が発生(数ヶ月に一度のメンテナンス)
  • ライセンスの上限
  • データ増加に伴いSANの費用が増加(月額課金のサービスを利用)

などです。

そこで2016年7月より、Oracle DBからAmazon Auroraへの移行を行い新システムの運用を開始しました。 弊社がAuroraを採用した理由としては

  • 高可用性
  • 高耐久性
  • 運用の手間が少ない
  • MySQL5.6互換
  • 拡張性

などがあります。

本稼働後の状況

パフォーマンス面では、参照性能はOracle RACとほぼ同等の性能を発揮。大量更新などの際に発生していた、性能劣化が改善しました。

可用性の面では、パラメータチューニングがほとんど不要で、容量追加作業が無くなり、費用面においては、旧システムでかかっていた費用と比較すると半額程度(推定)になりました。

以上のことから、旧システムに比べて随分と楽になった、やってよかった!という結果になりました。

アットホームでは新しい技術を積極的に取り入れるチャレンジをしています。

現在も弊社サービスのリプレイスにて、AWSに移行を伴うプロジェクトも始動中です。

(私もこのプロジェクトに開発メンバーとして参加しています!)

弊社では一緒に開発を行ってくれるエンジニアを募集しています。

興味がある方は下記からエントリお願いします。 ↓↓↓ athome-inc.jp

ASP.NET Coreの環境はASPNETCORE_ENVIRONMENT未設定の場合Productionになる

情報システム部のやまだです。

大分久しぶりに書きますが、これを機にまた書き始めようかな、と。

さて、以前弊社の高野が下記の記事にて

dblog.athome.co.jp

なんでデフォルトがProductionなんだろうか? なにか変な設定が入っているのかな?

と書いていたのですが、その理由も含めてわかったので書き留めておきます。

What happened?

上記にもあるように、アプリケーションの設定をappsettings.jsonに記載してありまして、ステージングや本番など環境ごとにさらに下記のようなファイルを作成していました。

  • ステージング環境(appsettings.staging.json)
  • 本番環境(appsettings.production.json)

appsettingsのあとのファイル名は環境変数のASPNETCORE_ENVIRONMENTの値で決まり、値がstagingならばappsettings.jsonに加えてappsettings.staging.jsonも読み込まれるという、環境変数によって設定を変えるというよくある感じです。RailsだとRAILS_ENV、ExpressだとNODE_ENVですね。

で、ある日、IISで設定したはずのASPNETCORE_ENVIRONMENTの設定が消えていて、気が付いたら本番の設定が適用されていて危機一髪ということがありました。

why?

そもそもIISの設定が無くなったのかはともかくとして、

「そもそもデフォルトってProductionなの?」

「なぜデフォルトがProductionなの?」

ということでちょっと調べて(ググって)みたら見つかりました。

デフォルトってProductionなの?

下記の通りProductionでした。

Hosting/HostingEnvironment.cs at dev · aspnet/Hosting · GitHub

なぜデフォルトだとProductionなの?

TL; DR セキュリティ的によろしくないからProductionにしている

なんでDevelopmentじゃなくてProductionなの?

RailsやExpressはDevelopmentなのに。

ということでさらにググってみたところ、なんかそのものズバリなissueを発見。

Why is hosting environment default value "production"? · Issue #863 · aspnet/Hosting · GitHub

以下、関係ありそうな部分を引用してみます。

blowdart commented on 14 Oct 2016
So, the reason we went with production by default is because both our templates, and in general other people's code add detailed error messages, debug logging, and other things that can cause information disclosure vulnerabilities (at best), or things like auth bypass at worst, depending on what you wrapped in an env.IsDevelopment() check.

By defaulting to production we remove that risk.

In addition launching from inside VS and VS Code sets the environment as development, and best practice generally acknowledges developers should not have every day access to production assets, the risk of defaulting to development is far greater than defaulting to release/production.

 よし、翻訳だ。

ブローダート はコメントしました on 14 Oct 2016
だから、我々がデフォルトでプロダクションを行ったのは、テンプレートと一般的に他の人のコードの両方が、詳細なエラーメッセージ、デバッグログ、および情報漏えいの脆弱性を引き起こす可能性のあるもの、あなたがenv.IsDevelopment()チェックでラップした内容に応じて、最悪です。

デフォルトでは、プロダクションはそのリスクを排除します。

さらに、VSとVSコードの内部からの起動は、開発環境として環境を設定し、開発者が毎回運用資産にアクセスする必要がないことを一般的に認めているため、開発の不履行リスクはリリース/生産のデフォルトよりもはるかに大きくなります。

デバッグ情報とかだだもれしちゃうから安全な方に振っておくよ、ということですね。

ほか、こちらのコメントの方が端的でわかりやすいかもしれません。

DefaultEnvironmentName should be development · Issue #712 · aspnet/Home · GitHub

blowdart commented on 1 Jul 2015
Yes, but your example of web.config is precisely why files don't work. People didn't build in release and publish because it wasn't the default. The amount of debug web sites, with detailed error messages out there is awful. 

ブローダート はコメントしました on 1 Jul 2015
はい。しかし、web.configの例は、ファイルが機能しない理由です。人々はデフォルトではなかったので、リリースでビルドして公開しませんでした。詳細なエラーメッセージが表示されたデバッグWebサイトの量はひどいです。

あー、なるほど。

この辺の方針というか思想というかデザインというかは、言語やフレームワークによって違いそうなので、気を付けたいですね。

ASP.NET Coreのアクセス認可機構

情報システム部の中嶋です。アットホームには2017年4月にJoinし、現在は社内向けの営業支援システムの開発を担当しています。
今日は、ASP.NET Coreが持つアクセス認可の定義とその確認方法について紹介してみたいと思います。


業務システムでは、利用者の役職や役割に応じて利用できる機能、できない機能が存在する場合が一般的です。
ユーザー認証(authentication:いま操作している人がAさんであることを確認する行為)についてはActive Directoryなど外部のシステムに依存することも多いですが、アクセス認可(authorization:Aさんが機能Bを利用しても良いこと)の定義とその確認に関しては各システムが持つ機能に依存するため、それぞれのシステムで用意することが多いと思います。ユーザビリティを考慮するとアクセス認可の確認結果はメニューの表示/非表示で表現されますが*1、URLを直接入力してアクセスされる可能性も考慮するとサーバー上のコントローラークラスにあるアクションメソッドにもアクセス認可を確認する機構が必要となります。
ASP.NET Coreには、この「コントローラークラスのアクションメソッドにおけるアクセス認可の確認」が簡単に行える機構が備わっています。

アクションメソッドの利用を制限する

コントローラークラスのアクションメソッドにおいてアクセス認可の確認を行う方法は非常に簡単で、そのメソッドにAuthorize属性を付けるだけです。メソッドにつけられている場合はそのメソッド、クラスにつけられている場合にはそのクラスに含まれるすべてのメソッドの利用が、許可されたユーザーのみに制限されます。

public class UserController
{
    [Authorize]
    public void ChangePassword(string newPassword)
    {
        // ...
    }
}

例えばパスワード変更のような「許可されたユーザー」が「ユーザー認証されたすべてのユーザー」の場合は引数なしのAuthorize属性使用しますが、「Administratorロールに属しているユーザー」のように特定の条件に制限される場合はAuthorize属性に引数を付けて使用します。

Authorize属性の引数には、ロールを指定するRolesと、ポリシーを指定するPolicyの2つがあります。

ロールを指定して制限する

例えば、Administratorロールに属するユーザーのみが使用可能なアクションメソッドには、Roles引数に「Administrator」というロールの名前を指定します。

public class UserController
{
    [Authorize(Roles = "Administrator")]
    public void GetData(string newPassword)
    {
        // ...
    }
}

こうすることによって、CurrentThreadのCurrentPrincipalがAdministratorロールに含まれている場合のみ、このメソッドを使用できるように制限できます。
またRolesに複数のロールの名前を指定することによって、「いずれかのロールに属しているユーザーが利用できる」という制限を行うこともできます*2

ポリシーを指定して制限する

「ユーザー認証されたユーザーのうち一部のユーザーだけが利用可能だが、その条件はロールではない」という場合には、ポリシーを利用します。
ポリシーを利用する場合には、事前にポリシーを作成したうえで、Authorize属性にPolicy引数を付けて指定します。

public class UserController
{
    [Authorize(Policy = "EmployeeOnly")]
    public void GetData(string newPassword)
    {
        // ...
    }
}

この例では「EmployeeOnly」というポリシーを指定していますが、このポリシーは自分で定義する必要があります。

独自のポリシーを定義する

アクションメソッドの使用を制限する際にロール以外の条件を使用するためには、独自のポリシーを定義する必要があります。
ポリシーはシステム全体に影響する定義ですのでStartupクラスのConfigureServicesメソッドにおいて定義しますが、その方法は判断基準となる属性によって「ロールによって判断するもの」「クレームによって判断するもの」「その他の属性によって判断するもの」の3つに分けられます*3。このうち、「ロールに属していること」と「クレームがアサインされていること」については、簡単にポリシーを定義するために専用のメソッドが用意されています。

ロールを使用してポリシーを定義する

例えばAdministratorロールに属していることを要件とするポリシーは、RequireRoleメソッドを使用して定義し、AddAuthorizationメソッドによってアクセス認可の1つとして登録します。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddAuthorization(options => options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator")));

        // ...
    }
}

クレームを使用してポリシーを定義する

同じようにEmployeeNumberという名前のクレームが存在することを要件とするポリシーは、RequireClaimメソッドを使用して下記のように定義します。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddAuthorization(options => options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber")));

        // ...
    }
}

また、EmployeeNumberというクレームの値が01であることを要件とするポリシーは、下記のように定義します。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddAuthorization(options => options.AddPolicy("SpecialUserOnly", policy => policy.RequireClaim("EmployeeNumber", "01")));

        // ...
    }
}

その他の方法でポリシーを定義する

ロールやクレーム以外の属性を使用してアクセス認可を行う場合についてですが、この場合は、ポリシーとともに「要件」と「その要件を満たすことを判断するための処理(ハンドラー)」を作成する必要があります。

独自の要件を定義する

例えば、「関東の営業担当であること」と言ったポリシーを作成する場合を考えます。
このような場合は、まず「特定地域の営業担当であること」という要件を作成します。
ここでは「要件」と表現していますが、「アクセス認可の判断基準となる評価軸」という方がより正確かもしれません。「特定地域の営業担当であること」という評価軸を作成しておくことにより、「関東の営業担当であること」だけでなく「北海道の営業担当であること」「九州と沖縄の営業担当であること」なども簡単に作成できるようになります。これは「Administrator」というロール名を指定してポリシーを作成するRequireRoleメソッドと同じような考え方です。

public class SalesAreaRequirement : IAuthorizationRequirement
{
    public string[] SalesAreas { get; protected set; }

    public SalesAreaRequirement(string[] areas)
    {
        this.SalesAreas = areas;
    }
}

次にこの要件を使用したポリシーを作成します。この時、要件の引数に「関東」と設定することで、「関東の営業担当であること」というポリシーになります。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddAuthorization(options => options.AddPolicy("RequireSalesAtKantoArea", policy => policy.Requirements.Add(new SalesAreaRequirement(new string[] { "関東" }))));

        // ...
    }
}

最後に、この要件を実際に確認するハンドラーを作成します。このメソッドの中でcontext.Succeed(requirement)が実行された場合、アクションメソッドに対するアクセスが許可されます。

public class SalesAreaHandler : AuthorizationHandler<SalesAreaRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SalesAreaRequirement requirement)
    {
        // ここでDBなどからログインユーザーの担当地域を取得しておき、salesAreaに格納しておく。

        if (requirement.SalesAreas.Contains(salesArea))
        {
            context.Succeed(requirement);
            return Task.CompletedTask;
        }

        return Task.CompletedTask;
    }
}

また、このハンドラーはASP.NET Coreのフレームワークによってパイプラインの一部としてインジェクションされるため、その定義も必要となります。

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // ...

        services.AddSingleton<IAuthorizationHandler, SalesAreaHandler>();

        // ...
    }
}

このように、ASP.NET Coreではアクセス認可をポリシーとそのポリシーを満たすための要件として表現することで、容易にわかりやすく定義することができます。
また、ここで作成したポリシーはRazor構文の中でも使用できるため、アクションメソッドのアクセス認可と同じ条件でメニュー等の表示/非表示を制御することもできます。


ということで、今回はASP.NET Coreが持つアクセス認可の定義とその確認方法について紹介してみました。
アクセス認可などは比較的「車輪の再発明」をしがちな分野かと思いますが、用意されているものをうまく利用することで、全体としてより大きな価値が提供できていければ良いなぁと考えています。

*1:Aさんが参照しても良いデータ、参照できないデータの判断もアクセス認可の一部ですが、今回は機能だけに注目して話を進めていますので、メニューでの表現が中心になります。

*2:Rolesに複数のロールを指定すれば「or」ですが、Authorize属性自体を複数つけると「and」になります。

*3:ロールによる判断はポリシーを定義せずともAuthorize属性のRoles引数で可能ですが、「メニューの表示/非表示の制御」のようにRazor構文の中で使用しようとするとポリシーが必要となります。

HoloLensでオブジェクトを回転してみる

こんにちは、情報システム部の高野です。
ちょっと間が空いてしまいましたが
拡大縮小に引き続き、今度は回転です。
今回は、Y軸に対する回転だけやります。
(いまのところそれしか使わなそうなので)

準備

前回の拡大縮小で作成したプロジェクトにそのまま追加していきます。

回転用のハンドルCubeとスクリプトの作成

回転ハンドルを作成する

HierarchyウィンドウのCube内のWireを右クリックし
「3D Obejct」→「Sphere」を選択します。
名称をRotateHandle1に変更します。
サイズと位置を変更します。
Position X=0.5 Y=0 Z=-0.5
Size X/Y/Z=0.05
前回作成したHandle用のマテリアルをアタッチします。

スクリプトを作成する

Projectウィンドウで「C# Script」を作成し
名称をRotateControllerに変更します。
これをRotateHandle1オブジェクトにアタッチします。

RotateController.csをVisualStudioで開き以下の様に修正します。

using HoloToolkit.Unity.InputModule;
using UnityEngine;

public class RotateController : MonoBehaviour, IInputHandler, ISourceStateHandler
{
    private bool isHold;
    private IInputSource currentInputSource;
    private uint currentInputSourceId;
    private Vector3 prevPos;
    private GameObject targetObj;

    // Use this for initialization
    void Start()
    {
        // rootから取れば良かった
        targetObj = transform.root.gameObject;
    }

    // Update is called once per frame
    void Update()
    {
        if (!isHold) return;

        Vector3 handPos;
        currentInputSource.TryGetPosition(currentInputSourceId, out handPos);
        // ワールド空間からローカル空間に変換
        handPos = Camera.main.transform.InverseTransformDirection(handPos);

        var diff = prevPos - handPos;
        prevPos = handPos;

        targetObj.transform.Rotate(0f, diff.x * 360, 0f, Space.World);
    }

    public void OnInputUp(InputEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }

    public void OnInputDown(InputEventData eventData)
    {
        if (!eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.Position))
            return;

        if (isHold) return;

        isHold = true;
        InputManager.Instance.PushModalInputHandler(gameObject);

        currentInputSource = eventData.InputSource;
        currentInputSourceId = eventData.SourceId;

        currentInputSource.TryGetPosition(currentInputSourceId, out prevPos);
        prevPos = Camera.main.transform.InverseTransformDirection(prevPos);
    }

    public void OnSourceDetected(SourceStateEventData eventData)
    {
    }

    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (!isHold) return;

        isHold = false;
        InputManager.Instance.PopModalInputHandler();
    }
}

TransformクラスのInverseTransformDirectionメソッドで
ワールド空間からローカル空間に変換してますが
オブジェクトの正面からだけ操作するのであればこれは必要ありません。
オブジェクトの横や後ろに回って操作すると
これが無いと手の動きと回転が合わなくなります。
下図のような位置関係の場合は、手を横に動かすとZ方向に動かすことになります。
このままだとXの移動距離では無くZの移動距離が必要になります。
f:id:taktak1974:20170620160225p:plain
自分とオブジェクトの位置関係に応じて
Xの移動距離だったりZの移動距離だったりに変更するのは大変なので
カメラのローカル空間に変換して手の横移動は常にXの移動にしました。
これがよくわからずに嵌りました(Unity初心者なので色々、嵌ります)


後は、各辺にハンドルをコピーします。
完成したものがこちらになります。

hololens rotate object


これを作っている間にMRDesignLabs_Unityが公開されて
作る必要なかったなって感じにはなったのですが
勉強にはなったので良かった。
MRDesignLabs_Unityは、HoloToolkitのイベントを使ってないので
やり方は結構違いますけどね。*1


弊社ではエンジニアを募集しています。
興味がある方は下記からエントリお願いします。
Unityできる人もいるとうれしいな
athome-inc.jp

*1:MRDesignLabs_UnityだとHoloLens以外でも動かせそう