summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorMarkus Koller <markus-koller@gmx.ch>2016-12-09 18:36:50 +0100
committerAlexis Reigel <mail@koffeinfrei.org>2017-03-07 14:54:35 +0100
commitc4982890489d254da2fe998aab30bf257767ed5e (patch)
tree0828e0cab70cabaceffcc0d588db32ac38ddf310 /spec
parentfb4a486605e10692b5577f0700fbce38bebcc311 (diff)
downloadgitlab-ce-c4982890489d254da2fe998aab30bf257767ed5e.tar.gz
Implement OpenID Connect identity provider
Diffstat (limited to 'spec')
-rw-r--r--spec/factories/oauth_access_grants.rb11
-rw-r--r--spec/factories/oauth_access_tokens.rb3
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/initializers/secret_token_spec.rb25
-rw-r--r--spec/requests/openid_connect_spec.rb134
-rw-r--r--spec/routing/openid_connect_spec.rb30
6 files changed, 200 insertions, 5 deletions
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
new file mode 100644
index 00000000000..543b3e99274
--- /dev/null
+++ b/spec/factories/oauth_access_grants.rb
@@ -0,0 +1,11 @@
+FactoryGirl.define do
+ factory :oauth_access_grant do
+ resource_owner_id { create(:user).id }
+ application
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ expires_in 2.hours
+
+ redirect_uri { application.redirect_uri }
+ scopes { application.scopes }
+ end
+end
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index ccf02d0719b..a46bc1d8ce8 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -2,6 +2,7 @@ FactoryGirl.define do
factory :oauth_access_token do
resource_owner
application
- token '123456'
+ token { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
+ scopes { application.scopes }
end
end
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index d116a573830..86cdc208268 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,7 +1,7 @@
FactoryGirl.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
name { FFaker::Name.name }
- uid { FFaker::Name.name }
+ uid { Doorkeeper::OAuth::Helpers::UniqueToken.generate }
redirect_uri { FFaker::Internet.uri('http') }
owner
owner_type 'User'
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index ad7f032d1e5..65c97da2efd 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -6,6 +6,9 @@ describe 'create_tokens', lib: true do
let(:secrets) { ActiveSupport::OrderedOptions.new }
+ HEX_KEY = /\h{128}/
+ RSA_KEY = /\A-----BEGIN RSA PRIVATE KEY-----\n.+\n-----END RSA PRIVATE KEY-----\n\Z/m
+
before do
allow(File).to receive(:write)
allow(File).to receive(:delete)
@@ -15,7 +18,7 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:exit)
end
- context 'setting secret_key_base and otp_key_base' do
+ context 'setting secret keys' do
context 'when none of the secrets exist' do
before do
stub_env('SECRET_KEY_BASE', nil)
@@ -24,19 +27,29 @@ describe 'create_tokens', lib: true do
allow(self).to receive(:warn_missing_secret)
end
- it 'generates different secrets for secret_key_base, otp_key_base, and db_key_base' do
+ it 'generates different hashes for secret_key_base, otp_key_base, and db_key_base' do
create_tokens
keys = secrets.values_at(:secret_key_base, :otp_key_base, :db_key_base)
expect(keys.uniq).to eq(keys)
- expect(keys.map(&:length)).to all(eq(128))
+ expect(keys).to all(match(HEX_KEY))
+ end
+
+ it 'generates an RSA key for jws_private_key' do
+ create_tokens
+
+ keys = secrets.values_at(:jws_private_key)
+
+ expect(keys.uniq).to eq(keys)
+ expect(keys).to all(match(RSA_KEY))
end
it 'warns about the secrets to add to secrets.yml' do
expect(self).to receive(:warn_missing_secret).with('secret_key_base')
expect(self).to receive(:warn_missing_secret).with('otp_key_base')
expect(self).to receive(:warn_missing_secret).with('db_key_base')
+ expect(self).to receive(:warn_missing_secret).with('jws_private_key')
create_tokens
end
@@ -48,6 +61,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq(secrets.secret_key_base)
expect(new_secrets['otp_key_base']).to eq(secrets.otp_key_base)
expect(new_secrets['db_key_base']).to eq(secrets.db_key_base)
+ expect(new_secrets['jws_private_key']).to eq(secrets.jws_private_key)
end
create_tokens
@@ -63,6 +77,7 @@ describe 'create_tokens', lib: true do
context 'when the other secrets all exist' do
before do
secrets.db_key_base = 'db_key_base'
+ secrets.jws_private_key = 'jws_private_key'
allow(File).to receive(:exist?).with('.secret').and_return(true)
allow(File).to receive(:read).with('.secret').and_return('file_key')
@@ -73,6 +88,7 @@ describe 'create_tokens', lib: true do
stub_env('SECRET_KEY_BASE', 'env_key')
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not issue a warning' do
@@ -98,6 +114,7 @@ describe 'create_tokens', lib: true do
before do
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
+ secrets.jws_private_key = 'jws_private_key'
end
it 'does not write any files' do
@@ -112,6 +129,7 @@ describe 'create_tokens', lib: true do
expect(secrets.secret_key_base).to eq('secret_key_base')
expect(secrets.otp_key_base).to eq('otp_key_base')
expect(secrets.db_key_base).to eq('db_key_base')
+ expect(secrets.jws_private_key).to eq('jws_private_key')
end
it 'deletes the .secret file' do
@@ -135,6 +153,7 @@ describe 'create_tokens', lib: true do
expect(new_secrets['secret_key_base']).to eq('file_key')
expect(new_secrets['otp_key_base']).to eq('file_key')
expect(new_secrets['db_key_base']).to eq('db_key_base')
+ expect(new_secrets['jws_private_key']).to eq('jws_private_key')
end
create_tokens
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
new file mode 100644
index 00000000000..5206634bca5
--- /dev/null
+++ b/spec/requests/openid_connect_spec.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe 'OpenID Connect requests' do
+ include ApiHelpers
+
+ let(:user) { create :user }
+ 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 }
+
+ def request_access_token
+ login_as user
+
+ post '/oauth/token',
+ grant_type: 'authorization_code',
+ code: access_grant.token,
+ redirect_uri: application.redirect_uri,
+ client_id: application.uid,
+ client_secret: application.secret
+ end
+
+ def request_user_info
+ get '/oauth/userinfo', nil, 'Authorization' => "Bearer #{access_token.token}"
+ end
+
+ def hashed_subject
+ Digest::SHA256.hexdigest("#{user.id}-#{Rails.application.secrets.secret_key_base}")
+ end
+
+ context 'Application without OpenID scope' do
+ let(:application) { create :oauth_application, scopes: 'api' }
+
+ it 'token response does not include an ID token' do
+ request_access_token
+
+ expect(json_response).to include 'access_token'
+ expect(json_response).not_to include 'id_token'
+ end
+
+ it 'userinfo response is unauthorized' do
+ request_user_info
+
+ expect(response).to have_http_status 403
+ 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
+ request_access_token
+
+ expect(json_response).to include 'id_token'
+ end
+
+ context 'UserInfo payload' do
+ let(:user) do
+ create(
+ :user,
+ name: 'Alice',
+ username: 'alice',
+ emails: [private_email, public_email],
+ email: private_email.email,
+ public_email: public_email.email,
+ website_url: 'https://example.com',
+ avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png"),
+ )
+ end
+
+ let(:public_email) { build :email, email: 'public@example.com' }
+ let(:private_email) { build :email, email: 'private@example.com' }
+
+ it 'includes all user information' do
+ request_user_info
+
+ expect(json_response).to eq({
+ 'sub' => hashed_subject,
+ 'name' => 'Alice',
+ 'nickname' => 'alice',
+ 'email' => 'public@example.com',
+ 'email_verified' => true,
+ 'website' => 'https://example.com',
+ 'profile' => 'http://localhost/alice',
+ 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png",
+ })
+ end
+ end
+
+ context 'ID token payload' do
+ before do
+ request_access_token
+ @payload = JSON::JWT.decode(json_response['id_token'], :skip_verification)
+ end
+
+ it 'includes the Gitlab root URL' do
+ expect(@payload['iss']).to eq Gitlab.config.gitlab.url
+ end
+
+ it 'includes the hashed user ID' do
+ expect(@payload['sub']).to eq hashed_subject
+ end
+
+ it 'includes the time of the last authentication' do
+ expect(@payload['auth_time']).to eq user.current_sign_in_at.to_i
+ end
+
+ it 'does not include any unknown properties' do
+ expect(@payload.keys).to eq %w[iss sub aud exp iat auth_time]
+ end
+ end
+
+ context 'when user is blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+
+ context 'when user is ldap_blocked' do
+ it 'returns authentication error' do
+ access_grant
+ user.ldap_block
+
+ expect do
+ request_access_token
+ end.to throw_symbol :warden
+ end
+ end
+ end
+end
diff --git a/spec/routing/openid_connect_spec.rb b/spec/routing/openid_connect_spec.rb
new file mode 100644
index 00000000000..2c3bc08f1a1
--- /dev/null
+++ b/spec/routing/openid_connect_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+# oauth_discovery_keys GET /oauth/discovery/keys(.:format) doorkeeper/openid_connect/discovery#keys
+# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
+# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
+describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
+ it "to #provider" do
+ expect(get('/.well-known/openid-configuration')).to route_to('doorkeeper/openid_connect/discovery#provider')
+ end
+
+ it "to #webfinger" do
+ expect(get('/.well-known/webfinger')).to route_to('doorkeeper/openid_connect/discovery#webfinger')
+ end
+
+ it "to #keys" do
+ expect(get('/oauth/discovery/keys')).to route_to('doorkeeper/openid_connect/discovery#keys')
+ end
+end
+
+# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+# POST /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
+describe Doorkeeper::OpenidConnect::UserinfoController, 'routing' do
+ it "to #show" do
+ expect(get('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+
+ it "to #show" do
+ expect(post('/oauth/userinfo')).to route_to('doorkeeper/openid_connect/userinfo#show')
+ end
+end