From f9f1649feb5db4bfc8a60adda823210547c4b909 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 19 Jul 2013 14:41:49 +0300 Subject: [PATCH 1/9] Implement ldap group search functionality --- Gemfile | 1 + Gemfile.lock | 1 + lib/gitlab/ldap.rb | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 lib/gitlab/ldap.rb 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/lib/gitlab/ldap.rb b/lib/gitlab/ldap.rb new file mode 100644 index 0000000000..bfc0465c64 --- /dev/null +++ b/lib/gitlab/ldap.rb @@ -0,0 +1,39 @@ +module Gitlab + class LDAP + attr_reader :ldap + + def initialize + @ldap = Net::LDAP.new( + host: config['host'], + port: config['port'], + auth: { + method: config['method'], + username: config['bind_dn'], + password: config['password'] + } + ) + 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: "ou=Groups,#{config['base']}", + filter: Net::LDAP::Filter.eq("cn", cn) + } + + ldap.search(options) + end + + private + + def config + @config ||= Gitlab.config.ldap + end + end +end From 07de7bcaf6fd551684763ef0bd0bde7cef2f5907 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 29 Jul 2013 18:07:20 +0300 Subject: [PATCH 2/9] Add group base to config. Join auth options only if provided --- config/gitlab.yml.example | 14 +++++++++++++- lib/gitlab/ldap.rb | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) 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/lib/gitlab/ldap.rb b/lib/gitlab/ldap.rb index bfc0465c64..c3796b9a99 100644 --- a/lib/gitlab/ldap.rb +++ b/lib/gitlab/ldap.rb @@ -3,15 +3,24 @@ module Gitlab attr_reader :ldap def initialize - @ldap = Net::LDAP.new( + 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 @@ -23,7 +32,7 @@ module Gitlab # def groups(cn = "*") options = { - base: "ou=Groups,#{config['base']}", + base: config['group_base'], filter: Net::LDAP::Filter.eq("cn", cn) } From 51d5364a3d2b9748ee9419ee4aa4a95ec6797ef6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 29 Jul 2013 18:11:24 +0300 Subject: [PATCH 3/9] Add license file to lib/ldap --- lib/gitlab/ldap.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/gitlab/ldap.rb b/lib/gitlab/ldap.rb index c3796b9a99..6099254f6c 100644 --- a/lib/gitlab/ldap.rb +++ b/lib/gitlab/ldap.rb @@ -1,3 +1,15 @@ +#------------------------------------------------------------------- +# +# 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 class LDAP attr_reader :ldap From 1e9b27332c7b4885b4d16986ad95dff73c250b4f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 30 Jul 2013 15:54:05 +0300 Subject: [PATCH 4/9] Add LDAP::Group and LDAP::Person classes. Create LDAP module. Added method for selecting ldap users --- lib/gitlab/ldap.rb | 60 ----------------------------- lib/gitlab/ldap/adapter.rb | 79 ++++++++++++++++++++++++++++++++++++++ lib/gitlab/ldap/group.rb | 47 +++++++++++++++++++++++ lib/gitlab/ldap/person.rb | 35 +++++++++++++++++ 4 files changed, 161 insertions(+), 60 deletions(-) delete mode 100644 lib/gitlab/ldap.rb create mode 100644 lib/gitlab/ldap/adapter.rb create mode 100644 lib/gitlab/ldap/group.rb create mode 100644 lib/gitlab/ldap/person.rb diff --git a/lib/gitlab/ldap.rb b/lib/gitlab/ldap.rb deleted file mode 100644 index 6099254f6c..0000000000 --- a/lib/gitlab/ldap.rb +++ /dev/null @@ -1,60 +0,0 @@ -#------------------------------------------------------------------- -# -# 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 - class LDAP - 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) - end - - private - - def config - @config ||= Gitlab.config.ldap - end - end -end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb new file mode 100644 index 0000000000..568239fc89 --- /dev/null +++ b/lib/gitlab/ldap/adapter.rb @@ -0,0 +1,79 @@ +#------------------------------------------------------------------- +# +# 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(cn = "*") + options = { + base: config['base'], + filter: Net::LDAP::Filter.eq("cn", cn) + } + + entries = ldap.search(options).select do |entry| + entry.respond_to? :uid + end + + entries.map do |entry| + Gitlab::LDAP::Person.new(entry) + end + 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..6501bd32e3 --- /dev/null +++ b/lib/gitlab/ldap/group.rb @@ -0,0 +1,47 @@ +#------------------------------------------------------------------- +# +# 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 + if entry.respond_to? :member + entry.meber + elsif entry.respond_to? :uniquemember + entry.uniquemember + elsif entry.respond_to? :memberof + entry.memberof + else + raise 'Unsupported member attribute' + end + end + + private + + def entry + @entry + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb new file mode 100644 index 0000000000..5437b60f13 --- /dev/null +++ b/lib/gitlab/ldap/person.rb @@ -0,0 +1,35 @@ +#------------------------------------------------------------------- +# +# 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 initialize(entry) + @entry = entry + end + + def name + entry.cn.join(" ") + end + + def username + entry.uid.join(" ") + end + + private + + def entry + @entry + end + end + end +end From 7ba37d86be1247ab20c21845e4647a6fdc0849af Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 31 Jul 2013 14:58:04 +0300 Subject: [PATCH 5/9] Gitlab::LDAP - ability to collect group members --- lib/gitlab/ldap/adapter.rb | 8 ++++++-- lib/gitlab/ldap/group.rb | 26 +++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 568239fc89..af6c972220 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -54,10 +54,10 @@ module Gitlab end end - def users(cn = "*") + def users(uid = "*") options = { base: config['base'], - filter: Net::LDAP::Filter.eq("cn", cn) + filter: Net::LDAP::Filter.eq("uid", uid) } entries = ldap.search(options).select do |entry| @@ -69,6 +69,10 @@ module Gitlab end end + def user(uid) + users(uid).first + end + private def config diff --git a/lib/gitlab/ldap/group.rb b/lib/gitlab/ldap/group.rb index 6501bd32e3..ff6f93d67b 100644 --- a/lib/gitlab/ldap/group.rb +++ b/lib/gitlab/ldap/group.rb @@ -26,8 +26,26 @@ module Gitlab end def members + member_uids.map do |uid| + adapter.user(uid) + end.compact + end + + private + + 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 + + def member_dns if entry.respond_to? :member - entry.meber + entry.member elsif entry.respond_to? :uniquemember entry.uniquemember elsif entry.respond_to? :memberof @@ -37,11 +55,13 @@ module Gitlab end end - private - def entry @entry end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new + end end end end From 2c8ee981929dd4a6a435838de6ea3de154c8f54d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 31 Jul 2013 15:04:04 +0300 Subject: [PATCH 6/9] We can collect user groups now --- lib/gitlab/ldap/group.rb | 4 ++-- lib/gitlab/ldap/person.rb | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ldap/group.rb b/lib/gitlab/ldap/group.rb index ff6f93d67b..5be03e8f44 100644 --- a/lib/gitlab/ldap/group.rb +++ b/lib/gitlab/ldap/group.rb @@ -31,8 +31,6 @@ module Gitlab end.compact end - private - def member_uids if entry.respond_to? :memberuid entry.memberuid @@ -43,6 +41,8 @@ module Gitlab end.compact end + private + def member_dns if entry.respond_to? :member entry.member diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 5437b60f13..74e6896e99 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -21,15 +21,29 @@ module Gitlab entry.cn.join(" ") end - def username + 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 From 4b3fa67cb7a5b028299396dcde1a765778769e5a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 2 Aug 2013 15:58:40 +0300 Subject: [PATCH 7/9] Add ldap_cn field to Group --- app/models/group.rb | 2 ++ app/views/groups/edit.html.haml | 16 ++++++++++++++++ .../20130802124933_add_ldap_settings_to_group.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20130802124933_add_ldap_settings_to_group.rb 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/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/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" From 7fe779b86f05b8eb5a174976c20235d16f1872d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Aug 2013 15:15:58 +0300 Subject: [PATCH 8/9] Add/remove user to appropriate groups based on LDAP settings --- app/views/admin/groups/edit.html.haml | 6 ++++ lib/gitlab/ldap/access.rb | 40 +++++++++++++++++++++++++++ lib/gitlab/ldap/person.rb | 11 ++++++++ 3 files changed, 57 insertions(+) create mode 100644 lib/gitlab/ldap/access.rb 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/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/person.rb b/lib/gitlab/ldap/person.rb index 74e6896e99..1ba3c7a817 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -13,6 +13,17 @@ 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 From 073e8b674bc7560252b9a4b45e0acab21fe2f9da Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Aug 2013 15:31:04 +0300 Subject: [PATCH 9/9] Update ldap user permissions on each login --- app/controllers/omniauth_callbacks_controller.rb | 3 +++ 1 file changed, 3 insertions(+) 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