Azureのアプリ登録からアクセス権限の設定、テストまで

### はじめに...

調査した内容を時系列にまとめた資料なので、その点ご了承ください。
- そろそろ新しいMacを手配せねば…

## 目次

1. 実行環境
1. 実現したいこと
1. 作業の流れ
1. 例題
1. (i) 要件の定義
1. (ii) アクセス許可の確認
1. (iii) Azure Active Directoryにてアプリ登録
1. (iv) アクセス許可の追加と管理者の同意
1. (v) クライアントシークレットを作成
1. (vi) Powershellの雛形を更新し、動作を確認する
1. アクセス許可でエラーが出た時には...
1. まとめ

--------

### 1. 実行環境

|  項目  |  情報 |
| ---- | ---- |
|  OS  |  macOS 11.6  |
|  HW  |  MacBook Air (11-inch, Mid 2013)   |
|  pwsh  |  7.2.0  |
|  MDM環境  |  microsoft intune  |

### 2. 実現したいこと

- Azureのアプリ登録からアクセス権限の設定、テストまで気軽に実施できる手順を準備したい
 - ※今回はClientSecretを使って実装する

### 3. 作業の流れ

以下の流れを元に例題を定めて実践する

i. 今回の要件を定義する
- xxxxxの情報を取得したい、xxxxxxの情報を追加したいなど
ii. Microsoft Graph REST API v1.0 リファレンスからURLと必要なアクセス許可を確認する
- https://docs.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-1.0
- https://docs.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-beta
iii. Azure Active Directoryを使ってアプリの登録をする
- https://portal.azure.com/
iv. アプリにアクセス許可の追加と管理者の同意を与える
v. クライアントシークレットを作成する
vi. Powershellの雛形を更新し、動作を確認する

### 4. 例題

業務用デバイス(もちろんmac)の登録と確認を行いたい

### 5. (i) 要件の定義

intuneにて業務用デバイスのSerial登録と登録結果の確認を行う

![img-001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461335/1547d80c-dfd4-44ea-891b-dc3f873b630c.png)

### 6. (ii) アクセス許可の確認

Microsoft Graph REST API リファレンスからアクセスするURLと必要なアクセス許可を確認する
- https://docs.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-1.0
- https://docs.microsoft.com/ja-jp/graph/api/overview?view=graph-rest-beta


| 作業内容 | URL(抜粋) |アクセス権 |
| ----    | ----     | ---- |
| 業務用デバイスの登録 |POST /deviceManagement/importedDeviceIdentities | DeviceManagementServiceConfig.ReadWrite.All |
| 業務用デバイスの一覧 |GET /deviceManagement/importedDeviceIdentities|DeviceManagementServiceConfig.Read.All, DeviceManagementServiceConfig.ReadWrite.All|

### 7.  (iii) Azure Active Directoryにてアプリ登録

- Azure Active Directory > アプリの登録を開き、「新規登録」ボタンを押下する
- 名前は任意の名前を指定(ここでは、intune-device-get)
- オプションは、テストのため、「この組織ディレクトリのみに含まれるアカウント (none のみ - シングル テナント)」を指定

![img-002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461335/7cca731a-9ce9-afea-a8b7-3c31f5436dea.png)


### 8.  (iv) アクセス許可の追加と管理者の同意

- アプリがアクセスできる機能を制御するために、アプリの権限を付与する
- 権限付与には、「検索してアクセス許可の追加」と「管理者の同意を与える」が必要
- 具体的には、アプリの登録からアプリを開き、APIのアクセス許可から「アクセス許可の追加」ボタンを押下
- Microsoft Graph -> アプリケーションの許可 -> (ii)で調べたアクセス許可をもとに検索、「アクセス許可の追加」を押下
- XXXXに管理者の同意を与えますを押下

![img-003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461335/559706f8-4b58-d52e-c07b-e41cded084e5.png)


### 9. (v) クライアントシークレットを作成

- アプリの登録からアプリを開き、証明書とシークレットから「新しいクライアントシークレット」を押下する
- 注意点
 - 有効期限が存在するため、有効期限の管理が必要
 - クライアントシークレットは、生成時しかコピーできないため、必ずコピーする

| 項目| 名称 |
| ---- | ---- |
|アプリケーション名|[application name]|
|クライアントID|[clientid]|
|クライアントシークレット|[secret]|
|アクセス権|DeviceManagementServiceConfig.Read.All, DeviceManagementServiceConfig.ReadWrite.All|

![img-004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1461335/6462f010-4510-a1cf-fd88-75033be84475.png)


### 10.  (vi) Powershellの雛形を更新し、動作を確認する

- 基本メソッド
- Get-AuthToken-Secret
 - Tenantid, Clientid, ClientSecretからリクエストヘッダを生成
- Invoke-GraphMethod
 -　Header, Bodyを指定してGraphAPIにGet/Post

- 追加メソッド
- Get-CorporateDeviceIdentifiers
 - 企業用デバイスの一覧を取得
- Set-CorporateDeviceIdentifiers
 - 企業用デバイスを登録

<details><summary>スクリプト雛形</summary><div>

```console
Function Get-AuthToken-Secret(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $tenantId,
        [Parameter(Mandatory=$true)]
        $clientId,
        [Parameter(Mandatory=$true)]
        $clientSecret
    )

    $tokenRequestUri = "https://login.microsoftonline.com/{0}/oauth2/token" -f $tenantId

    $header = @{
        "Content-Type" = "application/x-www-form-urlencoded"
    }

    $body = @{
        "grant_type" = "client_credentials"
        "client_id" = $clientId
        "client_secret" = $clientSecret
        "resource" = "https://graph.microsoft.com”
    }
    
    $accessToken = (Invoke-WebRequest -Method "POST" -Uri $tokenRequestUri -Headers $header -Body $body).Content | ConvertFrom-Json

    $authHeader = @{
        'Content-Type'='application/json'
        'Authorization'="Bearer " + $accessToken.access_token
        'ExpiresOn'=$accessToken.expires_on
    }

    $authHeader
}

Function Invoke-GraphMethod(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true,ParameterSetName="GET")]
        [Switch]
        $GET,
        [Parameter(Mandatory=$true,ParameterSetName="POST")]
        [Switch]
        $POST,
        [Parameter(Mandatory=$true)]
        [String]
        $graphApiVersion,
        [Parameter(Mandatory=$true)]
        [String]
        $resource,
        [Parameter(Mandatory=$true)]
        $authToken,
        [Parameter(Mandatory=$false,ParameterSetName="GET")]
        [Parameter(Mandatory=$true,ParameterSetName="POST")]
        [String]
        $body
    )

    try{
        if($GET){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)"
            (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).value
        }
        
        if($POST){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)"
            (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $body -Headers $authToken).value
        }
    }catch{
        $_
    }


}
```
</div>
</details>

<details><summary>スクリプト更新(追加後)</summary><div>

```console
Function Get-AuthToken-Secret(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $tenantId,
        [Parameter(Mandatory=$true)]
        $clientId,
        [Parameter(Mandatory=$true)]
        $clientSecret
    )

    $tokenRequestUri = "https://login.microsoftonline.com/{0}/oauth2/token" -f $tenantId

    $header = @{
        "Content-Type" = "application/x-www-form-urlencoded"
    }

    $body = @{
        "grant_type" = "client_credentials"
        "client_id" = $clientId
        "client_secret" = $clientSecret
        "resource" = "https://graph.microsoft.com”
    }
    
    $accessToken = (Invoke-WebRequest -Method "POST" -Uri $tokenRequestUri -Headers $header -Body $body).Content | ConvertFrom-Json

    $authHeader = @{
        'Content-Type'='application/json'
        'Authorization'="Bearer " + $accessToken.access_token
        'ExpiresOn'=$accessToken.expires_on
    }

    $authHeader
}

Function Invoke-GraphMethod(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true,ParameterSetName="GET")]
        [Switch]
        $GET,
        [Parameter(Mandatory=$true,ParameterSetName="POST")]
        [Switch]
        $POST,
        [Parameter(Mandatory=$true)]
        [String]
        $graphApiVersion,
        [Parameter(Mandatory=$true)]
        [String]
        $resource,
        [Parameter(Mandatory=$true)]
        $authToken,
        [Parameter(Mandatory=$false,ParameterSetName="GET")]
        [Parameter(Mandatory=$true,ParameterSetName="POST")]
        [String]
        $body
    )

    try{
        if($GET){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($resource)"
            (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).value
        }
        
        if($POST){
            $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)"
            (Invoke-RestMethod -Uri $uri -Method Post -ContentType "application/json" -Body $body -Headers $authToken).value
        }
    }catch{
        $_
    }


}

Function Get-CorporateDeviceIdentifiers(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        $authToken,
        [Parameter(Mandatory=$false)]
        $deviceIdentifiers
    )
    $graphApiVersion = "beta"

    try{
        if($DeviceIdentifier){
            $Resource = "deviceManagement/importedDeviceIdentities?`$filter=contains(importedDeviceIdentifier,'$DeviceIdentifier')"

        }
        else {
            $Resource = "deviceManagement/importedDeviceIdentities"

        }
        Invoke-GraphMethod -GET -graphApiVersion $graphApiVersion -authToken $authToken -resource $Resource

    }catch{
        $_
    }

}

Function Set-CorporateDeviceIdentifiers(){
    [cmdletbinding()]
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateSet("imei","serialNumber")]
        $deviceIdentifierType,
        [Parameter(Mandatory=$true)]
        [ValidateSet("false","true")]
        $overwriteDeviceIdentities,
        [Parameter(Mandatory=$true)]
        $deviceIdentifier,
        [Parameter(Mandatory=$false)]
        $description,
        [Parameter(Mandatory=$true)]
        $authToken
    )

    $graphApiVersion = "beta"
    $Resource = "deviceManagement/importedDeviceIdentities/importDeviceIdentityList"

    try{
        $body = @"
{
    "overwriteImportedDeviceIdentities": $overwriteDeviceIdentities,
    "importedDeviceIdentities": [ { 
        "importedDeviceIdentifier": "$deviceIdentifier",
        "importedDeviceIdentityType": "$deviceIdentifierType",
    "description": "$description"}]
}
"@
        if($deviceIdentifierType -eq "imei" -and ($deviceIdentifier -notmatch "^[0-9]+$" -or $deviceIdentifier.length -lt 15)){
            throw "imei format is invalid"
        }

        if($deviceIdentifierType -eq "serialNumber" -and $deviceIdentifier -notmatch "^[a-zA-Z0-9]+$"){
            throw "serial format is invalid"
        }

        Invoke-GraphMethod -POST -graphApiVersion $graphApiVersion -body $body -authToken $authToken -resource $Resource

    }catch{
        $_
    }

}

$tenantId = "<TENANTID>"
$clientId = "<CLIENTID>"
$clientSecret = "<CLIENT_SECRET>"

$deviceID = "<DEVICEID>"
$deviceDescription = "<DEVICE_DESCRIPTION>"

$authToken = Get-AuthToken-Secret -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret
$deviceList = @(Get-CorporateDeviceIdentifiers -authToken $authToken )
$deviceList

$authToken = Get-AuthToken-Secret -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret
Set-CorporateDeviceIdentifiers -authToken $authToken -deviceIdentifierType serialNumber -overwriteDeviceIdentities false -deviceIdentifier $deviceID -description $deviceDescription
```
</div>
</details>


### 11. アクセス許可でエラーが出た時には...

- 適切にアクセス許可を設定した場合でも、以下の通り、エラーが出る場合がある。
- その場合には、エラー時に必要なアクセス許可が出てくるので、それを設定して再度試してみよう。

```console
Line |
 XXX |          (Invoke-RestMethod -Uri $uri -Method Post -ContentType "appli …
     |           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | {"error":{"code":"Forbidden","message":"{\r\n  \"_version\": 3,\r\n  \"Message\": \"Application is not authorized to perform this operation.
     | Application must have one of the following scopes: DeviceManagementServiceConfiguration.ReadWrite.All,
     | DeviceManagementServiceConfig.ReadWrite.All - Operation ID (for customer support): 00000000-0000-0000-0000-000000000000 - Activity ID:
     | xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - Url:
     | https://fef.msuc02.manage.microsoft.com/DeviceEnrollmentFE_2111/StatelessDeviceEnrollmentFEService/deviceManagement/importedDeviceIdentities/microsoft.management.services.api.importDeviceIdentityList?api-version=xxxx-xx-xx\",\r\n  \"CustomApiErrorPhrase\": \"\",\r\n  \"RetryAfter\": null,\r\n  \"ErrorSourceService\": \"\",\r\n  \"HttpHeaders\": \"{}\"\r\n}","innerError":{"date":"yyyy-mm-ddThh:mm:1ss","request-id":"xxxxxxxx-xxxxx-xxxx-xxxx-xxxxxxxxxxxx","client-request-id":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}}}
```

### 12.  まとめ
- Azureのアプリ登録からアクセス権限の設定、テストまでの手順が整備できた
- 今後、こちらの記事を参考に作成してみたいと思う