Basic FASP support (#34031)
This commit is contained in:
parent
e5fd61a84e
commit
97b9994743
45 changed files with 1423 additions and 1 deletions
20
app/controllers/admin/fasp/debug/callbacks_controller.rb
Normal file
20
app/controllers/admin/fasp/debug/callbacks_controller.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Fasp::Debug::CallbacksController < Admin::BaseController
|
||||
def index
|
||||
authorize [:admin, :fasp, :provider], :update?
|
||||
|
||||
@callbacks = Fasp::DebugCallback
|
||||
.includes(:fasp_provider)
|
||||
.order(created_at: :desc)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize [:admin, :fasp, :provider], :update?
|
||||
|
||||
callback = Fasp::DebugCallback.find(params[:id])
|
||||
callback.destroy
|
||||
|
||||
redirect_to admin_fasp_debug_callbacks_path
|
||||
end
|
||||
end
|
19
app/controllers/admin/fasp/debug_calls_controller.rb
Normal file
19
app/controllers/admin/fasp/debug_calls_controller.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Fasp::DebugCallsController < Admin::BaseController
|
||||
before_action :set_provider
|
||||
|
||||
def create
|
||||
authorize [:admin, @provider], :update?
|
||||
|
||||
@provider.perform_debug_call
|
||||
|
||||
redirect_to admin_fasp_providers_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_provider
|
||||
@provider = Fasp::Provider.find(params[:provider_id])
|
||||
end
|
||||
end
|
47
app/controllers/admin/fasp/providers_controller.rb
Normal file
47
app/controllers/admin/fasp/providers_controller.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Fasp::ProvidersController < Admin::BaseController
|
||||
before_action :set_provider, only: [:show, :edit, :update, :destroy]
|
||||
|
||||
def index
|
||||
authorize [:admin, :fasp, :provider], :index?
|
||||
|
||||
@providers = Fasp::Provider.order(confirmed: :asc, created_at: :desc)
|
||||
end
|
||||
|
||||
def show
|
||||
authorize [:admin, @provider], :show?
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize [:admin, @provider], :update?
|
||||
end
|
||||
|
||||
def update
|
||||
authorize [:admin, @provider], :update?
|
||||
|
||||
if @provider.update(provider_params)
|
||||
redirect_to admin_fasp_providers_path
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize [:admin, @provider], :destroy?
|
||||
|
||||
@provider.destroy
|
||||
|
||||
redirect_to admin_fasp_providers_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def provider_params
|
||||
params.expect(fasp_provider: [capabilities_attributes: {}])
|
||||
end
|
||||
|
||||
def set_provider
|
||||
@provider = Fasp::Provider.find(params[:id])
|
||||
end
|
||||
end
|
23
app/controllers/admin/fasp/registrations_controller.rb
Normal file
23
app/controllers/admin/fasp/registrations_controller.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Fasp::RegistrationsController < Admin::BaseController
|
||||
before_action :set_provider
|
||||
|
||||
def new
|
||||
authorize [:admin, @provider], :create?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize [:admin, @provider], :create?
|
||||
|
||||
@provider.update_info!(confirm: true)
|
||||
|
||||
redirect_to edit_admin_fasp_provider_path(@provider)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_provider
|
||||
@provider = Fasp::Provider.find(params[:provider_id])
|
||||
end
|
||||
end
|
81
app/controllers/api/fasp/base_controller.rb
Normal file
81
app/controllers/api/fasp/base_controller.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::BaseController < ApplicationController
|
||||
class Error < ::StandardError; end
|
||||
|
||||
DIGEST_PATTERN = /sha-256=:(.*?):/
|
||||
KEYID_PATTERN = /keyid="(.*?)"/
|
||||
|
||||
attr_reader :current_provider
|
||||
|
||||
skip_forgery_protection
|
||||
|
||||
before_action :check_fasp_enabled
|
||||
before_action :require_authentication
|
||||
after_action :sign_response
|
||||
|
||||
private
|
||||
|
||||
def require_authentication
|
||||
validate_content_digest!
|
||||
validate_signature!
|
||||
rescue Error, Linzer::Error, ActiveRecord::RecordNotFound => e
|
||||
logger.debug("FASP Authentication error: #{e}")
|
||||
authentication_error
|
||||
end
|
||||
|
||||
def authentication_error
|
||||
respond_to do |format|
|
||||
format.json { head 401 }
|
||||
end
|
||||
end
|
||||
|
||||
def validate_content_digest!
|
||||
content_digest_header = request.headers['content-digest']
|
||||
raise Error, 'content-digest missing' if content_digest_header.blank?
|
||||
|
||||
digest_received = content_digest_header.match(DIGEST_PATTERN)[1]
|
||||
|
||||
digest_computed = OpenSSL::Digest.base64digest('sha256', request.body&.string || '')
|
||||
|
||||
raise Error, 'content-digest does not match' if digest_received != digest_computed
|
||||
end
|
||||
|
||||
def validate_signature!
|
||||
signature_input = request.headers['signature-input']&.encode('UTF-8')
|
||||
raise Error, 'signature-input is missing' if signature_input.blank?
|
||||
|
||||
keyid = signature_input.match(KEYID_PATTERN)[1]
|
||||
provider = Fasp::Provider.find(keyid)
|
||||
linzer_request = Linzer.new_request(
|
||||
request.method,
|
||||
request.original_url,
|
||||
{},
|
||||
{
|
||||
'content-digest' => request.headers['content-digest'],
|
||||
'signature-input' => signature_input,
|
||||
'signature' => request.headers['signature'],
|
||||
}
|
||||
)
|
||||
message = Linzer::Message.new(linzer_request)
|
||||
key = Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
|
||||
signature = Linzer::Signature.build(message.headers)
|
||||
Linzer.verify(key, message, signature)
|
||||
@current_provider = provider
|
||||
end
|
||||
|
||||
def sign_response
|
||||
response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
|
||||
|
||||
linzer_response = Linzer.new_response(response.body, response.status, { 'content-digest' => response.headers['content-digest'] })
|
||||
message = Linzer::Message.new(linzer_response)
|
||||
key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
|
||||
signature = Linzer.sign(key, message, %w(@status content-digest))
|
||||
|
||||
response.headers.merge!(signature.to_h)
|
||||
end
|
||||
|
||||
def check_fasp_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.fasp_enabled?
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::Debug::V0::Callback::ResponsesController < Api::Fasp::BaseController
|
||||
def create
|
||||
Fasp::DebugCallback.create(
|
||||
fasp_provider: current_provider,
|
||||
ip: request.remote_ip,
|
||||
request_body: request.raw_post
|
||||
)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head 201 }
|
||||
end
|
||||
end
|
||||
end
|
26
app/controllers/api/fasp/registrations_controller.rb
Normal file
26
app/controllers/api/fasp/registrations_controller.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::Fasp::RegistrationsController < Api::Fasp::BaseController
|
||||
skip_before_action :require_authentication
|
||||
|
||||
def create
|
||||
@current_provider = Fasp::Provider.create!(
|
||||
name: params[:name],
|
||||
base_url: params[:baseUrl],
|
||||
remote_identifier: params[:serverId],
|
||||
provider_public_key_base64: params[:publicKey]
|
||||
)
|
||||
|
||||
render json: registration_confirmation
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def registration_confirmation
|
||||
{
|
||||
faspId: current_provider.id.to_s,
|
||||
publicKey: current_provider.server_public_key_base64,
|
||||
registrationCompletionUri: new_admin_fasp_provider_registration_url(current_provider),
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Z"/></svg>
|
After Width: | Height: | Size: 396 B |
1
app/javascript/material-icons/400-24px/extension.svg
Normal file
1
app/javascript/material-icons/400-24px/extension.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M352-120H200q-33 0-56.5-23.5T120-200v-152q48 0 84-30.5t36-77.5q0-47-36-77.5T120-568v-152q0-33 23.5-56.5T200-800h160q0-42 29-71t71-29q42 0 71 29t29 71h160q33 0 56.5 23.5T800-720v160q42 0 71 29t29 71q0 42-29 71t-71 29v160q0 33-23.5 56.5T720-120H568q0-50-31.5-85T460-240q-45 0-76.5 35T352-120Zm-152-80h85q24-66 77-93t98-27q45 0 98 27t77 93h85v-240h80q8 0 14-6t6-14q0-8-6-14t-14-6h-80v-240H480v-80q0-8-6-14t-14-6q-8 0-14 6t-6 14v80H200v88q54 20 87 67t33 105q0 57-33 104t-87 68v88Zm260-260Z"/></svg>
|
After Width: | Height: | Size: 591 B |
76
app/lib/fasp/request.rb
Normal file
76
app/lib/fasp/request.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::Request
|
||||
def initialize(provider)
|
||||
@provider = provider
|
||||
end
|
||||
|
||||
def get(path)
|
||||
perform_request(:get, path)
|
||||
end
|
||||
|
||||
def post(path, body: nil)
|
||||
perform_request(:post, path, body:)
|
||||
end
|
||||
|
||||
def delete(path, body: nil)
|
||||
perform_request(:delete, path, body:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def perform_request(verb, path, body: nil)
|
||||
url = @provider.url(path)
|
||||
body = body.present? ? body.to_json : ''
|
||||
headers = request_headers(verb, url, body)
|
||||
response = HTTP.headers(headers).send(verb, url, body:)
|
||||
validate!(response)
|
||||
|
||||
response.parse if response.body.present?
|
||||
end
|
||||
|
||||
def request_headers(verb, url, body = '')
|
||||
result = {
|
||||
'accept' => 'application/json',
|
||||
'content-digest' => content_digest(body),
|
||||
}
|
||||
result.merge(signature_headers(verb, url, result))
|
||||
end
|
||||
|
||||
def content_digest(body)
|
||||
"sha-256=:#{OpenSSL::Digest.base64digest('sha256', body || '')}:"
|
||||
end
|
||||
|
||||
def signature_headers(verb, url, headers)
|
||||
linzer_request = Linzer.new_request(verb, url, {}, headers)
|
||||
message = Linzer::Message.new(linzer_request)
|
||||
key = Linzer.new_ed25519_key(@provider.server_private_key_pem, @provider.remote_identifier)
|
||||
signature = Linzer.sign(key, message, %w(@method @target-uri content-digest))
|
||||
Linzer::Signer.send(:populate_parameters, key, {})
|
||||
|
||||
signature.to_h
|
||||
end
|
||||
|
||||
def validate!(response)
|
||||
content_digest_header = response.headers['content-digest']
|
||||
raise SignatureVerification::SignatureVerificationError, 'content-digest missing' if content_digest_header.blank?
|
||||
raise SignatureVerification::SignatureVerificationError, 'content-digest does not match' if content_digest_header != content_digest(response.body)
|
||||
|
||||
signature_input = response.headers['signature-input']&.encode('UTF-8')
|
||||
raise SignatureVerification::SignatureVerificationError, 'signature-input is missing' if signature_input.blank?
|
||||
|
||||
linzer_response = Linzer.new_response(
|
||||
response.body,
|
||||
response.status,
|
||||
{
|
||||
'content-digest' => content_digest_header,
|
||||
'signature-input' => signature_input,
|
||||
'signature' => response.headers['signature'],
|
||||
}
|
||||
)
|
||||
message = Linzer::Message.new(linzer_response)
|
||||
key = Linzer.new_ed25519_public_key(@provider.provider_public_key_pem)
|
||||
signature = Linzer::Signature.build(message.headers)
|
||||
Linzer.verify(key, message, signature)
|
||||
end
|
||||
end
|
10
app/models/concerns/fasp/provider/debug_concern.rb
Normal file
10
app/models/concerns/fasp/provider/debug_concern.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Fasp::Provider::DebugConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def perform_debug_call
|
||||
Fasp::Request.new(self)
|
||||
.post('/debug/v0/callback/logs', body: { hello: 'world' })
|
||||
end
|
||||
end
|
7
app/models/fasp.rb
Normal file
7
app/models/fasp.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Fasp
|
||||
def self.table_name_prefix
|
||||
'fasp_'
|
||||
end
|
||||
end
|
10
app/models/fasp/capability.rb
Normal file
10
app/models/fasp/capability.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Fasp::Capability
|
||||
include ActiveModel::Model
|
||||
include ActiveModel::Attributes
|
||||
|
||||
attribute :id, :string
|
||||
attribute :version, :string
|
||||
attribute :enabled, :boolean, default: false
|
||||
end
|
16
app/models/fasp/debug_callback.rb
Normal file
16
app/models/fasp/debug_callback.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: fasp_debug_callbacks
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# ip :string not null
|
||||
# request_body :text not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# fasp_provider_id :bigint(8) not null
|
||||
#
|
||||
class Fasp::DebugCallback < ApplicationRecord
|
||||
belongs_to :fasp_provider, class_name: 'Fasp::Provider'
|
||||
end
|
141
app/models/fasp/provider.rb
Normal file
141
app/models/fasp/provider.rb
Normal file
|
@ -0,0 +1,141 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: fasp_providers
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# base_url :string not null
|
||||
# capabilities :jsonb not null
|
||||
# confirmed :boolean default(FALSE), not null
|
||||
# contact_email :string
|
||||
# fediverse_account :string
|
||||
# name :string not null
|
||||
# privacy_policy :jsonb
|
||||
# provider_public_key_pem :string not null
|
||||
# remote_identifier :string not null
|
||||
# server_private_key_pem :string not null
|
||||
# sign_in_url :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class Fasp::Provider < ApplicationRecord
|
||||
include DebugConcern
|
||||
|
||||
has_many :fasp_debug_callbacks, inverse_of: :fasp_provider, class_name: 'Fasp::DebugCallback', dependent: :delete_all
|
||||
|
||||
validates :name, presence: true
|
||||
validates :base_url, presence: true, url: true
|
||||
validates :provider_public_key_pem, presence: true
|
||||
validates :remote_identifier, presence: true
|
||||
|
||||
before_create :create_keypair
|
||||
after_commit :update_remote_capabilities
|
||||
|
||||
def capabilities
|
||||
read_attribute(:capabilities).map do |attributes|
|
||||
Fasp::Capability.new(attributes)
|
||||
end
|
||||
end
|
||||
|
||||
def capabilities_attributes=(attributes)
|
||||
capability_objects = attributes.values.map { |a| Fasp::Capability.new(a) }
|
||||
self[:capabilities] = capability_objects.map(&:attributes)
|
||||
end
|
||||
|
||||
def enabled_capabilities
|
||||
capabilities.select(&:enabled).map(&:id)
|
||||
end
|
||||
|
||||
def capability?(capability_name)
|
||||
return false unless confirmed?
|
||||
|
||||
capabilities.present? && capabilities.any? do |capability|
|
||||
capability.id == capability_name
|
||||
end
|
||||
end
|
||||
|
||||
def capability_enabled?(capability_name)
|
||||
return false unless confirmed?
|
||||
|
||||
capabilities.present? && capabilities.any? do |capability|
|
||||
capability.id == capability_name && capability.enabled
|
||||
end
|
||||
end
|
||||
|
||||
def server_private_key
|
||||
@server_private_key ||= OpenSSL::PKey.read(server_private_key_pem)
|
||||
end
|
||||
|
||||
def server_public_key_base64
|
||||
Base64.strict_encode64(server_private_key.raw_public_key)
|
||||
end
|
||||
|
||||
def provider_public_key_base64=(string)
|
||||
return if string.blank?
|
||||
|
||||
self.provider_public_key_pem =
|
||||
OpenSSL::PKey.new_raw_public_key(
|
||||
'ed25519',
|
||||
Base64.strict_decode64(string)
|
||||
).public_to_pem
|
||||
end
|
||||
|
||||
def provider_public_key
|
||||
@provider_public_key ||= OpenSSL::PKey.read(provider_public_key_pem)
|
||||
end
|
||||
|
||||
def provider_public_key_raw
|
||||
provider_public_key.raw_public_key
|
||||
end
|
||||
|
||||
def provider_public_key_fingerprint
|
||||
OpenSSL::Digest.base64digest('sha256', provider_public_key_raw)
|
||||
end
|
||||
|
||||
def url(path)
|
||||
base = base_url
|
||||
base = base.chomp('/') if path.start_with?('/')
|
||||
"#{base}#{path}"
|
||||
end
|
||||
|
||||
def update_info!(confirm: false)
|
||||
self.confirmed = true if confirm
|
||||
provider_info = Fasp::Request.new(self).get('/provider_info')
|
||||
assign_attributes(
|
||||
privacy_policy: provider_info['privacyPolicy'],
|
||||
capabilities: provider_info['capabilities'],
|
||||
sign_in_url: provider_info['signInUrl'],
|
||||
contact_email: provider_info['contactEmail'],
|
||||
fediverse_account: provider_info['fediverseAccount']
|
||||
)
|
||||
save!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_keypair
|
||||
self.server_private_key_pem ||=
|
||||
OpenSSL::PKey.generate_key('ed25519').private_to_pem
|
||||
end
|
||||
|
||||
def update_remote_capabilities
|
||||
return unless saved_change_to_attribute?(:capabilities)
|
||||
|
||||
old, current = saved_change_to_attribute(:capabilities)
|
||||
old ||= []
|
||||
current.each do |capability|
|
||||
update_remote_capability(capability) if capability.key?('enabled') && !old.include?(capability)
|
||||
end
|
||||
end
|
||||
|
||||
def update_remote_capability(capability)
|
||||
version, = capability['version'].split('.')
|
||||
path = "/capabilities/#{capability['id']}/#{version}/activation"
|
||||
if capability['enabled']
|
||||
Fasp::Request.new(self).post(path)
|
||||
else
|
||||
Fasp::Request.new(self).delete(path)
|
||||
end
|
||||
end
|
||||
end
|
23
app/policies/admin/fasp/provider_policy.rb
Normal file
23
app/policies/admin/fasp/provider_policy.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Fasp::ProviderPolicy < ApplicationPolicy
|
||||
def index?
|
||||
role.can?(:manage_federation)
|
||||
end
|
||||
|
||||
def show?
|
||||
role.can?(:manage_federation)
|
||||
end
|
||||
|
||||
def create?
|
||||
role.can?(:manage_federation)
|
||||
end
|
||||
|
||||
def update?
|
||||
role.can?(:manage_federation)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
role.can?(:manage_federation)
|
||||
end
|
||||
end
|
10
app/views/admin/fasp/debug/callbacks/_callback.html.haml
Normal file
10
app/views/admin/fasp/debug/callbacks/_callback.html.haml
Normal file
|
@ -0,0 +1,10 @@
|
|||
%tr
|
||||
%td= callback.fasp_provider.name
|
||||
%td= callback.fasp_provider.base_url
|
||||
%td= callback.ip
|
||||
%td
|
||||
%time.relative-formatted{ datetime: callback.created_at.iso8601 }
|
||||
%td
|
||||
%code= callback.request_body
|
||||
%td
|
||||
= table_link_to 'close', t('admin.fasp.debug.callbacks.delete'), admin_fasp_debug_callback_path(callback), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
22
app/views/admin/fasp/debug/callbacks/index.html.haml
Normal file
22
app/views/admin/fasp/debug/callbacks/index.html.haml
Normal file
|
@ -0,0 +1,22 @@
|
|||
- content_for :page_title do
|
||||
= t('admin.fasp.debug.callbacks.title')
|
||||
|
||||
- content_for :heading do
|
||||
%h2= t('admin.fasp.debug.callbacks.title')
|
||||
= render 'admin/fasp/shared/links'
|
||||
|
||||
- unless @callbacks.empty?
|
||||
%hr.spacer
|
||||
|
||||
.table-wrapper
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th= t('admin.fasp.providers.name')
|
||||
%th= t('admin.fasp.providers.base_url')
|
||||
%th= t('admin.fasp.debug.callbacks.ip')
|
||||
%th= t('admin.fasp.debug.callbacks.created_at')
|
||||
%th= t('admin.fasp.debug.callbacks.request_body')
|
||||
%th
|
||||
%tbody
|
||||
= render partial: 'callback', collection: @callbacks
|
19
app/views/admin/fasp/providers/_provider.html.haml
Normal file
19
app/views/admin/fasp/providers/_provider.html.haml
Normal file
|
@ -0,0 +1,19 @@
|
|||
%tr
|
||||
%td= provider.name
|
||||
%td= provider.base_url
|
||||
%td
|
||||
- if provider.confirmed?
|
||||
= t('admin.fasp.providers.active')
|
||||
- else
|
||||
= t('admin.fasp.providers.registration_requested')
|
||||
%td
|
||||
- if provider.confirmed?
|
||||
= table_link_to 'edit', t('admin.fasp.providers.edit'), edit_admin_fasp_provider_path(provider)
|
||||
- else
|
||||
= table_link_to 'check', t('admin.fasp.providers.finish_registration'), new_admin_fasp_provider_registration_path(provider)
|
||||
- if provider.sign_in_url.present?
|
||||
= table_link_to 'open_in_new', t('admin.fasp.providers.sign_in'), provider.sign_in_url, target: '_blank'
|
||||
- if provider.capability_enabled?('callback')
|
||||
= table_link_to 'repeat', t('admin.fasp.providers.callback'), admin_fasp_provider_debug_calls_path(provider), data: { method: :post }
|
||||
|
||||
= table_link_to 'close', t('admin.fasp.providers.delete'), admin_fasp_provider_path(provider), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
|
16
app/views/admin/fasp/providers/edit.html.haml
Normal file
16
app/views/admin/fasp/providers/edit.html.haml
Normal file
|
@ -0,0 +1,16 @@
|
|||
- content_for :page_title do
|
||||
= t('admin.fasp.providers.edit')
|
||||
|
||||
= simple_form_for [:admin, @provider] do |f|
|
||||
= render 'shared/error_messages', object: @provider
|
||||
|
||||
%h4= t('admin.fasp.providers.select_capabilities')
|
||||
|
||||
.fields_group
|
||||
= f.fields_for :capabilities do |cf|
|
||||
= cf.input :id, as: :hidden
|
||||
= cf.input :version, as: :hidden
|
||||
= cf.input :enabled, as: :boolean, label: cf.object.id, wrapper: :with_label
|
||||
|
||||
.actions
|
||||
= f.button :button, t('admin.fasp.providers.save'), type: :submit
|
20
app/views/admin/fasp/providers/index.html.haml
Normal file
20
app/views/admin/fasp/providers/index.html.haml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- content_for :page_title do
|
||||
= t('admin.fasp.providers.title')
|
||||
|
||||
- content_for :heading do
|
||||
%h2= t('admin.fasp.providers.title')
|
||||
= render 'admin/fasp/shared/links'
|
||||
|
||||
- unless @providers.empty?
|
||||
%hr.spacer
|
||||
|
||||
.table-wrapper
|
||||
%table.table#providers
|
||||
%thead
|
||||
%tr
|
||||
%th= t('admin.fasp.providers.name')
|
||||
%th= t('admin.fasp.providers.base_url')
|
||||
%th= t('admin.fasp.providers.status')
|
||||
%th
|
||||
%tbody
|
||||
= render partial: 'provider', collection: @providers
|
19
app/views/admin/fasp/registrations/new.html.haml
Normal file
19
app/views/admin/fasp/registrations/new.html.haml
Normal file
|
@ -0,0 +1,19 @@
|
|||
- content_for :page_title do
|
||||
= t('admin.fasp.providers.registrations.title')
|
||||
|
||||
%p= t('admin.fasp.providers.registrations.description')
|
||||
|
||||
%table.table.inline-table
|
||||
%tbody
|
||||
%tr
|
||||
%th= t('admin.fasp.providers.name')
|
||||
%td= @provider.name
|
||||
%tr
|
||||
%th= t('admin.fasp.providers.public_key_fingerprint')
|
||||
%td
|
||||
%code= @provider.provider_public_key_fingerprint
|
||||
|
||||
= form_with url: admin_fasp_provider_registration_path(@provider), class: :simple_form do |f|
|
||||
.actions
|
||||
= link_to t('admin.fasp.providers.registrations.reject'), admin_fasp_provider_path(@provider), data: { method: :delete }, class: 'btn negative'
|
||||
= f.button t('admin.fasp.providers.registrations.confirm'), type: :submit, class: 'btn'
|
5
app/views/admin/fasp/shared/_links.html.haml
Normal file
5
app/views/admin/fasp/shared/_links.html.haml
Normal file
|
@ -0,0 +1,5 @@
|
|||
.content__heading__tabs
|
||||
= render_navigation renderer: :links do |primary|
|
||||
:ruby
|
||||
primary.item :providers, safe_join([material_symbol('database'), t('admin.fasp.providers.providers')]), admin_fasp_providers_path
|
||||
primary.item :debug_callbacks, safe_join([material_symbol('repeat'), t('admin.fasp.debug.callbacks.title')]), admin_fasp_debug_callbacks_path
|
Loading…
Add table
Add a link
Reference in a new issue