diff --git a/CHANGELOG-EE b/CHANGELOG-EE index 7b84bb7067..4d57fb8d74 100644 --- a/CHANGELOG-EE +++ b/CHANGELOG-EE @@ -4,6 +4,7 @@ v 7.6.0 - Dont show LDAP groups settings if LDAP disabled - Rebase on merge request. Introduced merge request option to rebase before merging - Better message for failed pushes because of git hooks + - Kerberos support for web interface and git HTTP v 7.5.3 - Only set up Sidetiq from a Sidekiq server process (fixes Redis::InheritedError) diff --git a/Gemfile b/Gemfile index 773484a8a0..62f8870cff 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,7 @@ gem 'omniauth-google-oauth2' gem 'omniauth-twitter' gem 'omniauth-github' gem 'omniauth-shibboleth' +gem 'omniauth-kerberos' # Extracting information from a git repository # Provide access to Gitlab::Git library diff --git a/Gemfile.lock b/Gemfile.lock index 8222fe7f9e..80076150c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -323,6 +323,11 @@ GEM omniauth-google-oauth2 (0.2.5) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-kerberos (0.2.0) + omniauth-multipassword + timfel-krb5-auth (~> 0.8) + omniauth-multipassword (0.4.1) + omniauth (~> 1.0) omniauth-oauth (1.0.1) oauth omniauth (~> 1.0) @@ -536,6 +541,7 @@ GEM thread_safe (0.3.4) tilt (1.4.1) timers (1.1.0) + timfel-krb5-auth (0.8) tinder (1.9.3) eventmachine (~> 1.0) faraday (~> 0.8) @@ -661,6 +667,7 @@ DEPENDENCIES omniauth (~> 1.1.3) omniauth-github omniauth-google-oauth2 + omniauth-kerberos omniauth-shibboleth omniauth-twitter org-ruby (= 0.9.9) diff --git a/app/assets/images/authbuttons/kerberos_32.png b/app/assets/images/authbuttons/kerberos_32.png new file mode 100644 index 0000000000..66b6f91d86 Binary files /dev/null and b/app/assets/images/authbuttons/kerberos_32.png differ diff --git a/app/assets/images/authbuttons/kerberos_64.png b/app/assets/images/authbuttons/kerberos_64.png new file mode 100644 index 0000000000..f22fbc57da Binary files /dev/null and b/app/assets/images/authbuttons/kerberos_64.png differ diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 3e984e5007..529825f183 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,5 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + protect_from_forgery except: :kerberos Gitlab.config.omniauth.providers.each do |provider| define_method provider['name'] do handle_omniauth diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index df18db71c8..889eeaf431 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -3,6 +3,14 @@ module OauthHelper Gitlab.config.ldap.enabled end + def kerberos_enabled? + enabled_oauth_providers.include?(:kerberos) + end + + def standard_login_form_only? + ldap_enabled? || kerberos_enabled? + end + def default_providers [:twitter, :github, :google_oauth2, :ldap] end @@ -13,11 +21,13 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :github, :google_oauth2].include?(name.to_sym) + [:twitter, :github, :google_oauth2, :kerberos].include?(name.to_sym) end end def additional_providers - enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} + enabled_oauth_providers.reject do |provider| + provider.to_s.starts_with?('ldap') || provider == :kerberos + end end end diff --git a/app/views/devise/sessions/_new_kerberos.html.haml b/app/views/devise/sessions/_new_kerberos.html.haml new file mode 100644 index 0000000000..73b5130675 --- /dev/null +++ b/app/views/devise/sessions/_new_kerberos.html.haml @@ -0,0 +1,5 @@ += form_tag(user_omniauth_callback_path(provider), id: 'new_kerberos_user' ) do + = text_field_tag :username, nil, {class: "form-control top", placeholder: "Kerberos Login", autofocus: "autofocus"} + = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} + %br/ + = button_tag "Kerberos Sign in", class: "btn-save btn" \ No newline at end of file diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index ca7e9570b4..d5e60d53e7 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -2,18 +2,26 @@ .login-heading %h3 Sign in .login-body - - if ldap_enabled? + - if standard_login_form_only? %ul.nav.nav-tabs - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if ldap_enabled? + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if kerberos_enabled? + %li{class: (:active unless ldap_enabled?)} + = link_to "Kerberos", "#tab-kerberos", 'data-toggle' => 'tab' - if gitlab_config.signin_enabled %li = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' .tab-content - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} - = render 'devise/sessions/new_ldap', provider: server['provider_name'] + - if ldap_enabled? + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + = render 'devise/sessions/new_ldap', provider: server['provider_name'] + - if kerberos_enabled? + %div#tab-kerberos.tab-pane{class: (:active unless ldap_enabled?)} + = render 'devise/sessions/new_kerberos', provider: :kerberos - if gitlab_config.signin_enabled %div#tab-signin.tab-pane = render 'devise/sessions/new_base' diff --git a/db/schema.rb b/db/schema.rb index 95bab864c0..5acf6f8ee9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -262,9 +262,9 @@ ActiveRecord::Schema.define(version: 20141205134006) do t.datetime "updated_at" t.string "type" t.string "description", default: "", null: false + t.string "avatar" t.string "ldap_cn" t.integer "ldap_access" - t.string "avatar" end add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree @@ -325,8 +325,8 @@ ActiveRecord::Schema.define(version: 20141205134006) do t.boolean "archived", default: false, null: false t.string "import_status" t.float "repository_size", default: 0.0 - t.text "merge_requests_template" t.integer "star_count", default: 0, null: false + t.text "merge_requests_template" t.boolean "merge_requests_rebase_enabled", default: false end diff --git a/doc/integration/README.md b/doc/integration/README.md index b5b88159f3..00131a8d50 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -10,6 +10,7 @@ See the documentation below for details on how to configure these services. - [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth. - [Jenkins](jenkins.md) Integrate with the Jenkins CI - [Slack](slack.md) Integrate with the Slack chat service +- [Kerberos](kerberos.md) Integrate with Kerberos ## Project services diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md new file mode 100644 index 0000000000..42252f64e4 --- /dev/null +++ b/doc/integration/kerberos.md @@ -0,0 +1,16 @@ +# Kerberos integration + +GitLab can be configured to allow your users to sign with their Kerberos credentials. +Kerberos integration can be enabled as a regular omniauth provider, edit [gitlab.rb (omnibus-gitlab)`](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#omniauth-google-twitter-github-login) or [gitlab.yml (source installations)](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/gitlab.yml.example) on your GitLab server and restart GitLab. You only need to specify the provider name. For example: + +``` +{ name: 'kerberos'} +``` + +You still need to configure your system for Kerberos usage, such as specifying realms. GitLab will make use of the system's Kerberos settings. + +The first time a user signs in with Kerberos credentials, GitLab will create a new GitLab user associated with the email, which is built from the kerberos username and realm. Existing GitLab users can go to profile > account and attach a Kerberos account. + +## HTTP git access + +A linked Kerberos account enables you to `git pull` and `git push` using your Kerberos account, as well as your standard GitLab credentials. \ No newline at end of file diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 30509528b8..6520b82e2a 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -2,6 +2,11 @@ module Gitlab class Auth def find(login, password) user = User.by_login(login) + + if Devise.omniauth_providers.include?(:kerberos) + kerberos_user = Gitlab::Kerberos::Authentication.login(login, password) + return kerberos_user if kerberos_user + end # If no user is found, or it's an LDAP server, try LDAP. # LDAP users are only authenticated via LDAP diff --git a/lib/gitlab/kerberos/authentication.rb b/lib/gitlab/kerberos/authentication.rb new file mode 100644 index 0000000000..6e9a2e6216 --- /dev/null +++ b/lib/gitlab/kerberos/authentication.rb @@ -0,0 +1,41 @@ +require "krb5_auth" +# This calls helps to authenticate to Kerberos by providing username and password + +module Gitlab + module Kerberos + class Authentication + def self.login(login, password) + return unless Devise.omniauth_providers.include?(:kerberos) + return unless login.present? && password.present? + + auth = new(login, password) + auth.login + end + + def initialize(login, password) + @login = login + @password = password + @krb5 = ::Krb5Auth::Krb5.new + end + + def valid? + @krb5.get_init_creds_password(@login, @password) + rescue ::Krb5Auth::Krb5::Exception + false + end + + def login + valid? && find_by_login(@login) + end + + private + + def find_by_login(login) + identity = ::Identity. + where(provider: :kerberos). + where('lower(extern_uid) = ?', login).last + identity && identity.user + end + end + end +end diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb index 453699136e..a4bc1a18c0 100644 --- a/spec/helpers/oauth_helper_spec.rb +++ b/spec/helpers/oauth_helper_spec.rb @@ -17,4 +17,16 @@ describe OauthHelper do helper.additional_providers.should == [] end end + + describe "kerberos_enabled?" do + it 'returns true' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github, :kerberos] } + helper.kerberos_enabled?.should be_true + end + + it 'returns false' do + allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] } + helper.kerberos_enabled?.should be_false + end + end end \ No newline at end of file diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 95fc7e16a1..fe85819396 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -35,6 +35,17 @@ describe Gitlab::Auth do expect( gl_auth.find(username, password) ).to_not eql user end + context "with kerberos" do + before { Devise.stub(omniauth_providers: [:kerberos]) } + + it "finds user" do + Gitlab::Kerberos::Authentication.stub(valid?: true) + Gitlab::Kerberos::Authentication.stub(email: user.email) + + expect( gl_auth.find(username, password) ).to eql user + end + end + context "with ldap enabled" do before { Gitlab::LDAP::Config.stub(enabled?: true) } diff --git a/spec/lib/gitlab/kerberos/authentication_spec.rb b/spec/lib/gitlab/kerberos/authentication_spec.rb new file mode 100644 index 0000000000..f75c4d0b23 --- /dev/null +++ b/spec/lib/gitlab/kerberos/authentication_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::Kerberos::Authentication do + let(:klass) { Gitlab::Kerberos::Authentication } + let(:user) { create(:omniauth_user, provider: :kerberos, extern_uid: 'gitlab') } + let(:login) { 'john' } + let(:password) { 'password' } + + describe :login do + before do + Devise.stub(omniauth_providers: [:kerberos]) + end + + it "finds the user if authentication is successful" do + kerberos_realm = user.email.sub(/.*@/, '') + ::Krb5Auth::Krb5.any_instance.stub(get_init_creds_password: true) + ::Krb5Auth::Krb5.any_instance.stub(get_default_realm: kerberos_realm) + + expect(klass.login('gitlab', password)).to be_true + end + + it "returns false if there is no such user in kerberos" do + kerberos_login = "some-login" + kerberos_realm = user.email.sub(/.*@/, '') + ::Krb5Auth::Krb5.any_instance.stub(get_init_creds_password: true) + ::Krb5Auth::Krb5.any_instance.stub(get_default_realm: kerberos_realm) + + expect(klass.login(kerberos_login, password)).to be_false + end + end +end \ No newline at end of file