Skip to content
This repository was archived by the owner on Apr 8, 2026. It is now read-only.
Draft
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
11 changes: 8 additions & 3 deletions app/interactors/datapass_webhook/create_or_prolong_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ def call
return if %w[approve validate].exclude?(context.event)
return if context.modalities.exclude?('params')

affect_scopes_to_authorization_request
token = create_or_prolong_token

if token.persisted?
affect_scopes(token)
copy_scopes_to_token(token)
context.token_id = token.id
else
context.fail!(message: 'Fail to create token')
Expand Down Expand Up @@ -44,8 +45,12 @@ def create_token
)
end

def affect_scopes(token)
token.update!(scopes:)
def affect_scopes_to_authorization_request
authorization_request.update!(scopes:)
end

def copy_scopes_to_token(token)
token.update!(scopes: authorization_request.scopes)
end

def token_already_exists?
Expand Down
4 changes: 2 additions & 2 deletions app/interactors/datapass_webhook/extract_mailjet_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ def add_token_scopes
end

def token_present?
authorization_request.token.present?
authorization_request.scopes.present?
end

def token_roles
@token_roles ||= authorization_request.token.scopes
@token_roles ||= authorization_request.scopes
end

def events_from_instructor
Expand Down
33 changes: 33 additions & 0 deletions app/lib/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def perform
create_data_for_api_particulier
create_data_shared
create_audit_notifications
create_oauth2_test_data
end

def flushdb
Expand All @@ -31,6 +32,38 @@ def create_scopes(api)
YAML.load_file(Rails.root.join("config/data/scopes/#{api}.yml"))
end

def create_oauth2_test_data
ar = create_authorization_request(
external_id: '9001',
intitule: 'Test OAuth2 Authorization Request',
status: 'validated',
api: 'entreprise',
siret: '13002526500013',
scopes: %w[unites_legales_etablissements_insee associations_djepva],
validated_at: Time.current,
first_submitted_at: Time.current
)

Token.create!(
authorization_request: ar,
iat: Time.current.to_i,
exp: 18.months.from_now.to_i,
version: '1.0',
scopes: ar.scopes
)

editor = Editor.find_by!(name: 'UMAD Corp')
oauth_app = OAuthApplication.create!(
name: "OAuth - #{editor.name}",
owner: editor,
uid: 'oauth-test-client-id',
secret: 'oauth-test-client-secret'
)
editor.update!(oauth_application: oauth_app)

EditorDelegation.create!(editor:, authorization_request: ar)
end

private

def create_data_for_api_entreprise
Expand Down
2 changes: 1 addition & 1 deletion app/mailers/api_entreprise/authorization_request_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class APIEntreprise::AuthorizationRequestMailer < APIEntrepriseMailer
send('define_method', method) do |args|
@all_scopes = I18n.t('api_entreprise.tokens.token.scope')
@authorization_request = args[:authorization_request]
@authorization_request_scopes = @authorization_request.token.scopes.map(&:to_sym) if @authorization_request.token.present?
@authorization_request_scopes = @authorization_request.scopes.map(&:to_sym) if @authorization_request.scopes.present?
@authorization_request_datapass_url = datapass_authorization_request_url(@authorization_request)

@full_name_demandeur = @authorization_request.demandeur.full_name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class APIParticulier::AuthorizationRequestMailer < APIParticulierMailer
send('define_method', method) do |args|
@all_scopes = I18n.t('api_particulier.tokens.token.scope')
@authorization_request = args[:authorization_request]
@authorization_request_scopes = @authorization_request.token.scopes.map(&:to_sym) if @authorization_request.token.present?
@authorization_request_scopes = @authorization_request.scopes.map(&:to_sym) if @authorization_request.scopes.present?
@authorization_request_datapass_url = datapass_authorization_request_url(@authorization_request)

@full_name_demandeur = @authorization_request.demandeur.full_name
Expand Down
13 changes: 12 additions & 1 deletion app/models/authorization_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ class AuthorizationRequest < ApplicationRecord
optional: true,
dependent: nil

belongs_to :oauth_application, optional: true

has_many :editor_delegations, dependent: :destroy

has_many :user_authorization_request_roles, dependent: :destroy do
def for_user(user)
where(user:)
Expand Down Expand Up @@ -104,12 +108,19 @@ def archive!

def revoke!
token&.update!(blacklisted_at: Time.zone.now)

update!(status: 'revoked')
end

def prolong_token_expecting_updates?
token&.last_prolong_token_wizard.present? &&
token.last_prolong_token_wizard.requires_update?
end

def generate_oauth_credentials!
return oauth_application if oauth_application.present?

OAuthApplication.create!(name: "OAuth - #{intitule || external_id}", owner: self).tap { update!(oauth_application: it) }
end

def oauth_scopes = scopes
end
21 changes: 19 additions & 2 deletions app/models/editor.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
class Editor < ApplicationRecord
has_many :users,
dependent: :nullify
belongs_to :oauth_application, optional: true

has_many :users, dependent: :nullify
has_many :editor_delegations, dependent: :destroy
has_many :delegated_authorization_requests,
through: :editor_delegations,
source: :authorization_request

validates :name, presence: true

Expand All @@ -9,4 +14,16 @@ def authorization_requests(api:)
.where(api:)
.where(demarche: form_uids)
end

def generate_oauth_credentials!
return oauth_application if oauth_application.present?

oauth_app = OAuthApplication.create!(name: "OAuth - #{name}", owner: self)
update!(oauth_application: oauth_app)
oauth_app
end

def can_access_authorization_request?(authorization_request)
editor_delegations.active.exists?(authorization_request:)
end
end
19 changes: 19 additions & 0 deletions app/models/editor_delegation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class EditorDelegation < ApplicationRecord
belongs_to :editor
belongs_to :authorization_request

scope :active, -> { where(revoked_at: nil) }
scope :revoked, -> { where.not(revoked_at: nil) }

def revoke!
update!(revoked_at: Time.current)
end

def revoked?
revoked_at.present?
end

def active?
!revoked?
end
end
19 changes: 19 additions & 0 deletions app/models/oauth_application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class OAuthApplication < ApplicationRecord
belongs_to :owner, polymorphic: true, optional: true

has_many :editors, dependent: :nullify
has_many :authorization_requests, dependent: :nullify

validates :name, presence: true
validates :uid, presence: true, uniqueness: true
validates :secret, presence: true

before_validation :generate_credentials, on: :create

private

def generate_credentials
self.uid ||= SecureRandom.hex(32)
self.secret ||= SecureRandom.hex(64)
end
end
17 changes: 17 additions & 0 deletions db/migrate/20260122100000_create_oauth_applications.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class CreateOAuthApplications < ActiveRecord::Migration[8.1]
def change
create_table :oauth_applications, id: :uuid, default: -> { 'gen_random_uuid()' } do |t|
t.string :name, null: false
t.string :uid, null: false
t.string :secret, null: false
t.string :scopes, default: '', null: false
t.string :owner_type
t.uuid :owner_id

t.timestamps
end

add_index :oauth_applications, :uid, unique: true
add_index :oauth_applications, %i[owner_type owner_id]
end
end
16 changes: 16 additions & 0 deletions db/migrate/20260122100001_create_editor_delegations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class CreateEditorDelegations < ActiveRecord::Migration[8.1]
def change
create_table :editor_delegations, id: :uuid, default: -> { 'gen_random_uuid()' } do |t|
t.references :editor, null: false, foreign_key: true, type: :uuid
t.references :authorization_request, null: false, foreign_key: true, type: :uuid
t.datetime :revoked_at

t.timestamps
end

add_index :editor_delegations, %i[editor_id authorization_request_id],
unique: true,
where: 'revoked_at IS NULL',
name: 'index_active_editor_delegations_unique'
end
end
7 changes: 7 additions & 0 deletions db/migrate/20260122100002_add_oauth_application_to_editors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddOAuthApplicationToEditors < ActiveRecord::Migration[8.1]
disable_ddl_transaction!

def change
add_reference :editors, :oauth_application, type: :uuid, index: { algorithm: :concurrently }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class AddOAuthApplicationToAuthorizationRequests < ActiveRecord::Migration[8.1]
disable_ddl_transaction!

def change
add_reference :authorization_requests, :oauth_application, type: :uuid, index: { algorithm: :concurrently }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddScopesToAuthorizationRequests < ActiveRecord::Migration[8.1]
disable_ddl_transaction!

def change
add_column :authorization_requests, :scopes, :jsonb, default: [], null: false
add_index :authorization_requests, :scopes, using: :gin, algorithm: :concurrently
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class BackfillScopesOnAuthorizationRequests < ActiveRecord::Migration[8.1]
disable_ddl_transaction!

def up
AuthorizationRequest.find_each do |ar|
next if ar.token.blank?

ar.update!(scopes: ar.token.scopes)
end
end

def down
AuthorizationRequest.update_all(scopes: []) # rubocop:disable Rails/SkipsModelValidations
end
end
36 changes: 34 additions & 2 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading