croods 0.1.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +19 -0
- data/lib/croods.rb +58 -0
- data/lib/croods/action.rb +17 -0
- data/lib/croods/api.rb +26 -0
- data/lib/croods/api/initial_schema.json +52 -0
- data/lib/croods/attribute.rb +15 -0
- data/lib/croods/controller.rb +28 -0
- data/lib/croods/controller/actions.rb +71 -0
- data/lib/croods/controller/already_taken.rb +27 -0
- data/lib/croods/controller/authentication.rb +22 -0
- data/lib/croods/controller/authorization.rb +31 -0
- data/lib/croods/controller/collection.rb +15 -0
- data/lib/croods/controller/member.rb +45 -0
- data/lib/croods/controller/model.rb +17 -0
- data/lib/croods/controller/multi_tenancy.rb +54 -0
- data/lib/croods/controller/not_found.rb +22 -0
- data/lib/croods/controller/record_invalid.rb +22 -0
- data/lib/croods/controller/resource.rb +30 -0
- data/lib/croods/middleware.rb +44 -0
- data/lib/croods/middleware/base.rb +20 -0
- data/lib/croods/middleware/request_validation.rb +10 -0
- data/lib/croods/middleware/response_validation.rb +10 -0
- data/lib/croods/model.rb +41 -0
- data/lib/croods/policy.rb +118 -0
- data/lib/croods/policy/scope.rb +131 -0
- data/lib/croods/railtie.rb +6 -0
- data/lib/croods/resource.rb +39 -0
- data/lib/croods/resource/actions.rb +51 -0
- data/lib/croods/resource/attributes.rb +53 -0
- data/lib/croods/resource/attributes/base.rb +30 -0
- data/lib/croods/resource/attributes/request.rb +29 -0
- data/lib/croods/resource/attributes/response.rb +13 -0
- data/lib/croods/resource/authentication.rb +46 -0
- data/lib/croods/resource/authorization.rb +47 -0
- data/lib/croods/resource/controller.rb +50 -0
- data/lib/croods/resource/filters.rb +32 -0
- data/lib/croods/resource/identifier.rb +13 -0
- data/lib/croods/resource/json_schema.rb +32 -0
- data/lib/croods/resource/json_schema/definition.rb +34 -0
- data/lib/croods/resource/json_schema/definitions.rb +31 -0
- data/lib/croods/resource/json_schema/initial_schema.json +8 -0
- data/lib/croods/resource/json_schema/links.rb +40 -0
- data/lib/croods/resource/json_schema/links/collection.rb +70 -0
- data/lib/croods/resource/json_schema/links/create.rb +41 -0
- data/lib/croods/resource/json_schema/links/destroy.rb +40 -0
- data/lib/croods/resource/json_schema/links/index.rb +17 -0
- data/lib/croods/resource/json_schema/links/member.rb +40 -0
- data/lib/croods/resource/json_schema/links/show.rb +40 -0
- data/lib/croods/resource/json_schema/links/update.rb +40 -0
- data/lib/croods/resource/json_schema/properties.rb +32 -0
- data/lib/croods/resource/json_schema/required.rb +34 -0
- data/lib/croods/resource/model.rb +35 -0
- data/lib/croods/resource/names.rb +23 -0
- data/lib/croods/resource/paths.rb +16 -0
- data/lib/croods/resource/policy.rb +51 -0
- data/lib/croods/resource/services.rb +18 -0
- data/lib/croods/resource/sorting.rb +13 -0
- data/lib/croods/routes.rb +46 -0
- data/lib/croods/service.rb +28 -0
- data/lib/croods/version.rb +5 -0
- data/lib/tasks/croods_tasks.rake +4 -0
- metadata +316 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Croods
|
4
|
+
class Policy
|
5
|
+
class Scope
|
6
|
+
def initialize(tenant:, user:, scope:)
|
7
|
+
self.user = user
|
8
|
+
self.scope = scope
|
9
|
+
self.tenant = user&.tenant || tenant
|
10
|
+
end
|
11
|
+
|
12
|
+
def resolve
|
13
|
+
self.scope = tenant_scope(scope) if Croods.multi_tenancy?
|
14
|
+
|
15
|
+
return scope if action.public
|
16
|
+
|
17
|
+
return scope if super?
|
18
|
+
|
19
|
+
return owner_scope(scope) if owner?
|
20
|
+
|
21
|
+
scope
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_accessor :tenant, :user, :scope
|
27
|
+
|
28
|
+
def scope_is_the_owner?(scope, target)
|
29
|
+
return scope.reflect_on_association(target) unless target == :user
|
30
|
+
|
31
|
+
scope.resource.user_is_the_owner? &&
|
32
|
+
scope.reflect_on_association(target)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reflection_path(scope, target, path = [])
|
36
|
+
return path if scope_is_the_owner?(scope, target)
|
37
|
+
|
38
|
+
associations = scope.reflect_on_all_associations(:belongs_to)
|
39
|
+
|
40
|
+
return path if associations.empty?
|
41
|
+
|
42
|
+
associations.each do |association|
|
43
|
+
model = association.class_name.constantize
|
44
|
+
expanded_path = path + [association]
|
45
|
+
association_path = reflection_path(model, target, expanded_path)
|
46
|
+
return association_path if association_path != path
|
47
|
+
end
|
48
|
+
|
49
|
+
path
|
50
|
+
end
|
51
|
+
|
52
|
+
def joins(path)
|
53
|
+
joins = nil
|
54
|
+
|
55
|
+
path.reverse.each do |association|
|
56
|
+
joins = joins ? { association.name => joins } : association.name
|
57
|
+
end
|
58
|
+
|
59
|
+
joins
|
60
|
+
end
|
61
|
+
|
62
|
+
def immediate_tenant_scope(scope)
|
63
|
+
scope.where(Croods.tenant_attribute => tenant.id)
|
64
|
+
end
|
65
|
+
|
66
|
+
def immediate_tenant_scope?(scope)
|
67
|
+
scope.has_attribute?(Croods.tenant_attribute)
|
68
|
+
end
|
69
|
+
|
70
|
+
def tenant_scope(scope)
|
71
|
+
return immediate_tenant_scope(scope) if immediate_tenant_scope?(scope)
|
72
|
+
|
73
|
+
path = reflection_path(scope, Croods.multi_tenancy_by)
|
74
|
+
return scope if path.empty?
|
75
|
+
|
76
|
+
scope.joins(joins(path)).where(
|
77
|
+
path.last.plural_name => { Croods.tenant_attribute => tenant.id }
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def user_owner_scope(scope)
|
82
|
+
scope.where(id: user.id)
|
83
|
+
end
|
84
|
+
|
85
|
+
def user_owner_scope?(scope)
|
86
|
+
scope == User || scope.try(:klass) == User
|
87
|
+
end
|
88
|
+
|
89
|
+
def immediate_owner_scope(scope)
|
90
|
+
scope.where(user_id: user.id)
|
91
|
+
end
|
92
|
+
|
93
|
+
def immediate_owner_scope?(scope)
|
94
|
+
scope.resource.user_is_the_owner? && scope.has_attribute?(:user_id)
|
95
|
+
end
|
96
|
+
|
97
|
+
def owner_scope(scope)
|
98
|
+
return user_owner_scope(scope) if user_owner_scope?(scope)
|
99
|
+
|
100
|
+
return immediate_owner_scope(scope) if immediate_owner_scope?(scope)
|
101
|
+
|
102
|
+
path = reflection_path(scope, :user)
|
103
|
+
return scope if path.empty?
|
104
|
+
|
105
|
+
scope.joins(joins(path)).where(
|
106
|
+
path.last.plural_name => { user_id: user.id }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
def super?
|
111
|
+
super_roles.each do |role|
|
112
|
+
return true if user&.send("#{role}?")
|
113
|
+
end
|
114
|
+
|
115
|
+
false
|
116
|
+
end
|
117
|
+
|
118
|
+
def roles
|
119
|
+
action.roles || DEFAULT_ROLES
|
120
|
+
end
|
121
|
+
|
122
|
+
def super_roles
|
123
|
+
roles.reject { |role| role == :owner }
|
124
|
+
end
|
125
|
+
|
126
|
+
def owner?
|
127
|
+
roles.include?(:owner)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'resource/names'
|
4
|
+
require_relative 'resource/paths'
|
5
|
+
require_relative 'resource/identifier'
|
6
|
+
require_relative 'resource/model'
|
7
|
+
require_relative 'resource/policy'
|
8
|
+
require_relative 'resource/controller'
|
9
|
+
require_relative 'resource/actions'
|
10
|
+
require_relative 'resource/attributes'
|
11
|
+
require_relative 'resource/json_schema'
|
12
|
+
require_relative 'resource/authentication'
|
13
|
+
require_relative 'resource/authorization'
|
14
|
+
require_relative 'resource/filters'
|
15
|
+
require_relative 'resource/sorting'
|
16
|
+
require_relative 'resource/services'
|
17
|
+
|
18
|
+
module Croods
|
19
|
+
module Resource
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
class_methods do
|
23
|
+
include Names
|
24
|
+
include Paths
|
25
|
+
include Identifier
|
26
|
+
include Model
|
27
|
+
include Policy
|
28
|
+
include Controller
|
29
|
+
include Actions
|
30
|
+
include Attributes
|
31
|
+
include JsonSchema
|
32
|
+
include Authentication
|
33
|
+
include Authorization
|
34
|
+
include Filters
|
35
|
+
include Sorting
|
36
|
+
include Services
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Croods
|
4
|
+
module Resource
|
5
|
+
module Actions
|
6
|
+
DEFAULT_ACTIONS = %i[index create update destroy show].freeze
|
7
|
+
|
8
|
+
def default_actions
|
9
|
+
DEFAULT_ACTIONS.map do |name|
|
10
|
+
Croods::Action.new name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def actions(*names)
|
15
|
+
return filtered_actions if names.empty?
|
16
|
+
|
17
|
+
@actions = names.map do |name|
|
18
|
+
Croods::Action.new name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def filtered_actions
|
23
|
+
@actions ||= default_actions
|
24
|
+
|
25
|
+
@actions.reject { |action| ignored_actions.include?(action.name) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_action(name, method: :get, on: :member, &block)
|
29
|
+
additional_actions << Action.new(
|
30
|
+
name, method: method, on: on, block: block
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def additional_actions
|
35
|
+
@additional_actions ||= []
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_actions(*names)
|
39
|
+
names.each do |name|
|
40
|
+
ignored_actions << name.to_sym
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ignored_actions
|
45
|
+
@ignored_actions ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
alias remove_action remove_actions
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'attributes/base'
|
4
|
+
require_relative 'attributes/request'
|
5
|
+
require_relative 'attributes/response'
|
6
|
+
|
7
|
+
module Croods
|
8
|
+
module Resource
|
9
|
+
module Attributes
|
10
|
+
include Base
|
11
|
+
|
12
|
+
def request(&block)
|
13
|
+
request_instance.instance_eval(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def response(&block)
|
17
|
+
response_instance.instance_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def request_instance
|
21
|
+
@request_instance ||= Request.new(ignore_user: user_is_the_owner?)
|
22
|
+
end
|
23
|
+
|
24
|
+
def response_instance
|
25
|
+
@response_instance ||= Response.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def merged_attributes(type, hash = nil)
|
29
|
+
(hash || attributes)
|
30
|
+
.merge(type.additional_attributes)
|
31
|
+
.reject { |name| type.ignored_attributes.include?(name) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def request_attributes
|
35
|
+
merged_attributes(request_instance)
|
36
|
+
end
|
37
|
+
|
38
|
+
def response_attributes
|
39
|
+
merged_attributes(response_instance)
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes
|
43
|
+
merged_attributes(self, model.columns_hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
def definitions
|
47
|
+
attributes
|
48
|
+
.merge(request_instance.additional_attributes)
|
49
|
+
.merge(response_instance.additional_attributes)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Croods
|
4
|
+
module Resource
|
5
|
+
module Attributes
|
6
|
+
module Base
|
7
|
+
def add_attribute(name, type, **options)
|
8
|
+
attribute = Croods::Attribute.new(name, type, **options)
|
9
|
+
additional_attributes[name.to_s] = attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
def additional_attributes
|
13
|
+
@additional_attributes ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_attributes(*names)
|
17
|
+
names.each do |name|
|
18
|
+
ignored_attributes << name.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def ignored_attributes
|
23
|
+
@ignored_attributes ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
alias remove_attribute remove_attributes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Croods
|
6
|
+
module Resource
|
7
|
+
module Attributes
|
8
|
+
class Request
|
9
|
+
include Base
|
10
|
+
|
11
|
+
attr_accessor :ignore_user
|
12
|
+
|
13
|
+
def initialize(ignore_user:)
|
14
|
+
self.ignore_user = ignore_user
|
15
|
+
end
|
16
|
+
|
17
|
+
def ignored_attributes
|
18
|
+
@ignored_attributes ||= default_ignored_attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_ignored_attributes
|
22
|
+
return [] unless ignore_user
|
23
|
+
|
24
|
+
['user_id']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Croods
|
4
|
+
module Resource
|
5
|
+
module Authentication
|
6
|
+
OPTIONS = %i[
|
7
|
+
database_authenticatable registerable recoverable rememberable
|
8
|
+
trackable validatable
|
9
|
+
].freeze
|
10
|
+
|
11
|
+
ATTRIBUTES = %i[
|
12
|
+
allow_password_change confirmation_sent_at confirmation_token
|
13
|
+
encrypted_password confirmed_at provider uid remember_created_at
|
14
|
+
reset_password_sent_at reset_password_token tokens unconfirmed_email
|
15
|
+
current_sign_in_at current_sign_in_ip last_sign_in_at last_sign_in_ip
|
16
|
+
sign_in_count
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
def use_for_authentication!(*options)
|
20
|
+
add_model_authentication(*options)
|
21
|
+
|
22
|
+
remove_attributes(*ATTRIBUTES)
|
23
|
+
remove_attribute(Croods.tenant_attribute) if Croods.multi_tenancy?
|
24
|
+
|
25
|
+
request do
|
26
|
+
add_attribute :password, :string, null: false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_model_authentication(*options)
|
31
|
+
extend_model do
|
32
|
+
before_create do
|
33
|
+
self.uid = email unless uid.present?
|
34
|
+
end
|
35
|
+
|
36
|
+
extend Devise::Models
|
37
|
+
|
38
|
+
devise_options = options.empty? ? OPTIONS : options
|
39
|
+
devise(*devise_options)
|
40
|
+
|
41
|
+
include DeviseTokenAuth::Concerns::User
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Croods
|
4
|
+
module Resource
|
5
|
+
module Authorization
|
6
|
+
def authorize(*roles, on: nil)
|
7
|
+
return if roles.empty?
|
8
|
+
|
9
|
+
on = [on] if on&.is_a?(Symbol)
|
10
|
+
|
11
|
+
actions.each do |action|
|
12
|
+
next if on && !on.include?(action.name)
|
13
|
+
|
14
|
+
action.roles = roles
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def public_actions(*names)
|
19
|
+
return unless names
|
20
|
+
|
21
|
+
names = [names] if names&.is_a?(Symbol)
|
22
|
+
|
23
|
+
extend_controller do
|
24
|
+
skip_before_action :authenticate_user!, only: names
|
25
|
+
end
|
26
|
+
|
27
|
+
actions.each do |action|
|
28
|
+
next unless names.include?(action.name)
|
29
|
+
|
30
|
+
action.public = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
alias public_action public_actions
|
35
|
+
|
36
|
+
def user_is_not_the_owner!
|
37
|
+
@user_is_the_owner = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def user_is_the_owner?
|
41
|
+
return @user_is_the_owner unless @user_is_the_owner.nil?
|
42
|
+
|
43
|
+
@user_is_the_owner = true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|