186 lines
6.3 KiB
Ruby
186 lines
6.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe Gitlab::Auth::DpopTokenUser, feature_category: :system_access do
|
|
include Auth::DpopTokenHelper
|
|
|
|
let_it_be(:user, freeze: true) { create(:user) }
|
|
let_it_be(:personal_access_token, freeze: true) { create(:personal_access_token, user: user) }
|
|
|
|
let(:personal_access_token_plaintext) { personal_access_token.token }
|
|
|
|
let(:ssh_public_key) { nil }
|
|
let(:ath) { nil }
|
|
let(:public_key_in_jwk) { nil }
|
|
let(:no_ath_claim) { false }
|
|
let(:exp) { Time.now.to_i + Gitlab::Auth::DpopToken::MAX_EXPIRY_TIME_IN_SECS }
|
|
let(:iat) { Time.now.to_i }
|
|
let(:dpop_proof) do
|
|
generate_dpop_proof_for(user, exp: exp, iat: iat, ssh_public_key: ssh_public_key, ath: ath,
|
|
public_key_in_jwk: public_key_in_jwk, no_ath_claim: no_ath_claim)
|
|
end
|
|
|
|
let(:dpop_token) do
|
|
Gitlab::Auth::DpopToken.new(data: dpop_proof.proof)
|
|
end
|
|
|
|
describe '#validate!' do
|
|
subject(:validate!) do
|
|
described_class.new(token: dpop_token, user: user,
|
|
personal_access_token_plaintext: personal_access_token_plaintext).validate!
|
|
end
|
|
|
|
context 'when the token is valid' do
|
|
it 'initializes with valid token' do
|
|
expect { validate! }.not_to raise_error
|
|
end
|
|
end
|
|
|
|
context "when input isn't valid" do
|
|
context 'when the DPoP token is invalid' do
|
|
let(:dpop_token) { Gitlab::Auth::DpopToken.new(data: 'invalid') }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Malformed JWT, unable to decode. Not enough or too many segments/)
|
|
end
|
|
end
|
|
|
|
context "when the PAT doesn't belong to the user" do
|
|
let(:personal_access_token_plaintext) { 'invalid' }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Personal access token does not belong to the requesting user/)
|
|
end
|
|
end
|
|
|
|
context "when the DPoP token isn't valid for the user" do
|
|
context 'when the DPoP token is expired' do
|
|
let(:exp) { Time.now.to_i - 400 }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Signature expired/)
|
|
end
|
|
end
|
|
|
|
context 'when the DPoP token is missing a claim' do
|
|
let(:no_ath_claim) { true }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Missing required claim ath/)
|
|
end
|
|
end
|
|
|
|
context 'when the DPoP token has invalid iat' do
|
|
let(:iat) { Time.now.to_i + 4000 }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Invalid IAT value/)
|
|
end
|
|
end
|
|
|
|
context "when the jwk value is malformed" do
|
|
let(:public_key_in_jwk) { { kty: Auth::DpopTokenHelper::VALID_KTY } }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Key format is invalid for RSA/)
|
|
end
|
|
end
|
|
|
|
context "when the jwk value is invalid" do
|
|
let(:public_key_in_jwk) { { kty: Auth::DpopTokenHelper::VALID_KTY, n: '', e: '' } }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Failed to parse JWK: invalid JWK/)
|
|
end
|
|
end
|
|
|
|
context "when the jwk contains a private key" do
|
|
jwk_private_key = JWT::JWK.new(OpenSSL::PKey::RSA.generate(2048))
|
|
let(:public_key_in_jwk) do
|
|
{
|
|
kty: jwk_private_key[:kty],
|
|
n: jwk_private_key[:n],
|
|
e: jwk_private_key[:e],
|
|
d: jwk_private_key[:d],
|
|
p: jwk_private_key[:p],
|
|
q: jwk_private_key[:q],
|
|
dp: jwk_private_key[:dp],
|
|
dq: jwk_private_key[:dq],
|
|
qi: jwk_private_key[:qi],
|
|
kid: jwk_private_key[:kid]
|
|
}
|
|
end
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/JWK contains private key/)
|
|
end
|
|
end
|
|
end
|
|
|
|
context 'when the access token hash is incorrect' do
|
|
let(:ath) { 'incorrect' }
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Incorrect access token hash in JWT/)
|
|
end
|
|
end
|
|
|
|
context 'when the SSH public key is invalid' do
|
|
it 'raises DpopValidationError' do
|
|
allow(SSHData::PublicKey).to receive(:parse_openssh)
|
|
.with(dpop_proof.ssh_public_key)
|
|
.and_raise(SSHData::DecodeError)
|
|
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Unable to parse public key/)
|
|
end
|
|
end
|
|
|
|
context 'when then SSH public key is unsupported' do
|
|
let(:ssh_public_key) do
|
|
# rubocop:disable Layout/LineLength -- Value is not important
|
|
'ssh-dss AAAAB3NzaC1kc3MAAACBAIOcJlrYLOHfYxRY/i5nw2vcQZ8QpkNLObfjEBl5DsTWCXbKkkbNIkABpMjpH22nxJxzqolyiG8hwIRvtPmwUd3o4x+kvaWXmabPQhs6xDlHMV30ZXwliT5qjb04AkBtH1QH1wz6e9tEAMlUi7pCU76NREWjTjc30s+NmOIqItBhAAAAFQDhJ3PVkV2ytA24pZkr6QMppFzPxQAAAIBF4oicI5FBc0w4C8USL37NIa0uxrAPQ/Zkz3b0hdNNAmnNFjN9PihGrsTURa9zBgpV5tM2LfvS9qlAgnrbu7VY8NV0OSTxXeUfkn+k40DspHfY0sl8IKGvCYt0uO3tpfu0lZOJFgr/vFc4ODzE5QEm9eKMsWX8SJbRuXaGMn0myQAAAIBSzNgHW/lU/BgycBefJpe1NGnVVGRBPI9QHjxh/6HvyFHYc2N506wPDiRXyX03QREoUe4VXMWbOoFHpjZWX9dhwvvg3vBBQMeH7I5V0o5sEXbvdgtXBDtoiZlbZSiSC9wvw4c7rwSWsfm+iF1Ub1XPf2ALwh/BWHJzb93viCePcg=='
|
|
# rubocop:enable Layout/LineLength
|
|
end
|
|
|
|
it 'raises DpopValidationError' do
|
|
expect do
|
|
validate!
|
|
end.to raise_error(Gitlab::Auth::DpopValidationError,
|
|
/Currently only RSA keys are supported/)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|