atlassian-jwt-authentication 0.9.0.pre8 → 0.9.1.pre9
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d4a3c825a4085266a663c307db837c9daf4c12c002c0dc8a31daddfab67993ef
|
4
|
+
data.tar.gz: b342bd603a6a1ab474bea589babfb232b0aeb49486e9df1418151c6675a291e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4440680e3c850ec815176ec2d6f29c88d466e53c72a22f85aefb7f0ee86c85505477a84562232ad72fceaf557f46c67a72aefeeeed9f3a2ad0e7e013c195c86
|
7
|
+
data.tar.gz: 496ed5d17f08b4577db4b74a6dac5473807ee250378509a431fd891e0818e26ddc4da3a05f99d3689980105f2efd51aa0b96731cbe5618629ae9a420de7a3a4c
|
data/README.md
CHANGED
@@ -256,6 +256,12 @@ Config | Environment variable | Description | Default |
|
|
256
256
|
`AtlassianJwtAuthentication.debug_requests` | `AJA_DEBUG_REQUESTS` | when `true` HTTP requests will include body content, implicitly turns on `log_requests` | `false`
|
257
257
|
`AtlassianJwtAuthentication.signed_install` | `AJA_SIGNED_INSTALL` | Installation lifecycle security improvements. Migration process described [here](https://community.developer.atlassian.com/t/action-required-atlassian-connect-installation-lifecycle-security-improvements/49046). In the descriptor set `"apiMigrations":{"signed-install":AtlassianJwtAuthentication.signed_install}` | `false`
|
258
258
|
|
259
|
+
### Middleware configuration
|
260
|
+
|
261
|
+
Parameter | Description /
|
262
|
+
--------- |-------------|
|
263
|
+
`force_asymmetric_verify` | A `proc` expected to return 'true' if the currently processed request must be validated with RS256 algorithm. Used for _signed_install_ endpoints.
|
264
|
+
|
259
265
|
## Requirements
|
260
266
|
|
261
267
|
Ruby 2.0+, ActiveRecord 4.1+
|
@@ -24,17 +24,17 @@ module AtlassianJwtAuthentication
|
|
24
24
|
base_url = params[:baseUrl]
|
25
25
|
api_base_url = params[:baseApiUrl] || base_url
|
26
26
|
|
27
|
+
# All install (including upgrades) / uninstall hooks are asymmetrically
|
28
|
+
# signed with RS256 (RSA Signature with SHA-256) algorithm
|
29
|
+
return false unless _verify_jwt(addon_key, force_asymmetric_verify: true)
|
30
|
+
|
27
31
|
jwt_auth = JwtToken.where(client_key: client_key, addon_key: addon_key).first
|
28
|
-
if jwt_auth
|
29
|
-
# The add-on was previously installed on this client
|
30
|
-
return false unless _verify_jwt(addon_key)
|
31
|
-
if jwt_auth.id != current_jwt_token.id
|
32
|
-
# Update request was issued to another plugin
|
33
|
-
render_forbidden
|
34
|
-
return false
|
35
|
-
end
|
36
|
-
else
|
32
|
+
if jwt_auth.nil?
|
37
33
|
self.current_jwt_token = JwtToken.new(jwt_token_params)
|
34
|
+
elsif jwt_auth.id != current_jwt_token.id
|
35
|
+
# Update request was issued to another plugin
|
36
|
+
render_forbidden
|
37
|
+
return false
|
38
38
|
end
|
39
39
|
|
40
40
|
current_jwt_token.addon_key = addon_key
|
@@ -55,7 +55,7 @@ module AtlassianJwtAuthentication
|
|
55
55
|
def on_add_on_uninstalled
|
56
56
|
addon_key = params[:key]
|
57
57
|
|
58
|
-
return unless _verify_jwt(addon_key)
|
58
|
+
return unless _verify_jwt(addon_key, force_asymmetric_verify: true)
|
59
59
|
|
60
60
|
client_key = params[:clientKey]
|
61
61
|
|
@@ -67,7 +67,7 @@ module AtlassianJwtAuthentication
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def verify_jwt(addon_key, skip_qsh_verification: false)
|
70
|
-
_verify_jwt(addon_key, true, skip_qsh_verification: skip_qsh_verification)
|
70
|
+
_verify_jwt(addon_key, consider_param: true, skip_qsh_verification: skip_qsh_verification)
|
71
71
|
end
|
72
72
|
|
73
73
|
def ensure_license
|
@@ -93,7 +93,7 @@ module AtlassianJwtAuthentication
|
|
93
93
|
end
|
94
94
|
|
95
95
|
unless response.data['state'] == 'ENABLED' &&
|
96
|
-
|
96
|
+
response.data['license'] && response.data['license']['active']
|
97
97
|
log(:error, "client #{current_jwt_token.client_key}: no active & enabled license was found")
|
98
98
|
render_payment_required
|
99
99
|
return false
|
@@ -107,7 +107,7 @@ module AtlassianJwtAuthentication
|
|
107
107
|
|
108
108
|
private
|
109
109
|
|
110
|
-
def _verify_jwt(addon_key, consider_param
|
110
|
+
def _verify_jwt(addon_key, consider_param: false, skip_qsh_verification: false, force_asymmetric_verify: false)
|
111
111
|
self.current_jwt_token = nil
|
112
112
|
self.current_account_id = nil
|
113
113
|
self.current_jwt_context = nil
|
@@ -130,7 +130,7 @@ module AtlassianJwtAuthentication
|
|
130
130
|
jwt = possible_jwt if algorithm == 'JWT'
|
131
131
|
end
|
132
132
|
|
133
|
-
jwt_verification = AtlassianJwtAuthentication::JWTVerification.new(addon_key, nil, jwt, request)
|
133
|
+
jwt_verification = AtlassianJwtAuthentication::JWTVerification.new(addon_key, nil, force_asymmetric_verify, jwt, request)
|
134
134
|
jwt_verification.exclude_qsh_params = exclude_qsh_params
|
135
135
|
jwt_verification.logger = logger if defined?(logger)
|
136
136
|
|
@@ -150,8 +150,8 @@ module AtlassianJwtAuthentication
|
|
150
150
|
|
151
151
|
def jwt_token_params
|
152
152
|
{
|
153
|
-
|
154
|
-
|
153
|
+
client_key: params.permit(:clientKey)['clientKey'],
|
154
|
+
addon_key: params.permit(:key)['key']
|
155
155
|
}
|
156
156
|
end
|
157
157
|
|
@@ -13,6 +13,7 @@ module AtlassianJwtAuthentication
|
|
13
13
|
@addon_key = options[:addon_key]
|
14
14
|
@if = options[:if]
|
15
15
|
@audience = options[:audience]
|
16
|
+
@force_asymmetric_verify = options[:force_asymmetric_verify]
|
16
17
|
end
|
17
18
|
|
18
19
|
def call(env)
|
@@ -31,7 +32,9 @@ module AtlassianJwtAuthentication
|
|
31
32
|
end
|
32
33
|
|
33
34
|
if jwt
|
34
|
-
|
35
|
+
force_asymmetric_verify = @force_asymmetric_verify && @force_asymmetric_verify.call(env)
|
36
|
+
|
37
|
+
jwt_verification = JWTVerification.new(@addon_key, @audience, force_asymmetric_verify, jwt, request)
|
35
38
|
jwt_auth, account_id, context, qsh_verified = jwt_verification.verify
|
36
39
|
|
37
40
|
if jwt_auth
|
@@ -2,11 +2,12 @@ require 'jwt'
|
|
2
2
|
|
3
3
|
module AtlassianJwtAuthentication
|
4
4
|
class JWTVerification
|
5
|
-
attr_accessor :addon_key, :jwt, :audience, :request, :exclude_qsh_params, :logger
|
5
|
+
attr_accessor :addon_key, :jwt, :audience, :force_asymmetric_verify, :request, :exclude_qsh_params, :logger
|
6
6
|
|
7
|
-
def initialize(addon_key, audience, jwt, request, &block)
|
7
|
+
def initialize(addon_key, audience, force_asymmetric_verify, jwt, request, &block)
|
8
8
|
self.addon_key = addon_key
|
9
9
|
self.audience = audience
|
10
|
+
self.force_asymmetric_verify = force_asymmetric_verify
|
10
11
|
self.jwt = jwt
|
11
12
|
self.request = request
|
12
13
|
|
@@ -39,18 +40,14 @@ module AtlassianJwtAuthentication
|
|
39
40
|
addon_key: addon_key
|
40
41
|
).first
|
41
42
|
|
42
|
-
unless jwt_auth
|
43
|
-
log(:error, "Could not find jwt_token for client_key #{data['iss']} and addon_key #{addon_key}")
|
44
|
-
return false
|
45
|
-
end
|
46
|
-
|
47
43
|
# Discard the tokens without verification
|
48
44
|
if encoding_data['alg'] == 'none'
|
49
45
|
log(:error, "The JWT checking algorithm was set to none for client_key #{data['iss']} and addon_key #{addon_key}")
|
50
46
|
return false
|
51
47
|
end
|
52
48
|
|
53
|
-
if
|
49
|
+
if force_asymmetric_verify ||
|
50
|
+
AtlassianJwtAuthentication.signed_install && encoding_data['alg'] == 'RS256'
|
54
51
|
response = Faraday.get("https://connect-install-keys.atlassian.com/#{encoding_data['kid']}")
|
55
52
|
unless response.success? && response.body
|
56
53
|
log(:error, "Error retrieving atlassian public key. Response code #{response.status} and kid #{encoding_data['kid']}")
|
@@ -58,15 +55,24 @@ module AtlassianJwtAuthentication
|
|
58
55
|
end
|
59
56
|
|
60
57
|
decode_key = OpenSSL::PKey::RSA.new(response.body)
|
61
|
-
decode_options = {algorithms: ['RS256'], verify_aud: true, aud: audience}
|
62
58
|
else
|
59
|
+
unless jwt_auth
|
60
|
+
log(:error, "Could not find jwt_token for client_key #{data['iss']} and addon_key #{addon_key}")
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
|
63
64
|
decode_key = jwt_auth.shared_secret
|
64
|
-
|
65
|
+
end
|
66
|
+
|
67
|
+
decode_options = {}
|
68
|
+
if encoding_data['alg'] == 'RS256'
|
69
|
+
decode_options = { algorithms: ['RS256'] }
|
65
70
|
end
|
66
71
|
|
67
72
|
# Decode the token again, this time with signature & claims verification
|
68
73
|
options = JWT::DefaultOptions::DEFAULT_OPTIONS.merge(verify_expiration: AtlassianJwtAuthentication.verify_jwt_expiration).merge(decode_options)
|
69
74
|
decoder = JWT::Decode.new(jwt, decode_key, true, options)
|
75
|
+
|
70
76
|
begin
|
71
77
|
payload, header = decoder.decode_segments
|
72
78
|
rescue JWT::VerificationError
|
@@ -85,7 +91,7 @@ module AtlassianJwtAuthentication
|
|
85
91
|
if data['qsh']
|
86
92
|
# Verify the query has not been tampered by Creating a Query Hash and
|
87
93
|
# comparing it against the qsh claim on the verified token
|
88
|
-
if jwt_auth.base_url.present? && request.url.include?(jwt_auth.base_url)
|
94
|
+
if jwt_auth.present? && jwt_auth.base_url.present? && request.url.include?(jwt_auth.base_url)
|
89
95
|
path = request.url.gsub(jwt_auth.base_url, '')
|
90
96
|
else
|
91
97
|
path = request.path.gsub(AtlassianJwtAuthentication::context_path, '')
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module AtlassianJwtAuthentication
|
2
2
|
MAJOR_VERSION = "0"
|
3
3
|
MINOR_VERSION = "9"
|
4
|
-
|
4
|
+
PATCH_VERSION = "1"
|
5
5
|
|
6
6
|
# rubygems don't support semantic versioning - https://github.com/rubygems/rubygems/issues/592, using GITHUB_RUN_NUMBER to represent build number
|
7
7
|
# going to release pre versions automatically
|
@@ -12,7 +12,7 @@ module AtlassianJwtAuthentication
|
|
12
12
|
[
|
13
13
|
MAJOR_VERSION,
|
14
14
|
MINOR_VERSION,
|
15
|
-
|
15
|
+
PATCH_VERSION
|
16
16
|
].join(".") + BUILD_NUMBER
|
17
17
|
).freeze
|
18
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: atlassian-jwt-authentication
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.1.pre9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Laura Barladeanu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: addressable
|