以下の内容はhttps://thinkami.hatenablog.com/entry/2025/08/24/211635より取得しました。


権限管理の1つであるUnixパーミッションをTypeScriptで実装してみた

アプリケーションを作っていると、権限制御が必要になってくることがあります。

幸いなことに、権限制御の詳しい内容を理解しなくても、各プログラミング言語ごとに便利なライブラリがあります。このブログでは過去にいくつかのライブラリをさわってきました。

 
一方で、権限制御まわりはライブラリにお任せしてきたため、以下のような権限制御の記事を読んでも「ACL、RBAC、ABACなどの概念は分かるけど、権限の仕組みの違いは雰囲気でしか理解してないかも...」と思うこともよくありました。
アプリケーションにおける権限設計の課題 - kenfdev’s blog

 
ただ、それらの権限制御の概念は、OAuthやOpenID Connectとは異なりRFCなどで標準化された規格的なものがない認識です*1。そのため、個人で何か作りながら学ぼうとしても、「世間一般で言われている権限制御の概念はこれでいいのかな...」となっていました。

 
そんな中、Claude Codeを始めとする生成AIが最近出てきたことで、「世間一般で言われている権限制御の概念は、生成AIとともに学べばいいのでは...?」となりました。

そこで、まずはUnixパーミッションはどんな仕組みで実現できるのか実装してみたことから、メモを残します。

 
目次

 

環境

  • mac
  • Claude Code + Opus 4.0/4.1
    • 学んでいる最中にOpusのバージョンが上がったため、両方使うことになりました
  • TypeScript 5.7.3
  • Bun 1.2.19
  • Biome 2.1.2

 

学び方の方針

最初は「Webアプリケーションを作成して、ユーザーごとの権限管理ができているかを確認する」みたいなことを考えていました。

ただ、それだと権限管理以外のことも気にしないといけないため、Claudeに「権限管理を学びたい背景と、どんな内容を学びたいか」について相談し、

  • Webアプリケーションではなく、権限管理のロジックだけを実装したクラスを用意する
  • ユーザーごとの権限管理は、テストコードで動作を確認する

方針でいくことにしました。

 
学び方の細かいところはこんな感じです。

  • 題材は、ファイル管理システム
  • リソースに対する権限制御をクラスとして定義
  • プログラミング言語はTypeScript
    • 使ってみたかっただけ
  • 実行環境はBun
    • TypeScriptの実行が容易なのと、テストランナーがBunに組み込まれているため
  • メインのロジックは自分で実装するが、それ以外の部分(型・クラス・メソッドなどの枠やテストコードなど)はClaude Codeに任せる
    • ただし、Claude Codeが出したコードはレビューする

 

今回実装するUnixのファイルパーミッションについて

Unixのファイルパーミッションの仕様について、Wikipediaには以下のように記載されています。

ファイルごとに定義された、読み出し・書き込みなどのアクセスに対する許可情報。通常は、ファイルシステム内のファイルごとに、特定のユーザーやグループに対してアクセス権を設定する。

ファイルパーミッション - Wikipedia

 
ただ、今回は学習用途なので、Wikipediaの内容からもう少し簡単な仕様としました。

  • ファイルの実行権限は考慮しない
  • root ユーザーなどの特殊なケースは考慮しない

 

実装

権限とリソースの表現

PermissionBits 型にて、読み込み権限・書き込み権限の有無を表現します。

export type PermissionBits = {
  read: boolean
  write: boolean
}

 
次に、 Mode 型にて、ユーザー(owner)、グループ(group)、その他のユーザー(others)ごとに、読み込み・書き込み権限があるかを表現します。

export type Mode = {
  owner: PermissionBits
  group: PermissionBits
  others: PermissionBits
}

 
続いて、ファイルのようなリソースを UnixResource 型として表現します。 name はリソースの名前(foo.txt)、ownergroupはそれぞれの名前を設定する想定です。また、このリソースに対する権限制御は permissions に定義します。

なお、今回は学習用途の簡易的な実装とするためディレクトリ・ファイルの区別は行わず、それらをまるっとリソースとして表現します。

export type UnixResource = {
  name: string
  owner: string
  group: string
  permissions: Mode
}

 

権限判定処理

リソースを保有するクラス UnixPermission を定義します。これはリソースごとに1インスタンスを作成する想定です。

export class UnixPermission {
  private resource: UnixResource

  constructor(resource: UnixResource) {
    this.resource = resource
  }
  
  // 略
}

 
このクラスには、権限判定を行うメソッド hasPermission を定義します。

このメソッドではユーザーとユーザーが所属するグループを受け取ることで、権限の判定を行います。Unixパーミッションには「特定のユーザー・グループを明示的に拒否する」という概念がないため、ユーザー名やグループを元に権限有無をチェックするだけな処理となりました。

また、「Unixパーミッション所有者 > グループ > その他の順で判定され、最初にマッチしたカテゴリで権限の有無を判断する」も実装しています。これにより、ある所有者が「所有者とグループでマッチする」状況で

  • 所有者では、書き込み不可
  • グループでは、書き込み可

という権限設定の場合、hasPermissionは「書き込み不可(=最初にマッチした所有者)」と判定します。

export class UnixPermission {
  hasPermission(userName: string, userGroupNames: string[], action: 'read' | 'write'): boolean {
    if (userName === this.resource.owner) {
      return this.resource.permissions.owner[action]
    }

    if (userGroupNames.includes(this.resource.group)) {
      return this.resource.permissions.group[action]
    }

    return this.resource.permissions.others[action]
  }
}

 

動作確認

今回はテストコードで動作を確認します。

以前の記事で見たように、Bunではパラメタライズドテストができます。

今回のテストでは「ユーザーとグループとactionの組み合わせで権限の有無が確認できる」ことから、パラメタライズドテストで検証することにしました。こんな感じです。

describe('UnixPermission', () => {
  describe('hasPermission', () => {
    describe('読み込み権限について', () => {
      describe('ownerのみ権限がある', () => {
        const resource: UnixResource = {
          name: 'test.txt',
          owner: 'my_user',
          group: 'my_group',
          permissions: {
            owner: { read: true, write: false },
            group: { read: false, write: false },
            others: { read: false, write: false }
          }
        }

        const permission = new UnixPermission(resource)

        it.each([
          {
            title: 'ユーザーがowner',
            userName: 'my_user',
            groupNames: ['another_group'],
            expected: true
          },
          {
            title: 'ユーザーが単一groupに所属',
            userName: 'another_user',
            groupNames: ['my_group'],
            expected: false
          },
          {
            title: 'ユーザーが複数groupに所属',
            userName: 'another_user',
            groupNames: ['my_group', 'another_group'],
            expected: false
          },
          {
            title: 'ユーザーがowner、かつ、groupに所属',
            userName: 'my_user',
            groupNames: ['my_group'],
            expected: true
          },
          {
            title: 'ユーザーが何も所属していない',
            userName: 'another_user',
            groupNames: ['another_group'],
            expected: false
          }
        ])('$title 時の結果が $expected であること', ({ userName, groupNames, expected }) => {
          expect(permission.hasPermission(userName, groupNames, 'read')).toBe(expected)
        })
      })
// 略

 
テストコードの全体は後述のソースコードを確認してください。

テストを実行したところすべてのテストがパスしたことから、Unixのファイルパーミッションが実装できました。

 

ソースコード

GitHubに上げました。
https://github.com/thinkAmi-sandbox/authorization_practice_in_memory

今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/authorization_practice_in_memory/pull/1/files

なお、Claude Codeに任せた部分と自分で書いた部分は、それぞれ別のコミットにしています。また、Claude Codeに任せたコミットには、Claude Codeに対するプロンプトも記載してあります。

*1:認識が誤っていたらツッコミをお願いします




以上の内容はhttps://thinkami.hatenablog.com/entry/2025/08/24/211635より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14