0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintoneの全アプリの権限をAPIで洗い出したら、アプリが1つ足りなかった(2026年11月確認版)

0
Last updated at Posted at 2026-11-06

更新情報(2026年11月10日):書き方を見直しました。検証の結果とスクリプトは変えていません。

はじめに

棚卸しの前に、kintoneの全アプリのアクセス権をスクリプトで一覧にしようとしました。

検証用の環境には、アプリが6つあります。ところが、アプリの一覧API(/k/v1/apps.json)が返したのは5つでした。kintoneシステム管理者で実行しているのに、です。

原因を追っていくと、「全アプリを見た」つもりで抜ける所が、ほかにも3つ見つかりました。

結果

先に結果です。

抜けたところ 防いだ方法
閲覧権限のないアプリが、一覧のAPIに出ない 一覧にないアプリIDを1つずつ確かめる
ゲストスペースのアプリは、送り先が違う ゲストスペースの送り先で取り直す
アクセス権は、上の行が優先される 行の順番ごと残す
1回きりの一覧では、前回と比べられない 日付入りのファイルで残す

4つまとめて防ぐスクリプトは、後半に載せています。

検証環境

  • kintone(2026年11月6日時点)、kintoneシステム管理者のユーザー
  • REST APIは、ブラウザのコンソールから実行
  • アプリは6つ。アプリ5は、このユーザーにレコードの閲覧権限がないアプリ。アプリ6は、ゲストスペースのアプリ

1. 閲覧できないアプリは、一覧に出てこない

/k/v1/apps.json に出るのは、実行した人がレコードを閲覧(または追加)できるアプリだけでした。システム管理者でも変わりません。

閲覧権限のないアプリ5と、存在しないアプリ99に、3つのAPIを投げて比べました。

API アプリ5(閲覧権限なし) アプリ99(存在しない)
/k/v1/apps.json 出ない 出ない
/k/v1/app.json CB_NO02(権限がありません) GAIA_AP01(見つかりません)
/k/v1/app/acl.json 読める GAIA_AP01

アクセス権は読めるのに、一覧には出てこない。ここは意外でした。人事や経理のように、見る人を絞ったアプリほど抜けます。

一覧にないIDは、/k/v1/app.json で1つずつ確かめます。GAIA_AP01 以外が返ってきたら、そのアプリはあります。

2. ゲストスペースのアプリは、送り先が違う

ゲストスペースのアプリは、一覧には出ます。ただ、/k/v1/... に送ると GAIA_IL23 が返ってきます。

ここを「読めなかった」で済ませると、社外の人がいるスペースのアプリが点検から抜けます。一覧の spaceId を使って、送り先を /k/guest/(spaceId)/v1/... に変えると読めました。

3. アクセス権は、上の行が優先される

1人が複数の行に当てはまると、上の行の設定が使われます。

たとえば1行目が「Everyone:閲覧・編集」、2行目が「営業部:閲覧だけ」なら、営業部の人も編集できます。一覧にするときに行の順番を捨てると、ここを読み違えます。/k/v1/app/acl.json の rights は優先される順に返ってくるので、順番も一緒に残します。

4. 1回きりの一覧では、前回と比べられない

次の点検で聞かれるのは「前回から何が変わったか」です。画面に出すだけでは比べられません。

日付入りのファイルで残しておけば、次の点検で差分を取れます。jq が入っている環境なら、キーの順をそろえてから比べると読みやすくなります。

diff <(jq -S . kintone-acl-2026-11-06.json) <(jq -S . kintone-acl-2026-12-06.json)

4つまとめて防ぐスクリプト

kintoneのどれかのアプリの画面で、開発者ツールのコンソール(Windowsは Ctrl+Shift+J、Macは command+option+J)に貼り付けて実行します。読み取るだけなので、設定は変わりません。

kintone-acl-check.js
(async () => {
  // 1. アプリの一覧(apps.json)に出るアプリを、100件ずつ最後まで取る
  const listed = [];
  for (let offset = 0; ; offset += 100) {
    const res = await kintone.api('/k/v1/apps.json', 'GET', { limit: 100, offset });
    listed.push(...res.apps);
    if (res.apps.length < 100) break;
  }
  const ids = new Set(listed.map((a) => Number(a.appId)));
  const max = Math.max(0, ...ids);

  // 2. 一覧に出ないアプリを、アプリIDから探す(ないIDは GAIA_AP01 が返る)
  const hidden = [];
  for (let id = 1; id <= max + 20; id++) {
    if (ids.has(id)) continue;
    try {
      await kintone.api('/k/v1/app.json', 'GET', { id });
      hidden.push({ appId: id, error: '' });
    } catch (e) {
      if (e.code !== 'GAIA_AP01') hidden.push({ appId: id, error: e.code });
    }
  }

  // 3. アクセス権を取る。ゲストスペースのアプリ(GAIA_IL23)は、ゲストスペースの送り先で取り直す
  const get = async (path, app) => {
    try {
      return await kintone.api(path, 'GET', { app: app.appId });
    } catch (e) {
      if (e.code !== 'GAIA_IL23' || !app.spaceId) throw e;
      return kintone.api(path.replace('/k/v1/', `/k/guest/${app.spaceId}/v1/`), 'GET', { app: app.appId });
    }
  };
  const RIGHTS = { appEditable: 'アプリ管理', recordViewable: '閲覧', recordAddable: '追加', recordEditable: '編集',
    recordDeletable: '削除', recordImportable: '読み込み', recordExportable: '書き出し' };
  const result = { takenAt: new Date().toISOString(), host: location.host, listed: listed.length, hidden, apps: {} };
  for (const app of listed) {
    try {
      const { rights } = await get('/k/v1/app/acl.json', app);
      result.apps[app.appId] = {
        name: app.name,
        // 上の行ほど優先されるので、順番を残す
        rights: rights.map((r, i) => ({
          order: i + 1,
          target: r.entity.type + (r.entity.code ? ':' + r.entity.code : '') + (r.includeSubs ? '(下位組織を含む)' : ''),
          can: Object.keys(RIGHTS).filter((k) => r[k]).map((k) => RIGHTS[k]).join('・') || 'なし',
        })),
      };
    } catch (e) {
      result.apps[app.appId] = { name: app.name, error: e.code };
    }
  }

  // 4. 次の点検で前回と比べられるよう、日付入りのJSONで保存する
  const day = result.takenAt.slice(0, 10);
  const a = document.createElement('a');
  a.href = URL.createObjectURL(new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' }));
  a.download = `kintone-acl-${day}.json`;
  a.click();
  const failed = Object.values(result.apps).filter((x) => x.error).length;
  console.log(`一覧に出たアプリ ${listed.length}個/一覧に出ないアプリ ${hidden.length}個/読めなかったアプリ ${failed}個`);
})();

検証環境で実行すると kintone-acl-2026-11-06.json が保存され、コンソールにはこう出ました。

一覧に出たアプリ 5個/一覧に出ないアプリ 1個/読めなかったアプリ 0個

※ アクセス権を取れるのは、実行した人に「アプリ管理」の権限があるアプリだけです。取れなかったアプリは error に残ります。
※ 一覧にないアプリは、一覧の最大IDの先20個まで探します。足りなければ max + 20 を増やしてください。
※ レコードとフィールドのアクセス権も、/k/v1/record/acl.json と /k/v1/field/acl.json で同じように取れます。

点検の記録に残しておくこと

上司への報告や監査では、「どこまで見たか」を聞かれます。次の4つを残しておくと答えられます。

  • 点検した日と、点検した人
  • 一覧に出たアプリ・出なかったアプリ・読めなかったアプリ
  • 見直す設定と、担当・期限
  • 前回からの変更と、前回の指摘が直ったか

さいごに

一覧のAPIに出てこないアプリがある。知っていれば数行で防げますが、知らないと報告書に「全アプリを点検」と書いてしまいます。

筆者は、この点検を点検台帳(Excel)と報告書にまとめるkintoneのプラグイン「棚卸し・変更監視パック」を作っています(有料)。見直したほうがよい設定が何件あるかは、申し込みなしで確かめられます。

関連記事

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?