2022-07-16 23:28:13 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Atlassian
|
|
|
|
module JiraConnect
|
|
|
|
module Jwt
|
|
|
|
# See documentation about Atlassian asymmetric JWT verification:
|
|
|
|
# https://developer.atlassian.com/cloud/jira/platform/understanding-jwt-for-connect-apps/#verifying-a-asymmetric-jwt-token-for-install-callbacks
|
|
|
|
|
|
|
|
class Asymmetric
|
|
|
|
include Gitlab::Utils::StrongMemoize
|
|
|
|
|
|
|
|
KeyFetchError = Class.new(StandardError)
|
|
|
|
|
|
|
|
ALGORITHM = 'RS256'
|
2023-01-13 00:05:48 +05:30
|
|
|
DEFAULT_PUBLIC_KEY_CDN_URL = 'https://connect-install-keys.atlassian.com'
|
|
|
|
PROXY_PUBLIC_KEY_PATH = '/-/jira_connect/public_keys'
|
2022-07-16 23:28:13 +05:30
|
|
|
UUID4_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/.freeze
|
|
|
|
|
|
|
|
def initialize(token, verification_claims)
|
|
|
|
@token = token
|
|
|
|
@verification_claims = verification_claims
|
|
|
|
end
|
|
|
|
|
|
|
|
def valid?
|
|
|
|
claims.present? && claims['qsh'] == verification_qsh
|
|
|
|
end
|
|
|
|
|
|
|
|
def iss_claim
|
|
|
|
return unless claims
|
|
|
|
|
|
|
|
claims['iss']
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def claims
|
|
|
|
strong_memoize(:claims) do
|
|
|
|
_, jwt_headers = decode_token
|
|
|
|
public_key = retrieve_public_key(jwt_headers['kid'])
|
|
|
|
|
|
|
|
decoded_claims(public_key)
|
|
|
|
rescue JWT::DecodeError, OpenSSL::PKey::PKeyError, KeyFetchError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def decoded_claims(public_key)
|
|
|
|
decode_token(
|
|
|
|
public_key,
|
|
|
|
true,
|
|
|
|
**relevant_claims,
|
|
|
|
verify_aud: true,
|
|
|
|
verify_iss: true,
|
|
|
|
algorithm: ALGORITHM
|
|
|
|
).first
|
|
|
|
end
|
|
|
|
|
|
|
|
def decode_token(key = nil, verify = false, **claims)
|
|
|
|
Atlassian::Jwt.decode(@token, key, verify, **claims)
|
|
|
|
end
|
|
|
|
|
|
|
|
def retrieve_public_key(key_id)
|
|
|
|
raise KeyFetchError unless UUID4_REGEX.match?(key_id)
|
|
|
|
|
2023-01-13 00:05:48 +05:30
|
|
|
public_key = Gitlab::HTTP.try_get("#{public_key_cdn_url}/#{key_id}").try(:body)
|
2022-07-16 23:28:13 +05:30
|
|
|
|
|
|
|
raise KeyFetchError if public_key.blank?
|
|
|
|
|
|
|
|
OpenSSL::PKey.read(public_key)
|
|
|
|
end
|
|
|
|
|
|
|
|
def relevant_claims
|
|
|
|
@verification_claims.slice(:aud, :iss)
|
|
|
|
end
|
|
|
|
|
|
|
|
def verification_qsh
|
|
|
|
@verification_claims[:qsh]
|
|
|
|
end
|
2023-01-13 00:05:48 +05:30
|
|
|
|
|
|
|
def public_key_cdn_url
|
2023-03-04 22:38:38 +05:30
|
|
|
public_key_cdn_url_setting.presence || DEFAULT_PUBLIC_KEY_CDN_URL
|
2023-01-13 00:05:48 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def public_key_cdn_url_setting
|
|
|
|
@public_key_cdn_url_setting ||=
|
2023-03-17 16:20:25 +05:30
|
|
|
if Gitlab::CurrentSettings.jira_connect_proxy_url.present?
|
2023-01-13 00:05:48 +05:30
|
|
|
Gitlab::Utils.append_path(Gitlab::CurrentSettings.jira_connect_proxy_url, PROXY_PUBLIC_KEY_PATH)
|
|
|
|
end
|
|
|
|
end
|
2022-07-16 23:28:13 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|