Google Workspaceをサービスアカウントで、PHPからAPIで操作する方法

## いまさらなんでこんなこと書いてるの？
表題の通りの条件で解説のある記事が無かったからです。
ユーザーの一覧を取るのに半日かかったんですが……

とりあえず忘れないように、乱文ですが、投稿します。
手が空いたら加筆修正するかも。
間違っているよ！や、もっといい方法や考え方があるよ！は気軽にコメントいただければありがたいです。

今回の条件は以下の通りです。
* Google Workspace（組織管理）されているGoogleサービス
* サービスアカウントを使った（ユーザー権限ではない）APIアクセス
* PHPからのREST API（[google-api-php-client](https://github.com/googleapis/google-api-php-client)）を使用

## 前提
* Google Workspaceを使用中
* 組織の管理者権限が手元にある。もしくは変更を依頼できる
* PHPのテスト環境がある
* Composerがインストール済み

## 検証環境
* Windows 10
* PHP 7.4.2
* Apache2
* Chrome 113
* Composer version 2.3.6
* google-api-php-client　2.12.1

## 1. GCPでの作業
https://developers.google.com/workspace/guides/get-started?hl=ja

上記の1,2,5
### 1-0. 組織をGCPに紐づける
Google Workspaceを使って初めての場合、次の手順で組織が選べないようです。
左メニューからIAMと管理→IDと組織を開き、組織向けGoogle Cloud設定の管理から
「チェックリストに移動」をクリック。ウィザードに従って操作。
（一度やるとクリック後の手順が表示されないようで詳細はないです。すみません。）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/c99c20b5-5496-849f-286e-363a2de10cf3.png)

以下ページを参考に「現在Google Workspaceを利用している」を選択する
組織が設定されたら、ウィザードは途中でやめてしまって大丈夫です。

https://datumstudio.jp/blog/0526_gcp_resource-management/


### 1-1. プロジェクトを作る
以下にアクセスして項目を入力し作成
https://console.cloud.google.com/projectcreate

プロジェクト名：　適当
組織：　　　　　　ドメイン
場所：　　　　　　組織トップ（ドメイン）

### 1-2. 追加したいAPIを選択
公式マニュアルには左メニューから選択とあるけど、遠いので、検索ボックスに好きなAPIを入力
MARKETPLACEの欄から追加したいAPIを選ぶ（今回は、Admin SDK API）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/846836b1-edc1-1a66-cef9-3950013dbb1b.png)
有効にするを押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/4c0ddb1c-f8b6-58fc-8956-0cbd7e031255.png)

### 1-5. サービスアカウントを追加
例のごとく検索ボックスが早い
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/60e134bc-df6d-4d76-05d0-0a68e046aa4d.png)
上の方にある、+ CREATE SERVICE ACCOUNT を押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/2179913e-2f04-eaa7-44cb-4bc53067abc6.png)

適当に名前を入れて、完了を押す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/d61eb15c-73f7-e725-df86-db29dab9d473.png)

一覧に戻ったらメールアドレスをコピーしておく
しばらく後で使うのでメモ帳などにコピー
メール欄をクリック
キーのタブを選択鍵を追加を押して新しい鍵を作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/664661e5-0ede-cfbc-4408-3506500c975c.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/5339f3e2-2a7e-a689-88a9-ab09034ac0b4.png)

JSONを選択（そのまま）して、作成
ファイルがダウンロードされるから取っておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/5e87221c-52e0-0de0-5e1f-56168439c27a.png)

## 2. GoogleWorkspaceの管理コンソールでの作業
### 2-1. 管理ロールの追加 
左メニューから、アカウント→管理ロールを開く
サービス管理者にマウスオーバーすると右側に管理者を割り当てが表示されるのでクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/3152c42d-1023-18d0-1ca4-df2649388991.png)

サービスアカウントへの割り当てをクリック
1-5でコピーしておいたメールアドレスを入力して追加
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/9befcd8f-3834-b00b-0a2a-484253104fa6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/3a2dc336-cf3f-81e5-1756-2780ec9e9ed4.png)

不足しているロールがある場合は、
管理ロールの一覧画面に戻って、新しいロールを作成
必要なロールを追加して、同じようにサービスアカウントを割り当てる。
今回は、ユーザー一覧が欲しいので、以下のページから認可スコープの欄を参照する。
と以下のスコープが必要なので、ロールに追加する。

https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/list?hl=ja

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/b3141bd9-e6d0-09ee-fe0d-44b15e154ae1.png)
* https://www.googleapis.com/auth/admin.directory.user
* https://www.googleapis.com/auth/admin.directory.user.readonly
* https://www.googleapis.com/auth/cloud-platform

`admin.directory.user`があるので、読み込みのみの権限だと動かなかった。
ユーザーに全てチェックをつける
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/250eac9c-6442-8c55-ca65-d731f4ff90b2.png)

`cloud-platform`はサービス管理者が付いているから？か、特に追加設定の必要はなかった。

## 3. PHPでの作業
### 3-1. google-api-php-clientをインストール
https://github.com/googleapis/google-api-php-client

公式のインストールの通り。
```bat
composer require google/apiclient
```
バージョンは公式にしたがって最新をインストール

### 3.2 PHPでクライアントを起動
```php
require_once './autoload.php'; // Composer Autoload
$client = new Google\Client();
$client->setAuthConfig(__DIR__.'key.json'); // 1-5でDLしたjsonの鍵
$client->useApplicationDefaultCredentials();
$client->setApplicationName("Your App Name"); // UAに記述されるだけ？のようなんでもいい
// APIリファレンスの認証スコープ
// https://developers.google.com/admin-sdk/directory/reference/rest/v1/users/list?hl=ja#authorization-scopes
$client->setScopes([
    'https://www.googleapis.com/auth/admin.directory.user',
    'https://www.googleapis.com/auth/admin.directory.user.readonly',
    'https://www.googleapis.com/auth/cloud-platform'
]);
// 名前空間のルールはcomposerでインストールされたディレクトリで、
// google/apiclient-services/src/ 以下から見つけてくる。
$service = new Google\Service\Directory($client);
try {
    // APIリファレンスではlistなのに、ここではlistUsersなので注意
    // domainか、customerをどちらか指定しないと動かない
    $users = $service->users->listUsers(['domain' => 'yourdomain.example.com']);
} catch (Exception $e) {
    vardump($e);
    exit;
}
// 長いので今回は、ユーザーのメールアドレスのみ表示
vardump(array_map(fn($v) => $v['primaryEmail'], $users['users']));
```

こんな感じで表示されればOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/194701/b48e96c5-1623-0e47-c688-dd52cdaf0d76.png)

listUsersの中を見るとこんな感じで書いてあるのですが、ここに、
`domain`か、`customer`をどちらか指定と書いてあります。
細かいクライアントの仕様は、ソースコード内にかなり詳細に書いてくれているので、
メソッドの説明を参照するのがいいかも。
```php 
/**
   * Retrieves a paginated list of either deleted users or all users in a domain.
   * (users.listUsers)
   *
   * @param array $optParams Optional parameters.
   *
   * @opt_param string customFieldMask A comma-separated list of schema names. All
   * fields from these schemas are fetched. This should only be set when
   * `projection=custom`.
   * @opt_param string customer The unique ID for the customer's Google Workspace
   * account. In case of a multi-domain account, to fetch all groups for a
   * customer, use this field instead of `domain`. You can also use the
   * `my_customer` alias to represent your account's `customerId`. The
   * `customerId` is also returned as part of the [Users](/admin-
   * sdk/directory/v1/reference/users) resource. You must provide either the
   * `customer` or the `domain` parameter.
   * @opt_param string domain The domain name. Use this field to get groups from
   * only one domain. To return all domains for a customer account, use the
   * `customer` query parameter instead. Either the `customer` or the `domain`
   * parameter must be provided.
   * @opt_param string event Event on which subscription is intended (if
   * subscribing)
   * @opt_param int maxResults Maximum number of results to return.
   * @opt_param string orderBy Property to use for sorting results.
   * @opt_param string pageToken Token to specify next page in the list
   * @opt_param string projection What subset of fields to fetch for this user.
   * @opt_param string query Query string for searching user fields. For more
   * information on constructing user queries, see [Search for Users](/admin-
   * sdk/directory/v1/guides/search-users).
   * @opt_param string showDeleted If set to `true`, retrieves the list of deleted
   * users. (Default: `false`)
   * @opt_param string sortOrder Whether to return results in ascending or
   * descending order, ignoring case.
   * @opt_param string viewType Whether to fetch the administrator-only or domain-
   * wide public view of the user. For more information, see [Retrieve a user as a
   * non-administrator](/admin-sdk/directory/v1/guides/manage-
   * users#retrieve_users_non_admin).
   * @return UsersModel
   */
  public function listUsers($optParams = [])
  {
    $params = [];
    $params = array_merge($params, $optParams);
    return $this->call('list', [$params], UsersModel::class);
  }
```

## 4. はまりポイント
ドはまりして、半日も時間を使った原因は以下の通りです。

1. 認証スコープをいくら付けても、管理コンソールで管理ロールを追加しないと動かないことに、気が付かなかった。
1. API Clientのマニュアルがソース内にあるのに気が付かず、記述方法が分からなかった。

## 5. 感想
今回は、OutlookとのAPI連携可能なサービスに、Googleカレンダーとの連携選択肢にとのことで、
始めてGoogle WorkspaceのAPIに触れました。
Outlookと考え方がいろいろ違って面白いなと思いました、

API Clientを用意していただけてるのはうれしい限りですが、使い方がさくっと過ぎてかなり難航しました。
クライアントのパッケージがあるからか、クライアントライブラリを使わず、
直接HTTPでたたくときの手法がドキュメント内で見つからなかったのも難航した理由かもです。

それから、普通はユーザー認証を通して使うケースが多いからか、
サービスアカウントでの認証回りや、注意点がなかなか見つからなく、結構探し回りました。
なので、この記事がすこしでも参考になればいいなと思います。

## 6. 参考文献
https://developers.google.com/workspace/guides/get-started?hl=ja

https://developers.google.com/admin-sdk/overview?hl=ja

https://cloud.google.com/iam/docs/service-account-overview?hl=ja

https://qiita.com/shin1kt/items/8da153ff4d8e25196459

https://blog.web-arena.com/output_users_participating_group_in_php/

https://sqlazure.jp/r/google/504/

https://datumstudio.jp/blog/0526_gcp_resource-management/