active_record_api-rest 1.0.11 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
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