apill 4.1.0 → 4.2.0

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