diff --git a/Gemfile b/Gemfile index e679f3ee1a..e39d673976 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'gitlab-grack', '~> 1.0.1', require: 'grack' # LDAP Auth gem 'gitlab_omniauth-ldap', '1.0.3', require: "omniauth-ldap" +gem 'net-ldap' # Syntax highlighter gem "gitlab-pygments.rb", '~> 0.3.2', require: 'pygments.rb' diff --git a/Gemfile.lock b/Gemfile.lock index 6eedadc74c..da40c69fc2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -591,6 +591,7 @@ DEPENDENCIES minitest (~> 4.7.0) modernizr (= 2.6.2) mysql2 + net-ldap omniauth (~> 1.1.3) omniauth-github omniauth-google-oauth2 diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index c4ebf0e488..17a56fedf4 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -21,6 +21,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController if @user.persisted? @user.remember_me = true end + + Gitlab::LDAP::Access.new.update_permissions(@user) + sign_in_and_redirect @user end diff --git a/app/models/group.rb b/app/models/group.rb index bd4d412ba9..fe5522ad8c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -19,6 +19,8 @@ class Group < Namespace has_many :project_group_links, dependent: :destroy has_many :shared_projects, through: :project_group_links, source: 'project' + attr_accessible :ldap_cn + after_create :add_owner def human_name diff --git a/app/views/admin/groups/edit.html.haml b/app/views/admin/groups/edit.html.haml index f4d931f480..d0c3aba1b1 100644 --- a/app/views/admin/groups/edit.html.haml +++ b/app/views/admin/groups/edit.html.haml @@ -26,6 +26,12 @@ %li It will change web url for access group and group projects. %li It will change the git path to repositories under this group. + .clearfix + = f.label :ldap_cn do + LDAP Group cn + .input + = f.text_field :ldap_cn, class: "xxlarge left" + .form-actions = f.submit 'Save changes', class: "btn btn-primary" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index ba3333832e..088a619e7c 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -9,6 +9,8 @@ = link_to '#tab-projects', 'data-toggle' => 'tab' do %i.icon-folder-close Projects + %li + = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' %li = link_to 'Transfer', '#tab-transfer', 'data-toggle' => 'tab' %li @@ -65,6 +67,20 @@ - if @group.projects.blank? %p.nothing_here_message This group has no projects yet + .tab-pane#tab-ldap + .ui-box + .title LDAP group settings + %div.form-holder + = form_for @group do |f| + .clearfix + = f.label :ldap_cn do + LDAP Group cn + .input + = f.text_field :ldap_cn, placeholder: "Ex. QA group", class: "xxlarge left" + + .form-actions + = f.submit 'Save group', class: "btn btn-save" + .tab-pane#tab-transfer .ui-box.ui-box-danger .title Transfer group diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index b6a8e68bf8..1a988f43bb 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -91,7 +91,6 @@ production: &base ldap: enabled: false host: '_your_ldap_server' - base: '_the_base_where_you_search_for_users' port: 636 uid: 'sAMAccountName' method: 'ssl' # "ssl" or "plain" @@ -99,6 +98,19 @@ production: &base password: '_the_password_of_the_bind_user' allow_username_or_email_login: true + # Base where we can search for users + # + # Ex. ou=People,dc=gitlab,dc=example + # + base: '' + + # Base where we can search for groups + # + # Ex. ou=Groups,dc=gitlab,dc=example + # + group_base: '' + + ## OmniAuth settings omniauth: # Allow login via Twitter, Google, etc. using OmniAuth providers diff --git a/db/migrate/20130802124933_add_ldap_settings_to_group.rb b/db/migrate/20130802124933_add_ldap_settings_to_group.rb new file mode 100644 index 0000000000..2fed8cb7e1 --- /dev/null +++ b/db/migrate/20130802124933_add_ldap_settings_to_group.rb @@ -0,0 +1,5 @@ +class AddLdapSettingsToGroup < ActiveRecord::Migration + def change + add_column :namespaces, :ldap_cn, :string, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6adf85e3b3..bae65d66ae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130711063759) do +ActiveRecord::Schema.define(:version => 20130802124933) do create_table "deploy_keys_projects", :force => true do |t| t.integer "deploy_key_id", :null => false @@ -129,6 +129,7 @@ ActiveRecord::Schema.define(:version => 20130711063759) do t.datetime "updated_at", :null => false t.string "type" t.string "description", :default => "", :null => false + t.string "ldap_cn" end add_index "namespaces", ["name"], :name => "index_namespaces_on_name" diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb new file mode 100644 index 0000000000..274f118d77 --- /dev/null +++ b/lib/gitlab/ldap/access.rb @@ -0,0 +1,40 @@ +#------------------------------------------------------------------- +# +# The GitLab Enterprise Edition (EE) license +# +# Copyright (c) 2013 GitLab.com +# +# All Rights Reserved. No part of this software may be reproduced without +# prior permission of GitLab.com. By using this software you agree to be +# bound by the GitLab Enterprise Support Subscription Terms. +# +#------------------------------------------------------------------- + +module Gitlab + module LDAP + class Access + def update_permissions(user) + ldap_user = Gitlab::LDAP::Person.find(user.extern_uid) + ldap_groups = ldap_user.groups + ldap_groups_cn = ldap_groups.map(&:name) + groups = ::Group.where(ldap_cn: ldap_groups_cn) + + # First lets add user to new groups + groups.each do |group| + group.add_users([user.id], UsersGroup::DEVELOPER) + end + + # Remove groups with LDAP if user lost access to it + user.authorized_groups.where('ldap_cn IS NOT NULL').each do |group| + if ldap_groups_cn.include?(group.ldap_cn) + # ok user still in group + else + # user lost access to this group in ldap + membership = group.users_groups.where(user_id: user.id).last + membership.destroy if membership + end + end + end + end + end +end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb new file mode 100644 index 0000000000..af6c972220 --- /dev/null +++ b/lib/gitlab/ldap/adapter.rb @@ -0,0 +1,83 @@ +#------------------------------------------------------------------- +# +# The GitLab Enterprise Edition (EE) license +# +# Copyright (c) 2013 GitLab.com +# +# All Rights Reserved. No part of this software may be reproduced without +# prior permission of GitLab.com. By using this software you agree to be +# bound by the GitLab Enterprise Support Subscription Terms. +# +#------------------------------------------------------------------- + +module Gitlab + module LDAP + class Adapter + attr_reader :ldap + + def initialize + options = { + host: config['host'], + port: config['port'], + } + + auth_options = { + auth: { + method: config['method'], + username: config['bind_dn'], + password: config['password'] + } + } + + if config['password'] || config['bind_dn'] + options.merge!(auth_options) + end + + @ldap = Net::LDAP.new(options) + end + + # Get LDAP groups from ou=Groups + # + # cn - filter groups by name + # + # Ex. + # groups("dev*") # return all groups start with 'dev' + # + def groups(cn = "*") + options = { + base: config['group_base'], + filter: Net::LDAP::Filter.eq("cn", cn) + } + + ldap.search(options).map do |entry| + Gitlab::LDAP::Group.new(entry) + end + end + + def users(uid = "*") + options = { + base: config['base'], + filter: Net::LDAP::Filter.eq("uid", uid) + } + + entries = ldap.search(options).select do |entry| + entry.respond_to? :uid + end + + entries.map do |entry| + Gitlab::LDAP::Person.new(entry) + end + end + + def user(uid) + users(uid).first + end + + private + + def config + @config ||= Gitlab.config.ldap + end + end + end +end diff --git a/lib/gitlab/ldap/group.rb b/lib/gitlab/ldap/group.rb new file mode 100644 index 0000000000..5be03e8f44 --- /dev/null +++ b/lib/gitlab/ldap/group.rb @@ -0,0 +1,67 @@ +#------------------------------------------------------------------- +# +# The GitLab Enterprise Edition (EE) license +# +# Copyright (c) 2013 GitLab.com +# +# All Rights Reserved. No part of this software may be reproduced without +# prior permission of GitLab.com. By using this software you agree to be +# bound by the GitLab Enterprise Support Subscription Terms. +# +#------------------------------------------------------------------- + +module Gitlab + module LDAP + class Group + def initialize(entry) + @entry = entry + end + + def name + entry.cn.join(" ") + end + + def path + name.parameterize + end + + def members + member_uids.map do |uid| + adapter.user(uid) + end.compact + end + + def member_uids + if entry.respond_to? :memberuid + entry.memberuid + else + member_dns.map do |dn| + $1 if dn =~ /uid=([a-zA-Z0-9.-]+)/ + end + end.compact + end + + private + + def member_dns + if entry.respond_to? :member + entry.member + elsif entry.respond_to? :uniquemember + entry.uniquemember + elsif entry.respond_to? :memberof + entry.memberof + else + raise 'Unsupported member attribute' + end + end + + def entry + @entry + end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb new file mode 100644 index 0000000000..1ba3c7a817 --- /dev/null +++ b/lib/gitlab/ldap/person.rb @@ -0,0 +1,60 @@ +#------------------------------------------------------------------- +# +# The GitLab Enterprise Edition (EE) license +# +# Copyright (c) 2013 GitLab.com +# +# All Rights Reserved. No part of this software may be reproduced without +# prior permission of GitLab.com. By using this software you agree to be +# bound by the GitLab Enterprise Support Subscription Terms. +# +#------------------------------------------------------------------- + +module Gitlab + module LDAP + class Person + def self.find(user_uid) + uid = if user_uid =~ /uid=([a-zA-Z0-9.-]+)/ + $1 + else + user_uid + end + + + Gitlab::LDAP::Adapter.new.user(uid) + end + + def initialize(entry) + @entry = entry + end + + def name + entry.cn.join(" ") + end + + def uid + entry.uid.join(" ") + end + + def username + uid + end + + def groups + adapter.groups.select do |group| + group.member_uids.include?(uid) + end + end + + private + + def entry + @entry + end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new + end + end + end +end