Skip to content
This repository has been archived by the owner on Apr 17, 2023. It is now read-only.

Commit

Permalink
Merge pull request #2091 from mssola/ldap-groups
Browse files Browse the repository at this point in the history
Ldap groups
  • Loading branch information
mssola authored Jan 15, 2019
2 parents 4cec2f2 + f99abb6 commit 188001b
Show file tree
Hide file tree
Showing 28 changed files with 1,224 additions and 67 deletions.
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ vagrant
packaging
log/*
.DS_Store
.bundle/config
.bundle/config
config/config-local.yml
52 changes: 46 additions & 6 deletions app/models/team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
#
# Table name: teams
#
# id :integer not null, primary key
# name :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# hidden :boolean default(FALSE)
# description :text(65535)
# id :integer not null, primary key
# name :string(255)
# created_at :datetime not null
# updated_at :datetime not null
# hidden :boolean default(FALSE)
# description :text(65535)
# ldap_group_checked :integer default(0)
# checked_at :datetime
#
# Indexes
#
Expand All @@ -21,6 +23,8 @@ class Team < ApplicationRecord
include SearchCop
include ::Activity::Fallback

enum ldap_status: { unchecked: 0, checked: 1, disabled: 2 }

search_scope :search do
attributes :name, :description
end
Expand Down Expand Up @@ -95,4 +99,40 @@ def self.make_valid(name)

name
end

# Checks whether the current team exists on the LDAP server as a group. If so,
# it will add users that already exist on the database into this team as team
# members (with the given ldap.group_sync_default_role role, unless the user
# is a Portus administrator, in which case it will be added as an owner).
def ldap_add_members!
Rails.logger.tagged(:ldap) { Rails.logger.info "Looking up an LDAP group matching '#{name}'" }

portus_user = User.portus
usernames = users.map(&:username)

::Portus::LDAP::Search.new.find_group_and_members(name).each do |member|
next if usernames.include?(member)
next unless User.exists?(username: member)

add_team_member!(portus_user, member)
end

update!(ldap_group_checked: Team.ldap_statuses[:checked], checked_at: Time.zone.now)
end

# If possible, add the user with the given username into the team. The
# activity will set the given author as the tracker.
def add_team_member!(author, username)
role = APP_CONFIG["ldap"]["group_sync"]["default_role"]
params = { id: id, role: TeamUser.roles[role], user: username }

team_user = ::TeamUsers::BuildService.new(author, params).execute
team_user = ::TeamUsers::CreateService.new(author, team_user).execute
return true if team_user.valid? && team_user.persisted?

Rails.logger.tagged(:ldap) do
Rails.logger.warn "Could not add team member: #{team_user.errors.full_messages.join(", ")}"
end
false
end
end
24 changes: 24 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
# provider :string(255)
# uid :string(255)
# bot :boolean default(FALSE)
# ldap_group_checked :integer default(0)
#
# Indexes
#
Expand All @@ -41,6 +42,8 @@
class User < ApplicationRecord
include PublicActivity::Common

enum ldap_status: { unchecked: 0, checked: 1, disabled: 2 }

enabled_devise_modules = [:database_authenticatable, :registerable, :lockable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable,
Expand Down Expand Up @@ -295,6 +298,27 @@ def suggest_username(data)
self.username = suggest_username unless user
end

# Checks memberships for this user on LDAP groups and tries to add this same
# user into existing teams as a member.
def ldap_add_as_member!
Rails.logger.tagged(:ldap) do
Rails.logger.info "Looking up an LDAP group membership for '#{username}'"
end

portus_user = User.portus

::Portus::LDAP::Search.new.user_groups(username).each do |group|
t = Team.find_by(name: group)
next if t.nil?
next if t.ldap_group_checked == Team.ldap_statuses[:disabled]
next if t.users.map(&:id).include?(id)

t.add_team_member!(portus_user, username)
end

update!(ldap_group_checked: User.ldap_statuses[:checked])
end

protected

# Validations can no longer be skipped after calling this method.
Expand Down
4 changes: 3 additions & 1 deletion bin/background.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#

require "portus/background/garbage_collector"
require "portus/background/ldap"
require "portus/background/registry"
require "portus/background/security_scanning"
require "portus/background/sync"
Expand All @@ -20,7 +21,8 @@
::Portus::Background::Registry.new,
::Portus::Background::SecurityScanning.new,
::Portus::Background::Sync.new,
::Portus::Background::GarbageCollector.new
::Portus::Background::GarbageCollector.new,
::Portus::Background::LDAP.new
].select(&:enabled?)

values = they.map { |v| "'#{v}'" }.join(", ")
Expand Down
4 changes: 3 additions & 1 deletion bin/test-integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ for p in ${profiles[*]}; do
status=$?
set -e

cleanup_containers "docker-compose.$p.yml"
if [[ ! -z "$TEARDOWN_TESTS" ]]; then
cleanup_containers "docker-compose.$p.yml"
fi

if [ $status -ne 0 ]; then
exit $status
Expand Down
13 changes: 13 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ ldap:
# Portus administrators automatically.
admin_base: ""

# The base where groups are located (e.g. "ou=groups,dc=example,dc=com").
group_base: ""

# User filter (e.g. "mail=george*").
filter: ""

Expand All @@ -125,6 +128,16 @@ ldap:
bind_dn: ""
password: ""

# Automatically add team members from a matching LDAP group into each
# team. Note that this will not delete nor update existing team members. Only
# `groupOfNames` and `groupOfUniqueNames` are supported.
group_sync:
enabled: true

# The role in which new team members will be added. Note that Portus
# administrators will be added as team owners regardless of this setting.
default_role: "viewer"

# Portus needs an email for each user, but there's no standard way to get
# that from LDAP servers. You can tell Portus how to get the email from users
# registered in the LDAP server with this configurable value. There are three
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20181224123453_add_ldap_group_checked_to_teams.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddLdapGroupCheckedToTeams < ActiveRecord::Migration[5.2]
def change
add_column :teams, :ldap_group_checked, :int, default: Team.ldap_statuses[:unchecked]
end
end
5 changes: 5 additions & 0 deletions db/migrate/20190103113934_add_checked_at_to_team.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddCheckedAtToTeam < ActiveRecord::Migration[5.2]
def change
add_column :teams, :checked_at, :datetime, default: nil
end
end
5 changes: 5 additions & 0 deletions db/migrate/20190103124548_add_ldap_group_checked_to_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddLdapGroupCheckedToUser < ActiveRecord::Migration[5.2]
def change
add_column :users, :ldap_group_checked, :integer, default: User.ldap_statuses[:unchecked]
end
end
3 changes: 3 additions & 0 deletions db/schema.mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@
t.datetime "updated_at", null: false
t.boolean "hidden", default: false
t.text "description"
t.integer "ldap_group_checked", default: 0
t.datetime "checked_at"
t.index ["name"], name: "index_teams_on_name", unique: true
end

Expand Down Expand Up @@ -168,6 +170,7 @@
t.string "provider"
t.string "uid"
t.boolean "bot", default: false
t.integer "ldap_group_checked", default: 0
t.index ["display_name"], name: "index_users_on_display_name", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["namespace_id"], name: "index_users_on_namespace_id"
Expand Down
3 changes: 3 additions & 0 deletions db/schema.postgresql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@
t.datetime "updated_at", null: false
t.boolean "hidden", default: false
t.text "description"
t.integer "ldap_group_checked", default: 0
t.datetime "checked_at"
t.index ["name"], name: "index_teams_on_name", unique: true
end

Expand Down Expand Up @@ -171,6 +173,7 @@
t.string "provider"
t.string "uid"
t.boolean "bot", default: false
t.integer "ldap_group_checked", default: 0
t.index ["display_name"], name: "index_users_on_display_name", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["namespace_id"], name: "index_users_on_namespace_id"
Expand Down
17 changes: 17 additions & 0 deletions examples/compose/docker-compose.ldap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ services:
- PORTUS_LDAP_PORT=389
- PORTUS_LDAP_UID=uid
- PORTUS_LDAP_BASE=dc=example,dc=org
- PORTUS_LDAP_GROUP_BASE=ou=canigo,dc=example,dc=org
- PORTUS_LDAP_FILTER=
- PORTUS_LDAP_AUTHENTICATION_ENABLED=true
- PORTUS_LDAP_AUTHENTICATION_BIND_DN=cn=admin,dc=example,dc=org
Expand Down Expand Up @@ -70,11 +71,27 @@ services:
- PORTUS_KEY_PATH=/certificates/portus.key
- PORTUS_PASSWORD=${PORTUS_PASSWORD}

# LDAP
- PORTUS_LDAP_ENABLED=true
- PORTUS_LDAP_HOSTNAME=ldap
- PORTUS_LDAP_PORT=389
- PORTUS_LDAP_UID=uid
- PORTUS_LDAP_BASE=dc=example,dc=org
- PORTUS_LDAP_GROUP_BASE=ou=canigo,dc=example,dc=org
- PORTUS_LDAP_FILTER=
- PORTUS_LDAP_AUTHENTICATION_ENABLED=true
- PORTUS_LDAP_AUTHENTICATION_BIND_DN=cn=admin,dc=example,dc=org
- PORTUS_LDAP_AUTHENTICATION_PASSWORD=admin
- PORTUS_LDAP_ENCRYPTION_METHOD=start_tls
- PORTUS_LDAP_ENCRYPTION_OPTIONS_CA_FILE=/ldap-certificates/ca.pem
- PORTUS_LDAP_ENCRYPTION_OPTIONS_SSL_VERSION=TLSv1_2

- PORTUS_BACKGROUND=true
links:
- db
volumes:
- ./secrets:/certificates:ro
- ./secrets/ldap:/ldap-certificates:ro

db:
image: library/mariadb:10.0.23
Expand Down
28 changes: 28 additions & 0 deletions lib/api/v1/teams.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,34 @@ class Teams < Grape::API
end
end

desc "Disables any LDAP check for the team",
params: API::Entities::Teams.documentation.slice(:id),
failure: [
[400, "Bad request", API::Entities::ApiErrors],
[401, "Authentication fails"],
[403, "Authorization fails"],
[404, "Not found"],
[405, "Method Not Allowed", API::Entities::ApiErrors]
],
consumes: ["application/x-www-form-urlencoded", "application/json"]

params do
requires :id, documentation: { desc: "Team id" }
end

post ":id/ldap_check" do
if APP_CONFIG.disabled?("ldap") || APP_CONFIG.disabled?("ldap.group_sync")
method_not_allowed!("this instance has disabled this endpoint")
else
attrs = permitted_params.merge(id: params[:id])
team = Team.find(attrs[:id])
authorize team, :update?

team.update!(ldap_group_checked: Team.ldap_statuses[:disabled])
status :ok
end
end

route_param :id, type: String, requirements: { id: /.*/ } do
resource :namespaces do
desc "Returns the list of namespaces for the given team",
Expand Down
Loading

0 comments on commit 188001b

Please sign in to comment.