apill 4.1.0 → 4.2.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.
@@ -0,0 +1,62 @@
1
+ require 'jwt'
2
+ require 'json/jwt'
3
+ require 'apill/tokens/json_web_token/invalid'
4
+ require 'apill/tokens/json_web_token/null'
5
+
6
+ module Apill
7
+ module Tokens
8
+ class JsonWebToken
9
+ attr_accessor :token
10
+
11
+ def initialize(token:)
12
+ self.token = token
13
+ end
14
+
15
+ def valid?
16
+ true
17
+ end
18
+
19
+ def blank?
20
+ false
21
+ end
22
+
23
+ def to_h
24
+ token
25
+ end
26
+
27
+ def self.convert(raw_token:, token_private_key: Apill.configuration.token_private_key)
28
+ return JsonWebToken::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
57
+
58
+ JsonWebToken::Invalid.instance
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ require 'singleton'
2
+
3
+ module Apill
4
+ module Tokens
5
+ module JsonWebTokens
6
+ class Invalid
7
+ include Singleton
8
+
9
+ def valid?
10
+ false
11
+ end
12
+
13
+ def blank?
14
+ false
15
+ end
16
+
17
+ def to_h
18
+ [{}, {}]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'singleton'
2
+
3
+ module Apill
4
+ module Tokens
5
+ module JsonWebTokens
6
+ class Null
7
+ include Singleton
8
+
9
+ def valid?
10
+ true
11
+ end
12
+
13
+ def blank?
14
+ true
15
+ end
16
+
17
+ def to_h
18
+ [{}, {}]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,3 +1,3 @@
1
1
  module Apill
2
- VERSION = '4.1.0'.freeze
2
+ VERSION = '4.2.0'.freeze
3
3
  end
@@ -0,0 +1,70 @@
1
+ require 'rspectacular'
2
+ require 'apill/authorizers/parameters/filtering'
3
+
4
+ module Apill
5
+ module Authorizers
6
+ class Parameters
7
+ describe Filtering do
8
+ let(:params) { { filter: { name: 'Bill', age: 26 } } }
9
+
10
+ it 'can authorize new filter parameters', verify: false do
11
+ filter_params = Filtering.new(token: '1234',
12
+ user: '1234',
13
+ params: params)
14
+
15
+ allow(params).to receive(:permit)
16
+
17
+ filter_params.send(:add_filterable_parameters, :name, :age)
18
+ filter_params.call
19
+
20
+ expect(params).to have_received(:permit).
21
+ with(:sort, include(filter: include(:name, :age)))
22
+ end
23
+
24
+ it 'can authorize parameters if they come in as arrays', verify: false do
25
+ params = {
26
+ filter: {
27
+ name: 'Bill',
28
+ ary: %w{hello},
29
+ },
30
+ }
31
+ filter_params = Filtering.new(token: '1234',
32
+ user: '1234',
33
+ params: params)
34
+
35
+ allow(params).to receive(:permit)
36
+
37
+ filter_params.send(:add_filterable_parameters, :name, :ary)
38
+ filter_params.call
39
+
40
+ expect(params).to have_received(:permit).
41
+ with(:sort, include(filter: include(:name, ary: [])))
42
+ end
43
+
44
+ it 'has default authorized parameters', verify: false do
45
+ filter_params = Filtering.new(token: '1234',
46
+ user: '1234',
47
+ params: params)
48
+
49
+ allow(params).to receive(:permit)
50
+
51
+ filter_params.call
52
+
53
+ expect(params).to have_received(:permit).
54
+ with(:sort,
55
+ page: %i{
56
+ number
57
+ size
58
+ offset
59
+ limit
60
+ cursor
61
+ },
62
+ filter: [
63
+ :query,
64
+ {},
65
+ ])
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspectacular'
2
+ require 'apill/authorizers/parameters/resource'
3
+
4
+ module Apill
5
+ module Authorizers
6
+ class Parameters
7
+ describe Resource do
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ require 'rspectacular'
2
+ require 'apill/authorizers/parameters'
3
+
4
+ module Apill
5
+ module Authorizers
6
+ describe Parameters do
7
+ it 'defaults to nothing' do
8
+ parameters = Parameters.new(token: '123',
9
+ user: 'my_user',
10
+ params: { foo: 'bar' })
11
+
12
+ expect(parameters.call).to eql(foo: 'bar')
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'rspectacular'
2
+ require 'apill/authorizers/query'
3
+
4
+ module Apill
5
+ module Authorizers
6
+ describe Query do
7
+ it 'does not authorize the resource by default' do
8
+ authorizer = Query.new(token: '123',
9
+ user: 'my_user',
10
+ resource: 'my_resource')
11
+
12
+ expect(authorizer).to be_able_to_index
13
+ expect(authorizer).not_to be_able_to_show
14
+ expect(authorizer).not_to be_able_to_create
15
+ expect(authorizer).not_to be_able_to_update
16
+ expect(authorizer).not_to be_able_to_destroy
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ require 'rspectacular'
2
+ require 'ostruct'
3
+ require 'apill/authorizers/scope'
4
+
5
+ module Apill
6
+ module Authorizers
7
+ describe Scope do
8
+ it 'defaults to nothing' do
9
+ scope = Scope.new(token: '123',
10
+ user: 'my_user',
11
+ scoped_user_id: '456',
12
+ params: {},
13
+ scope_root: OpenStruct.new(none: []))
14
+
15
+ expect(scope.call).to be_empty
16
+ end
17
+ end
18
+ end
19
+ end
@@ -132,7 +132,7 @@ describe ApiRequest, singletons: HumanError::Configuration do
132
132
  request = {
133
133
  'HTTP_HOST' => 'api.example.com',
134
134
  'HTTP_ACCEPT' => 'application/vnd.matrix+zion;version=1.0.0',
135
- 'HTTP_AUTHORIZATION' => "Token #{valid_jwt_token}",
135
+ 'HTTP_AUTHORIZATION' => "Token #{valid_jwe_token}",
136
136
  'QUERY_STRING' => 'accept=application/vnd.matrix+zion;version=1.0.0',
137
137
  }
138
138
 
@@ -150,7 +150,7 @@ describe ApiRequest, singletons: HumanError::Configuration do
150
150
  request = {
151
151
  'HTTP_HOST' => 'api.example.com',
152
152
  'HTTP_ACCEPT' => 'application/vnd.matrix+zion;version=1.0.0',
153
- 'HTTP_AUTHORIZATION' => "Token #{invalid_jwt_token}",
153
+ 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
154
154
  'QUERY_STRING' => 'accept=application/vnd.matrix+zion;version=1.0.0',
155
155
  }
156
156
 
@@ -54,7 +54,7 @@ describe Rack do
54
54
 
55
55
  it 'finds the authorization token from the header' do
56
56
  raw_request = {
57
- 'HTTP_AUTHORIZATION' => "Token #{valid_jwt_token}",
57
+ 'HTTP_AUTHORIZATION' => "Token #{valid_jwe_token}",
58
58
  'QUERY_STRING' => '',
59
59
  }
60
60
  request = Rack.new(token_private_key: test_private_key,
@@ -98,7 +98,7 @@ describe Rack do
98
98
 
99
99
  it 'ignores incorrectly passed in tokens since we do not know what to do' do
100
100
  raw_request = {
101
- 'HTTP_AUTHORIZATION' => "#{valid_jwt_token}",
101
+ 'HTTP_AUTHORIZATION' => "#{valid_jwe_token}",
102
102
  'QUERY_STRING' => '',
103
103
  }
104
104
  request = Rack.new(token_private_key: test_private_key,
@@ -112,8 +112,8 @@ describe Rack do
112
112
  'the header is invalid and the authorization token from the params is valid' do
113
113
 
114
114
  raw_request = {
115
- 'HTTP_AUTHORIZATION' => "Token #{invalid_jwt_token}",
116
- 'QUERY_STRING' => "token_jwt=#{valid_jwt_token}",
115
+ 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
116
+ 'QUERY_STRING' => "token_jwt=#{valid_jwe_token}",
117
117
  }
118
118
  request = Rack.new(token_private_key: test_private_key,
119
119
  request: raw_request)
@@ -130,7 +130,7 @@ describe Rack do
130
130
  'the header is not present and the authorization token from the params is valid' do
131
131
 
132
132
  raw_request = {
133
- 'QUERY_STRING' => "token_jwt=#{valid_jwt_token}",
133
+ 'QUERY_STRING' => "token_jwt=#{valid_jwe_token}",
134
134
  }
135
135
  request = Rack.new(token_private_key: test_private_key,
136
136
  request: raw_request)
@@ -156,7 +156,7 @@ describe Rack do
156
156
 
157
157
  it 'finds the JSON web token from the params' do
158
158
  raw_request = {
159
- 'QUERY_STRING' => "token_jwt=#{valid_jwt_token}",
159
+ 'QUERY_STRING' => "token_jwt=#{valid_jwe_token}",
160
160
  }
161
161
  request = Rack.new(token_private_key: test_private_key,
162
162
  request: raw_request)
@@ -193,7 +193,7 @@ describe Rack do
193
193
  expect(request.authorization_token_from_params).not_to be_blank
194
194
 
195
195
  raw_request = {
196
- 'QUERY_STRING' => "token_jwt=#{invalid_jwt_token}",
196
+ 'QUERY_STRING' => "token_jwt=#{invalid_jwe_token}",
197
197
  }
198
198
  request = Rack.new(token_private_key: test_private_key,
199
199
  request: raw_request)
@@ -45,7 +45,7 @@ describe Rails do
45
45
  it 'finds the authorization token from the header' do
46
46
  raw_request = OpenStruct.new(
47
47
  headers: {
48
- 'HTTP_AUTHORIZATION' => "Token #{valid_jwt_token}",
48
+ 'HTTP_AUTHORIZATION' => "Token #{valid_jwe_token}",
49
49
  },
50
50
  params: {})
51
51
  request = Rails.new(token_private_key: test_private_key,
@@ -90,7 +90,7 @@ describe Rails do
90
90
  it 'ignores incorrectly passed in tokens since we do not know what to do' do
91
91
  raw_request = OpenStruct.new(
92
92
  headers: {
93
- 'HTTP_AUTHORIZATION' => "#{valid_jwt_token}",
93
+ 'HTTP_AUTHORIZATION' => "#{valid_jwe_token}",
94
94
  },
95
95
  params: {})
96
96
  request = Rails.new(token_private_key: test_private_key,
@@ -105,9 +105,9 @@ describe Rails do
105
105
 
106
106
  raw_request = OpenStruct.new(
107
107
  headers: {
108
- 'HTTP_AUTHORIZATION' => "Token #{invalid_jwt_token}",
108
+ 'HTTP_AUTHORIZATION' => "Token #{invalid_jwe_token}",
109
109
  },
110
- params: { 'token_jwt' => valid_jwt_token })
110
+ params: { 'token_jwt' => valid_jwe_token })
111
111
  request = Rails.new(token_private_key: test_private_key,
112
112
  request: raw_request)
113
113
 
@@ -124,7 +124,7 @@ describe Rails do
124
124
 
125
125
  raw_request = OpenStruct.new(
126
126
  headers: {},
127
- params: { 'token_jwt' => valid_jwt_token })
127
+ params: { 'token_jwt' => valid_jwe_token })
128
128
  request = Rails.new(token_private_key: test_private_key,
129
129
  request: raw_request)
130
130
 
@@ -150,7 +150,7 @@ describe Rails do
150
150
  it 'finds the JSON web token from the params' do
151
151
  raw_request = OpenStruct.new(
152
152
  headers: {},
153
- params: { 'token_jwt' => valid_jwt_token })
153
+ params: { 'token_jwt' => valid_jwe_token })
154
154
  request = Rails.new(token_private_key: test_private_key,
155
155
  request: raw_request)
156
156
 
@@ -187,7 +187,7 @@ describe Rails do
187
187
 
188
188
  raw_request = OpenStruct.new(
189
189
  headers: {},
190
- params: { 'token_jwt' => invalid_jwt_token })
190
+ params: { 'token_jwt' => invalid_jwe_token })
191
191
  request = Rails.new(token_private_key: test_private_key,
192
192
  request: raw_request)
193
193
 
@@ -4,46 +4,131 @@ require 'apill/tokens/json_web_token'
4
4
  module Apill
5
5
  module Tokens
6
6
  describe JsonWebToken do
7
- it 'can convert an empty token' do
8
- token = JsonWebToken.convert(token_private_key: test_private_key,
9
- raw_token: nil)
7
+ it 'can convert an empty encrypted token' do
8
+ token = JsonWebToken.from_jwe(nil,
9
+ private_key: test_private_key)
10
10
 
11
11
  expect(token).to be_a JsonWebTokens::Null
12
12
  end
13
13
 
14
- it 'can convert an invalid token' do
15
- token = JsonWebToken.convert(token_private_key: test_private_key,
16
- raw_token: invalid_jwt_token)
14
+ it 'can convert an invalid encrypted token' do
15
+ token = JsonWebToken.from_jwe(invalid_jwe_token,
16
+ private_key: test_private_key)
17
17
 
18
18
  expect(token).to be_a JsonWebTokens::Invalid
19
19
  end
20
20
 
21
- it 'can verify an expired token' do
22
- expired_jwe = valid_jwt_token('exp' => 1.day.ago.to_i,
21
+ it 'can verify an expired encrypted token' do
22
+ expired_jwe = valid_jwe_token('exp' => 1.day.ago.to_i,
23
23
  'baz' => 'bar')
24
- token = JsonWebToken.convert(
25
- token_private_key: test_private_key,
26
- raw_token: expired_jwe)
24
+ token = JsonWebToken.from_jwe(expired_jwe,
25
+ private_key: test_private_key)
27
26
 
28
27
  expect(token).to be_a JsonWebTokens::Invalid
29
28
  end
30
29
 
31
- it 'can convert an invalidly signed token' do
30
+ it 'can convert an invalidly signed encrypted token' do
32
31
  other_private_key = OpenSSL::PKey::RSA.new(2048)
33
- token = JsonWebToken.convert(
34
- token_private_key: other_private_key,
35
- raw_token: valid_jwt_token)
32
+ token = JsonWebToken.from_jwe(valid_jwe_token,
33
+ private_key: other_private_key)
36
34
 
37
35
  expect(token).to be_a JsonWebTokens::Invalid
38
36
  end
39
37
 
40
- it 'can convert a valid token' do
41
- token = JsonWebToken.convert(token_private_key: test_private_key,
42
- raw_token: valid_jwt_token)
38
+ it 'can convert a valid encrypted token' do
39
+ token = JsonWebToken.from_jwe(valid_jwe_token,
40
+ private_key: test_private_key)
43
41
 
44
42
  expect(token).to be_a JsonWebToken
45
43
  expect(token.to_h).to eql([{ 'bar' => 'baz' }, { 'typ' => 'JWT', 'alg' => 'RS256' }])
46
44
  end
45
+
46
+ it 'can convert an empty signed token' do
47
+ token = JsonWebToken.from_jws(nil,
48
+ private_key: test_private_key)
49
+
50
+ expect(token).to be_a JsonWebTokens::Null
51
+ end
52
+
53
+ it 'can verify an expired signed token' do
54
+ expired_jws = valid_jws_token('exp' => 1.day.ago.to_i,
55
+ 'baz' => 'bar')
56
+ token = JsonWebToken.from_jws(expired_jws,
57
+ private_key: test_private_key)
58
+
59
+ expect(token).to be_a JsonWebTokens::Invalid
60
+ end
61
+
62
+ it 'can convert an invalidly signed token' do
63
+ other_private_key = OpenSSL::PKey::RSA.new(2048)
64
+ token_signed_with_another_key = JsonWebToken.from_jws(valid_jws_token,
65
+ private_key: other_private_key)
66
+ invalid_token = JsonWebToken.from_jws(invalid_jws_token,
67
+ private_key: test_private_key)
68
+
69
+ expect(token_signed_with_another_key).to be_a JsonWebTokens::Invalid
70
+ expect(invalid_token).to be_a JsonWebTokens::Invalid
71
+ end
72
+
73
+ it 'can convert a valid signed token' do
74
+ token = JsonWebToken.from_jws(valid_jws_token,
75
+ private_key: test_private_key)
76
+
77
+ expect(token).to be_a JsonWebToken
78
+ expect(token.to_h).to eql([{ 'bar' => 'baz' }, { 'typ' => 'JWT', 'alg' => 'RS256' }])
79
+ end
80
+
81
+ it 'can transform into a JWT' do
82
+ token = JsonWebToken.new(data: { 'foo' => 'bar' },
83
+ private_key: test_private_key)
84
+
85
+ jwt = token.to_jwt
86
+ jwt_s = token.to_jwt_s
87
+
88
+ expect(jwt.to_h).to eql('foo' => 'bar')
89
+ expect(jwt_s).to eql('eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJmb28iOiJiYXIifQ.')
90
+ end
91
+
92
+ # rubocop:disable Metrics/LineLength
93
+ it 'can transform into a JWS and back' do
94
+ token = JsonWebToken.new(data: { 'foo' => 'bar' },
95
+ private_key: test_private_key)
96
+
97
+ jws = token.to_jws
98
+ jws_s = token.to_jws_s
99
+
100
+ expect(jws.to_h).to eql('foo' => 'bar')
101
+ expect(jws_s).to eql('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.DhPBu9Bfha08hSoy1a8Ps5YGxv2_KJCoNALH8dzd8b_VgKCPRQlIaHZwQfS5N1yfZczc2EqXIhPma4I2i-L92oDxyugZYfhMH6XUXSgB6F7SU5WtiglQ8gfgxC_u_K5htD_6zpRaHi6UTNbG8NF3RFBYK9za4GFPPWQawRQpdH2CxjyZP6pilmkynLuKx0OeQbJf1yzdgn1cDt60M8uoZZTzPgoU598ilDjYEETwyGyCi79S3A3ix8oDaJLhM8stPOHLUeglKrkwxOFglzVs7bULjzxZlygZujsHfWu16cjp_P3b4TIH_hiH0-Cjin-EVt4va2TnfGJ8HDxHxzWn7g')
102
+
103
+ converted_token = JsonWebToken.from_jws(jws_s,
104
+ private_key: test_private_key)
105
+
106
+ expect(converted_token.to_h).to eql [
107
+ { 'foo' => 'bar' },
108
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
109
+ ]
110
+ end
111
+ # rubocop:enable Metrics/LineLength
112
+
113
+ # rubocop:disable Metrics/LineLength
114
+ it 'can transform into a JWE and back' do
115
+ token = JsonWebToken.new(data: { 'foo' => 'bar' },
116
+ private_key: test_private_key)
117
+
118
+ jwe = token.to_jwe
119
+ jwe_s = token.to_jwe_s
120
+
121
+ expect(jwe.plain_text).to eql('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.DhPBu9Bfha08hSoy1a8Ps5YGxv2_KJCoNALH8dzd8b_VgKCPRQlIaHZwQfS5N1yfZczc2EqXIhPma4I2i-L92oDxyugZYfhMH6XUXSgB6F7SU5WtiglQ8gfgxC_u_K5htD_6zpRaHi6UTNbG8NF3RFBYK9za4GFPPWQawRQpdH2CxjyZP6pilmkynLuKx0OeQbJf1yzdgn1cDt60M8uoZZTzPgoU598ilDjYEETwyGyCi79S3A3ix8oDaJLhM8stPOHLUeglKrkwxOFglzVs7bULjzxZlygZujsHfWu16cjp_P3b4TIH_hiH0-Cjin-EVt4va2TnfGJ8HDxHxzWn7g')
122
+
123
+ converted_token = JsonWebToken.from_jwe(jwe_s,
124
+ private_key: test_private_key)
125
+
126
+ expect(converted_token.to_h).to eql [
127
+ { 'foo' => 'bar' },
128
+ { 'typ' => 'JWT', 'alg' => 'RS256' },
129
+ ]
130
+ end
131
+ # rubocop:enable Metrics/LineLength
47
132
  end
48
133
  end
49
134
  end