active_record_api-rest 1.0.11 → 1.0.12

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: 4ab132ecc0b2f3ccd7c4c151674e9c2623450c674fb88aee69e4277f9510a7ba
4
- data.tar.gz: d2e16fa7cb20a80c1104f2f71f576a0791e585d52e8f227d54eb3d79472d0417
3
+ metadata.gz: 611e471608273e7647e08ba152cfec1e5e425000d836c7f74de9a89bd90310b2
4
+ data.tar.gz: e68c84e08ea9cd77936f211e414e74a2b9f55bdb388d1dec972408913fa7e051
5
5
  SHA512:
6
- metadata.gz: 6bbd71ac2d4987ac9c54a3eb840d71b536959c1f47aa1116124a4f805477e1c4f0300548ab28714ba1f14338156dcaf202662d18bf4cf62aa046b66f66db5798
7
- data.tar.gz: c22e0faaae9fea5191fdf34449368fe8533ca85de1fe7111bb6aa6b8c419ae5bb6782cfaa357bfd186fbf6bc163134154524249886b8bd0e65e192e03ab4dac0
6
+ metadata.gz: 15de71085aafd16d806be6b554352344420c88ed9eeb8bb1bea2d38905047b40bdf6cb16588c939a2e8831e0c7ab27ace372136872f7e387b1346e5e39315131
7
+ data.tar.gz: 053014c8a27878aeb7a71b1e0942c5a7d310ea572aac40a4284e65ea519e669d501201390cae087ad80878502da30970b754b6468e754c08969d06ece10228df
data/.gitlab-ci.yml CHANGED
@@ -45,6 +45,7 @@ rspec:
45
45
 
46
46
  codequality-verify:
47
47
  retry: 2
48
+ allow_failure: true
48
49
  image: ruby:2.5.1
49
50
  script:
50
51
  - ruby -r json -r yaml -e "puts JSON.parse(File.open('gl-code-quality-report.json', 'r', &:read)).to_yaml"
@@ -0,0 +1,15 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ module Auth
4
+ class AccessDeniedException < StandardError
5
+ attr_reader :action
6
+ attr_reader :controller
7
+ def initialize(controller, action, message)
8
+ super(message)
9
+ @action = action
10
+ @controller = controller
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ module Auth
4
+ class BadSessionException < StandardError
5
+ attr_reader :action
6
+ attr_reader :controller
7
+ def initialize(controller, action)
8
+ super("No user for session on #{action} #{controller}")
9
+ @action = action
10
+ @controller = controller
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ module Auth
4
+ class Controller < ActiveRecordApi::Rest::Controller
5
+ before_action :authorize
6
+
7
+ protected
8
+
9
+ def initialize_model
10
+ @model = authorized_models.update_attributes(modifiable_params)
11
+ end
12
+
13
+ def authorize
14
+ raise BadSessionException.new(controller_name, action_name) if session.nil?
15
+ raise AccessDeniedException.new(controller_name, action_name, 'Insufficient permissions') unless can?
16
+ end
17
+
18
+ def can?
19
+ @can ||= policy.can?
20
+ end
21
+
22
+ def authorized_models
23
+ @authorized_models ||= scope.authorized_models
24
+ end
25
+
26
+ def policy
27
+ @policy ||= policy_klass.new(session: session, model_klass: model_klass, action_name: action_name, params: params)
28
+ end
29
+
30
+ def policy_klass
31
+ "#{self.class.name.remove(/Controller$/)}Policy".safe_constantize || Policy
32
+ end
33
+
34
+ def scope
35
+ @scope ||= scope_klass.new(session: session, model_klass: model_klass, action_name: action_name)
36
+ end
37
+
38
+ def scope_klass
39
+ "#{self.class.name.remove(/Controller$/)}Scope".safe_constantize || Scope
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ module Auth
4
+ class Policy
5
+ include ActiveAttr::Model
6
+ attr_accessor :session, :model_klass, :action_name, :params
7
+
8
+ def can?
9
+ send("#{action_name}?")
10
+ end
11
+
12
+ def can_manage?
13
+ false
14
+ end
15
+
16
+ def can_read?
17
+ false
18
+ end
19
+
20
+ protected
21
+
22
+ def index?
23
+ can_manage? || can_read?
24
+ end
25
+
26
+ def show?
27
+ can_manage? || can_read?
28
+ end
29
+
30
+ def create?
31
+ can_manage?
32
+ end
33
+
34
+ def update?
35
+ can_manage?
36
+ end
37
+
38
+ def destroy?
39
+ can_manage?
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ module Auth
4
+ class Scope
5
+ include ActiveAttr::Model
6
+ attr_accessor :session, :model_klass, :action_name
7
+
8
+ def authorized_models
9
+ send(action_name)
10
+ end
11
+
12
+ protected
13
+
14
+ def index
15
+ model_klass.where('1=0')
16
+ end
17
+
18
+ def show
19
+ model_klass.where('1=0')
20
+ end
21
+
22
+ def create
23
+ model_klass.new
24
+ end
25
+
26
+ def update
27
+ model_klass.where('1=0')
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,14 +1,18 @@
1
- require_relative 'index_controller'
2
-
1
+ puts 'I was loaded'
3
2
  module ActiveRecordApi
4
3
  module Rest
5
- class Controller < IndexController
6
- attr_reader :model
7
-
4
+ class Controller < ApplicationController
5
+ include GracefulErrors
6
+ delegate :validate_params, :previous_id, :limit, :queryable_params, :modifiable_params, :not_allowed_params, to: :parameters
7
+ delegate :next_url, :redirect_url, to: :request_url_generator
8
8
  before_action :initialize_model, only: :create
9
- before_action :load_model, only: %i[show update destroy]
10
- before_action :only_valid_params, only: %i[create update]
11
- before_action :authorize
9
+ before_action :validate_params
10
+
11
+ def index
12
+ response.headers['x-total'] = models.count
13
+ response.headers['x-link-next'] = next_url
14
+ render json: models, each_serializer: serializer
15
+ end
12
16
 
13
17
  def show
14
18
  return unless stale?(last_modified: model.updated_at)
@@ -24,7 +28,7 @@ module ActiveRecordApi
24
28
  end
25
29
 
26
30
  def update
27
- if model.update(filtered_params)
31
+ if model.update(modifiable_params)
28
32
  redirect_to_model
29
33
  else
30
34
  render json: model.errors, status: :unprocessable_entity
@@ -38,69 +42,67 @@ module ActiveRecordApi
38
42
 
39
43
  protected
40
44
 
41
- def redirect_to_model
42
- if request.path.include?(model.id)
43
- redirect_to "#{protocol}#{request.host_with_port}#{request.path}", status: :see_other
44
- else
45
- redirect_to "#{protocol}#{request.host_with_port}#{request.path}/#{model.id}", status: :see_other
46
- end
45
+ def validate_params
46
+ return unless not_allowed_params.present?
47
+ render json: { base: "Extra parameters are not allow: #{not_allowed_params.join(', ')}" }, status: :unprocessable_entity
47
48
  end
48
49
 
49
- def protocol
50
- return 'http://' if Rails.env.development?
51
- 'https://'
50
+ def redirect_to_model
51
+ redirect_to redirect_url(model), status: :see_other
52
52
  end
53
53
 
54
- def only_valid_params
55
- return unless not_allowed_params.present?
56
- render json: { base: "Extra parameters are not allow: #{not_allowed_params.join(', ')}" }, status: :unprocessable_entity
54
+ def model
55
+ @model ||= authorized_models.find_by(id: queryable_params[:id])
57
56
  end
58
57
 
59
- def not_allowed_params
60
- @not_allowed_params ||= ((params.keys.map(&:to_sym) - [:controller, :action, :id, :created_at, :updated_at, controller_name.singularize.to_sym]) - filtered_params.keys.map(&:to_sym))
58
+ def models
59
+ return @models unless @models.nil?
60
+ @models = authorized_models.where(queryable_params)
61
+ return @models unless pagination_param_value.present?
62
+ @models = @models&.where("\"#{pagination_param_name}\" > ?", pagination_param_value)
61
63
  end
62
64
 
63
- def load_model
64
- @model ||= scope_filter(model_klass).find(params[:id])
65
+ def initialize_model
66
+ @model = model_klass.new(modifiable_params)
65
67
  end
66
68
 
67
- def initialize_model
68
- @model = model_klass.new(filtered_params)
69
+ def authorized_models
70
+ model_klass.where('1 = 1')
69
71
  end
70
72
 
71
- def authorize
72
- raise BadSessionException.new(controller_name, action_name) if session.nil?
73
- raise AccessDeniedException.new(controller_name, action_name, 'Insufficient permissions') unless policy.send("#{action_name}?")
73
+ def model_klass
74
+ @model_klass ||= controller_name.classify.constantize
74
75
  end
75
76
 
76
- def scope_filter(scope)
77
- "#{policy.class.name}::Scope".constantize.new(session, scope).resolve
77
+ def pagination_param_name
78
+ @pagination_param_name ||= :id
78
79
  end
79
80
 
80
- def filtered_params
81
- @simple_params = params.permit!.to_h.select! { |key, _value| allowed_params.include?(key.to_sym) }
81
+ def pagination_param_value
82
+ @pagination_param_value = params[pagination_param_name]
82
83
  end
83
84
 
84
- def allowed_params
85
- @allowed_params ||= model_klass.column_names.flat_map do |column_name|
86
- col = column_name.to_s.gsub('encrypted_', '').to_sym
87
- col_def = column_definition(column_name)
88
- if col_def&.type == :jsonb
89
- [col, col => {}]
90
- else
91
- col
92
- end
93
- end - %i[created_at updated_at id]
85
+ def serializer
86
+ "#{model_klass.name}Serializer".safe_constantize
94
87
  end
95
88
 
96
- def column_definition(column_name)
97
- model_klass.columns.detect do |column|
98
- column.name == column_name
99
- end
89
+ def parameters
90
+ @parameters ||= Parameters.new(
91
+ model_klass: model_klass,
92
+ controller_name: controller_name,
93
+ pagination_param_name: pagination_param_name,
94
+ params: params,
95
+ action_name: action_name
96
+ )
100
97
  end
101
98
 
102
- def policy
103
- @policy ||= ("#{controller_name.classify}Policy".safe_constantize) ? "#{controller_name.classify}Policy".constantize.new(session, model_klass) : ApplicationPolicy.new(session, model_klass)
99
+ def request_url_generator
100
+ RequestUrlGenerator.new(
101
+ action_name: action_name,
102
+ request: request,
103
+ models: models,
104
+ pagination_param_name: pagination_param_name
105
+ )
104
106
  end
105
107
  end
106
108
  end
@@ -8,11 +8,11 @@ module ActiveRecordApi
8
8
  render status: :not_found, json: { base: exception.message }
9
9
  end
10
10
 
11
- rescue_from BadSessionException do |exception|
12
- render status: :unauthorized, json: { base: "No user for session on #{exception.action} #{exception.controller}" }
11
+ rescue_from ActiveRecordApi::Rest::Auth::BadSessionException do |exception|
12
+ render status: :unauthorized, json: { base: "No user for session on #{exception.action} #{exception.controller}", message: exception.message }
13
13
  end
14
14
 
15
- rescue_from AccessDeniedException do |exception|
15
+ rescue_from ActiveRecordApi::Rest::Auth::AccessDeniedException do |exception|
16
16
  render status: :forbidden, json: { base: "Access denied on #{exception.action} #{exception.controller}", message: exception.message }
17
17
  end
18
18
  end
@@ -0,0 +1,54 @@
1
+ module ActiveRecordApi
2
+ module Rest
3
+ class Parameters
4
+ include ActiveAttr::Model
5
+ attr_accessor :model_klass, :pagination_param_name, :params, :controller_name, :action_name
6
+
7
+ def modifiable_params
8
+ @modifiable = params.permit!.to_h.select! { |key, _value| clean(modifiable_names).include?(key.to_sym) }
9
+ end
10
+
11
+ def queryable_params
12
+ @queryable = params.permit!.to_h.select! { |key, _value| clean(queryable_names).include?(key.to_sym) }
13
+ end
14
+
15
+ def valid_params
16
+ queryable_names
17
+ end
18
+
19
+ def limit
20
+ @limit ||= params[:limit]&.to_i || 50
21
+ end
22
+
23
+ def not_allowed_params
24
+ @not_allowed_params ||= clean_request_names - valid_params
25
+ end
26
+
27
+ protected
28
+
29
+ def clean_request_names
30
+ params.keys.map(&:to_sym) - [:controller, :action, controller_name.to_sym, controller_name.singularize.to_sym]
31
+ end
32
+
33
+ def modifiable_names
34
+ @model_columns ||= clean(queryable_names - %i[created_at updated_at id])
35
+ end
36
+
37
+ def queryable_names
38
+ @model_columns ||= clean(model_klass.column_names.flatten)
39
+ end
40
+
41
+ def clean(attributes)
42
+ @allowed ||= attributes.map do |column_name|
43
+ column_name.to_s.gsub('encrypted_', '').to_sym
44
+ end
45
+ end
46
+
47
+ def column_definition(column_name)
48
+ model_klass.columns.detect do |column|
49
+ column.name == column_name
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_attr'
2
+ module ActiveRecordApi
3
+ module Rest
4
+ class RequestUrlGenerator
5
+ include ActiveAttr::Model
6
+ attribute :request
7
+ attribute :action_name
8
+ attribute :models
9
+ attribute :pagination_param_name
10
+ delegate :host_with_port, :path, :query_params, to: :request
11
+
12
+ def next_url
13
+ return if models.count == 0
14
+ "#{current_url}?#{new_params}"
15
+ end
16
+
17
+ def new_params
18
+ request.query_parameters.dup.merge(pagination_param_name.to_s => models.last.send(pagination_param_name))
19
+ end
20
+
21
+ def current_url
22
+ "#{protocol}#{host_with_port}#{path}"
23
+ end
24
+
25
+ def redirect_url(model)
26
+ if action_name == 'update'
27
+ current_url
28
+ else
29
+ "#{current_url}/#{model.id}"
30
+ end
31
+ end
32
+
33
+ def protocol
34
+ return 'http://' if Rails.env.development?
35
+ 'https://'
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module ActiveRecordApi
4
4
  module Rest
5
- VERSION = '1.0.11'.freeze
5
+ VERSION = '1.0.12'.freeze
6
6
  end
7
7
  end
@@ -5,13 +5,19 @@ require 'active_support'
5
5
  module ActiveRecordApi
6
6
  module Rest
7
7
  extend ActiveSupport::Autoload
8
- autoload :AccessDeniedException
9
- autoload :ApplicationPolicy
10
- autoload :BadSessionException
11
8
  autoload :Controller
12
9
  autoload :GracefulErrors
13
10
  autoload :RequestUrlGenerator
11
+ autoload :Parameters
14
12
  autoload :VERSION
13
+ module Auth
14
+ extend ActiveSupport::Autoload
15
+ autoload :Policy
16
+ autoload :Scope
17
+ autoload :AccessDeniedException
18
+ autoload :BadSessionException
19
+ autoload :Controller
20
+ end
15
21
  end
16
22
  end
17
23
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_record_api-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Full Measure Education
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-04-10 00:00:00.000000000 Z
11
+ date: 2019-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -144,12 +144,15 @@ files:
144
144
  - bin/console
145
145
  - bin/setup
146
146
  - lib/active_record_api-rest.rb
147
- - lib/active_record_api/rest/access_denied_exception.rb
148
- - lib/active_record_api/rest/application_policy.rb
149
- - lib/active_record_api/rest/bad_session_exception.rb
147
+ - lib/active_record_api/rest/auth/access_denied_exception.rb
148
+ - lib/active_record_api/rest/auth/bad_session_exception.rb
149
+ - lib/active_record_api/rest/auth/controller.rb
150
+ - lib/active_record_api/rest/auth/policy.rb
151
+ - lib/active_record_api/rest/auth/scope.rb
150
152
  - lib/active_record_api/rest/controller.rb
151
153
  - lib/active_record_api/rest/graceful_errors.rb
152
- - lib/active_record_api/rest/index_controller.rb
154
+ - lib/active_record_api/rest/parameters.rb
155
+ - lib/active_record_api/rest/request_url_generator.rb
153
156
  - lib/active_record_api/rest/spec.rb
154
157
  - lib/active_record_api/rest/spec/rest_controller_shared_example.rb
155
158
  - lib/active_record_api/rest/version.rb
@@ -1,13 +0,0 @@
1
- module ActiveRecordApi
2
- module Rest
3
- class AccessDeniedException < Exception
4
- attr_reader :action
5
- attr_reader :controller
6
- def initialize(controller, action, message)
7
- super(message)
8
- @action = action
9
- @controller = controller
10
- end
11
- end
12
- end
13
- end
@@ -1,61 +0,0 @@
1
- module ActiveRecordApi
2
- module Rest
3
- class ApplicationPolicy
4
- class Scope
5
- attr_reader :session, :scope
6
-
7
- def initialize(session, scope)
8
- @session = session
9
- @scope = scope
10
- end
11
-
12
- def resolve
13
- @scope.where(id: false)
14
- end
15
- end
16
-
17
- attr_reader :session
18
- attr_reader :model_class
19
-
20
- READ = 'read'.freeze
21
- MANAGE = 'manage'.freeze
22
-
23
- def initialize(session, model_class)
24
- @session = session
25
- @model_class = model_class
26
- end
27
-
28
- def index?
29
- can?(READ)
30
- end
31
-
32
- def show?
33
- can?(READ)
34
- end
35
-
36
- def create?
37
- can?(MANAGE)
38
- end
39
-
40
- def update?
41
- can?(MANAGE)
42
- end
43
-
44
- def destroy?
45
- can?(MANAGE)
46
- end
47
-
48
- protected
49
-
50
- def can?(action)
51
- session['permissions'].include?("#{service_name}__#{@model_class.name.downcase}:#{action}")
52
- end
53
-
54
- private
55
-
56
- def service_name
57
- File.basename(Rails.root.to_s)
58
- end
59
- end
60
- end
61
- end
@@ -1,12 +0,0 @@
1
- module ActiveRecordApi
2
- module Rest
3
- class BadSessionException < Exception
4
- attr_reader :action
5
- attr_reader :controller
6
- def initialize(controller, action)
7
- @action = action
8
- @controller = controller
9
- end
10
- end
11
- end
12
- end
@@ -1,66 +0,0 @@
1
- module ActiveRecordApi
2
- module Rest
3
- class IndexController < ApplicationController
4
- attr_reader :models
5
- include GracefulErrors
6
- before_action :load_models, only: :index
7
-
8
- def index
9
- response.headers['x-total'] = @total
10
- response.headers['x-link-next'] = next_link
11
- render json: models, each_serializer: serializer
12
- end
13
-
14
- protected
15
-
16
- def next_link
17
- return unless @remaining_count > limit
18
- new_params = request.query_parameters.dup
19
- new_params['previous_id'] = models.last.id
20
- RequestUrlGenerator.new(request: request, new_params: new_params).url
21
- end
22
-
23
- def serializer
24
- "#{model_klass.name}Serializer".safe_constantize
25
- end
26
-
27
- def allowed_params_query
28
- @allowed_params_query ||= model_klass.column_names.flat_map do |column_name|
29
- col = column_name.to_s.gsub('encrypted_', '').to_sym
30
- [col, col => []]
31
- end
32
- end
33
-
34
- def filtered_params_query
35
- @filtered_params_query ||= params.permit(allowed_params_query).to_h
36
- end
37
-
38
- def load_models
39
- @models = scope_filter(model_klass).where(filtered_params_query)
40
- @total = models.count
41
- page_models
42
- end
43
-
44
- def page_models
45
- id_field = model_klass.arel_table[:id]
46
- updated_models = @models.order(:id)
47
- updated_models = updated_models.where(id_field.gt previous_id) if previous_id.present?
48
- @models = updated_models
49
- @remaining_count = models.count
50
- @models = @models.limit(limit)
51
- end
52
-
53
- def model_klass
54
- @model_klass ||= controller_name.classify.constantize
55
- end
56
-
57
- def limit
58
- @limit ||= params[:limit]&.to_i || 50
59
- end
60
-
61
- def previous_id
62
- @previous_id ||= params[:previous_id]
63
- end
64
- end
65
- end
66
- end