apill 4.1.0 → 4.2.0

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
  SHA1:
3
- metadata.gz: 905997a468c58168a9f87fbae4df382ced3cc1fd
4
- data.tar.gz: d354947d9ca949dadaad2d25969c5f7865b0652c
3
+ metadata.gz: cab5c932811642e80f8c28a2cd9bf132f2427e72
4
+ data.tar.gz: 9cb2a0d0e0b1a844c37e88eeab958a5952ee5d08
5
5
  SHA512:
6
- metadata.gz: a8576261ae11512401546d1b2e22b6e435921ab67ec2efce00e0ddeabcfff846522d1a2b918dbeff6c7175166004aee450db10eb6fed6cd136487c6c568b867e
7
- data.tar.gz: fdd74cbe6f6245d3b6caa3b730487cbbf04bb0c80c08deecc5a5dab7b84f216fe403732142191d57892a0313d2a65515b30c18474e6a41d5b388b9b1a249192a
6
+ metadata.gz: e10c046dcd6bb020b106670cbe6ea39e3324dba4a4b870f03895d4e25a5124e1fdd9b0eb33fa904398c760c49c0417015775e913c2172cc3509ce736baefa05a
7
+ data.tar.gz: 0f2c1c2de41e550eaa5a66b1d7a779dd52ca5ae331c9736e34b27e97f71839724380fad56ac75e248af6aa34e59cc1515f00f8f1090166d255f2562764481dd6
@@ -1,5 +1,10 @@
1
1
  require 'apill/version'
2
2
 
3
+ require 'apill/authorizers/parameters'
4
+ require 'apill/authorizers/parameters/filtering'
5
+ require 'apill/authorizers/parameters/resource'
6
+ require 'apill/authorizers/query'
7
+ require 'apill/authorizers/scope'
3
8
  require 'apill/configuration'
4
9
  require 'apill/matchers/accept_header'
5
10
  require 'apill/matchers/subdomain'
@@ -0,0 +1,160 @@
1
+ require 'apill/resource/naming'
2
+ require 'apill/resource/model'
3
+
4
+ module Apill
5
+ module AuthorizableResource
6
+ RESOURCE_COLLECTION_ACTIONS = %w{index}.freeze
7
+
8
+ module ClassMethods
9
+ def authorizer_prefix
10
+ @authorizer_prefix ||= name[Resource::Naming::CONTROLLER_RESOURCE_NAME_PATTERN, 2]
11
+ end
12
+
13
+ def authorizer_class
14
+ @authorizer_class ||= "#{authorizer_prefix}" \
15
+ 'Authorizers::' \
16
+ "#{resource_class_name}".
17
+ constantize
18
+ rescue NameError
19
+ 'Apill::Authorizers::Query'.constantize
20
+ end
21
+
22
+ def authorizer_scope_class
23
+ @authorizer_scope_class ||= "#{authorizer_prefix}" \
24
+ 'Authorizers::' \
25
+ "#{resource_class_name}" \
26
+ '::Scope'.
27
+ constantize
28
+ rescue NameError
29
+ 'Apill::Authorizers::Scope'.constantize
30
+ end
31
+
32
+ def authorizer_resource_params_class
33
+ @authorizer_resource_params_class ||= "#{authorizer_prefix}" \
34
+ 'Authorizers::' \
35
+ "#{resource_class_name}" \
36
+ '::ResourceParameters'.
37
+ constantize
38
+ rescue NameError
39
+ 'Apill::Authorizers::Parameters::Resource'.constantize
40
+ end
41
+
42
+ def authorizer_filtering_params_class
43
+ @authorizer_filtering_params_class ||= "#{authorizer_prefix}" \
44
+ 'Authorizers::' \
45
+ "#{resource_class_name}::" \
46
+ 'FilteringParameters'.
47
+ constantize
48
+ rescue NameError
49
+ 'Apill::Authorizers::Parameters::Filtering'.constantize
50
+ end
51
+ end
52
+
53
+ def self.included(base)
54
+ base.include Resource::Naming
55
+ base.extend ClassMethods
56
+
57
+ base.before_action :authorize
58
+ end
59
+
60
+ private
61
+
62
+ def authorize
63
+ HumanError.raise(
64
+ 'ForbiddenError',
65
+ resource_name: self.class.singular_resource_name,
66
+ resource_id: [params[:id]],
67
+ action: action_name,
68
+ ) unless authorizer.public_send(authorization_query)
69
+ end
70
+
71
+ def authorizer
72
+ @authorizer ||= self.
73
+ class.
74
+ authorizer_class.
75
+ new(token: token,
76
+ user: authorized_user,
77
+ resource: authorized_resource)
78
+ end
79
+
80
+ def authorized_scope
81
+ @authorized_scope ||= self.
82
+ class.
83
+ authorizer_scope_class.
84
+ new(token: token,
85
+ user: authorized_user,
86
+ scoped_user_id: scoped_user_id,
87
+ params: authorized_params,
88
+ scope_root: authorized_scope_root).
89
+ call
90
+ end
91
+
92
+ def authorized_params
93
+ @authorized_params ||= authorizer_params_class.
94
+ new(token: token,
95
+ user: authorized_user,
96
+ params: params).
97
+ call
98
+ end
99
+
100
+ def authorized_resource
101
+ return nil if RESOURCE_COLLECTION_ACTIONS.include?(action_name)
102
+
103
+ @authorized_resource ||= public_send(self.class.singular_resource_name)
104
+ end
105
+
106
+ def authorized_collection
107
+ return nil unless RESOURCE_COLLECTION_ACTIONS.include?(action_name)
108
+
109
+ @authorized_collection ||= Resource::Model.
110
+ new(resource: public_send(self.class.plural_resource_name),
111
+ parameters: authorized_params)
112
+ end
113
+
114
+ def authorizer_params_class
115
+ @authorizer_params_class ||= \
116
+ if RESOURCE_COLLECTION_ACTIONS.include?(action_name)
117
+ self.class.authorizer_filtering_params_class
118
+ else
119
+ self.class.authorizer_resource_params_class
120
+ end
121
+ end
122
+
123
+ def authorized_scope_root
124
+ @authorized_scope_root ||= "#{self.class.authorizer_prefix}" \
125
+ "#{self.class.resource_class_name}".
126
+ constantize
127
+ end
128
+
129
+ def scoped_user_id
130
+ @scoped_user_id ||= if requested_user_id.blank?
131
+ nil
132
+ else
133
+ requested_user_id
134
+ end
135
+ end
136
+
137
+ def requested_user_id
138
+ @requested_user_id ||= params.
139
+ fetch(:filter, {}).
140
+ fetch(authorized_user_underscored_class_name,
141
+ authorized_user.id)
142
+ end
143
+
144
+ def authorized_user
145
+ current_user
146
+ end
147
+
148
+ def authorized_user_underscored_class_name
149
+ @authorized_user_underscored_class_name ||= authorized_user.
150
+ class.
151
+ name[/([^:]+)\z/, 1].
152
+ underscore.
153
+ downcase
154
+ end
155
+
156
+ def authorization_query
157
+ @authorization_query ||= "able_to_#{action_name}?"
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,23 @@
1
+ module Apill
2
+ module Authorizers
3
+ class Parameters
4
+ attr_accessor :token,
5
+ :user,
6
+ :params
7
+
8
+ def initialize(token:, user:, params:, **other)
9
+ self.token = token
10
+ self.user = user
11
+ self.params = params
12
+
13
+ other.each do |name, value|
14
+ public_send("#{name}=", value)
15
+ end
16
+ end
17
+
18
+ def call
19
+ params
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,49 @@
1
+ require 'apill/authorizers/parameters'
2
+
3
+ module Apill
4
+ module Authorizers
5
+ class Parameters
6
+ class Filtering < Authorizers::Parameters
7
+ def call
8
+ params.permit(*authorized_params)
9
+ end
10
+
11
+ private
12
+
13
+ def authorized_params
14
+ @authorized_params ||= [
15
+ :sort,
16
+ page: %i{
17
+ number
18
+ size
19
+ offset
20
+ limit
21
+ cursor
22
+ },
23
+ filter: [
24
+ :query,
25
+ {},
26
+ ],
27
+ ]
28
+ end
29
+
30
+ def add_filterable_parameter(name)
31
+ param = params.fetch(:filter, {}).
32
+ fetch(name, nil)
33
+
34
+ if param.class == Array
35
+ authorized_params[1][:filter][1][name] = []
36
+ else
37
+ authorized_params[1][:filter] << name
38
+ end
39
+ end
40
+
41
+ def add_filterable_parameters(*names)
42
+ names.each do |name|
43
+ add_filterable_parameter(name)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ require 'apill/authorizers/parameters'
2
+
3
+ module Apill
4
+ module Authorizers
5
+ class Parameters
6
+ class Resource < Authorizers::Parameters
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ module Apill
2
+ module Authorizers
3
+ class Query
4
+ attr_accessor :token,
5
+ :user,
6
+ :resource
7
+
8
+ def initialize(token:, user:, resource:, **other)
9
+ self.token = token
10
+ self.user = user
11
+ self.resource = resource
12
+
13
+ other.each do |name, value|
14
+ public_send("#{name}=", value)
15
+ end
16
+ end
17
+
18
+ def able_to_index?
19
+ true
20
+ end
21
+
22
+ def able_to_show?
23
+ false
24
+ end
25
+
26
+ def able_to_create?
27
+ false
28
+ end
29
+
30
+ def able_to_update?
31
+ false
32
+ end
33
+
34
+ def able_to_destroy?
35
+ false
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ module Apill
2
+ module Authorizers
3
+ class Scope
4
+ attr_accessor :token,
5
+ :user,
6
+ :scoped_user_id,
7
+ :params,
8
+ :scope_root
9
+
10
+ # rubocop:disable Metrics/ParameterLists
11
+ def initialize(token:, user:, params:, scoped_user_id:, scope_root:, **other)
12
+ self.token = token
13
+ self.user = user
14
+ self.params = params
15
+ self.scoped_user_id = scoped_user_id
16
+ self.scope_root = scope_root
17
+
18
+ other.each do |name, value|
19
+ public_send("#{name}=", value)
20
+ end
21
+ end
22
+ # rubocop:enable Metrics/ParameterLists
23
+
24
+ def call
25
+ scope_root.none
26
+ end
27
+ end
28
+ end
29
+ end
@@ -92,9 +92,9 @@ class Base
92
92
  def authorization_token_from_header
93
93
  case raw_authorization_header
94
94
  when JSON_WEB_TOKEN_HEADER_PATTERN
95
- Tokens::JsonWebToken.convert(
96
- token_private_key: token_private_key,
97
- raw_token: raw_authorization_header[JSON_WEB_TOKEN_HEADER_PATTERN, 1])
95
+ Tokens::JsonWebToken.from_jwe(
96
+ raw_authorization_header[JSON_WEB_TOKEN_HEADER_PATTERN, 1],
97
+ private_key: token_private_key)
98
98
  when BASE64_TOKEN_HEADER_PATTERN
99
99
  Tokens::Base64.convert(
100
100
  raw_token: raw_authorization_header[BASE64_TOKEN_HEADER_PATTERN, 1])
@@ -14,10 +14,9 @@ class Rack < Base
14
14
  def authorization_token_from_params
15
15
  case request['QUERY_STRING']
16
16
  when JSON_WEB_TOKEN_PARAM_PATTERN
17
- Tokens::JsonWebToken.convert(
18
- token_private_key: token_private_key,
19
- raw_token: request['QUERY_STRING'][JSON_WEB_TOKEN_PARAM_PATTERN, 1] || '',
20
- )
17
+ Tokens::JsonWebToken.from_jwe(
18
+ request['QUERY_STRING'][JSON_WEB_TOKEN_PARAM_PATTERN, 1] || '',
19
+ private_key: token_private_key)
21
20
  when BASE64_TOKEN_PARAM_PATTERN
22
21
  base64_token = request['QUERY_STRING'][BASE64_TOKEN_PARAM_PATTERN, 1]
23
22
 
@@ -10,9 +10,9 @@ class Rails < Base
10
10
  def authorization_token_from_params
11
11
  case
12
12
  when request.params.key?(JSON_WEB_TOKEN_PARAM_NAME)
13
- Tokens::JsonWebToken.convert(
14
- token_private_key: token_private_key,
15
- raw_token: request.params[JSON_WEB_TOKEN_PARAM_NAME] || '')
13
+ Tokens::JsonWebToken.from_jwe(
14
+ request.params[JSON_WEB_TOKEN_PARAM_NAME] || '',
15
+ private_key: token_private_key)
16
16
  when request.params.key?(BASE64_TOKEN_PARAM_NAME)
17
17
  Tokens::Base64.convert(raw_token: request.params[BASE64_TOKEN_PARAM_NAME] || '')
18
18
  else
@@ -1,13 +1,13 @@
1
- require 'apill/processable_resource'
1
+ require 'apill/authorizable_resource'
2
2
  require 'human_error/rescuable_resource'
3
3
  require 'human_error/verifiable_resource'
4
4
 
5
5
  module Apill
6
6
  module Resource
7
7
  def self.included(base)
8
- base.include Apill::ProcessableResource
9
8
  base.include HumanError::RescuableResource
10
9
  base.include HumanError::VerifiableResource
10
+ base.include Apill::AuthorizableResource
11
11
  end
12
12
  end
13
13
  end
@@ -0,0 +1,32 @@
1
+ module Apill
2
+ module Resource
3
+ module Naming
4
+ CONTROLLER_RESOURCE_NAME_PATTERN = /\A((.*?::)?.*?)(\w+)Controller\z/
5
+
6
+ module ClassMethods
7
+ def plural_resource_name
8
+ @plural_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
9
+ underscore.
10
+ pluralize.
11
+ downcase
12
+ end
13
+
14
+ def singular_resource_name
15
+ @singular_resource_name ||= name[CONTROLLER_RESOURCE_NAME_PATTERN, 3].
16
+ underscore.
17
+ singularize.
18
+ downcase
19
+ end
20
+
21
+ def resource_class_name
22
+ @resource_class_name ||= singular_resource_name.
23
+ camelize
24
+ end
25
+ end
26
+
27
+ def self.included(base)
28
+ base.extend ClassMethods
29
+ end
30
+ end
31
+ end
32
+ end
@@ -6,10 +6,33 @@ require 'apill/tokens/json_web_tokens/null'
6
6
  module Apill
7
7
  module Tokens
8
8
  class JsonWebToken
9
- attr_accessor :token
9
+ TRANSFORMATION_EXCEPTIONS = [
10
+ JSON::JWT::Exception,
11
+ JSON::JWT::InvalidFormat,
12
+ JSON::JWT::VerificationFailed,
13
+ JSON::JWT::UnexpectedAlgorithm,
14
+ JWT::DecodeError,
15
+ JWT::VerificationError,
16
+ JWT::ExpiredSignature,
17
+ JWT::IncorrectAlgorithm,
18
+ JWT::ImmatureSignature,
19
+ JWT::InvalidIssuerError,
20
+ JWT::InvalidIatError,
21
+ JWT::InvalidAudError,
22
+ JWT::InvalidSubError,
23
+ JWT::InvalidJtiError,
24
+ OpenSSL::PKey::RSAError,
25
+ OpenSSL::Cipher::CipherError,
26
+ ].freeze
10
27
 
11
- def initialize(token:)
12
- self.token = token
28
+ attr_accessor :data,
29
+ :private_key
30
+
31
+ def initialize(data:,
32
+ private_key: Apill.configuration.token_private_key)
33
+
34
+ self.data = data
35
+ self.private_key = private_key
13
36
  end
14
37
 
15
38
  def valid?
@@ -21,40 +44,66 @@ class JsonWebToken
21
44
  end
22
45
 
23
46
  def to_h
24
- token
25
- end
26
-
27
- def self.convert(raw_token:, token_private_key: Apill.configuration.token_private_key)
28
- return JsonWebTokens::Null.instance if raw_token.to_s == ''
29
-
30
- decrypted_token = JSON::JWT.decode(raw_token, token_private_key).plain_text
31
- decoded_token = JWT.decode(decrypted_token,
32
- token_private_key,
33
- true,
34
- algorithm: 'RS256',
35
- verify_expiration: true,
36
- verify_not_before: true,
37
- verify_iat: true,
38
- leeway: 5,
39
- )
40
-
41
- new(token: decoded_token)
42
- rescue JSON::JWT::Exception,
43
- JSON::JWT::InvalidFormat,
44
- JSON::JWT::VerificationFailed,
45
- JSON::JWT::UnexpectedAlgorithm,
46
- JWT::DecodeError,
47
- JWT::VerificationError,
48
- JWT::ExpiredSignature,
49
- JWT::IncorrectAlgorithm,
50
- JWT::ImmatureSignature,
51
- JWT::InvalidIssuerError,
52
- JWT::InvalidIatError,
53
- JWT::InvalidAudError,
54
- JWT::InvalidSubError,
55
- JWT::InvalidJtiError,
56
- OpenSSL::PKey::RSAError
47
+ data
48
+ end
49
+
50
+ def to_jwt
51
+ @jwt ||= JSON::JWT.new(data)
52
+ end
53
+
54
+ def to_jwt_s
55
+ @jwt_s ||= to_jwt.to_s
56
+ end
57
+
58
+ def to_jws
59
+ @jws ||= to_jwt.sign(private_key, 'RS256')
60
+ end
61
+
62
+ def to_jws_s
63
+ @jws_s ||= to_jws.to_s
64
+ end
65
+
66
+ def to_jwe
67
+ @jwe ||= to_jws.encrypt(private_key, 'RSA-OAEP', 'A256GCM')
68
+ end
69
+
70
+ def to_jwe_s
71
+ @jwe_s ||= to_jwe.to_s
72
+ end
73
+
74
+ def self.from_jwe(encrypted_token,
75
+ private_key: Apill.configuration.token_private_key)
76
+
77
+ return JsonWebTokens::Null.instance if encrypted_token.to_s == ''
78
+
79
+ decrypted_token = JSON::JWT.
80
+ decode(encrypted_token, private_key).
81
+ plain_text
82
+
83
+ from_jws(decrypted_token, private_key: private_key)
84
+ rescue *TRANSFORMATION_EXCEPTIONS
85
+ JsonWebTokens::Invalid.instance
86
+ end
87
+
88
+ def self.from_jws(signed_token,
89
+ private_key: Apill.configuration.token_private_key)
90
+
91
+ return JsonWebTokens::Null.instance if signed_token.to_s == ''
92
+
93
+ data = JWT.decode(
94
+ signed_token,
95
+ private_key,
96
+ true,
97
+ algorithm: 'RS256',
98
+ verify_expiration: true,
99
+ verify_not_before: true,
100
+ verify_iat: true,
101
+ leeway: 5,
102
+ )
57
103
 
104
+ new(data: data,
105
+ private_key: private_key)
106
+ rescue *TRANSFORMATION_EXCEPTIONS
58
107
  JsonWebTokens::Invalid.instance
59
108
  end
60
109
  end