からめもぶろぐ。

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

Office 365 管理 API を使って SharePoint Online の監査ログを取得する

いままでずっと SharePoint Online の監査ログの生データは取得できないと思っていたのですが、実は Office 365 管理 API というものを使えば取得できるのだそうです。Office 365 管理 API は SharePoint、Exchange、Teams、Power Platform などのさまざまな監査ログを取得できるのですが、特に利用状況の監視目的で SharePoint Online の監査ログを取りたいという要望は非常に多いため、この方法を使えば解決できるのはないかと思います。

なお今回は以下の記事を参考にしています。

qiita.com

サンプル コード

github.com

実行手順

Office 365 管理 API でログを取得するには以下の手順が必要になります。

  • アクセス トークンを取得する
  • サブスクリプションを作成する
  • コンテンツの URL を取得する
  • コンテンツを取得する

アクセス トークンを取得する

Office 365 管理 API を使用するためには、はじめに Azure Active Directory にアプリを登録する必要があります。[API のアクセス許可] では [Office 365 Management APIs] - [アプリケーションのアクセス許可] - [ActivityFeed.Read] を追加します。

f:id:karamem0:20200902135316p:plain

またアプリケーション シークレットも取得しておきます。

Office 365 管理 API は Azure Active Directory の v2.0 エンドポイントに対応していないので MSAL を使うことができません。ADAL を使ってもいいのですが、面倒なので、今回は直接 HttpClient で取りに行くようにします。といっても Client Credentials Grant なのでそれほど難しくはありません。

private static void AcquireToken()
{
    var httpRequestUrl = $"https://login.microsoftonline.com/{TenantId}/oauth2/token";
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, httpRequestUrl);
    var httpRequestContent = new FormUrlEncodedContent(new Dictionary<string, string>()
    {
        { "grant_type", "client_credentials" },
        { "resource", Resource },
        { "client_id", ClientId },
        { "client_secret", ClientSecret }
    });
    httpRequestMessage.Content = httpRequestContent;
    var httpResponseMessage = HttpClient.SendAsync(httpRequestMessage).GetAwaiter().GetResult();
    var httpResponseContent = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
    var httpResponseJson = JsonConvert.DeserializeObject<JToken>(httpResponseContent);
    AccessToken = httpResponseJson.Value<string>("access_token");
}

サブスクリプションを作成する

Office 365 管理 API のコンテンツ タイプ (Audit.SharePoint) に対してサブスクリプションを作成します。Webhook も登録できるようなのですが、今回は省略します。PublisherIdentifier は任意の GUID を指定してください。

private static void CreateSubscription()
{
    var httpRequestUrl = $"https://manage.office.com/api/v1.0/{TenantId}/activity/feed/subscriptions/start?contentType=Audit.SharePoint&PublisherIdentifier={PublisherIdentifier}";
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, httpRequestUrl);
    httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
    var httpResponseMessage = HttpClient.SendAsync(httpRequestMessage).GetAwaiter().GetResult();
    var httpResponseContent = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
}

コンテンツの URL を取得する

ログを取得する時間を指定してコンテンツ データのダウンロード先の URL を取得します。時間は 24 時間以内で過去 7 日以内である必要があります。今回は 1 日前のデータを取得するように指定します。

private static void GetContentUri()
{
    var startTime = DateTime.Today.AddDays(-1).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss");
    var endTime = DateTime.Today.AddSeconds(-1).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss");
    var httpRequestUrl = $"https://manage.office.com/api/v1.0/{TenantId}/activity/feed/subscriptions/content" +
        $"?contentType=Audit.SharePoint" +
        $"&PublisherIdentifier={PublisherIdentifier}" +
        $"&startTime={startTime}" +
        $"&endTime={endTime}";
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, httpRequestUrl);
    httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
    var httpResponseMessage = HttpClient.SendAsync(httpRequestMessage).GetAwaiter().GetResult();
    var httpResponseContent = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
    var httpResponseJson = JsonConvert.DeserializeObject<JArray>(httpResponseContent);
    ContentUri = httpResponseJson[0].Value<string>("contentUri");
}

コンテンツを取得する

取得した URL から JSON 形式のコンテンツを取得します。

private static void GetContents()
{
    var httpRequestUrl = ContentUri;
    var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, httpRequestUrl);
    httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
    var httpResponseMessage = HttpClient.SendAsync(httpRequestMessage).GetAwaiter().GetResult();
    var httpResponseContent = httpResponseMessage.Content.ReadAsStringAsync().GetAwaiter().GetResult();
    var httpResponseJson = JsonConvert.DeserializeObject<JArray>(httpResponseContent);
    Console.WriteLine(JsonConvert.SerializeObject(httpResponseJson, Formatting.Indented));
}

実行結果

以下のような JSON が出力されます。サンプルはファイルのダウンロードですが、それだけではなく、いろいろな種類のログが取得できます。

[
  {
    "CreationTime": "2020-09-01T14:00:03",
    "Id": "bea6430c-9db6-400c-ebbf-08d84e7f532c",
    "Operation": "FileDownloaded",
    "OrganizationId": "92dbed3f-d37a-4f19-a392-f6970505cc6a",
    "RecordType": 6,
    "UserKey": "i:0h.f|membership|10033fffac7f4b34@live.com",
    "UserType": 0,
    "Version": 1,
    "Workload": "OneDrive",
    "ClientIP": "52.185.144.178",
    "ObjectId": "https://karamem0jp-my.sharepoint.com/personal/takashi_shinohara_karamem0_jp/Documents/fitbit.json",
    "UserId": "takashi.shinohara@karamem0.jp",
    "CorrelationId": "1aba0151-abda-4b69-a102-a9319a2a9e15",
    "EventSource": "SharePoint",
    "ItemType": "File",
    "ListId": "ccf377fa-0605-41b5-925a-224e62839884",
    "ListItemUniqueId": "3afd549b-4c58-4026-a7e2-d4fcc1a2ebe0",
    "Site": "0815189e-2f76-44f2-89fe-f2dd7260e20d",
    "WebId": "15cdf073-9e78-4667-94ea-96abe6aa860f",
    "HighPriorityMediaProcessing": false,
    "SourceFileExtension": "json",
    "SiteUrl": "https://karamem0jp-my.sharepoint.com/personal/takashi_shinohara_karamem0_jp/",
    "SourceFileName": "fitbit.json",
    "SourceRelativeUrl": "Documents"
  },
...
]

何が取得できるかについては以下が参考になります。

docs.microsoft.com

まとめ

Office 365 管理 API に関する情報は以下にまとまっています。

docs.microsoft.com

オンプレの SharePoint で監査ログを取っていろいろやっていたことを、SharePoint Online に移行しても同じことをやりたい、というときにぜひ使っていただきたいと思います。

Power Apps で複数選択可能なコンボ ボックスでフィルターをする

Power Apps のコンボ ボックスは複数の値を選択することができますが、これを使って複数の値でフィルターをしたいと思いました。

具体的には以下の感じです。初期状態は何も選択されていないのですべてが表示されます。

f:id:karamem0:20200826100159p:plain

コンボ ボックスで複数の値を選択するとその値にしたがってフィルターされます。

f:id:karamem0:20200826100215p:plain

どうやって実現しているのかというと、コンボ ボックスの SelectedItems を見て、値が選択されていれば、データ ソースに条件を追加するようにしています。

f:id:karamem0:20200826100932p:plain

わかりやすいようにテキストも貼っておきます。

Filter(
    'Test List 1',
    Or(
        Or(
            IsEmpty(ComboBox1.SelectedItems),
            IsBlank(ComboBox1.SelectedItems)
        ),
        And(
            Not(
                IsBlank(
                    LookUp(
                        ComboBox1.SelectedItems,
                        Value = "Test List Item 1"
                    )
                )
            ),
            Title = "Test List Item 1"
        ),
        And(
            Not(
                IsBlank(
                    LookUp(
                        ComboBox1.SelectedItems,
                        Value = "Test List Item 2"
                    )
                )
            ),
            Title = "Test List Item 2"
        ),
        And(
            Not(
                IsBlank(
                    LookUp(
                        ComboBox1.SelectedItems,
                        Value = "Test List Item 3"
                    )
                )
            ),
            Title = "Test List Item 3"
        )
    )
)

コンボ ボックスの選択肢についてそれぞれ条件を書かないといけないので、コンボ ボックスの選択肢は固定である必要があります。委任の警告は出ないので SharePoint リストでもうまく動作するはずです(試していません)。

第 1 回 Japan M365 Dev User Group 勉強会を開催しました

2020/08/19 に第 1 回 Japan M365 Dev User Group 勉強会を開催しました。Japan M365 Dev User Group は今年の 5 月に立ち上げた Microsoft 365 技術者向けのコミュニティで、今回がはじめての開催となります。

jpm365dev.connpass.com

今回のテーマは「Microsoft Teams 開発」ということで 2 人の方にご登壇いただきました。

武田さん: これで始める!Teams アプリケーション開発 101 *1

日本マイクロソフト株式会社の武田さんに de:code での登壇内容をベースにより踏み込んだ内容のデモを行っていただきました。de:code は 5 月時点でしたが、そこからのアップデート情報も紹介いただき、非常に熱いセッションとなりました。特に先日の Inspire で発表された Teams 会議にアプリを組み込めるというのは気になる内容で、詳細情報を期待したいところです。

小張さん: Teams を利用したメッセージ通知

アドバンスド・ソリューション株式会社の小張さんに Teams でのメッセージ送信について説明をしていただきました。Incoming Webhook は URL に対してリクエストを投げることでメッセージを投稿できる機能で、Postman から投稿するデモを見せていただきました。また、Teams Bot のプロアクティブ メッセージ送信では、実際の開発のコードを見ながらどのように動いているかを紹介いただきました。*2 Bot のほうから先にチャットでメッセージを送るという仕組みをあまり考えたことがなかったので参考になりました。

ライトニング トーク

ライトニング トークはテーマの縛りなく募集したので、SharePoint、Office Scripts、Microsoft Graph といろいろなお題でお話ししていただきました。どれも新しい気付きのある内容でとても参考になりました。

まとめ

Microsoft 365 開発という限られた分野のなかで大勢の方にご参加いただきまして本当にありがとうございます。Microsoft 365 開発に興味がある方がたくさんいらっしゃるということが実感できましたので、これからも継続的に開催していただければと思います。重ねてになりますが、登壇者や参加者のみなさま本当にありがとうございました!

*1:101 は「初級編」という意味だそうですよ。知らなかった!

*2:コードがいっぱい出てきてあまり追いつけていけませんでした。すみません。

SharePoint Online でダウンロードを禁止する方法まとめ

SharePoint Online を使っていると「ダウンロードを禁止したい」というご要望を聞くことがよくあります。個人的には、システムで制限するよりも、そうさせない組織づくりをするほうが大事だと思うのですが、そうはいっても要望は要望としてあるので、いくつかの方法を考えてみたいと思います。

Azure Active Directory の条件付きアクセスで制御する

Microsoft 365 または単体での EMS の契約をしている場合に限りますが、SharePoint 管理センターから条件付きアクセス ポリシーを有効にすることで、テナント全体に対してダウンロードを禁止することができます。

f:id:karamem0:20200819141719p:plain

実際にダウンロードをしてみると以下のようにブロックされます。

f:id:karamem0:20200819141915p:plain

注意点として、テナント全体に適用されるため、サイト コレクションを指定しての禁止はできないという点です。本来は、社内アクセスの場合のみにダウンロードを許可する、というような使い方をするので、サイトやライブラリを指定しての禁止をしたい場合は他の方法を取る必要があります。
なお画像ファイルは右クリックからの [名前を付けて保存] は防止できないみたいです。

RMS を有効にする

これはダウンロードを禁止する仕組みではありませんが、ドキュメント ライブラリに対して RMS を有効にすることで、万が一ファイルが流出してしまった場合でも情報の漏洩を防ぐことができます。対象となるファイルが Office ファイルと PDF ファイルに限られてしまうのが難点ですが、本来の目的を達成する意味では最適な方法です。

f:id:karamem0:20200819142504p:plain

ちなみに対象外のファイルをアップロードさせないように構成することもできます。

f:id:karamem0:20200819143216p:plain

アクセス許可レベルの [アイテムを開く] を除外する

アクセス許可レベルから [アイテムを開く] の権限を除外することでファイルをダウンロードすることができなくなります。

f:id:karamem0:20200819143344p:plain

ただ、Office ファイルを PDF としてエクスポートできてしまったり、印刷ができてしまったりするようなので、完全な防止とはならないかもしれません。画像ファイルも条件付きアクセスと同じで保存できてしまいます。

まとめ

まとめるとこんな感じです。

方法 ライセンス 範囲 セキュリティ
条件付きアクセス 必要 テナント 強い
RMS 不要 ライブラリ 強い
アクセス許可レベル 不要 サイトまたはライブラリ 弱い

結論としては「ダウンロードは禁止できないけど RMS で本来の目的は達成できるんだから RMS にしましょう!」と言いたいです。実はどの場合でも割と困るのが画像と動画なんですよね。突き詰めるとキリがないので「どこまでなら許されるのか」をちゃんと考えておいたほうがいいです。

.NET ラボ勉強会 2020 年 7 月に登壇しました

2020/07/25 に行われた .NET ラボ勉強会 2020 年 7 月に「Azure AD でセキュリティ保護された Web アプリケーションで Microsoft Graph を使用する」という内容で登壇しました。

dotnetlab.connpass.com

当日のスライドはこちら。

セッションは de:code 2020 の MVP パーソナル スポンサーとして提供したサンプル コードをデモを交えて解説するという内容でした。特に Easy Auth については、使ったことはあっても内部のアーキテクチャまでは知らない方が多いと思いますので、今回ご紹介できてよかったです。これ単体でも使う価値は十分ある機能なのでぜひお試しいただければと思います。