82
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本」を読んで、実際に Azure AD を使って OpenID Connect のフローを確認してみた。

Last updated at Posted at 2019-09-28

#はじめに

私の認証・認可のプロトコルの前提知識として、SAML 2.0 の認証プロトコルのフローは、ほぼ把握できていますが、OAuth 2.0 と OpenID Connect については、実際にどんなフローで認可、認証しているのかが正直分からないという状況でした。

Auth屋(@authyasan)さんの「OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本」に出会うまでは。
上記書籍は、技術書展7 で販売されました。

現在は、下記 BOOTH というサイトから、電子版と物理版を購入することできます、ぜひ今回の記事を読んで興味を持っていただけたら、お手に取っていただけると幸いです。

URL:https://authya.booth.pm/

今回はその書籍を読んで、実際に Azure AD を使って、Web アプリケーションを登録し、OpenID Connect を使って、ユーザーが Web アプリケーションにアクセスするまでの流れを画面ショット付きで紹介したいと思います。

#OpenID Connect のフローについて

フロー図については Microsoft の下記公開情報を基に説明します。
今回検証したのは、3 つ(認可コード フロー、インプリシット フロー、ハイブリッド フロー)存在する OpenID Connect のフローのうち、インプリシット フローにて動作確認を行いましたので、インプリシット フローの全体図を載せています。

-参考情報
Microsoft ID プラットフォームと暗黙的な許可のフロー
URL:https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-oauth2-implicit-grant-flow

image.png

フロー図を順番に説明すると下記のとおりになります。

  1. アプリケーションを利用したいユーザーがアプリケーションにログインします。
  2. アプリケーション からリダイレクト処理が行われます。
  3. Azure AD (Identity Provider) の認可エンドポイント(/oauth2/authorize)にリダイレクトされ認証リクエストが要求されます。
  4. ログイン画面が表示されます。
  5. UPN とパスワードを入力します。
  6. ユーザー情報を提供するかどうかの確認画面が表示されます。
  7. 確認画面にて同意を行います。
  8. 同意を行うと Azure AD からアクセス トークンID トークンがユーザー宛に発行されます。
  9. ブラウザーを経由して、アプリケーションに設定しているリダイレクト URIに遷移します。
  10. アプリケーションは発行された ID トークンを公開鍵を使って検証します。
  11. 検証が完了すると認証が完了します。

なお、参考まで、認可コード フローの場合は、下記のようなフローで認証を行います。
(認可コード フローを使った検証は別途行う予定ですが、本記事では触れません。)

  1. アプリケーションを利用したいユーザーがアプリケーションにログインします。
  2. アプリケーション からリダイレクト処理が行われます。
  3. Azure AD (Identity Provider) の認可エンドポイント(/oauth2/authorize)にリダイレクトされ認証リクエストが要求されます。
  4. ログイン画面が表示されます。
  5. UPN とパスワードを入力します。
  6. ユーザー情報を提供するかどうかの確認画面が表示されます。
  7. 確認画面にて同意を行います。
  8. 同意を行うと Azure AD から認証レスポンス(認可コード)を得られます。
  9. ブラウザーを経由して、アプリケーションに設定しているリダイレクト URIに遷移します。
  10. Azure AD (Identity Provider) のトークン エンドポイント(/oauth2/token)にトークンのリクエストを行います。
  11. Azure AD からアクセス トークンID トークンがアプリケーション宛に発行されます。
  12. アプリケーションは発行された ID トークンを公開鍵を使って検証します。
  13. 検証が完了すると認証が完了します。

※アプリケーションは正確には OpenID Connect では Relying Party (RP) と呼びます。

#やってみる

OpenID Connect のフローを確認するために、環境を用意します。
登場人物は以下のとおりです。

・アプリケーション ユーザー (Azure AD ユーザー)
・Relying Party (JavaScript シングルページ アプリケーション)
・Identity Provider (Azure AD)
・Web API (Microsoft Graph API)

上記の中で、JavaScript シングルページ アプリケーションを用意する手順を以下に記載します。

Azure ポータルより、「Azure Active Directory」→「アプリの登録」の順にクリックします。

image.png

アプリの登録画面より画面上部の「+新規登録」をクリックします。
image.png

アプリケーションの登録画面より、名前欄に任意のアプリケーション名を入力し、「登録」をクリックします。
image.png

作成されたアプリ画面の左上にある、「クイック スタート」をクリックします。
image.png

クイック スタート ガイドの画面より、今回はシングル ページ アプリケーション欄の「JavaScript」をクリックします。
image.png

クイック スタートの画面より、前提条件の項目にある、「Node.js」と「Visual Studio Code」と「Visual Studio 2019」をそれぞれダウンロードし、インストールします。(詳細な手順については割愛します)
image.png

手順1. として、リダイレクト URI の追加と、3 つあるフロータイプのうち、インプリシット フロー(暗黙の付与)を有効にする必要があるので、画面にある、「これらの変更を行います」をクリックします。

※OpenID Connect のフローには、認可コード フローインプリシット フローハイブリッド フローの 3種類があります。
それぞれのフローの詳細については、ご案内している「OAuth、OAuth認証、OpenID Connectの違いを整理して理解できる本」を買ってご確認ください。
image.png

これらの変更を行います、をクリックすると、下記画面のとおり、リダイレクト URI とインプリシット フローを有効にする旨の確認画面が表示されるので、「更新する」をクリックします。
image.png

手順2. として、「コア プロジェクト ファイルをダウンロードします」のリンクより、プロジェクト ファイルをダウンロードします。
※ダウンロード先として、「C\test フォルダを指定しています。(任意のフォルダを指定してください)
image.png

image.png

同様に、「Visual Studio プロジェクトをダウンロード」のリンクより、同様のフォルダにダウンロードし、ダウンロードした zip ファイルを解凍します。

手順3. として、手順 2. でダウンロードした、コア プロジェクト ファイルのフォルダ(C:\test\active-directory-javascript-graphapi-v2-quickstart\JavaScriptSPA)配下にある、Index.html を以下のとおり変更します。

変更前)


 var msalConfig = {
        auth: {
            clientId: 'Enter_the_Application_Id_here', //This is your client ID
            authority: "https://login.microsoftonline.com/Enter_the_Tenant_Info_Here" //This is your tenant info
        },
        cache: {
            cacheLocation: "localStorage",
            storeAuthStateInCookie: true
        }
    };

変更後)
※実際の値はご利用になられる環境により異なりますので、適宜置き換えてください。


var msalConfig = {
    auth: {
        clientId: "8a0dbafc-c0bd-4af2-b2e3-8b05150611ef",
        authority: "https://login.microsoftonline.com/ca90ee67-329e-4615-b7a1-b2e158252733"
    },
    cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: true
    }
};

ここで設定している cliendId は、アプリの登録時に Azure AD から発行された下記アプリケーション(クライアント) ID のことを指しています。
image.png

手順4. として、Node.js を起動して以下手順を行います。
image.png

「スタート」→「Node.js」→「Node.js command prompt」の順にクリックします。
image.png

active-directory-javascript-graphapi-v2-quickstart ディレクトリで、以下コマンドを実行し、Relying Party のサーバーを起動します。

C:\test\active-directory-javascript-graphapi-v2-quickstart>npm install
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN active-directory-javascript-graphapi-v2@ No repository field.

added 54 packages from 38 contributors and audited 136 packages in 3.734s
found 0 vulnerabilities

C:\test\active-directory-javascript-graphapi-v2-quickstart>node server.js
Listening on port 30662…

アプリケーション サーバーが起動したので、ブラウザーから、対象の URL (http://localhost:30662/) にアクセスします。
その前に、通信状態のパケットを取得したいので Fiddler を起動しておきます。

Fiddler によるパケットキャプチャ手順は、下記記事を参考にしてください。

-参考情報
HTTPS パケット キャプチャ ツール Fiddler のインストールから使用開始まで。
URL:https://qiita.com/Shinya-Yamaguchi/items/37347ec532824c2dccad

ここで、OpenID Connect のインプリシット フローを再掲します。

  1. アプリケーションを利用したいユーザーがアプリケーションにログインします。
  2. アプリケーション からリダイレクト処理が行われます。
  3. Azure AD (Identity Provider) の認可エンドポイント(/oauth2/authorize)にリダイレクトされ認証リクエストが要求されます。
  4. ログイン画面が表示されます。
  5. UPN とパスワードを入力します。
  6. ユーザー情報を提供するかどうかの確認画面が表示されます。
  7. 確認画面にて同意を行います。
  8. 同意を行うと Azure AD からアクセス トークンID トークンがユーザー宛に発行されます。
  9. ブラウザーを経由して、アプリケーションに設定しているリダイレクト URIに遷移します。
  10. アプリケーションは発行された ID トークンを公開鍵を使って検証します。
  11. 検証が完了すると認証が完了します。

1.アプリケーションを利用したいユーザーがアプリケーション(http://localhost:30662/) にサインインします。
image.png

2.のリダイレクト処理のあとの、3. の認可エンドポイントに対する認証リクエストの内容は Fiddler で取得することができます。
具体的には、下記のような値が含まれています。

パラメータ
response_type id_token
scope user.read openid profile
client_id will
redirect_uri 8a0dbafc-c0bd-4af2-b2e3-8b05150611ef
left http://localhost:30662/
state 64fdfcfc-0f57-40d8-9d10-0c058efdc08b
nonce 977e5ae2-dfc2-4b7e-88af-67d1624ea2ee

#####response_type
response_typeが id_token になっていますが、これはインプリシットフローを意味します。

response_type に入る値によりフローの種類を見分けることができます。

response_type フロー
code 認可コードフロー
id_token インプリシットフロー
id_token token インプリシットフロー
code id_token ハイブリッドフロー
code token ハイブリッドフロー
code id_token token ハイブリッドフロー

#####scope
scope は Relying Party が要求するスコープを記載します。OpenID Connect の場合は、openidが必ず含まれています。

#####client_id
client_id は Azure AD のアプリの登録時に Azure AD が発行したクラインと ID が入ります。

#####redirect_uri

redirect_uri は上述のクイック スタートでアプリケーションの登録の構成時に設定した、リダイレクト先の URI になります。

#####state

state は Relying Party が自動生成したランダムな値がセットされます。
ユーザーのブラウザーセッションと紐づけることで、クロスサイト リクエスト フォージェリを防ぐことができます。

#####nonce
nonce は OpenID Connect 特有の設定値で、認証リクエストとしてセットされている値と、このあとに出てくる ID トークンの中に含まれる nonce の値と一致するかどうかで、当該のユーザーが要求したトークンであるかどうかのチェック(検証)が行えます。

4.ログイン画面が表示されます。
image.png

5.UPN とパスワードを入力します。
image.png

image.png

6.ユーザー情報を提供するかどうかの確認画面が表示されます。
image.png

7.確認画面にて承諾を行います。
image.png

8.同意を行うと Azure AD からアクセス トークンID トークンがユーザー宛に発行されます。

ID トークンとアクセス トークンの実体が下記です。

localhost:30662/#id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiI4YTBkYmFmYy1jMGJkLTRhZjItYjJlMy04YjA1MTUwNjExZWYiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vY2E5MGVlNjctMzI5ZS00NjE1LWI3YTEtYjJlMTU4MjUyNzMzL3YyLjAiLCJpYXQiOjE1Njk3MDMwNjMsIm5iZiI6MTU2OTcwMzA2MywiZXhwIjoxNTY5NzA2OTYzLCJhaW8iOiJBVFFBeS84TUFBQUF2TDkrbXhsODdjL24yVG9mQ3pxeXFMSVRSVXhhNFdiTjF6WUtkVTFzWUxrNytKUlQwdjVaQkpRNTBSb2V5UU1EIiwibmFtZSI6InRlc3QwMDNAc2h5YW1hZzAxNS5vbm1pY3Jvc29mdC5jb20iLCJub25jZSI6Ijk3N2U1YWUyLWRmYzItNGI3ZS04OGFmLTY3ZDE2MjRlYTJlZSIsIm9pZCI6ImQ1MWU3MGZhLTA0N2YtNDM4Ni04MDIwLTRhNWFkYWYyZDkyNyIsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QwMDNAc2h5YW1hZzAxNS5vbm1pY3Jvc29mdC5jb20iLCJzdWIiOiJnckhSZ0R4REpJRnNoRTR4Y3M5eTc4S0dsb3VTQjBSeE9DU1VFSEdUc1RjIiwidGlkIjoiY2E5MGVlNjctMzI5ZS00NjE1LWI3YTEtYjJlMTU4MjUyNzMzIiwidXRpIjoiRmtyNzE4d2FlRUtHZDJzTjJCckxBQSIsInZlciI6IjIuMCJ9.K6mIa04MhHYNpPdk9bvOCHAk_O8npes-1b3wNJjUK9PDN5iw_YHyhvaDNgoPi-S75oCkeWMT6_V0_sKLt8vkyeEV4uKcUUnfSL-ggjT11WmSwhsoJRvAsnTRzt15QglxL43yQmddvWwE3iukLe5Nam4Nff5uJ8MS4ngvCsH3FHQmd2b74Kuph9xaeo9w49yrEdIb3xzptVYCXSrDjtmXNHs60wkgkowTtlrAMTjOAtrhZu6-BQZDYBNKjR2NpZ_ewAAlzUVQPn8b_ppQCrQur8KeSt8dAut4xcGS5smIq4ZyAt_wQCrdOhC8Wv-iLy9xvcQZzMDeMqlZg-jU3whyOQ&client_info=eyJ1aWQiOiJkNTFlNzBmYS0wNDdmLTQzODYtODAyMC00YTVhZGFmMmQ5MjciLCJ1dGlkIjoiY2E5MGVlNjctMzI5ZS00NjE1LWI3YTEtYjJlMTU4MjUyNzMzIn0&state=64fdfcfc-0f57-40d8-9d10-0c058efdc08b&session_state=bc580cef-ed57-4e1f-85de-e55524557e8c

ここに記載されている ID トークンをコピーして、https://jwt.io にアクセスに、コピーした ID トークンを Encoded 枠にそのままペーストすると解析が行えます。

ID トークンは JWT(JSON Web Token:ジョット)という署名付きのトークン形式になります。

#####ヘッダー


{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "aPctw_odvROoENg3VoOlIh2tiEs"
}

Type が JWT であることを表し、alg は暗号アルゴリズムを意味します。
また、kid は公開鍵の ID になります。

#####ペイロード


{
  "aud": "8a0dbafc-c0bd-4af2-b2e3-8b05150611ef",
  "iss": "https://login.microsoftonline.com/ca90ee67-329e-4615-b7a1-b2e158252733/v2.0",
  "iat": 1569703063,
  "nbf": 1569703063,
  "exp": 1569706963,
  "aio": "ATQAy/8MAAAAvL9+mxl87c/n2TofCzqyqLITRUxa4WbN1zYKdU1sYLk7+JRT0v5ZBJQ50RoeyQMD",
  "name": "test003@shyamag015.onmicrosoft.com",
  "nonce": "977e5ae2-dfc2-4b7e-88af-67d1624ea2ee",
  "oid": "d51e70fa-047f-4386-8020-4a5adaf2d927",
  "preferred_username": "test003@shyamag015.onmicrosoft.com",
  "sub": "grHRgDxDJIFshE4xcs9y78KGlouSB0RxOCSUEHGTsTc",
  "tid": "ca90ee67-329e-4615-b7a1-b2e158252733",
  "uti": "Fkr718waeEKGd2sN2BrLAA",
  "ver": "2.0"
}

詳細については、ご紹介した書籍を読んでいただきたいのですが、ここで特に重要なのが nonce になります。
認証リクエストの中に含まれているnonce の値(977e5ae2-dfc2-4b7e-88af-67d1624ea2ee)と同一になっていることが分かります。

#####署名

ヘッダーの部分で確認した、暗号化のアルゴリズムが RS256 の場合は、公開鍵方式で暗号化されています。
そのため、署名の検証には公開鍵を利用します。
実際に、署名部分をデコードすると、下記のとおり、公開鍵の実体を確認できます。


-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp2DzxOZiWEHhtVavuwIm
ryTRxW4kJ0mbA1lbXon550DUnKDZCNZaztno8HpOl6NSbVbW+QLDz5VOqCn+PDvS
IRcw+2hrJPRnCNob4yGEuC7v9dPVpPDFRiUrOcwCbJak6xsK9PEsX8FQ/onFHO6Y
JkjsFG8S2nMhgRK+JdURUcuj9paywSBtW9ddeqjQPgCPbZJtk39ReouoBYNm9xiw
hTN0InY9Rt9PKUh4cRetg3OeKQ2E8TOVh1nHeTT2HIIYnAgB7ESUA07wYBuvet4U
GemC2SdfpTSWk2YqzjZONW8p01hJg9x8lcSeyaQVOxTP/SjQoP99la1V8lArF35q
xQIDAQAB
-----END PUBLIC KEY-----

9.ブラウザーを経由して、アプリケーションに設定しているリダイレクト URIに遷移します。
具体的に、Relying Party に渡すアクセス トークンの中身は下記のとおりになります。

Location: http://localhost:30662/#
access_token=eyJ0eXAiOiJKV1QiLCJub25jZSI6IkctcjJ0Vi1rLTZBbjhFaGFyb24yRU9IZDhEbUFMQ3VlUDNNekpOMEpGNmciLCJhbGciOiJSUzI1NiIsIng1dCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9jYTkwZWU2Ny0zMjllLTQ2MTUtYjdhMS1iMmUxNTgyNTI3MzMvIiwiaWF0IjoxNTY5NzAzMDY0LCJuYmYiOjE1Njk3MDMwNjQsImV4cCI6MTU2OTcwNjk2NCwiYWNjdCI6MCwiYWNyIjoiMSIsImFpbyI6IjQyRmdZTGlrYWN5Z3RFV2g5UjZ2NzlYcnVpbXM2dUYrM1c5REp5clArdlBQS25qR3RBc0EiLCJhbXIiOlsicHdkIl0sImFwcF9kaXNwbGF5bmFtZSI6IldlYmFwcC0yMDE5MDkyOSIsImFwcGlkIjoiOGEwZGJhZmMtYzBiZC00YWYyLWIyZTMtOGIwNTE1MDYxMWVmIiwiYXBwaWRhY3IiOiIwIiwiaXBhZGRyIjoiNjAuMTEzLjc2LjEzOSIsIm5hbWUiOiJ0ZXN0MDAzQHNoeWFtYWcwMTUub25taWNyb3NvZnQuY29tIiwib2lkIjoiZDUxZTcwZmEtMDQ3Zi00Mzg2LTgwMjAtNGE1YWRhZjJkOTI3IiwicGxhdGYiOiIzIiwicHVpZCI6IjEwMDMyMDAwNTA1OTJFODEiLCJzY3AiOiJvcGVuaWQgcHJvZmlsZSBVc2VyLlJlYWQgZW1haWwiLCJzdWIiOiJabWpGRWhBWTJMN1FSWlFBRTFDMm9xZWlWTndpUWlBbUItelZQekZKNEpjIiwidGlkIjoiY2E5MGVlNjctMzI5ZS00NjE1LWI3YTEtYjJlMTU4MjUyNzMzIiwidW5pcXVlX25hbWUiOiJ0ZXN0MDAzQHNoeWFtYWcwMTUub25taWNyb3NvZnQuY29tIiwidXBuIjoidGVzdDAwM0BzaHlhbWFnMDE1Lm9ubWljcm9zb2Z0LmNvbSIsInV0aSI6IkN5NDZaLWNFT0VLNEV3aUZ2YS1tQUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfc3QiOnsic3ViIjoiZ3JIUmdEeERKSUZzaEU0eGNzOXk3OEtHbG91U0IwUnhPQ1NVRUhHVHNUYyJ9LCJ4bXNfdGNkdCI6MTU1OTk5Mzg0MX0.cXO_hYr9pbE6MPlZ2AiZ7o5SA4RUZ_V-5Yh5ND8X2Mp8CZPLFa-Pzq8ysiu1zJ08l2fJe06HzZkqdc6XrCuT6YX5jpxCSpC2JjzCNj-_JDE2IGfmyleF7o4-RhCEqz6XPXfnvsFeSYxpDL8l8WprCfJ9amzIJlAcIG5Jki1TKMbCGd4mJcj2x2KORm1qOGuf3Xm7hTBJzo-EwXgDFqrhfIN4N8pkrYCrYG7wWuS_q-SRfOo4qfs8qoCW0OjwEQB5GART36oycaH391nzwTQ4Eef9mZgirxEwke63mz9kq0Xy34ea3BrmeRnueMfB_9xoRzSIosSP3NrAbiHLmivggw
&token_type=Bearer
&expires_in=3599
&scope=openid+profile+User.Read+email
&client_info=eyJ1aWQiOiJkNTFlNzBmYS0wNDdmLTQzODYtODAyMC00YTVhZGFmMmQ5MjciLCJ1dGlkIjoiY2E5MGVlNjctMzI5ZS00NjE1LWI3YTEtYjJlMTU4MjUyNzMzIn0
&state=64fdfcfc-0f57-40d8-9d10-0c058efdc08b
&session_state=bc580cef-ed57-4e1f-85de-e55524557e8c

主なパラメータの説明は以下のとおりです。

#####access_token
文字通りアクセス トークンの実体になります。

#####token_type
Bearer トークンであることを意味しています。

#####expires_in
アクセス トークンの有効期限となり、秒単位でセットされます。ほぼ 1 時間であることが分かります。

#####state
認可エンドポイントにリクエストを行った際に指定した state の値が入ります。

10.アプリケーションは発行された ID トークンを公開鍵を使って検証します。
11. 検証が完了すると認証が完了します。

下記が Web API (Microsoft Graph API) より取得した、UPN の情報になります。

Welcome test003@shyamag015.onmicrosoft.com to Microsoft Graph API
Sign Out 
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": [],
  "displayName": "test003@shyamag015.onmicrosoft.com",
  "givenName": null,
  "jobTitle": null,
  "mail": "test003@shyamag015.onmicrosoft.com",
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": "ja-JP",
  "surname": null,
  "userPrincipalName": "test003@shyamag015.onmicrosoft.com",
  "id": "d51e70fa-047f-4386-8020-4a5adaf2d927"
}

アプリ上では下記のような形で出力されることを確認できました。
image.png

#まとめ

今回は Auth屋(@authyasan)さんの書籍を読んだ後に、実際に Azure AD を使ってアプリケーションを登録し、OpenID Connect のインプリシット フローの動作検証をしてみました。

まだまだ、勉強の余地は大いにあるのですが、本書を読んだことで、得体の知れなかった OpenID Connect がどのような動きをするのかまで把握することができるようになってきました。私自身も繰り返し本書を読むことで理解を深めていきたいと思います。
また、次回は、「認可コード フロー」の動作検証を行いたいと思います。

皆さんもぜひ実際の動作を机上だけでなく、手を動かして確認していただくことで、理解が深まると思います。
少しでもこの記事が参考になれば幸いです。

82
106
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
82
106

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?