博多南ウェブサービスのblog

博多南ウェブサービスのサービス紹介

【Play Framework 2.8.x のServer-Sent Events(SSE) サンプル】Akka Receptionist(Akka Cluster) を使って、複数サーバー間でのメッセージ送信

Play Framework を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

前回のPlay Framework 2.8.x とAkka Typed を使ったServer-Sent Events において、複数サーバー間でのメッセージ送信をAkka Receptionist によって実装します。

以下、目次

やりたいこと

localhost:9000 にアクセスしたクライアントがサーバより受ける同じメッセージを、 localhost:9001 にアクセスしたクライアントも受けることができる

見た目

localhost:9000 と localhost:9001 は別のPlay インスタンス

f:id:hakataminamiWS:20210821002823g:plain
複数サーバー間でのSSE

設計のポイント

Receptionist は、(私の理解では)他のActor へのReference を取得する ための仕組み(で、local, cluster のどちらのActor もサポートしている)。

Receptionist にメッセージをPush するActor(のRef )を登録(自動的に各サーバーのReceptionist へ登録内容を伝搬)し、Push 時にはReceptionist から宛先を取得することで複数サーバーへのメッセージ送信を実現する。

Server1
Sender
Sen...
Receptionist
Receptionist
Receptionist
Receptionist
Actor2(Ref)
Actor2(Ref)
Actor3(Ref)
Actor3(Ref)
Actor4(Ref)
Actor4(Ref)
伝搬
伝搬
Actor4
Act...
Actor3
Act...
Actor1(Ref)
Actor1(Ref)
Actor2(Ref)
Actor2(Ref)
Actor3(Ref)
Actor3(Ref)
Actor4(Ref)
Actor4(Ref)
Actor1(Ref)
Actor1(Ref)
Actor2
Act...
Actor1
Act...
Server2
Client1
Client1
Client2
Client2
Client3
Client3
Client4
Client4
message
message
Find
Find
Viewer does not support full SVG 1.1

実装してみて

  • Receptionist に、Client へPush するActor (ActorSource.actorRef) を直接登録するとdeadLetters が発生するのかうまくいかない(下のログ参照)
  • なので、各サーバー内のActorSource.actorRef を管理するManager Actor をReceptionist に登録、Push 時には、Sender -> Manager とメッセージをリレーしている
DEBUG akka.remote.artery.Decoder Decoder(akka://application) Decoded message but unable to record hits for compression as no remoteAddress known. No association yet?
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.protobuf.ClusterMessageSerializer] for message [akka.cluster.InternalClusterAction$InitJoin]
DEBUG a.r.a.c.InboundManifestCompression InboundManifestCompression(akka://application) Initializing InboundManifestCompression for originUid [3659362368514754290]
DEBUG a.r.a.c.InboundActorRefCompression InboundActorRefCompression(akka://application) Initializing InboundActorRefCompression for originUid [3659362368514754290]
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.remote.serialization.ArteryMessageSerializer] for message [akka.remote.artery.OutboundHandshake$HandshakeRsp]
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoin message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon/firstSeedNodeProcess-1#-1187705133]], but this node is not initialized yet
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.protobuf.ClusterMessageSerializer] for message [akka.cluster.InternalClusterAction$InitJoinNack]
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoin message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon/firstSeedNodeProcess-1#-1187705133]], but this node is not initialized yet
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoin message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon/firstSeedNodeProcess-1#-1187705133]], but this node is not initialized yet
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoin message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon/firstSeedNodeProcess-1#-1187705133]], but this node is not initialized yet
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoin message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon/firstSeedNodeProcess-1#-1187705133]], but this node is not initialized yet
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Received InitJoinAck message from [Actor[akka://application@127.0.0.1:2551/system/cluster/core/daemon#-104449099]] to [akka://application@127.0.0.1:2552]
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.protobuf.ClusterMessageSerializer] for message [akka.cluster.InternalClusterAction$Join]
INFO  akka.cluster.Cluster Cluster(akka://application) Cluster Node [akka://application@127.0.0.1:2552] - Welcome from [akka://application@127.0.0.1:2551]
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.protobuf.ClusterMessageSerializer] for message [akka.cluster.GossipEnvelope]
DEBUG akka.cluster.sbr.SplitBrainResolver akka://application/system/cluster/core/daemon/downingProvider SBR add Up [Member(akka://application@127.0.0.1:2551, Up)]
DEBUG akka.cluster.sbr.SplitBrainResolver akka://application/system/cluster/core/daemon/downingProvider SBR reset stable deadline when members/unreachable changed
DEBUG akka.cluster.sbr.SplitBrainResolver akka://application/system/cluster/core/daemon/downingProvider SBR add Joining/WeaklyUp [Member(akka://application@127.0.0.1:2552, Joining)]
DEBUG a.c.t.i.r.ClusterReceptionist akka://application/system/clusterReceptionist ClusterReceptionist [akka://application@127.0.0.1:2552] - Node added [UniqueAddress(akka://application@127.0.0.1:2551,3659362368514754290)]
DEBUG akka.cluster.ddata.Replicator akka://application@127.0.0.1:2552/system/clusterReceptionist/replicator Received gossip status from [akka://application@127.0.0.1:2551], chunk [1] of [1] containing [ReceptionistKey_3].
DEBUG akka.cluster.ddata.Replicator akka://application@127.0.0.1:2552/system/clusterReceptionist/replicator Sending gossip to [akka://application@127.0.0.1:2551], containing [ReceptionistKey_3]
DEBUG akka.remote.artery.Association Association(akka://application) Using priority message stream for akka://application@127.0.0.1:2551/system/cluster/core/daemon/heartbeatSender
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.protobuf.ClusterMessageSerializer] for message [akka.cluster.ClusterHeartbeatSender$HeartbeatRsp]
DEBUG akka.cluster.ddata.Replicator akka://application@127.0.0.1:2552/system/clusterReceptionist/replicator Created [1] Gossip messages from [1] data entries.
DEBUG a.s.Serialization(akka://application) akka.serialization.Serialization(akka://application) Using serializer [akka.cluster.ddata.protobuf.ReplicatorMessageSerializer] for message [akka.cluster.ddata.Replicator$Internal$Gossip]
DEBUG a.a.L.Deserialization akka.actor.LocalActorRefProvider.Deserialization Resolve (deserialization) of path [system/Materializers/StreamSupervisor-0/$$d-actorRefSource#1662435177] doesn't match an active actor. It has probably been stopped, using deadLetters.

Githubこちら

以上でした。

【Play Framework 2.8.x のServer-Sent Events(SSE) サンプル】Akka Typed を使ってServer push する方法1つ

Play Framework を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

Play Framework 2.8.x とAkka Typed を使ったServer-Sent Events を実装したときのメモです。 この記事play-streaming-scala のサンプルを参考にしています。

以下、目次

前提

見た目

f:id:hakataminamiWS:20210713154255g:plain
Server-Sent Events の実装

実装の詳細

大まかな設計は、参考記事の「Source.actorRef()による実装」を踏襲しています。

実際に実装してみると、追加で以下の変更が必要でした。

  • ブラウザを閉じたときに、対応するSource.actorRefを終了するために、PlayAkkaHttp2Support Plugin を設定する必要がある (Http2 での接続をしたいわけではなく、Plugin を設定しないとブラウザを閉じて対応するSource.actorRefを終了しなかった。なぜだろう?)
  • ActourSource.actorRef を使うため、Akka のバージョンを2.6.15にする必要がある

application.conf

lazy val root = (project in file(".")).enablePlugins(PlayScala, PlayAkkaHttp2Support)
// lazy val root = (project in file(".")).enablePlugins(PlayScala)

val akkaVersion = "2.6.15"

// Akka dependencies used by Play
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-serialization-jackson" % akkaVersion,
  "com.typesafe.akka" %% "akka-stream-typed" % akkaVersion,
  "com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test
)

Heroku 公開はこちら

Githubこちら

以上でした。

Powershell 7.1 からWindows 10 のBluetooth ON/OFF できました

Powershell 7.1 からWindows 10 のBluetooth ON/OFF できました、の話。

結論

superuser.com にある回答を、以下のように編集。

[CmdletBinding()] Param (
    [Parameter(Mandatory=$true)][ValidateSet('Off', 'On')][string]$BluetoothStatus
)
If ((Get-Service bthserv).Status -eq 'Stopped') { Start-Service bthserv }
# Microsoft.Windows.SDK.NET.Ref を事前にインストールしとく
Add-Type -Path 'C:\Program Files\PackageManagement\NuGet\Packages\Microsoft.Windows.SDK.NET.Ref.10.0.19041.15\lib\Microsoft.Windows.SDK.NET.dll'
$asTaskGeneric = ([System.WindowsRuntimeSystemExtensions].GetMethods() | ? { $_.Name -eq 'AsTask' -and $_.GetParameters().Count -eq 1 -and $_.GetParameters()[0].ParameterType.Name -eq 'IAsyncOperation`1' })[0]
Function Await($WinRtTask, $ResultType) {
    $asTask = $asTaskGeneric.MakeGenericMethod($ResultType)
    $netTask = $asTask.Invoke($null, @($WinRtTask))
    $netTask.Wait(-1) | Out-Null
    $netTask.Result
}
Await ([Windows.Devices.Radios.Radio]::RequestAccessAsync()) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null
$radios = Await ([Windows.Devices.Radios.Radio]::GetRadiosAsync()) ([System.Collections.Generic.IReadOnlyList[Windows.Devices.Radios.Radio]])
$bluetooth = $radios.AdditionalTypeData[[Collections.IEnumerable].TypeHandle] | ? { $_.Kind -eq 'Bluetooth' }
Await ($bluetooth.SetStateAsync($BluetoothStatus)) ([Windows.Devices.Radios.RadioAccessStatus]) | Out-Null

詳細

なぜうまくいくかはあまりわかりません。
ので、参考にしたWeb ページをどうぞ。

【Play Framework 2.8.x のフォーム入力サンプル】price にマイナスを入れたときのメッセージを日本語に

Play Framework を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

Play Framework 2.8.x のScala 用フォーム入力サンプルに、 都道府県を選択するメニューを追加したときのメモです。

今回は、フォームのチェック時メッセージを日本語にしてみました。

以下、目次

前提

変更内容

  • conf/application.conf にplay.i18n.langs = ["ja"] を追加
  • conf/messages.ja 作成し、error.min, error.number=(メッセージ内容)を追加

結果

conf/messages.ja に追加した日本語メッセージが表示されました。

変更してみて

Play Framework 2.4 のドキュメントには、

メモ: RequestHeader が暗黙のスコープ内に存在する場合は、その Accept-Language ヘッダと MessagesApi の対応言語を考慮した上で適切な言語が決定され、使用されます。 テンプレートに @()(implicit messages: Messages) のように、暗黙のパラメータ Messages を追加する必要があります。

とあり、2.8 でもAccept-Language の内容で、確認するメッセージファイル(message.xx)が変更されるのを確認。

つまり、conf/application.conf にplay.i18n.langs = ["ja", "en"] と記載していた場合、 特段指定しなければ、Web ページを見たユーザーの Accept-Language ヘッダにより、 表示されるメッセージが選択されるということ。

メッセージは日本語化できたが、コンテンツの内容は自分で日本語を記載しないとだめ。 であるなら、日本語、英語のそれぞれのページは作る必要があり、 メッセージの日本語化はうれしいのか?

Heroku 公開はこちら

Githubこちら

以上でした。

SR-301のON、OFF ができました(Toggle じゃなくて)。

SR-301(YHT-S351)のリモコンである FSR51 WY57790 について、ON、OFF のコード解析結果です。

結論

  • フォーマット:NEC Format
  • ON -> Customer Code(16bit): 0001111011100001, Data: 01111110
  • OFF -> Customer Code(16bit): 0001111011100001, Data: 11111110

(Customer Code、Data ともに読み取り順)

以下、目次

詳細

詳細は、以下のとおりでした。
読み取り順が本来のData なのかはわからなかったので、 順序を逆にしたものも記載しています。

Customer Code は、(フォルダ上)(フォルダ下のみ): 1111111010000000(逆順: 0000000101111111)
それ以外は、0001111011100001(逆順: 1000011101111000)

ボタン 隠し機能 Data(8bit) 16進数 Data(8bit)の逆順 16進数
電源 00110011 33 11001100 CC
サラウンド 00101101 2D 10110100 B4
ステレオ 00001010 0A 01010000 50
クリアボイス 00111010 3A 01011100 5C
TV/STB 10001011 8B 11010001 D1
HDMI1-3 10010100 94 00101001 29
アナログ/FM 11010010 D2 01001011 4B
USB 00111101 3D 10111100 BC
モリー 01001101 4D 10110010 B2
>>(選局上) 01010101 55 10101010 AA
>(プリセット上) 11011000 D8 00011011 1B
表示 01110010 72 01001110 4E
オプション 11010100 D4 00101011 2B
>>(選局下) 10010101 95 10101001 A9
>(プリセット下) 00111000 38 00011100 1C
設定 10111001 B9 10011101 9D
メニュー 01110001 71 10001110 8E
>(フォルダ上) 01110000 70 00001110 0E
|<< 11111001 F9 10011111 9F
決定 10000011 83 11000001 C1
>>| 01111001 79 10011110 9E
■(戻る) 00000011 03 11000000 C0
>|| 11110001 F1 10001111 8F
>(フォルダ下) 00101000 28 00010100 14
+(サブウーファー) 00110010 32 01001100 4C
+(音量) 01111000 78 00011110 1E
-(サブウーファー) 10110010 B2 01001101 4D
消音 00111001 39 10011100 9C
-(音量) 11111000 F8 00011111 1F
エンハンサー 11010011 D3 11001011 CB
ユニボリューム 01010001 51 10001010 8A
レベル 00010010 12 01001000 48
(割り当てなし) ON 01111110 7E 01111110 7E
(割り当てなし) OFF 11111110 FE 01111111 7F
(割り当てなし) TV 11111011 FB 11011111 DF
(割り当てなし) STB 10010010 92 01001001 49
(割り当てなし) HDMI1 01010010 52 01001010 4A
(割り当てなし) HDMI2 00001011 0B 11010000 D0
(割り当てなし) HDMI3 01010100 54 00101010 2A
(割り当てなし) アナログ 01111011 7B 11011110 DE
(割り当てなし) FM 01101101 6D 10110110 B6
(割り当てなし) MOVIE 10011011 9B 11011001 D9
(割り当てなし) MUSIC 01011011 5B 11011010 DA
(割り当てなし) SPORTS 11011011 DB 11011011 DB
(割り当てなし) TV PROGRAM 00111011 3B 11011100 DC

(参考)ボタン配置(FSR51 WY57790)

電源
サラウンド ステレオ クリアボイス
TV/STB HDMI1-3 アナログ/FM USB
モリー >>(選局上) >(プリセット上) 表示
オプション >>(選局下) >(プリセット下) 設定
メニュー >(フォルダ上)
|<< 決定 >>|
■(戻る) >|| >(フォルダ下)
+(サブウーファー) +(音量)
-(サブウーファー) 消音 -(音量)
エンハンサー ユニボリューム レベル

Githubこちら

以上でした。

【Play Framework 2.8.x のフォーム入力サンプル】セキュリティで気にすることは?デフォルト設定だけど

Play Framework を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

Play Framework 2.8.x のScala 用フォーム入力サンプルに、 都道府県を選択するメニューを追加したときのメモです。

セキュリティ面で考えたことをまとめるためメモです。

免責事項

博多南ウェブサービス(以下、筆者)は、コンテンツを掲載するにあたって、その内容、機能等について細心の注意を払っておりますが、コンテンツの内容が正確であるかどうか、最新のものであるかどうか、安全なものであるか等について保証をするものではなく、何らの責任を負うものではありません。また、筆者は通知することなく掲載した情報の訂正、修正、追加、中断、削除等をいつでも行うことができるものとします。
また、コンテンツのご利用により、万一、ご利用者に何らかの不都合や損害が発生したとしても、筆者は何らの責任を負うものではありません。

以下、目次

方針

  • IPA のチェックリストを確認
  • Play Framework 2.8.x (以下、play)でのセキュリティ機能確認しつつ、IPA のチェックリストとplay の機能の対応表作成
  • play のデフォルト設定、追加するべき設定のまとめ作成

こんな感じで。

IPA のチェックリストを確認

以下のものを参考にしました。
IPA独立行政法人情報処理推進機構)「安全なウェブサイトの作り方」(https://www.ipa.go.jp/security/vuln/websecurity.html)(最終確認日:2021/02/22) より引用。

ウェブアプリケーションのセキュリティ実装(第1章の抜粋)
 「安全なウェブサイトの作り方」第1章では、ウェブアプリケーションのセキュリティ実装として、下記の脆弱性を取り上げ、発生しうる脅威、注意が必要なサイト、根本的解決および保険的対策を示しています。下記に第1章を脆弱性ごとに抜粋した内容を掲載しています。  

   1.1 SQLインジェクション
   1.2 OSコマンド・インジェクション
   1.3 パス名パラメータの未チェック/ディレクトリ・トラバーサル
   1.4 セッション管理の不備
   1.5 クロスサイト・スクリプティング
   1.6 CSRF(クロスサイト・リクエスト・フォージェリ)
   1.7 HTTPヘッダ・インジェクション
   1.8 メールヘッダ・インジェクション
   1.9 クリックジャッキング
   1.10 バッファオーバーフロー
   1.11 アクセス制御や認可制御の欠落

Play Framework 2.8.x (以下、play)でのセキュリティ機能確認しつつ、IPA のチェックリストとplay の機能の対応表作成

チェックリストの内容 -> play の機能にあるのかという感じで探しました。

1 SQLインジェクション

SQL文の組み立ては全てプレースホルダで実装する。

-> play のドキュメントにあるPaly Slick と、Anorm にはprepared statements 機能プレースホルダ機能がある。 なのでplay 機能にはあるが、気にすべきこと。

SQL文の構成を文字列連結により行う場合は、アプリケーションの変数をSQL文のリテラルとして正しく構成する。

ウェブアプリケーションに渡されるパラメータにSQL文を直接指定しない。

-> この辺は、play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

2 OSコマンド・インジェクション

シェルを起動できる言語機能の利用を避ける。

-> Scala ではscala.sys.process package が標準としてある。
また、CVE-2020-2200 では、Jenkins Play Framework Plugin での脆弱性が報告されている。 なのでplay 機能にはなく、気にすべきこと。

3 パス名パラメータの未チェック/ディレクトリ・トラバーサル

外部からのパラメータでウェブサーバ内のファイル名を直接指定する実装を避ける。

ファイルを開く際は、固定のディレクトリを指定し、かつファイル名にディレクトリ名が含まれないようにする。

-> play ではpublic フォルダに公開するファイルを置く。また、 Play 2.6.16以降ではWindows における脆弱性が対応されている。 なのでplay 機能にはあり、(public フォルダを使えば)気にする必要はない。

4 セッション管理の不備

セッションIDを推測が困難なものにする。

セッションIDをURLパラメータに格納しない。

-> play 2.x ではsession.getId() のようなセッションIDを取得するメソッドがない。2.6 以降 session cookieはJSON Web Token (JWT) format を利用し、秘密鍵により署名される。 なのでplay 機能にはなく、(セッションID が必要なら)気にすべきこと。

HTTPS通信で利用するCookieにはsecure属性を加える。

-> デフォルトはfalse。 なのでplay 機能にはあるが、気にすべきこと。

ログイン成功後に、新しくセッションを開始する。

ログイン成功後に、既存のセッションIDとは別に秘密情報を発行し、ページの遷移ごとにその値を確認する。

-> これらは、play 機能にはなく、(セッションID が必要なら)気にすべきこと。

5 クロスサイト・スクリプティング

ウェブページに出力する全ての要素に対して、エスケープ処理を施す。

-> play の標準テンプレートエンジンであるTwirl では、 標準で出力する要素に対してエスケープ処理をやっている。 なのでplay 機能にはあり、(@html を使わなければ)気にする必要はない。

URLを出力するときは、「http://」や 「https://」で始まるURLのみを許可する。

-> 関係しそうなplay 機能を見つけられませんでした。 なのでplay 機能にあるか不明。気にすべきこと。

要素の内容を動的に生成しない。

-> 関係しそうなplay 機能を見つけられませんでした。 なのでplay 機能にあるか不明。気にすべきこと。

スタイルシートを任意のサイトから取り込めるようにしない。

-> 関係しそうなplay 機能を見つけられませんでした。 なのでplay 機能にあるか不明。気にすべきこと。

入力されたHTMLテキストから構文解析木を作成し、スクリプトを含まない必要な要素のみを抽出する。

-> 関係しそうなplay 機能を見つけられませんでした。 なのでplay 機能にあるか不明。気にすべきこと。

HTTPレスポンスヘッダのContent-Typeフィールドに文字コード(charset)の指定を行う。

-> play ではデフォルト設定で、テキストベースのHTTPレスポンスにcharset=UTF-8 を指定する。 なのでplay 機能にはあり、気にする必要はない。

6 CSRF(クロスサイト・リクエスト・フォージェリ)

処理を実行するページを POST メソッドでアクセスするようにし、その「hidden パラメータ」に秘密情報が挿入されるよう、前のページを自動生成して、実行ページではその値が正しい場合のみ処理を実行する。

-> play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

処理を実行する直前のページで再度パスワードの入力を求め、実行ページでは、再度入力されたパスワードが正しい場合のみ処理を実行する。

-> play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

Refererが正しいリンク元かを確認し、正しい場合のみ処理を実行する。

-> play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

7 HTTPヘッダ・インジェクション

ヘッダの出力を直接行わず、ウェブアプリケーションの実行環境や言語に用意されているヘッダ出力用APIを使用する。

改行コードを適切に処理するヘッダ出力用APIを利用できない場合は、改行を許可しないよう、開発者自身で適切な処理を実装する。

-> これらは、play ではヘッダーへの追加にwithHeaderメソッドを使用する。 なのでplay 機能にはあり、気にする必要はない。

8 メールヘッダ・インジェクション

メールヘッダを固定値にして、外部からの入力はすべてメール本文に出力する。

ウェブアプリケーションの実行環境や言語に用意されているメール送信用APIを使用する(8-(i) を採用できない場合)。

HTMLで宛先を指定しない。

-> これらは、play のプラグインで設定し、使用する。 なのでplay 機能にはあるが、気にすべきこと。

9 クリックジャッキング

HTTPレスポンスヘッダに、X-Frame-Optionsヘッダフィールドを出力し、他ドメインのサイトからのframe要素やiframe要素による読み込みを制限する。

-> play では2.6以降、デフォルト設定で"DENY"となる。 なのでplay 機能にはあり、気にする必要はない。

処理を実行する直前のページで再度パスワードの入力を求め、実行ページでは、再度入力されたパスワードが正しい場合のみ処理を実行する。

-> play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

10 バッファオーバーフロー

直接メモリにアクセスできない言語で記述する。

直接メモリにアクセスできる言語で記述する部分を最小限にする。

-> これらは、play の機能ではない。 なのでplay 機能にはなく、(Scalaを使うなら)気にする必要はない。

脆弱性が修正されたバージョンのライブラリを使用する。

-> これらは、play の機能ではない。 なのでplay 機能にはなく、気にすべきこと。

11 アクセス制御や認可制御の欠落

アクセス制御機能による防御措置が必要とされるウェブサイトには、パスワード等の秘密情報の入力を必要とする認証機能を設ける。

認証機能に加えて認可制御の処理を実装し、ログイン中の利用者が他人になりすましてアクセスできないようにする。

-> play の機能ではなくどう実装するかだと思う。 なのでplay 機能にはなく、気にすべきこと。

play のデフォルト設定、追加するべき設定のまとめ作成

まとめたもの。 いい感じでまとまるかと思いましたが、まとまりませんでした。

以上でした。

【Play Framework 2.8.x のフォーム入力サンプル】都道府県のselect に入力値チェック追加(変な値をいれてPOST しても大丈夫)

Play Framework を始めたばかりの方向けに、サンプルを進めるうえで困ったところを共有する目的で書いています。

Play Framework 2.8.x のScala 用フォーム入力サンプルに、 都道府県を選択するメニューを追加したときのメモです。

今回は、都道府県のselect に入力値チェックを追加しました。

以下、目次

前提

変更内容

  • PalyFramework 2.8.x のドキュメント のサンプルを参考に、controllers/ にpackage 定数(Constraint[String])を定義し、
  • controllers/ の WidgetForm マッピング部分に.verifying(定義したpackage 定数) を追加
  • views/ の都道府県の表示部分に、(出力)値チェックを追加

app/controllers/WidgetFormConstraint.scala

package object controllers {

  import play.api.data.validation._

  val prefectureCheckConstraint: Constraint[String] =
    Constraint("都道府県チェック")({ value =>
      val errors = Prefecture.allPrefecture.contains(value) match {
        case false => Seq(ValidationError("error.prefecture"))
        case true  => Nil
      }
      if (errors.isEmpty) {
        Valid
      } else {
        Invalid(errors)
      }
    })
}

app/controllers/WidgetForm.scala

package controllers

object WidgetForm {
  import play.api.data.Forms._
  import play.api.data.Form

  /** A form processing DTO that maps to the form below.
    *
    * Using a class specifically for form binding reduces the chances
    * of a parameter tampering attack and makes code clearer.
    */
  case class Data(prefecture: String, price: Int)

  /** The form definition for the "create a widget" form.
    * It specifies the form fields and their types,
    * as well as how to convert from a Data to form data and vice versa.
    */

  val form = Form(
    mapping(
      "prefecture" -> nonEmptyText.verifying(prefectureCheckConstraint),
      "price" -> number(min = 0)
    )(Data.apply)(Data.unapply)
  )

}

app/views/listWidgets.scala.html

-    @for(w <- widgets) { <tr>
-       <td>@w.name</td>
+    @for(w <- widgets) { <tr>
+      <td>@{
+       if (controllers.Prefecture.allPrefecture.contains(w.prefecture)) controllers.Prefecture.allPrefecture(w.prefecture) else "none"
        }</td>

結果

都道府県のselect 入力値チェックを追加したことで、
不正な都道府県(key)のPOST 時に、「400 Bad Request」が表示された。 (ので、変な値を入れてPOST しても大丈夫かな??)

確認方法

POST のBODY の都道府県を(ブラウザの開発ツールを使って)変更

f:id:hakataminamiWS:20210222145603p:plain
POST のBODY の都道府県を(ブラウザの開発ツールを使って)変更

正常なPOST のパターン

存在するもの(GIFU)に変更 -> 200 OK

不正なPOST のパターン

存在するもの(XXX)に変更 -> 400 Bad Request

変更してみて

今回、都道府県の入力に対するバリデーションで考えた点は以下のようなこと。

  • "HOKKAIDO" -> "北海道"みたいな記述は1か所だけにしたい
  • どこに(models or controllers or views ? )記述する?

ちょっと調べると、 以下の考えに落ち着く。

  1. 一般的にどこに書くべきかは見つからない
  2. が、バリデーションは3種類との考え方はなるほど
  3. の場合、MVCのすべてに書いてもおかしくない

結果、

  • "HOKKAIDO" -> "北海道"みたいな記述はcontrollers/ に移動
  • 入力値のバリデーション -> controllers/
  • 出力値のバリデーション -> views/
  • (models で行うであろう)ビジネスロジックバリデーションは今回なし

としてみました。

Heroku 公開はこちら

Githubこちら

以上でした。