Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

外部サービスで BootCamp ログインができるようにした(OAuth2.0 プロバイダー機能実装) #8250

Open
wants to merge 42 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2515309
doorkeeper gem をインストールした
MikotoMakizuru Dec 8, 2024
f50e42e
doorkeeper ルーティングを拡張するメソッド追加
MikotoMakizuru Dec 8, 2024
b0ce9a4
管理者のみOauth2Provider画面にアクセスできるようにした
MikotoMakizuru Dec 8, 2024
d235179
マイグレーションファイル作成
MikotoMakizuru Dec 9, 2024
5a69440
doorkeeper関連のテーブル作成
MikotoMakizuru Dec 9, 2024
2715dab
Userモデルにaccess_grantsとaccess_tokensの関連付けを追加
MikotoMakizuru Dec 9, 2024
1293854
スコープの設定を追加
MikotoMakizuru Dec 9, 2024
c878468
require_login_for_api メソッドを show メソッド以外で before_action されるようにした
MikotoMakizuru Dec 15, 2024
d1ad2cf
Bootcamp ログイン情報取得 API のリクエスト時に、有効なアクセストークンを確認する処理を追加
MikotoMakizuru Dec 15, 2024
8c13d02
Bootcamp ログイン情報取得を取得する処理追加
MikotoMakizuru Dec 15, 2024
1ddd79b
外部サービスからのアクセス時に、Bootcamp 未ログインの場合はログイン画面にリダイレクトするようにした
MikotoMakizuru Dec 15, 2024
fb90dc9
doorkeeper_tokenが存在する場合にのみ、show APIの保護が行われるようにした
MikotoMakizuru Dec 19, 2024
1c1ddc0
APIが提供する情報を必要なものだけに絞る
MikotoMakizuru Dec 22, 2024
4e2f8ee
調査のため一時的にコミット
MikotoMakizuru Dec 25, 2024
af0e7d8
show API が提供する情報をもとに戻す(テストが通らなくなるため)
MikotoMakizuru Jan 1, 2025
0364ec0
traceroute の指摘修正
MikotoMakizuru Jan 1, 2025
d4f39e2
管理者メニューからOauth2 Provider機能のアプリケーション一覧画面に遷移できるようにした
MikotoMakizuru Jan 2, 2025
7035c08
ja.yml に OAuth2 Provider関連の翻訳を追加
MikotoMakizuru Jan 2, 2025
91cfcee
show API が提供する情報に email を追加
MikotoMakizuru Jan 2, 2025
e019c99
プロフィール情報読み取り認証画面の文言修正
MikotoMakizuru Jan 23, 2025
d3c75b0
Secret Id を Secret に修正
MikotoMakizuru Jan 25, 2025
cb4a5b2
notice の文章に句読点追加
MikotoMakizuru Jan 25, 2025
9476d8b
クライアントアプリケーションがBootCampアプリの利用を承認していないときに表示するメッセージ追加
MikotoMakizuru Jan 25, 2025
90929c6
アプリケーション詳細ページではアプリケーション名が表示されるようにした
MikotoMakizuru Jan 25, 2025
9f15477
アプリ追加・編集時の入力項目に説明を追加
MikotoMakizuru Jan 25, 2025
59de1f0
Redirect URI の翻訳キー追加
MikotoMakizuru Jan 25, 2025
87f432d
Redirect URIの値が無効だった場合に表示されるエラーメッセージの翻訳キー追加
MikotoMakizuru Jan 25, 2025
9ce1f70
provider 機能のテスト追加
MikotoMakizuru Jan 25, 2025
b252df1
シングルクォーテーション削除
MikotoMakizuru Jan 26, 2025
c169a3a
OAuth2プロバイダーテストの追加と修正
MikotoMakizuru Jan 26, 2025
8d7e23f
show API が提供する情報が許可されたものであるかをテストするテスト作成
MikotoMakizuru Feb 4, 2025
1d1ecb4
Redirect URL が無効な値である場合のエラーメッセージ追加
MikotoMakizuru Feb 5, 2025
11f97c2
oauth_access_grants と oauth_access_tokens のアソシエーションに inverse_of オプション追加
MikotoMakizuru Feb 11, 2025
772a793
不要な変更削除
MikotoMakizuru Feb 11, 2025
e501bea
doorkeeper の authorizations コントローラーの show アクションを無視する設定を追加
MikotoMakizuru Feb 11, 2025
4dde0da
class name オプション削除
MikotoMakizuru Feb 14, 2025
c93ebab
論理積を使用した条件分岐に修正
MikotoMakizuru Feb 14, 2025
d7531fa
Revert "論理積を使用した条件分岐に修正"
MikotoMakizuru Feb 15, 2025
291e14a
認証画面はカスタマイズ view を使用する設定を追加
MikotoMakizuru Feb 22, 2025
8377a6c
認証画面を作成
MikotoMakizuru Feb 22, 2025
a39572a
ページタイトル追加
MikotoMakizuru Feb 22, 2025
ed4d895
未認証の API にアクセス時に 401 レスポンスを返すテストを追加
MikotoMakizuru Feb 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .traceroute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ignore_unused_routes:
- any_login\/.*
- active_storage\/.*
- jobs/mass_update
- doorkeeper/authorizations#show
ignore_unreachable_actions:
- rails\/.*
- active_storage\/.*
Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ gem 'data_migrate'
gem 'diffy'
gem 'discord-notifier'
gem 'discordrb', '~> 3.5', require: false
gem 'doorkeeper'
gem 'good_job', '~> 3.14', github: 'komagata/good_job'
gem 'google-cloud-storage', '~> 1.25', require: false
gem 'holiday_jp'
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ GEM
rest-client (>= 2.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (5.8.0)
railties (>= 5)
erubi (1.12.0)
et-orbi (1.2.11)
tzinfo
Expand Down Expand Up @@ -621,6 +623,7 @@ DEPENDENCIES
diffy
discord-notifier
discordrb (~> 3.5)
doorkeeper
foreman
good_job (~> 3.14)!
google-cloud-storage (~> 1.25)
Expand Down
8 changes: 7 additions & 1 deletion app/controllers/api/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
class API::UsersController < API::BaseController
before_action :set_user, only: %i[show update]
before_action :require_login_for_api
before_action :require_login_for_api, except: :show
before_action :doorkeeper_authorize!, if: -> { doorkeeper_token.present? }, only: :show
PAGER_NUMBER = 24

def index
Expand Down Expand Up @@ -72,7 +74,11 @@ def target_users
end

def set_user
@user = User.find(params[:id])
@user = if params[:id] == 'show'
User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
else
User.find(params[:id])
end
end

def user_params
Expand Down
5 changes: 5 additions & 0 deletions app/models/oauth_access_grant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class OauthAccessGrant < ApplicationRecord
belongs_to :user
end
5 changes: 5 additions & 0 deletions app/models/oauth_access_token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class OauthAccessToken < ApplicationRecord
belongs_to :user
end
10 changes: 10 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@ class User < ApplicationRecord

has_many :coding_test_submissions, dependent: :destroy

has_many :oauth_access_grants,
foreign_key: 'resource_owner_id',
dependent: :delete_all,
inverse_of: 'user'

has_many :oauth_access_tokens,
foreign_key: 'resource_owner_id',
dependent: :delete_all,
inverse_of: 'user'

has_one_attached :avatar
has_one_attached :profile_image

Expand Down
2 changes: 1 addition & 1 deletion app/views/api/users/_user.json.jbuilder
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
columns = %i(id login_name long_name url roles primary_role icon_title)
columns = %i(id login_name email long_name url roles primary_role icon_title)
columns << :mentor_memo if admin_or_mentor_login?
json.(user, *columns)
json.avatar_url user.avatar_url
Expand Down
3 changes: 3 additions & 0 deletions app/views/application/_admin_menu.html.slim
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
li.header-dropdown__item
= link_to admin_campaigns_path, class: 'header-dropdown__item-link' do
| お試し延長
li.header-dropdown__item
= link_to oauth_applications_path, class: 'header-dropdown__item-link' do
| OAuth2 Provider
144 changes: 144 additions & 0 deletions app/views/layouts/authorization.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
background-color: #eee;
font-size: 14px;
}

#container {
background-color: #fff;
border: 1px solid #999;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 6px;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 20px rgba(0, 0, 0, 0.3);
margin: 2em auto;
max-width: 600px;
outline: 0;
padding: 1em;
width: 80%;
}

.page-header {
margin-top: 20px;
}

.text-info {
color: #17a2b8;
}

.actions {
border-top: 1px solid #eee;
margin-top: 1em;
padding-top: 9px;
text-align: center;
}

.actions > form > .btn {
margin-top: 5px;
}

.separator {
color: #eee;
padding: 0 0.5em;
}

.inline_block {
display: inline-block;
}

#oauth {
margin-bottom: 1em;
}

#oauth > .btn {
width: 7em;
}

td {
vertical-align: middle !important;
}

.btn {
width: 100%;
padding: 6px;
font-size: 22px;
line-height: 1.7;
border: 1px solid transparent;
border-radius: 4px;
}

.btn-success {
color: #fff;
background-color: #28a745;
border-color: #28a745;
}

.btn-success:hover {
color: #fff;
background-color: #218838;
border-color: #1e7e34;
}

.btn-danger {
color: #fff;
background-color: #dc3545;
border-color: #dc3545;
}

.btn-danger:hover {
color: #fff;
background-color: #c82333;
border-color: #bd2130;
}
</style>
</head>
<body>
<header class="page-header" role="banner">
<title><%= t('doorkeeper.authorizations.title') %></title>
</header>

<main role="main">
<div id="container">
<% if @pre_auth.scopes.count > 0 %>
<div id="oauth-permissions">
<p><%= t('doorkeeper.authorizations.new.able_to') %>:</p>

<ul class="text-info">
<% @pre_auth.scopes.each do |scope| %>
<li><%= t scope, scope: [:doorkeeper, :scopes] %></li>
<% end %>
</ul>
</div>
<% end %>

<div class="actions">
<%= form_tag oauth_authorization_path, method: :post do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %>
<%= hidden_field_tag :state, @pre_auth.state, id: nil %>
<%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %>
<%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %>
<%= hidden_field_tag :scope, @pre_auth.scope, id: nil %>
<%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %>
<%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %>
<%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
<% end %>
<%= form_tag oauth_authorization_path, method: :delete do %>
<%= hidden_field_tag :client_id, @pre_auth.client.uid, id: nil %>
<%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri, id: nil %>
<%= hidden_field_tag :state, @pre_auth.state, id: nil %>
<%= hidden_field_tag :response_type, @pre_auth.response_type, id: nil %>
<%= hidden_field_tag :response_mode, @pre_auth.response_mode, id: nil %>
<%= hidden_field_tag :scope, @pre_auth.scope, id: nil %>
<%= hidden_field_tag :code_challenge, @pre_auth.code_challenge, id: nil %>
<%= hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method, id: nil %>
<%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %>
<% end %>
</div>
</div>
</main>
</body>
</html>
4 changes: 4 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,9 @@ class Application < Rails::Application
config.active_storage.variant_processor = :vips

config.view_component.capture_compatibility_patch_enabled = true

config.to_prepare do
Doorkeeper::AuthorizationsController.layout "authorization"
end
end
end
21 changes: 21 additions & 0 deletions config/initializers/doorkeeper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

Doorkeeper.configure do
orm :active_record
resource_owner_authenticator do
current_user || redirect_to(login_path)
end

admin_authenticator do
if current_user
redirect_to root_path, alert: '管理者としてログインしてください' unless current_user.admin?
else
redirect_to login_path
end
end

# デフォルトのスコープを read に設定
# これにより承認されたアプリケーションは API から公開データの「読み取り」が可能
default_scopes :read
enforce_configured_scopes
end
66 changes: 66 additions & 0 deletions config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ ja:
coding_test_case:
input: 入力
output: 出力
doorkeeper/application:
redirect_uri: Redirect URL
enums:
user:
job:
Expand Down Expand Up @@ -390,6 +392,10 @@ ja:
attributes:
end_at:
format: 'は%{shortest_end_at}以降を入力してください。'
doorkeeper/application:
attributes:
redirect_uri:
relative_uri: Redirect URLの値が無効です
target:
student_and_trainee: 現役 + 研修生
student: 現役生
Expand Down Expand Up @@ -469,3 +475,63 @@ ja:
followup: "こんにちは。\nご登録から30日ほど経ちますが、学習について困っていることや詰まっている部分などありますでしょうか?\nもしあれば、メンターの皆さんにお気軽にご相談ください。"
comeback: "お帰りなさい!!復会ありがとうございます。\n休会中に何か変わったことがあれば、再びスムーズに学び始めることができるように全力でサポートします。何か困ったことや質問があれば、メンターの皆さんに遠慮なくご相談ください。\n\nまたフィヨルドブートキャンプの Discord のサーバーに入室できるように、再度、Doc にある Discord の招待 URL にアクセスをお願いします。\n<https://bootcamp.fjord.jp/practices/129#url>"
unregistered: 未登録
doorkeeper:
applications:
buttons:
authorize: 承認
cancel: キャンセル
destroy: 削除
edit: 編集
submit: 登録する
confirmations:
destroy: アプリケーションを削除しますか?
form:
error: 'フォームにエラーが無いか確認してください'
help:
confidential: '機密性の高いアプリケーションの場合はチェックしてください'
redirect_uri: 一行に一つのURLを入力してください
scopes: '空白の場合デフォルトスコープが使用されます'
edit:
title: 'アプリケーション編集'
index:
actions: ''
new: アプリケーション追加
title: アプリケーション一覧
new:
title: アプリケーション追加
show:
actions: ''
application_id: Application ID
secret: Secret
title: '%{name}'
authorizations:
title: Oauth2 Provider
buttons:
authorize: 承認
deny: キャンセル
new:
able_to: 次の許可をリクエストしています
title: ''
prompt: ''
errors:
messages:
invalid_client: アプリの認証に失敗しました
invalid_redirect_uri: Redirect URLが無効です
flash:
applications:
create:
notice: アプリケーションを追加しました。
destroy:
notice: アプリケーションを削除しました。
update:
notice: アプリケーションを更新しました。
layouts:
admin:
nav:
applications: ''
home: BootCamp
title: Oauth2 Provider
application:
title: Oauth2 Provider
scopes:
read: メールアドレスを含む基本的なプロフィール情報の読み取り
1 change: 1 addition & 0 deletions config/routes/api.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

Rails.application.routes.draw do
use_doorkeeper
namespace 'api' do
namespace 'admin' do
resource :count, controller: 'count', only: %i(show)
Expand Down
Loading