Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,5 @@ gem "rswag", "~> 2.16"
gem "warning", "~> 1.5"

gem "rack-cors", "~> 2.0"

gem "doorkeeper", "~> 5.8"
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ GEM
diff-lcs (1.5.1)
docile (1.4.0)
domain_name (0.6.20240107)
doorkeeper (5.8.2)
railties (>= 5)
dotenv (3.1.7)
dotenv-rails (3.1.7)
dotenv (= 3.1.7)
Expand Down Expand Up @@ -858,6 +860,7 @@ DEPENDENCIES
devise (~> 4.9)
devise-i18n (~> 1.13)
devise_zxcvbn (~> 6.0)
doorkeeper (~> 5.8)
dotenv-rails (~> 3.1)
erb_lint (~> 0.9.0)
factory_bot
Expand Down
59 changes: 59 additions & 0 deletions app/api/v0/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
}
}
],
"security": [
{
"client_credentials": [
"read"
]
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -151,6 +158,13 @@
}
}
],
"security": [
{
"client_credentials": [
"read"
]
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -238,6 +252,13 @@
}
}
],
"security": [
{
"client_credentials": [
"read"
]
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -351,6 +372,13 @@
}
}
],
"security": [
{
"client_credentials": [
"read"
]
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -419,6 +447,13 @@
}
}
],
"security": [
{
"client_credentials": [
"read"
]
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -556,6 +591,11 @@
}
}
],
"security": [
{
"client_credentials": []
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -673,6 +713,11 @@
}
}
],
"security": [
{
"client_credentials": []
}
],
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -1157,6 +1202,20 @@
}
],
"components": {
"securitySchemes": {
"client_credentials": {
"type": "oauth2",
"description": "Authentication with the OAuth2 Client Credentials grant flow. You can generate a client app and a long-lived bearer token at /oauth/applications.",
"flows": {
"clientCredentials": {
"tokenUrl": "/oauth/token",
"scopes": {
"read": "read any data accessible to the OAuth application's owner"
}
}
}
}
},
"schemas": {
"jsonld_context": {
"type": "array",
Expand Down
14 changes: 14 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
after_action :set_content_security_policy_header, if: -> { request.format.html? }

before_action :authenticate_user!, unless: -> { SiteSettings.multiuser_enabled? || has_signed_id? }
before_action :doorkeeper_token_authorize!, if: :is_api_request?
around_action :switch_locale
before_action :check_for_first_use
before_action :show_security_alerts
Expand Down Expand Up @@ -104,6 +105,19 @@ def random_delay

private

def is_api_request?
request.format.json_ld?
end

def doorkeeper_token_authorize!
app_owner = doorkeeper_token&.application&.owner
if app_owner&.active_for_authentication?
sign_in app_owner, store: false
else
doorkeeper_render_error
end
end

def user_not_authorized
if current_user
raise ActiveRecord::RecordNotFound
Expand Down
84 changes: 84 additions & 0 deletions app/controllers/doorkeeper_applications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
class DoorkeeperApplicationsController < ApplicationController
before_action :get_application, except: [:index, :new, :create]

def index
@applications = policy_scope(Doorkeeper::Application)
end

def show
get_access_token
end

def new
authorize Doorkeeper::Application
@application = Doorkeeper::Application.new(
redirect_uri: "urn:ietf:wg:oauth:2.0:oob",
scopes: Doorkeeper.configuration.default_scopes
)
end

def edit
end

def create
authorize Doorkeeper::Application
@application = Doorkeeper::Application.create(application_params.merge(owner: current_user))
if @application.valid?
redirect_to @application, notice: t(".success")
else
flash.now[:alert] = t(".failure")
render :new
end
end

def update
generate_token if application_params[:generate_token]
@application.update(application_params.except(:generate_token))
Comment thread
Floppy marked this conversation as resolved.
if @application.save
get_access_token
render :show, notice: t(".success")
else
flash.now[:alert] = t(".failure")
render :edit
end
end

def destroy
@application.destroy
redirect_to doorkeeper_applications_path, notice: t(".success")
end

private

def application_params
params.require(:doorkeeper_application).permit(
:name,
:redirect_uri,
:confidential,
:scopes,
:generate_token
)
end

def get_application
@application = policy_scope(Doorkeeper::Application).find(params[:id])
authorize @application
end

def generate_token
# Revoke existing tokens
Doorkeeper::Application.revoke_tokens_and_grants_for(@application, @application.owner)
# Create new access token
token = @application.access_tokens.create(
expires_in: 6.months,
resource_owner_id: @application.owner.id,
scopes: @application.scopes
)
# Make plaintext available to view
@plaintext_token = token.plaintext_token
end

def get_access_token
@access_token = @application.access_tokens.where(revoked_at: nil).first
end
end
16 changes: 16 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,22 @@ class User < ApplicationRecord
attribute :problem_settings, :json
attribute :file_list_settings, :json

has_many :access_grants, # rubocop:disable Rails/InverseOf
class_name: "Doorkeeper::AccessGrant",
foreign_key: :resource_owner_id,
dependent: :delete_all

has_many :access_tokens, # rubocop:disable Rails/InverseOf
class_name: "Doorkeeper::AccessToken",
foreign_key: :resource_owner_id,
dependent: :delete_all

has_many :oauth_applications,
class_name: "Doorkeeper::Application",
as: :owner,
dependent: :delete_all,
inverse_of: :owner

def federails_name
username
end
Expand Down
48 changes: 48 additions & 0 deletions app/policies/doorkeeper/application_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
class Doorkeeper::ApplicationPolicy < ApplicationPolicy
def index?
user.present?
end

def show?
one_of(
record.owner == user,
user&.is_moderator?
)
end

def create?
none_of(
SiteSettings.demo_mode_enabled?
)
end

def update?
all_of(
one_of(
user == record,
user&.is_administrator?
),
SiteSettings.multiuser_enabled?,
none_of(
SiteSettings.demo_mode_enabled?
)
)
end

def destroy?
update?
end

class Scope
attr_reader :user, :scope

def initialize(user, scope)
@user = user
@scope = scope
end

def resolve
user.is_moderator? ? scope : scope.where(owner: user)
end
end
end
12 changes: 12 additions & 0 deletions app/views/doorkeeper_applications/_breadcrumb.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<% content_for :breadcrumbs do %>
<nav aria-label="Breadcrumb" class="container-fluid">
<ol class="breadcrumb">
<li class="breadcrumb-item"><%= link_to t("doorkeeper_applications.index.title"), doorkeeper_applications_path %></li>
<% if @application&.persisted? %>
<li class="breadcrumb-item">
<%= link_to @application.name, @application %>
</li>
<% end %>
</ol>
</nav>
<% end %>
7 changes: 7 additions & 0 deletions app/views/doorkeeper_applications/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= form_with model: @application do |form| %>
<%= text_input_row form, :name, autofocus: true %>
<%= text_input_row form, :redirect_uri, help: t(".redirect_uri.help") %>
<%= text_input_row form, :scopes, help: t(".scopes.help") %>
<%= checkbox_input_row form, :confidential, help: t(".confidential.help") %>
<%= form.submit translate(".submit"), class: "btn btn-primary" %>
<% end %>
4 changes: 4 additions & 0 deletions app/views/doorkeeper_applications/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%= render "breadcrumb" %>
<h1><%= t(".title") %></h1>

<%= render "form" %>
22 changes: 22 additions & 0 deletions app/views/doorkeeper_applications/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<h1><%= t(".title") %></h1>

<p class="lead"><%= t(".description") %></p>

<table class="table table-striped">
<tr>
<td><%= Doorkeeper::Application.human_attribute_name(:name) %></td>
<%= content_tag(:td) { Doorkeeper::Application.human_attribute_name(:owner) } if current_user.is_moderator? %>
<td><%= t Doorkeeper::Application.human_attribute_name(:scopes) %></td>
<td><%= t Doorkeeper::Application.human_attribute_name(:created_at) %></td>
</tr>
<% @applications.find_each do |app| %>
<tr>
<td><%= link_to app.name, app %></td>
<%= content_tag(:td) { app.owner.username } if current_user.is_moderator? %>
<td><%= app.scopes %></td>
<td><%= app.created_at.to_fs(:long) %></td>
</tr>
<% end %>
</table>

<%= link_to t(".new"), new_doorkeeper_application_path, class: "btn btn-primary" %>
4 changes: 4 additions & 0 deletions app/views/doorkeeper_applications/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<%= render "breadcrumb" %>
<h1><%= t(".title") %></h1>

<%= render "form" %>
Loading