atomic_admin 2.0.0.beta.5 → 2.0.0.beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45e769d018db1fe843c0d6386ec5007465077b44e4731f27d4afa1efca6ff94e
4
- data.tar.gz: 298af2cf1c71df14fb6bb3c51dd884157d068182d066ffb2cd1674afbfff616d
3
+ metadata.gz: 8c6745a63396d5082de09298e3279be5f7cd6f7e00423c9fa3c78c2f7f022a6e
4
+ data.tar.gz: 0417afcd5e4b470c1d99c7d734b3c3634a5181d7de4132c245e62601975706d5
5
5
  SHA512:
6
- metadata.gz: 37e0a008ee8794c07a0a9f414acf838501ffbf70292812aee7471d682972104ccb0a54ec3bbb6a7ebcb1540dd54e4c0b64e7f2faa10e45ac924dc7170b8f6f46
7
- data.tar.gz: f121526e3fa12837bc090f7cd9bb8747def98fccfc88ff8e69ba0f6323391f68da0bafca426391abd93949bda41c6a6b7619b1e9ea853a75050099475327e118
6
+ metadata.gz: 4eba8d0fab5b8f740840ba325bc82cfc4c2150cfd3bb95de92adb79ce214158b9f912d0a07117b3614b4f32ab8abb7c41c5d31635a30cb4b992ba7169a7872ce
7
+ data.tar.gz: 07dae532165b817a0ca1cdecc625d2ab0c8ddb0a964ed296f0b0cd9f66bbb33b5e41bcf368522f19176aa62be0073c569837a772b3fb4d6fa9eed0cdc6061341
@@ -0,0 +1,4 @@
1
+
2
+ module AtomicAdmin::Api::Admin::V1
3
+ UsersController = AtomicAdmin::V1::UsersController
4
+ end
@@ -1,6 +1,7 @@
1
1
  module AtomicAdmin::V1
2
2
  class AdminController < ActionController::API
3
3
  include RequireJwtToken
4
+
4
5
  before_action :validate_admin_token
5
6
 
6
7
  rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class ApplicationInstancesController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_sort_columns %w[nickname]
6
6
  allowed_search_columns %w[nickname]
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class ApplicationsController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_sort_columns %w[name]
6
6
  allowed_search_columns %w[name]
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class LtiPlatformsController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_search_columns %w[iss]
6
6
  allowed_sort_columns %w[iss]
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class SitesController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_search_columns %w[url]
6
6
  allowed_sort_columns %w[url]
@@ -1,10 +1,12 @@
1
1
  module AtomicAdmin::V1
2
2
  class TenantClientIdStrategiesController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_search_columns %w[client_id, iss]
6
6
  allowed_sort_columns %w[client_id, iss]
7
7
 
8
+ before_action :check_restrictions, only: %i[create]
9
+
8
10
  def index
9
11
  query = AtomicTenant::PinnedClientId.where(application_instance_id:)
10
12
  page, meta = filter(query)
@@ -44,5 +46,32 @@ module AtomicAdmin::V1
44
46
  def find_pinned_client_id
45
47
  AtomicTenant::PinnedClientId.find_by(id: params[:id])
46
48
  end
49
+
50
+ def check_restrictions
51
+ return if interaction.nil?
52
+
53
+ if interaction.data[:restricted_client_id_prefixes]
54
+ prefixes = interaction.data[:restricted_client_id_prefixes]
55
+
56
+ prefixes.each do |prefix|
57
+ if create_params[:iss] == prefix[:iss] && create_params[:client_id].start_with?(prefix[:prefix])
58
+ render json: { error: prefix[:reason] }, status: :forbidden
59
+ return
60
+ end
61
+ end
62
+ end
63
+
64
+ if interaction.data[:restricted_client_id_issuers]
65
+ issuers = interaction.data[:restricted_client_id_issuers]
66
+ if issuers.any? { |issuer| create_params[:iss] == issuer }
67
+ render json: { error: "This ISS is blocked from client_id pinning. Pin by deployment instead." }, status: :forbidden
68
+ return
69
+ end
70
+ end
71
+ end
72
+
73
+ def interaction
74
+ @interaction ||= AtomicAdmin.application_instance_interactions.for_type(:lti_advantage).first
75
+ end
47
76
  end
48
77
  end
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class TenantDeploymentsController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_search_columns %w[deployment_id, iss]
6
6
  allowed_sort_columns %w[deployment_id, iss]
@@ -1,6 +1,6 @@
1
1
  module AtomicAdmin::V1
2
2
  class TenantPlatformGuidStrategiesController < AdminController
3
- include Filtering
3
+ include AtomicAdmin::Filtering
4
4
 
5
5
  allowed_search_columns %w[platform_guid, iss]
6
6
  allowed_sort_columns %w[platform_guid, iss]
@@ -0,0 +1,59 @@
1
+ module AtomicAdmin::V1
2
+ class UsersController < AdminController
3
+ include AtomicAdmin::Filtering
4
+
5
+ around_action :switch_tenants
6
+
7
+ allowed_sort_columns %w[name lti_user_id lms_user_id create_method]
8
+ allowed_search_columns %w[name lti_user_id lms_user_id]
9
+
10
+ def index
11
+ query = User.includes(:roles).where.not(lti_user_id: nil)
12
+
13
+ users, meta = filter(query)
14
+
15
+ render json: { users: json_for(users), meta: meta }
16
+ end
17
+
18
+ def show
19
+ @user = User.find(params[:id])
20
+ render json: { user: json_for(@user) }
21
+ end
22
+
23
+ def update
24
+ @user = User.find(params[:id])
25
+
26
+ do_update(@user, params)
27
+
28
+ render json: { user: json_for(@user) }
29
+ end
30
+
31
+ def bulk_update
32
+ params[:users].each do |user_params|
33
+ user = User.find(user_params[:id])
34
+ do_update(user, user_params)
35
+ end
36
+
37
+ render json: { success: true }
38
+ end
39
+
40
+ def do_update(user, params)
41
+ raise "Not implemented. This method should be implemented in a subclass of UsersController."
42
+ end
43
+
44
+ def json_for(relation)
45
+ relation.as_json(
46
+ include: :roles,
47
+ only: [:id, :name, :lti_user_id, :lms_user_id]
48
+ )
49
+ end
50
+
51
+ def switch_tenants(&)
52
+ application_instance = ApplicationInstance.find(
53
+ params[:application_instance_id]
54
+ )
55
+
56
+ Apartment::Tenant.switch(application_instance.tenant, &)
57
+ end
58
+ end
59
+ end
data/config/routes.rb CHANGED
@@ -36,6 +36,20 @@ AtomicAdmin::Engine.routes.draw do
36
36
  resources :tenant_platform_guid_strategies
37
37
  resources :tenant_deployments
38
38
  resources :stats
39
+ resources :users do
40
+ collection do
41
+ post :bulk_update
42
+ end
43
+ end
44
+
45
+ AtomicAdmin.application_instance_interactions.for_type(:resource).each do |interaction|
46
+ controller_name = interaction.key.to_s.pluralize
47
+ resources controller_name do
48
+ collection do
49
+ get :interactions
50
+ end
51
+ end
52
+ end
39
53
  end
40
54
  end
41
55
  end
@@ -1,4 +1,4 @@
1
- module Filtering
1
+ module AtomicAdmin::Filtering
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
@@ -27,7 +27,8 @@ module Filtering
27
27
  allowed_search_columns = self.class.class_variable_get(:@@allowed_search_columns)
28
28
 
29
29
  if params[:search].present? && params[:search_on].present? && allowed_search_columns.include?(params[:search_on])
30
- relation = relation.where("lower(#{params[:search_on]}) LIKE ?", "%#{params[:search].downcase}%")
30
+ table_name = relation.table_name
31
+ relation = relation.where("lower(#{table_name}.#{params[:search_on]}) LIKE ?", "%#{params[:search].downcase}%")
31
32
  end
32
33
 
33
34
  allowed_sort_columns = self.class.class_variable_get(:@@allowed_sort_columns)
@@ -0,0 +1,13 @@
1
+ module AtomicAdmin::Interaction
2
+ class Analytics < AtomicAdmin::Interaction::Base
3
+
4
+ def initialize(controller:, **kwargs)
5
+ super(**kwargs)
6
+ @controller = controller
7
+
8
+ Rails.application.config.to_prepare do
9
+ AtomicAdmin::Api::Admin::V1.const_set(:StatsController, controller.constantize)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ module AtomicAdmin::Interaction
2
+ class Base
3
+ attr_accessor :key, :type, :key, :title, :icon, :order, :data
4
+
5
+ def initialize(key:, type:, title: nil, icon: nil, order: 0, **kwargs)
6
+ @key = key
7
+ @type = type
8
+ @title = title
9
+ @icon = icon
10
+ @order = order
11
+ @data = kwargs
12
+ end
13
+
14
+ def resolve(**kwargs)
15
+ {
16
+ key: key,
17
+ type: type,
18
+ title: title,
19
+ icon: icon,
20
+ }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module AtomicAdmin::Interaction
2
+ class JsonForm < AtomicAdmin::Interaction::Base
3
+
4
+ def initialize(schema:, **kwargs)
5
+ super(**kwargs)
6
+ @schema_factory = schema
7
+ end
8
+
9
+ def resolve(**kwargs)
10
+ hash = super(**kwargs)
11
+
12
+ schema = @schema_factory.new(**kwargs)
13
+ hash[:schema] = schema.schema
14
+ hash[:uischema] = schema.uischema
15
+
16
+ hash
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module AtomicAdmin::Interaction
2
+ class Launch < AtomicAdmin::Interaction::Base
3
+
4
+ def initialize(launch:, aud:, **kwargs)
5
+ super(**kwargs)
6
+ @launch = launch
7
+ @aud = aud
8
+ end
9
+
10
+ def resolve(**kwargs)
11
+ hash = super(**kwargs)
12
+
13
+ hash[:launch_url] = @launch.call(**kwargs)
14
+ hash[:aud] = @aud
15
+
16
+ hash
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,58 @@
1
+ module AtomicAdmin::Interaction
2
+ class Manager
3
+ include Enumerable
4
+
5
+ INTERACTIONS = {
6
+ analytics: AtomicAdmin::Interaction::Analytics,
7
+ jsonform: AtomicAdmin::Interaction::JsonForm,
8
+ resource: AtomicAdmin::Interaction::Resource,
9
+ launch: AtomicAdmin::Interaction::Launch,
10
+ }.freeze
11
+
12
+ def initialize
13
+ @interactions = {}
14
+ @curr_index = 0
15
+ end
16
+
17
+ def each(&block)
18
+ @interactions.each do |key, interaction|
19
+ block.call(key, interaction)
20
+ end
21
+ end
22
+
23
+ def add(key, type:, **kwargs)
24
+ interaction_cls = INTERACTIONS[type] || AtomicAdmin::Interaction::Base
25
+
26
+ interaction = interaction_cls.new(key:, type:, order: @curr_index, **kwargs)
27
+ @interactions[key] = interaction
28
+ @curr_index += 1
29
+
30
+ nil
31
+ end
32
+
33
+ def get(key)
34
+ @interactions[key]
35
+ end
36
+
37
+ def [](key)
38
+ @interactions[key]
39
+ end
40
+
41
+ def tap
42
+ yield self
43
+ self
44
+ end
45
+
46
+ def for_type(type)
47
+ @interactions.values.select { |interaction| interaction.type == type }
48
+ end
49
+
50
+
51
+ def resolve(**kwargs)
52
+ sorted = @interactions.sort_by { |key, interaction| interaction.order }
53
+ sorted.map do |key, interaction|
54
+ interaction.resolve(**kwargs)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,45 @@
1
+ module AtomicAdmin::Interaction
2
+ class Resource < AtomicAdmin::Interaction::Base
3
+
4
+ def initialize(controller:, table:, actions: [], plural:, singular:, **kwargs)
5
+ super(**kwargs)
6
+ @controller = controller
7
+ @table = table
8
+ @actions = actions
9
+ @plural = plural
10
+ @singular = singular
11
+
12
+ Rails.application.config.to_prepare do
13
+ controller_name = controller.demodulize
14
+ AtomicAdmin::Api::Admin::V1.const_set(controller_name, controller.constantize)
15
+ end
16
+ end
17
+
18
+ def resolve(**kwargs)
19
+ hash = super(**kwargs)
20
+
21
+ hash[:plural] = @plural
22
+ hash[:singular] = @singular
23
+ hash[:table] = @table
24
+
25
+ hash[:actions] = @actions.map do |action|
26
+ action_hash = {
27
+ type: action[:type],
28
+ label: action[:label],
29
+ }
30
+
31
+ if action[:schema]
32
+ schema_factory = action[:schema]
33
+ schema = schema_factory.new(**kwargs)
34
+
35
+ action_hash[:schema] = schema.schema
36
+ action_hash[:uischema] = schema.uischema
37
+ end
38
+
39
+ action_hash
40
+ end
41
+
42
+ hash
43
+ end
44
+ end
45
+ end
@@ -1,58 +1,9 @@
1
- module AtomicAdmin::Interaction
2
- class Manager
3
- def initialize
4
- @interactions = {}
5
- @curr_index = 0
6
- end
7
-
8
- def add(key, **kwargs)
9
- @interactions[key] = {
10
- **kwargs,
11
- order: @curr_index,
12
- }
13
-
14
- if @interactions[key][:type] == :analytics && @interactions[key][:controller].present?
15
- controller_class = @interactions[key][:controller]
16
- Rails.application.config.to_prepare do
17
- AtomicAdmin::Api::Admin::V1.const_set(:StatsController, controller_class.constantize)
18
- end
19
- end
20
- @curr_index += 1
21
- end
22
-
23
- def get(key)
24
- @interactions[key]
25
- end
1
+ require_relative 'interaction/base'
2
+ require_relative 'interaction/json_form'
3
+ require_relative 'interaction/analytics'
4
+ require_relative 'interaction/resource'
5
+ require_relative 'interaction/launch'
6
+ require_relative 'interaction/manager'
26
7
 
27
- def tap
28
- yield self
29
- self
30
- end
31
-
32
- def resolve(**kwargs)
33
- sorted = @interactions.sort_by { |key, interaction| interaction[:order] }
34
- sorted.map do |key, interaction|
35
- type = interaction[:type]
36
- hash = {
37
- key: key,
38
- type: type,
39
- title: interaction[:title],
40
- icon: interaction[:icon],
41
- }
42
-
43
- case type
44
- when :jsonform
45
- schema_factory = interaction[:schema]
46
- schema = schema_factory.new(**kwargs)
47
- hash[:schema] = schema.schema
48
- hash[:uischema] = schema.uischema
49
- when :launch
50
- hash[:launch_url] = interaction[:launch_url].call(**kwargs)
51
- hash[:aud] = interaction[:aud]
52
- end
53
-
54
- hash
55
- end
56
- end
57
- end
8
+ module AtomicAdmin::Interaction
58
9
  end
@@ -1,3 +1,3 @@
1
1
  module AtomicAdmin
2
- VERSION = "2.0.0.beta.5".freeze
2
+ VERSION = "2.0.0.beta.6".freeze
3
3
  end
data/lib/atomic_admin.rb CHANGED
@@ -3,6 +3,7 @@ require "atomic_admin/engine"
3
3
  require "atomic_admin/jwt_token"
4
4
  require "atomic_admin/schema"
5
5
  require "atomic_admin/interaction"
6
+ require "atomic_admin/filtering"
6
7
 
7
8
  module AtomicAdmin
8
9
  mattr_accessor :admin_jwks_url
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: atomic_admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta.5
4
+ version: 2.0.0.beta.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Benoit
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-31 00:00:00.000000000 Z
11
+ date: 2026-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -55,6 +55,7 @@ files:
55
55
  - app/controllers/atomic_admin/api/admin/v1/tenant_client_id_strategies_controller.rb
56
56
  - app/controllers/atomic_admin/api/admin/v1/tenant_deployments_controller.rb
57
57
  - app/controllers/atomic_admin/api/admin/v1/tenant_platform_guid_strategies_controller.rb
58
+ - app/controllers/atomic_admin/api/admin/v1/users_controller.rb
58
59
  - app/controllers/atomic_admin/v1/admin_controller.rb
59
60
  - app/controllers/atomic_admin/v1/application_instances_controller.rb
60
61
  - app/controllers/atomic_admin/v1/applications_controller.rb
@@ -64,7 +65,7 @@ files:
64
65
  - app/controllers/atomic_admin/v1/tenant_client_id_strategies_controller.rb
65
66
  - app/controllers/atomic_admin/v1/tenant_deployments_controller.rb
66
67
  - app/controllers/atomic_admin/v1/tenant_platform_guid_strategies_controller.rb
67
- - app/controllers/concerns/filtering.rb
68
+ - app/controllers/atomic_admin/v1/users_controller.rb
68
69
  - app/controllers/concerns/require_jwt_token.rb
69
70
  - app/jobs/atomic_admin/application_job.rb
70
71
  - app/mailers/atomic_admin/application_mailer.rb
@@ -72,7 +73,14 @@ files:
72
73
  - config/routes.rb
73
74
  - lib/atomic_admin.rb
74
75
  - lib/atomic_admin/engine.rb
76
+ - lib/atomic_admin/filtering.rb
75
77
  - lib/atomic_admin/interaction.rb
78
+ - lib/atomic_admin/interaction/analytics.rb
79
+ - lib/atomic_admin/interaction/base.rb
80
+ - lib/atomic_admin/interaction/json_form.rb
81
+ - lib/atomic_admin/interaction/launch.rb
82
+ - lib/atomic_admin/interaction/manager.rb
83
+ - lib/atomic_admin/interaction/resource.rb
76
84
  - lib/atomic_admin/jwt_token.rb
77
85
  - lib/atomic_admin/jwt_token/jwks_decoder.rb
78
86
  - lib/atomic_admin/jwt_token/secret_decoder.rb
@@ -109,7 +117,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
117
  - !ruby/object:Gem::Version
110
118
  version: 1.3.1
111
119
  requirements: []
112
- rubygems_version: 3.3.7
120
+ rubygems_version: 3.4.10
113
121
  signing_key:
114
122
  specification_version: 4
115
123
  summary: Engine to provide apis that power the atomic jolt admin app