2024年10月9日水曜日

Rails の Devise で認証を実現したプロジェクトに、 Pundit で認可を追加する

前提

Rails の Devise で認証を実現する からの続き。

ロールが adminuser で取得できるリソースを分ける。

アカウントに role カラムを追加

マイグレーションファイルを作成

rails generate migration add_role_to_accounts

マイグレーションファイルで、 role カラムを追加

db/migrate/20241008113604_add_role_to_accounts.rb:

class AddRoleToAccounts < ActiveRecord::Migration[7.2]
  def change
    add_column , , , "user"
  end
end

マイグレーション実行

rails db:migrate

管理者は、 DB から直接 role カラムを修正することとし、ログインのビューは変更しない。

リソースの作成

一般ユーザーも触れるリソースの追加

rails generate scaffold AllWelcomeResource name:string
rails db:migrate

管理者のみ触れるリソースの追加

rails generate scaffold AdminOnlyResource name:string
rails db:migrate

トップ画面の更新

各リソースの index へ行けるように、トップ画面にリンクを作成。

app/views/top/index.html.erb:

<ul>
  <li>一般歓迎リソース</li>
  <li>
    <ul>
      <li>
        <%= link_to :all_welcome_resources, all_welcome_resources_path %>
      </li>
    </ul>
  </li>
  <li>管理者リソース</li>
  <li>
    <ul>
      <li>
        <%= link_to :admin_only_resources, admin_only_resources_path %>
      </li>
    </ul>
  </li>
</ul>

Pundit ジェムのインストール

bundle add pundit
rails generate pundit:install

AdminOnlyResource への認可処理追加

ざっくり手順は、以下。

  1. コントローラーに認可処理に必要なボイラープレートを記載
  2. ポリシーファイルに index, show, create, new, update, edit, destroy の 7 種類に対する認可ポリシーを記述する。

コントローラーにボイラープレートを記載

ApplicationController

今回は、 users テーブルではなく accounts テーブルを使って認証を行っているため、ログイン済みユーザーの取得には current_account 関数を使う必要がある。

Pundit のデフォルトでは、 current_user 関数を使う用になっているため、この定義を上書きする。

app/controllers/application_controller.rb:

class ApplicationController < ActionController::Base
  include Pundit    # この 4 行を追加
  def pundit_user   # この 4 行を追加
    current_account # この 4 行を追加
  end               # この 4 行を追加

  before_action 
  # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
  allow_browser 
end

AdminOnlyResourceController

app/controllers/admin_only_resources_controller.rb:

class AdminOnlyResourcesController < ApplicationController
  include Pundit                                                      # この 2 行を追加
  rescue_from Pundit::NotAuthorizedError,   # この 2 行を追加

  before_action , %i[ show edit update destroy ]

  # GET /admin_only_resources or /admin_only_resources.json
  def index
    authorize AdminOnlyResource # この行を追加
    @admin_only_resources = AdminOnlyResource.all
  end

  # GET /admin_only_resources/1 or /admin_only_resources/1.json
  def show
    authorize AdminOnlyResource # この行を追加
  end

  # GET /admin_only_resources/new
  def new
    authorize AdminOnlyResource # この行を追加
    @admin_only_resource = AdminOnlyResource.new
  end

  # GET /admin_only_resources/1/edit
  def edit
    authorize AdminOnlyResource # この行を追加
  end

  # POST /admin_only_resources or /admin_only_resources.json
  def create
    authorize AdminOnlyResource # この行を追加
    @admin_only_resource = AdminOnlyResource.new(admin_only_resource_params)

    respond_to do |format|
      if @admin_only_resource.save
        format.html { redirect_to @admin_only_resource, "Admin only resource was successfully created." }
        format.json { render , , @admin_only_resource }
      else
        format.html { render ,  }
        format.json { render @admin_only_resource.errors,  }
      end
    end
  end

  # PATCH/PUT /admin_only_resources/1 or /admin_only_resources/1.json
  def update
    authorize AdminOnlyResource # この行を追加
    respond_to do |format|
      if @admin_only_resource.update(admin_only_resource_params)
        format.html { redirect_to @admin_only_resource, "Admin only resource was successfully updated." }
        format.json { render , , @admin_only_resource }
      else
        format.html { render ,  }
        format.json { render @admin_only_resource.errors,  }
      end
    end
  end

  # DELETE /admin_only_resources/1 or /admin_only_resources/1.json
  def destroy
    authorize AdminOnlyResource # この行を追加
    @admin_only_resource.destroy!

    respond_to do |format|
      format.html { redirect_to admin_only_resources_path, , "Admin only resource was successfully destroyed." }
      format.json { head  }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_admin_only_resource
      @admin_only_resource = AdminOnlyResource.find(params[])
    end

    # Only allow a list of trusted parameters through.
    def admin_only_resource_params
      params.require().permit()
    end

    def user_not_authorized                                             # この 4 行を追加
      flash[] = "You are not authorized to perform this action."  # この 4 行を追加
      redirect_to(request.referer || root_path)                         # この 4 行を追加
    end                                                                 # この 4 行を追加
end
  • include Pundit: Pundit の機能を使えるようにする
  • rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized: 認証失敗時にトップページへリダイレクト
  • authorize <モデル名>: 認可処理、認可に失敗すると、 Pundit::NotAuthorizedError が発生する

ポリシーファイルに index, show, create, new, update, edit, destroy の 7 種類に対する認可ポリシーを記述する。

今回は、 AdminOnlyResource のポリシーを作成するので、 app/policies/admin_only_resource_policy.rb にポリシーを記述する。

ポリシーファイルの生成

app/policies/admin_only_resource_policy.rb:

class AdminOnlyResourcePolicy < ApplicationPolicy
  def index?
    user.role == "admin"
  end
  def show?
    user.role == "admin"
  end
  def new?
    user.role == "admin"
  end
  def edit?
    user.role == "admin"
  end
  def create?
    user.role == "admin"
  end
  def update?
    user.role == "admin"
  end
  def destroy?
    user.role == "admin"
  end
end

これで、 roleadmin のユーザー以外が見れないようになる。

A5SQL で role を user にしたり admin にしたりして試してみよう。

以上。

参考資料

Visual Studio で Vim をビルドする

この記事は Vim 駅伝 の 2024/10/09 の記事です。 前回の記事は monaqa さんによる、 2024/10/07 の「Vim で挿入モードから抜けることなく直前の単語を UPPER CASE にする」という記事でした。

次回は 2024/10/11 に投稿される予定です。

この記事はなに?

MinGW でビルドしたことはあるけど、 Visual Studio でビルドしたことがなかったので試してみる。

今回は、 GVim の 64 bit 版をビルドし、 %USERPROFILE%\app\vim にインストールします。

また、もろもろ環境を汚すのが嫌なので、Windows サンドボックス上で試していく。

前提

Windows サンドボックス環境の準備

winget のインストール

今回は、 Visual Studio Community や Git を winget でインストールするので、 Windows サンドボックスで winget を利用可能にする手順を実施する。

$progressPreference = 'silentlyContinue'
Write-Information "Downloading WinGet and its dependencies..."
Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx -OutFile Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle

†: 普通の Windows であれば winget 導入済みのはずなので不要なはず…

Vim ビルドの準備

Vim をビルドするために必要なパッケージをインストールしていく。

Visual Studio Community のインストール

winget install --silent --accept-source-agreements Microsoft.VisualStudio.2022.Community

これで、 Visual Studio Community と Visual Studio Installer がそれぞれ下記に配置され、

  • Visual Studio Community: C:\Program Files\Microsoft Visual Studio\2022\Community
  • Visual Studio Installer: C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe

次に、Vim のビルドに必要な NativeDesktop コンポーネントをインストール。

& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe" modify `
    --installPath "C:\Program Files\Microsoft Visual Studio\2022\Community" `
    --add Microsoft.VisualStudio.Workload.NativeDesktop `
    --add Microsoft.Component.MSBuild `
    --add Microsoft.VisualStudio.Component.VC.ATL `
    --add Microsoft.VisualStudio.Component.VC.CMake.Project `
    --add Microsoft.VisualStudio.Component.Windows11SDK.22621 `
    --quiet

†: インストール完了がとても分かりにくいので、 --quiet は付けない方が良いかも…?

Git のインストール

winget install --silent --accept-source-agreements Git.Git

Vim のビルド

以降のコマンドは、スタートメニューから x64 Native Tools Command Prompt for VS 2022 を起動し、その中で実行する。

Vim のクローン

git clone --depth 1 https://github.com/vim/vim

Vim のビルド

cd vim\src
.\msvc-latest.bat x86_amd64
nmake -f .\Make_mvc.mak GUI=yes

動作確認

src 直下に gvim.exe が生成されているので、それを実行する。

.\gvim.exe

gvim が起動すれば OK.

Windows 版 Vim の配布可能構成を作る

ここまは、「gvim.exe の実行ファイルを作成」しただけ。

そのほか gvim.exe の起動に必要なランタイムやデフォルトプラグインなどを指定の構成で配置する必要がある。

See: 17. Installing after building from sources

vim91 ディレクトリの作成

配布可能パッケージのルートとなるディレクトリを作成する。

cd ${VIM_REPOSITORY_ROOT}
mkdir vim91

runtime ディレクトリ内のファイルを vim91 内へコピー

copy runtime\* vim91
xcopy /s runtime\* vim91

ビルドした実行バイナリを vim91 内へコピー

copy src\*exe vim91
copy src\tee\tee.exe vim91
copy src\xxd\xxd.exe vim91
mkdir vim91\GvimExt64
copy src\GvimExt\gvimext.dll vim91\GvimExt64

gettext と iconv を取得し、配置

powershell.exe -Command Invoke-WebRequest -Uri https://github.com/mlocati/gettext-iconv-windows/releases/download/v0.21-v1.16/gettext0.21-iconv1.16-shared-64.zip -OutFile .\gettext0.21-iconv1.16-shared-64.zip
powershell.exe -Command Expand-Archive -Path .\gettext0.21-iconv1.16-shared-64.zip -DestinationPath .\gettext-iconv

copy gettext-iconv\bin\libintl-8.dll vim91
copy gettext-iconv\bin\libintl-8.dll vim91\GvimExt64
copy gettext-iconv\bin\libiconv-2.dll vim91
copy gettext-iconv\bin\libiconv-2.dll vim91\GvimExt64

Vim のインストール

インストールディレクトリの作成

今回は、 %USERPROFILE%\app\vim にインストールしたいので、そのディレクトリを作成する。

mkdir %USERPROFILE%\app\vim

Vim の配置

mkdir %USERPROFILE%\app\vim\vim91
xcopy /e vim91 %USERPROFILE%\app\vim\vim91

Vim のインストール

cd %USERPROFILE%\app\vim\vim91
install.exe

install.exe を実行すると、「インストール時に何をするか・しないか」のリストが表示されるので、数字を入力し、「する・しない」を切り替える。

切り替えた後 d を入力すると選択したものが実行される。

デフォルトで d を押して、デスクトップに表示された gvim9.1 のショートカットから起動できれば OK.

参考資料

2024年10月7日月曜日

Debian 12 上に Gleam 言語の環境を構築する

この記事はなに?

何か理由があって Gleam を試そうと思って環境構築を行った。

理由を忘れてしまったのでブログにだけはしておこうと思った。

前提

Debian on Docker on Ubuntu on WSL2 on Windows 環境で実施している。

  • OS: Windows 11 Pro 23H2 ビルド 22631.4169
  • Docker: Docker version 27.3.1, build ce12230
  • コンテナイメージ: debian:12-slim

Gleam のインストールに必要なパッケージのインストール

apt update
apt install -y \
    curl \
    gnupg2

Gleam のインストール

GLEAM_VERSION=v1.5.1

curl -LO https://github.com/gleam-lang/gleam/releases/download/${GLEAM_VERSION}/gleam-${GLEAM_VERSION}-x86_64-unknown-linux-musl.tar.gz
mkdir -p /opt/gleam/bin
tar xfv gleam-${GLEAM_VERSION}-x86_64-unknown-linux-musl.tar.gz -C /opt/gleam/bin
PATH=$PATH:/opt/gleam/bin
rm gleam-${GLEAM_VERSION}-x86_64-unknown-linux-musl.tar.gz

Erlang のインストール

curl https://binaries2.erlang-solutions.com/GPG-KEY-pmanager.asc -o /usr/share/keyrings/pmanager.gpg
apt-key add /usr/share/keyrings/pmanager.gpg

cat << EOF > /etc/apt/sources.list.d/erlang.sources
Types: deb
URIs: http://binaries2.erlang-solutions.com/ubuntu/
Suites: jammy-esl-erlang-25
Components: contrib

Types: deb
URIs: http://binaries2.erlang-solutions.com/debian/
Suites: bullseye-elixir-1.15
Components: contrib

Types: deb
URIs: http://binaries2.erlang-solutions.com/ubuntu/
Suites: bionic-mongooseim-6
Components: contrib
EOF

apt update
apt install -y esl-erlang

Gleam プロジェクトの作成・動作確認

gleam new helloworld
cd helloworld
gleam run

OK.

参考資料

2024年10月4日金曜日

devcontainer.vim で、コンテナ上の Vim に引きこもって作業を行う(ゼロから環境構築をしてみよう編)

この記事は Vim 駅伝 の 2024/10/04 の記事です。 前回の記事は yuys13 さんによる、 2024/10/02 の「Neovimが入れ子になるのを防ぐ」という記事でした。

次回は 2024/10/07 に投稿される予定です。

この記事はなに?

mikoto2000/devcontainer.vim: VSCode Dev Container の Vim 版。 VSCode 向けに作成された devcontainer.json とは別に、後付け設定ファイルを追加するだけで Vim による Dev Container 開発が可能になることを目指しています。 の Getting Started として書かれた記事です。

リンクの通り、 VSCode 向けに作成された devcontainer.json をそのまま使い、 Vim による Dev container 開発ができるようにするためのツールです。

前提

今回は、 Windows 上の WSL2 上の Ubuntu 24.04 で試すが、 Linux であれば同様に実行できるはず…。

また、 Windows + Docker Desktop の環境でも(マウントのパスを変えれば)実行可能なはず…

  • OS: Windows 11 Pro 23H2 ビルド 22631.4037
  • WSL2: Ubuntu: 24.04
  • docker コマンドと docker compose コマンドが利用可能であること

ゼロからコンテナを作る場合

手順概要

  1. devcontainer.vim templates apply コマンドで、 Dev container の設定ひな形を生成する
  2. 必要に応じて「1.」で作成した devcontainer.json を修正
  3. devcontainer.vim config -g > .devcontainer/devcontainer.vim.jsondevcontainer.vim の設定ひな形を生成する
    • 必要に応じて --home <ユーザーのホームディレクトリ> でホームディレクトリとする場所を変更
  4. 必要に応じて「3.」で作成した devcontainer.vim.json を修正
  5. devcontainer.vim start . を実行すると、コンテナ上で Vim が立ち上がる
  6. コンテナ上の Vim で開発を行う

Go 言語の環境を構築する

1. devcontainer.vim templates apply コマンドで、 Dev container の設定ひな形を生成する

devcontainer.vim templates apply . コマンドを実行すると、以下のようにどんな環境を構築するかの選択肢が出てくる。

$ devcontainer.vim templates apply .
Search:
? Select Template:
   Alpine
    Anaconda (Python 3)
    Anaconda (Python 3) & PostgreSQL
    C++
   C++ & MariaDB

この状態で Go と入力し、 Go にカーソルを合わせてエンターキーを押下する。

(ここで表示される環境は、 Available Dev Container Templates でリストされている)

$ devcontainer.vim templates apply .
 Go
/home/mikoto/.cache/devcontainer.vim/bin/devcontainer aleady exist, use this.
run devcontainer: `/home/mikoto/.cache/devcontainer.vim/bin/devcontainer templates apply --template-id ghcr.io/devcontainers/templates/go:4.0.0 --workspace-folder .`
[2024-09-29T11:01:54.691Z] @devcontainers/cli 0.68.0. Node.js v21.7.1. linux 5.15.153.1-microsoft-standard-WSL2 x64.
[2024-09-29T11:01:56.961Z] Files to omit: 'devcontainer-template.json, README.md, NOTES.md'
{"files":["./.devcontainer/devcontainer.json","./.github/dependabot.yml"]}

そうすると、 .devcontainer/devcontainer.json と、 .github/dependabot.yml が生成される。 (今回の開発環境構築に関係ないので、 dependabot.yml は無視する)

次のように、イメージ: mcr.microsoft.com/devcontainers/go:1-1.22-bookworm を使用する定義がされている。

$ cat .devcontainer/devcontainer.json
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
        "name": "Go",
        // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
        "image": "mcr.microsoft.com/devcontainers/go:1-1.22-bookworm"

        // Features to add to the dev container. More info: https://containers.dev/features.
        // "features": {},

        // Use 'forwardPorts' to make a list of ports inside the container available locally.
        // "forwardPorts": [],

        // Use 'postCreateCommand' to run commands after the container is created.
        // "postCreateCommand": "go version",

        // Configure tool-specific properties.
        // "customizations": {},

        // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
        // "remoteUser": "root"
}

2. 必要に応じて「1.」で作成した devcontainer.json を修正

今回は必要が無いので修正しない。

いつもの開発であれば、気になるのでコメントを削除したりしている。

3. devcontainer.vim config -g > .devcontainer/devcontainer.vim.jsondevcontainer.vim の設定ひな形を生成する

devcontainer.vim config -g で、「devcontainer.vim 用の設定ファイルひな形」が生成できるので、これを使って .devcontainer/devcontainer.vim.json を生成する。

次のように、 Vim や開発に必要な定義のひな形が生成される。

$ devcontainer.vim config -g > .devcontainer/devcontainer.vim.json
$ cat .devcontainer/devcontainer.vim.json
{
  "remoteEnv": {
    "EDITOR": "~/squashfs-root/AppRun",
    "PAGER": "sed -r 's/\\x1B\\[[0-9;]*[mGKH]//g' | ~/squashfs-root/AppRun -R -",
    "LESSCHARSET": "utf-8",
    "SHELL": "bash",
    "TERM": "xterm-256color",
    // If use WSLG
    // "DISPLAY": "${localEnv:DISPLAY}",
    // "WAYLAND_DISPLAY": "${localEnv:WAYLAND_DISPLAY}",
    // "XDG_RUNTIME_DIR": "${localEnv:XDG_RUNTIME_DIR}",
    // "PULSE_SERVER": "${localEnv:PULSE_SERVER}",
  },
  // devcontainer/cli はまだ forwardPorts に対応していないため、
  // 必要に応じて forwardPorts の定義を appPort に転記する。
  // ※ コンテナ側で Listen する際は、 `127.0.0.1` **ではなく** `0.0.0.0` で Listen すること。
  // "appPort": [
  // ],
  "mounts": [
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.vim",
      "target": "/home/vscode/.vim"
    },
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.gitconfig",
      "target": "/home/vscode/.gitconfig"
    },
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.ssh",
      "target": "/home/vscode/.ssh"
    },
    // If use host's bashrc
    //{
    //  "type": "bind",
    //  "source": "${localEnv:HOME}/.bashrc",
    //  "target": "/home/vscode/.bashrc"
    //},
    // If use WSLG
    //{
    //  "type": "bind",
    //  "source": "/tmp/.X11-unix",
    //  "target": "/tmp/.X11-unix"
    //},
    //{
    //  "type": "bind",
    //  "source": "/mnt/wslg",
    //  "target": "/mnt/wslg"
    //},
  ],
  //"features": {
  //  "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
  //  "ghcr.io/devcontainers/features/docker-in-docker:2": {}
  //}
}

4. 必要に応じて「3.」で作成した devcontainer.vim.json を修正

今回は必要ないため修正しない。

GUI 開発なら WSLG 向けのコメントを外したり、サーバー開発なら appPort の定義を追加したりする。 (私の場合、サーバー開発なら docker-compose.yml を作ってしまうので、この定義の出番はあまりない)

5. devcontainer.vim start . を実行すると、コンテナ上で Vim が立ち上がる

次のコマンドを入力すると、ターミナルで Vim が立ち上がります。

devcontainer.vim start .

docker runbash の代わりに vim を立ち上げた感じですね。

ただし、 Vim に必要な定義はバインドマウントでマウントされているため、いつも使っている CUI 版 Vim と同じ使い心地で使えます。

6. コンテナ上の Vim で開発を行う

main.go を実装し、ターミナルを立ち上げ、 go run main.go で Go プログラムが実行できる。

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!");
}

:terminal して go run main.go を実行する。

$ go run main.go
Hello, World!

大抵の LSP は自身の言語で書かれているので、 prabirshrestha/vim-lsp: async language server protocol plugin for vim and neovimmattn/vim-lsp-settings: Auto configurations for Language Server for vim-lsp

以上。

参考資料