からめもぶろぐ。

俺たちは雰囲気で OAuth をやっている

SharePoint Online の REST API を PowerShell から叩いてみる

この記事は「PowerShell Advent Calendar 2016」の参加記事です。

qiita.com

前回の記事で「REST API を Invoke-RestMethod で叩けるかも」と丸投げなことを書いてしまったので、実際にやってみたいと思います。

blog.karamem0.jp

OAuth についておさらい

SharePoint Online の REST API を実行するには、Azure Active Directory (AAD) の OAuth によるアクセス許可が必要です。AAD では Access Token を取得するためにいくつかの認可フローをサポートしています。

  • Authorization Code Grant
  • Implicit Grant
  • Client Credentials Grant

- 共有シークレット
- 証明書

  • Resource Owner Password Credentials Grant
  • Device Flow

Authorization Code と Implicit Grant については UI (Web ブラウザー) を使用するアプリケーションが前提なので、PowerShell の場合はそれ以外の方法を選択することになります。その他の方法を簡単に比較すると以下の通りになります。

認可フロー アクセス許可 難易度 無人化 セキュリティ
Client Credentials (共有シークレット) アプリケーション
Client Credentials (証明書) アプリケーション
Device Flow ユーザー 不可 -

どちらもメリットとデメリットがありますので目的に応じて使い分けるのがいいと思います。今回は Device Flow による認証を行います。

(2017/08/24 追記)
OAuth の認可フローについての不適切な内容を修正しました。

試してみる

アプリケーションの登録

Azure 管理ポータルから Azure Active Directory のアプリケーションを適当な名前で登録します。

使用する API に [Office 365 SharePoint Online] を選択します。

アクセス許可を選択します。今回はサンプルなのでフル コントロールを付けていますが、適切なアクセス許可を選択してください。

これで事前準備は完了です。

スクリプトの作成

C# だと WebClient や HttpClient を使って面倒な処理を書くことになりますが、PowerShell の場合は Invoke-RestMethod を使ってかなりすっきり書くことができます。

$tenantId = "{{tenantid}}"
$resourceUri = "{{resourceuri}}"
$clientId = "{{clientid}}"

# デバイス コードの取得
$uri = "https://login.microsoftonline.com/" + $TenantId + "/oauth2/devicecode?" + `
       "resource=" + [System.Uri]::EscapeDataString($resourceUri) + "&" + `
       "client_id=" + $clientId
$headers = @{
    "Accept" = "application/json"
}
$result = Invoke-RestMethod -Method "Get" -Uri $uri -Headers $headers

$userCode = $result.user_code
$deviceCode = $result.device_code

Write-Output $userCode
Start-Process "https://aka.ms/devicelogin"

Read-Host | Out-Null

# トークンの取得
$uri = "https://login.microsoftonline.com/" + $TenantId + "/oauth2/token"
$headers = @{
    "Accept" = "application/json"
    "Content-Type" = "application/x-www-form-urlencoded"
}
$body = "resource=" + [System.Uri]::EscapeDataString($resourceUri) + "&" + `
        "client_id=" + $clientId + "&" + `
        "grant_type=device_code&" + `
        "code=" + [System.Uri]::EscapeDataString($deviceCode)

$result = Invoke-RestMethod -Method "Post" -Uri $uri -Headers $headers -Body $body

$accessToken = $result.access_token

# サイトのタイトルを取得
$uri = $resourceUri + "/_api/web/title"
$headers = @{
    "Accept" = "application/json"
    "Authorization" = "Bearer " + $accessToken
}
$result = Invoke-RestMethod -Method "Get" -Uri $uri -Headers $headers
Write-Output $result.value

# ドキュメントの一覧を取得
$uri = $resourceUri + "/_api/web/getfolderbyserverrelativeurl('/Shared%20Documents')/files"
$headers = @{
    "Accept" = "application/json"
    "Authorization" = "Bearer " + $accessToken
}
$result = Invoke-RestMethod -Method "Get" -Uri $uri -Headers $headers
$result.value | select Name, TimeCreated, TimeLastModified

実行

実行するとプロンプトにコードが表示されます。認証が行われるまでスクリプトは入力待ち状態になっています。合わせてブラウザーが起動するので、コンソールに表示されたコードを入力します。

アプリケーション名を確認して [続行] をクリックします。組織アカウントでのサインインを要求されますのでユーザー名とパスワードを入力します。

サインインすると完了になります。ブラウザーは閉じても構いません。

プロンプトで Enter キーをクリックするとスクリプトを再開します。SharePoint Online からサイトのタイトルとドキュメント ライブラリのファイルの一覧を取得します。

まとめ

SharePoint Online 単体だと CSOM のほうが便利かもしれませんが、他の Office 365 サービスと連携する場合などは、REST API を使うと統一された方法で書くことができそうです。

おまけ

ちなみに、CSOM がどうやって SharePoint Online に認証をしているかというと、BPOSIDCRL という方式を使っているようです。BPOS の名前の通り、レガシーな方法なので、いつまでサポートされるんでしょうか。気になります。