SharePoint の権限を一括削除する

PnP の拡張メソッドはコードの量を減らしてくれる便利なメソッドが沢山あります。しかし、大量データを繰り返し処理する際に拡張メソッドを使う場合は注意が必要です。
本投稿は、PnP の拡張メソッドを使った場合の落とし穴の事例を紹介したいと思います。

# 環境
* .NET Framework 4.6.1
* Microsoft.SharePointOnline.CSOM 16.1.19515.12000
* SharePointPnPCoreOnline 3.16.1912

# サンプルコード
* [SecurableObjectSample.cs](https://github.com/shibatea/sp-csom-library-sample/blob/master/Csom.Library.Sample/SecurableObjectSample.cs)

# 前提
固有の権限が設定されたリストの権限をすべて削除するシナリオとします。
設定されている権限は以下の通りです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89309/54a626ae-5b92-57e7-b239-f1c5ebef1ffa.png)

# CSOM バージョン
CSOM で権限を削除するコードは以下の通りです。

```csharp
securableObject.EnsureProperties(s => s.RoleAssignments);

for (var index = securableObject.RoleAssignments.Count - 1; index >= 0; index--)
{
    var roleAssignment = securableObject.RoleAssignments[index];
    roleAssignment.DeleteObject();
}

securableObject.Context.ExecuteQueryRetry();
```
for 文内で都度 ExecuteQuery を実行すると時間を要するため、for 文の外で ExecuteQuery を実行しています。

# PnP バージョン
SecurableObject クラスの PnP 拡張メソッドに、コンテンツに付与された権限を削除する RemovePermissionLevelFromPrincipal メソッドがあります。サンプルコードは以下の通りです。

```csharp
securableObject.EnsureProperties(s => s.RoleAssignments);

for (var index = securableObject.RoleAssignments.Count - 1; index >= 0; index--)
{
    var roleAssignment = securableObject.RoleAssignments[index];
    securableObject.RemovePermissionLevelFromPrincipal(roleAssignment.Member, RoleType.Reader, true);
}
```
第二引数で RoleType.Reader を指定して、閲覧権限だけを削除する書き方になっていますが、第三引数に true を指定することで、ユーザーに割り当てられた権限をすべて削除します。（例えば、「ContosoCommunication 所有者」であれば「フル コントロール」と「デザイン」の両方を削除します）

CSOM と比べたらたったの1行ですが、コードの量が少なく済んでいますね。PnP 拡張メソッドは内部で ExecuteQuery を実行しているので、実装者が ExecuteQuery のコードをいちいち書かなくて済む点は良いと思います。
また、閲覧権限だけを削除するシナリオにおいては、第三引数を false にするだけで実現できるのでとても便利です。（CSOM の場合はさらにコード量が増えてくる）

# PnP の落とし穴
PnP 拡張メソッドは確かに便利です。しかし、今回のシナリオに関してはそうとも限りません。
リストに割り当てられた 14 個の権限を削除するだけのシナリオですが、処理時間は大きく違いが出ました。その結果が以下の通りです。

|CSOM バージョン|PnP バージョン|
|:-----------------|:------------------|
|1060 ms|6277 ms|

なぜこんなにも差がでるのか。それは RemovePermissionLevelFromPrincipal メソッドの中で Load & ExecuteQuery が複数回呼び出されている為、1 回あたりの実行時間が長いからです。それが for 文で繰り返し処理されることで積もり積もってこのような結果になったのです。
これが PnP を使う上で注意しなければならない点（落とし穴）です。

# 権限操作で知っておくべきこと
そもそもの話になりますが、SharePoint コンテンツにおける固有の権限を追加・削除という処理自体がコストの掛かる処理になります。このコストの掛かる操作を出来るだけ省くように工夫することが、処理パフォーマンスの向上につながります。

では、コストの掛からない権限操作はあるのか？
・・・答えは「あります」

それは、以下の 2 つです。

- 権限の継承を中止する操作（BreakRoleInheritance）
- 固有の権限を削除する操作（ResetRoleInheritance）

さらに、権限の継承を中止する操作（BreakRoleInheritance）にはオプションがあり、実行ユーザー以外の権限をすべて削除した上で、固有の権限にすることができます。今回のシナリオにおいては、このオプションを活用することで、処理パフォーマンスを向上させることができます。

ちなみに、前述で紹介したサンプルは、リストに割り当てられた権限の数が多ければ多いほど処理時間が長くなります。しかし、この後に紹介する BreakRoleInheritance のオプションを使ったサンプルは、リストに割り当てられた権限の数に左右されることなく、一定のパフォーマンスが期待できます。

# 改良バージョン
権限を削除する処理のパフォーマンスを改善したサンプルコードは以下の通りです。

```csharp
securableObject.EnsureProperties(s => s.HasUniqueRoleAssignments);

if (securableObject.HasUniqueRoleAssignments)
{
    // 一旦、固有の権限を削除してから、権限の継承を外す
    // その際に、実行ユーザー以外の権限をすべてクリアする
    // ※ BreakRoleInheritance メソッドの copyRoleAssignments を false にする
    securableObject.ResetRoleInheritance();
    securableObject.BreakRoleInheritance(false, false);
}
else
{
    securableObject.BreakRoleInheritance(false, false);
}

securableObject.EnsureProperties(s => s.RoleAssignments);

// 実行ユーザーしか残ってないので、for 文は実質 1 回しか繰り返すことがない
for (var index = securableObject.RoleAssignments.Count - 1; index >= 0; index--)
{
    var roleAssignment = securableObject.RoleAssignments[index];
    roleAssignment.DeleteObject();
}

securableObject.Context.ExecuteQueryRetry();
```
まず初めに、対象リストが固有の権限かどうかを確認し、固有の権限の場合は「固有の権限を削除（ResetRoleInheritance）」してから「権限の継承を中止（BreakRoleInheritance）」します。その際に、BreakRoleInheritance メソッドの第一引数に false を指定します。そうすることで、実行ユーザー以外の権限が削除されます。

for 文を実行する直前のリストの権限は以下のようになっています。
（プログラム実行ユーザー ＝ Takashi Shibata）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89309/0a9ff2f0-77cd-9723-dbb2-1cc952a0aa2f.png)

処理時間は以下のようになりました。

|CSOM バージョン|PnP バージョン|改良バージョン|
|:--|:--|:--|
|1060 ms|6277 ms|311 ms|

# 参照
[ロール割り当ての継承を解除する | Microsoft Docs](https://docs.microsoft.com/ja-jp/previous-versions/office/developer/sharepoint-2010/ee535228(v%3Doffice.14))