2017-08-17 22:00:37 +05:30
|
|
|
require 'spec_helper'
|
|
|
|
|
|
|
|
describe 'OpenID Connect requests' do
|
2018-11-08 19:23:39 +05:30
|
|
|
let(:user) do
|
|
|
|
create(
|
|
|
|
:user,
|
|
|
|
name: 'Alice',
|
|
|
|
username: 'alice',
|
|
|
|
email: 'private@example.com',
|
|
|
|
emails: [public_email],
|
|
|
|
public_email: public_email.email,
|
|
|
|
website_url: 'https://example.com',
|
|
|
|
avatar: fixture_file_upload('spec/fixtures/dk.png')
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:public_email) { build :email, email: 'public@example.com' }
|
|
|
|
|
2017-08-17 22:00:37 +05:30
|
|
|
let(:access_grant) { create :oauth_access_grant, application: application, resource_owner_id: user.id }
|
|
|
|
let(:access_token) { create :oauth_access_token, application: application, resource_owner_id: user.id }
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
let(:hashed_subject) do
|
|
|
|
Digest::SHA256.hexdigest("#{user.id}-#{Rails.application.secrets.secret_key_base}")
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:id_token_claims) do
|
|
|
|
{
|
|
|
|
'sub' => user.id.to_s,
|
|
|
|
'sub_legacy' => hashed_subject
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:user_info_claims) do
|
|
|
|
{
|
|
|
|
'name' => 'Alice',
|
|
|
|
'nickname' => 'alice',
|
|
|
|
'email' => 'public@example.com',
|
2019-03-02 22:35:43 +05:30
|
|
|
'email_verified' => false,
|
2018-11-08 19:23:39 +05:30
|
|
|
'website' => 'https://example.com',
|
|
|
|
'profile' => 'http://localhost/alice',
|
|
|
|
'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png",
|
|
|
|
'groups' => kind_of(Array)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def request_access_token!
|
2017-08-17 22:00:37 +05:30
|
|
|
login_as user
|
|
|
|
|
|
|
|
post '/oauth/token',
|
2019-02-15 15:39:39 +05:30
|
|
|
params: {
|
|
|
|
grant_type: 'authorization_code',
|
|
|
|
code: access_grant.token,
|
|
|
|
redirect_uri: application.redirect_uri,
|
|
|
|
client_id: application.uid,
|
|
|
|
client_secret: application.secret
|
|
|
|
}
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
def request_user_info!
|
2019-02-15 15:39:39 +05:30
|
|
|
get '/oauth/userinfo', params: {}, headers: { 'Authorization' => "Bearer #{access_token.token}" }
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'Application without OpenID scope' do
|
|
|
|
let(:application) { create :oauth_application, scopes: 'api' }
|
|
|
|
|
|
|
|
it 'token response does not include an ID token' do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_access_token!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
expect(json_response).to include 'access_token'
|
|
|
|
expect(json_response).not_to include 'id_token'
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'userinfo response is unauthorized' do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_user_info!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
expect(response).to have_gitlab_http_status 403
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(response.body).to be_blank
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'Application with OpenID scope' do
|
|
|
|
let(:application) { create :oauth_application, scopes: 'openid' }
|
|
|
|
|
|
|
|
it 'token response includes an ID token' do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_access_token!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
expect(json_response).to include 'id_token'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'UserInfo payload' do
|
2018-03-27 19:54:05 +05:30
|
|
|
let!(:group1) { create :group }
|
|
|
|
let!(:group2) { create :group }
|
|
|
|
let!(:group3) { create :group, parent: group2 }
|
|
|
|
let!(:group4) { create :group, parent: group3 }
|
|
|
|
|
|
|
|
before do
|
|
|
|
group1.add_user(user, GroupMember::OWNER)
|
|
|
|
group3.add_user(user, Gitlab::Access::DEVELOPER)
|
2018-11-08 19:23:39 +05:30
|
|
|
|
|
|
|
request_user_info!
|
2018-03-27 19:54:05 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes all user information and group memberships' do
|
2018-11-08 19:23:39 +05:30
|
|
|
expect(json_response).to match(id_token_claims.merge(user_info_claims))
|
2018-03-27 19:54:05 +05:30
|
|
|
|
|
|
|
expected_groups = [group1.full_path, group3.full_path]
|
2019-10-12 21:52:04 +05:30
|
|
|
expected_groups << group4.full_path
|
2018-03-27 19:54:05 +05:30
|
|
|
expect(json_response['groups']).to match_array(expected_groups)
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
2018-11-08 19:23:39 +05:30
|
|
|
|
|
|
|
it 'does not include any unknown claims' do
|
|
|
|
expect(json_response.keys).to eq %w[sub sub_legacy] + user_info_claims.keys
|
|
|
|
end
|
2019-03-02 22:35:43 +05:30
|
|
|
|
|
|
|
it 'includes email and email_verified claims' do
|
|
|
|
expect(json_response.keys).to include('email', 'email_verified')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has public email in email claim' do
|
|
|
|
expect(json_response['email']).to eq(user.public_email)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has false in email_verified claim' do
|
|
|
|
expect(json_response['email_verified']).to eq(false)
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
context 'ID token payload' do
|
|
|
|
before do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_access_token!
|
2017-08-17 22:00:37 +05:30
|
|
|
@payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
|
|
|
|
end
|
|
|
|
|
2018-11-08 19:23:39 +05:30
|
|
|
it 'includes the subject claims' do
|
|
|
|
expect(@payload).to match(a_hash_including(id_token_claims))
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2018-12-05 23:21:45 +05:30
|
|
|
it 'includes the GitLab root URL' do
|
2018-11-08 19:23:39 +05:30
|
|
|
expect(@payload['iss']).to eq Gitlab.config.gitlab.url
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
|
2017-09-10 17:25:29 +05:30
|
|
|
it 'includes the time of the last authentication', :clean_gitlab_redis_shared_state do
|
2017-08-17 22:00:37 +05:30
|
|
|
expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'does not include any unknown properties' do
|
2018-11-08 19:23:39 +05:30
|
|
|
expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time sub_legacy]
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
# These 2 calls shouldn't actually throw, they should be handled as an
|
|
|
|
# unauthorized request, so we should be able to check the response.
|
|
|
|
#
|
|
|
|
# This was not possible due to an issue with Warden:
|
|
|
|
# https://github.com/hassox/warden/pull/162
|
|
|
|
#
|
|
|
|
# When the patch gets merged and we update Warden, these specs will need to
|
|
|
|
# updated to check the response instead of a raised exception.
|
|
|
|
# https://gitlab.com/gitlab-org/gitlab-ce/issues/40218
|
2017-08-17 22:00:37 +05:30
|
|
|
context 'when user is blocked' do
|
|
|
|
it 'returns authentication error' do
|
|
|
|
access_grant
|
2018-11-08 19:23:39 +05:30
|
|
|
user.block!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
expect do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_access_token!
|
2018-03-17 18:26:18 +05:30
|
|
|
end.to raise_error UncaughtThrowError
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'when user is ldap_blocked' do
|
|
|
|
it 'returns authentication error' do
|
|
|
|
access_grant
|
2018-11-08 19:23:39 +05:30
|
|
|
user.ldap_block!
|
2017-08-17 22:00:37 +05:30
|
|
|
|
|
|
|
expect do
|
2018-11-08 19:23:39 +05:30
|
|
|
request_access_token!
|
2018-03-17 18:26:18 +05:30
|
|
|
end.to raise_error UncaughtThrowError
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2018-05-09 12:01:36 +05:30
|
|
|
|
|
|
|
context 'OpenID configuration information' do
|
|
|
|
it 'correctly returns the configuration' do
|
|
|
|
get '/.well-known/openid-configuration'
|
|
|
|
|
|
|
|
expect(response).to have_gitlab_http_status(200)
|
2018-11-08 19:23:39 +05:30
|
|
|
expect(json_response['issuer']).to eq('http://localhost')
|
|
|
|
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
|
2019-07-31 22:56:46 +05:30
|
|
|
expect(json_response['scopes_supported']).to eq(%w[api read_user read_repository write_repository sudo openid profile email])
|
2019-03-02 22:35:43 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'Application with OpenID and email scopes' do
|
|
|
|
let(:application) { create :oauth_application, scopes: 'openid email' }
|
|
|
|
|
|
|
|
it 'token response includes an ID token' do
|
|
|
|
request_access_token!
|
|
|
|
|
|
|
|
expect(json_response).to include 'id_token'
|
|
|
|
end
|
|
|
|
|
|
|
|
context 'UserInfo payload' do
|
|
|
|
before do
|
|
|
|
request_user_info!
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'includes the email and email_verified claims' do
|
|
|
|
expect(json_response.keys).to include('email', 'email_verified')
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has private email in email claim' do
|
|
|
|
expect(json_response['email']).to eq(user.email)
|
|
|
|
end
|
|
|
|
|
|
|
|
it 'has true in email_verified claim' do
|
|
|
|
expect(json_response['email_verified']).to eq(true)
|
|
|
|
end
|
2018-05-09 12:01:36 +05:30
|
|
|
end
|
|
|
|
end
|
2017-08-17 22:00:37 +05:30
|
|
|
end
|