Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
31f11627e1
21
.travis.yml
21
.travis.yml
@ -1,17 +1,20 @@
|
||||
sudo: required
|
||||
dist: focal
|
||||
language: ruby
|
||||
rvm:
|
||||
- "2.0.0"
|
||||
- "2.4.0"
|
||||
- ruby-head
|
||||
env:
|
||||
- "PGVERSION=10.0-1-linux-x64 PATH=\"/opt/PostgreSQL/10/bin:$PATH\""
|
||||
- "PGVERSION=9.3.19-1-linux-x64 PATH=\"/opt/PostgreSQL/9.3/bin:$PATH\""
|
||||
- "PGVERSION=14"
|
||||
- "PGVERSION=9.6"
|
||||
before_install:
|
||||
- gem install bundler
|
||||
- gem install bundler --no-doc --conservative
|
||||
- bundle install
|
||||
# Download and install postgresql version to test against in /opt
|
||||
- |
|
||||
wget http://get.enterprisedb.com/postgresql/postgresql-$PGVERSION.run && \
|
||||
chmod +x postgresql-$PGVERSION.run && \
|
||||
sudo ./postgresql-$PGVERSION.run --extract-only 1 --mode unattended
|
||||
# Download and install postgresql version to test against in /opt (for non-cross compile only)
|
||||
- echo "deb http://apt.postgresql.org/pub/repos/apt/ ${TRAVIS_DIST}-pgdg main $PGVERSION" | sudo tee -a /etc/apt/sources.list.d/pgdg.list
|
||||
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
|
||||
- sudo apt -y update
|
||||
- sudo apt -y --allow-downgrades install postgresql-$PGVERSION libpq-dev
|
||||
- export PATH=/usr/lib/postgresql/$PGVERSION/bin:$PATH
|
||||
|
||||
script: rake test
|
||||
|
@ -1,3 +1,12 @@
|
||||
## 0.3.0 / 2022-01-18
|
||||
|
||||
* Add config option :bothcase_name .
|
||||
This adds both spellings "Fred_Flintstone" and "fred_flintstone" as PostgreSQL users/groups.
|
||||
* Update gem dependencies
|
||||
* Fix compatibility with PostgreSQL-14
|
||||
* Require ruby-2.4+
|
||||
|
||||
|
||||
## 0.2.0 / 2018-03-13
|
||||
|
||||
* Update gem dependencies
|
||||
|
@ -1,4 +1,4 @@
|
||||
[![Build Status](https://travis-ci.org/larskanis/pg-ldap-sync.svg?branch=master)](https://travis-ci.org/larskanis/pg-ldap-sync) [![Build status](https://ci.appveyor.com/api/projects/status/09xn9q5p64jbxtka/branch/master?svg=true)](https://ci.appveyor.com/project/larskanis/pg-ldap-sync/branch/master)
|
||||
[![Build Status](https://app.travis-ci.com/larskanis/pg-ldap-sync.svg?branch=master)](https://app.travis-ci.com/larskanis/pg-ldap-sync) [![Build status](https://ci.appveyor.com/api/projects/status/09xn9q5p64jbxtka/branch/master?svg=true)](https://ci.appveyor.com/project/larskanis/pg-ldap-sync/branch/master)
|
||||
|
||||
# Use LDAP permissions in PostgreSQL
|
||||
|
||||
|
10
appveyor.yml
10
appveyor.yml
@ -1,3 +1,5 @@
|
||||
image: Visual Studio 2019
|
||||
|
||||
init:
|
||||
- set PATH=C:/Ruby%ruby_version%/bin;c:/Program Files/Git/cmd;c:/Windows/system32;C:/Windows/System32/WindowsPowerShell/v1.0
|
||||
- set RUBYOPT=--verbose
|
||||
@ -6,7 +8,7 @@ install:
|
||||
- ver
|
||||
- ruby --version
|
||||
- gem --version
|
||||
- gem install bundler --conservative
|
||||
- gem install bundler --no-doc --conservative
|
||||
- bundle install
|
||||
|
||||
build_script:
|
||||
@ -19,7 +21,7 @@ test_script:
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- ruby_version: "25-x64"
|
||||
PGVER: 10
|
||||
- ruby_version: "22"
|
||||
- ruby_version: "27-x64"
|
||||
PGVER: 13
|
||||
- ruby_version: "24"
|
||||
PGVER: 10
|
||||
|
@ -1,6 +1,6 @@
|
||||
# With this sample config the distinction between LDAP-synchronized
|
||||
# groups/users from is done by the membership to ldap_user and
|
||||
# ldap_group. These two roles has to be defined manally before
|
||||
# ldap_group. These two roles have to be defined manally before
|
||||
# pg_ldap_sync can run.
|
||||
|
||||
# Connection parameters to LDAP server
|
||||
@ -25,6 +25,8 @@ ldap_users:
|
||||
name_attribute: sAMAccountName
|
||||
# lowercase name for use as PG role name
|
||||
lowercase_name: true
|
||||
# Add lowercase name *and* original name for use as PG role names (useful for migrating between case types)
|
||||
bothcase_name: false
|
||||
|
||||
# Search parameters for LDAP groups which should be synchronized
|
||||
ldap_groups:
|
||||
|
@ -23,6 +23,9 @@ mapping:
|
||||
"uppercase_name":
|
||||
type: bool
|
||||
required: no
|
||||
"bothcase_name":
|
||||
type: bool
|
||||
required: no
|
||||
|
||||
"ldap_groups":
|
||||
type: map
|
||||
@ -43,6 +46,9 @@ mapping:
|
||||
"uppercase_name":
|
||||
type: bool
|
||||
required: no
|
||||
"bothcase_name":
|
||||
type: bool
|
||||
required: no
|
||||
"member_attribute":
|
||||
type: str
|
||||
required: yes
|
||||
|
@ -15,11 +15,11 @@ class Application
|
||||
|
||||
def string_to_symbol(hash)
|
||||
if hash.kind_of?(Hash)
|
||||
return hash.inject({}){|h, v|
|
||||
return hash.inject({}) do |h, v|
|
||||
raise "expected String instead of #{h.inspect}" unless v[0].kind_of?(String)
|
||||
h[v[0].intern] = string_to_symbol(v[1])
|
||||
h
|
||||
}
|
||||
end
|
||||
else
|
||||
return hash
|
||||
end
|
||||
@ -61,12 +61,22 @@ class Application
|
||||
log.warn "user attribute #{ldap_user_conf[:name_attribute].inspect} not defined for #{entry.dn}"
|
||||
next
|
||||
end
|
||||
name.downcase! if ldap_user_conf[:lowercase_name]
|
||||
name.upcase! if ldap_user_conf[:uppercase_name]
|
||||
|
||||
log.info "found user-dn: #{entry.dn}"
|
||||
user = LdapRole.new name, entry.dn
|
||||
users << user
|
||||
|
||||
names = if ldap_user_conf[:bothcase_name]
|
||||
[name, name.downcase].uniq
|
||||
elsif ldap_user_conf[:lowercase_name]
|
||||
[name.downcase]
|
||||
elsif ldap_user_conf[:uppercase_name]
|
||||
[name.upcase]
|
||||
else
|
||||
[name]
|
||||
end
|
||||
|
||||
names.each do |n|
|
||||
users << LdapRole.new(n, entry.dn)
|
||||
end
|
||||
entry.each do |attribute, values|
|
||||
log.debug " #{attribute}:"
|
||||
values.each do |value|
|
||||
@ -89,12 +99,22 @@ class Application
|
||||
log.warn "user attribute #{ldap_group_conf[:name_attribute].inspect} not defined for #{entry.dn}"
|
||||
next
|
||||
end
|
||||
name.downcase! if ldap_group_conf[:lowercase_name]
|
||||
name.upcase! if ldap_group_conf[:uppercase_name]
|
||||
|
||||
log.info "found group-dn: #{entry.dn}"
|
||||
group = LdapRole.new name, entry.dn, entry[ldap_group_conf[:member_attribute]]
|
||||
groups << group
|
||||
|
||||
names = if ldap_group_conf[:bothcase_name]
|
||||
[name, name.downcase].uniq
|
||||
elsif ldap_group_conf[:lowercase_name]
|
||||
[name.downcase]
|
||||
elsif ldap_group_conf[:uppercase_name]
|
||||
[name.upcase]
|
||||
else
|
||||
[name]
|
||||
end
|
||||
|
||||
names.each do |n|
|
||||
groups << LdapRole.new(n, entry.dn, entry[ldap_group_conf[:member_attribute]])
|
||||
end
|
||||
entry.each do |attribute, values|
|
||||
log.debug " #{attribute}:"
|
||||
values.each do |value|
|
||||
@ -108,8 +128,8 @@ class Application
|
||||
|
||||
PgRole = Struct.new :name, :member_names
|
||||
|
||||
# List of default roles taken from https://www.postgresql.org/docs/current/static/default-roles.html
|
||||
PG_BUILTIN_ROLES = %w[ pg_signal_backend pg_monitor pg_read_all_settings pg_read_all_stats pg_stat_scan_tables]
|
||||
# List of default roles taken from https://www.postgresql.org/docs/current/predefined-roles.html
|
||||
PG_BUILTIN_ROLES = %w[ pg_read_all_data pg_write_all_data pg_read_all_settings pg_read_all_stats pg_stat_scan_tables pg_monitor pg_database_owner pg_signal_backend pg_read_server_files pg_write_server_files pg_execute_server_program ]
|
||||
|
||||
def search_pg_users
|
||||
pg_users_conf = @config[:pg_users]
|
||||
@ -185,12 +205,12 @@ class Application
|
||||
r.type = type
|
||||
end
|
||||
|
||||
log.info{
|
||||
log.info do
|
||||
roles.each do |role|
|
||||
log.debug{ "#{role.state} #{role.type}: #{role.name}" }
|
||||
end
|
||||
"#{type} stat: create: #{roles.count{|r| r.state==:create }} drop: #{roles.count{|r| r.state==:drop }} keep: #{roles.count{|r| r.state==:keep }}"
|
||||
}
|
||||
end
|
||||
return roles
|
||||
end
|
||||
|
||||
@ -236,42 +256,42 @@ class Application
|
||||
MatchedMembership = Struct.new :role_name, :has_member, :state
|
||||
|
||||
def match_memberships(ldap_roles, pg_roles)
|
||||
ldap_by_dn = ldap_roles.inject({}){|h,r| h[r.dn] = r; h }
|
||||
ldap_by_m2m = ldap_roles.inject([]){|a,r|
|
||||
hash_of_arrays = Hash.new { |h, k| h[k] = [] }
|
||||
ldap_by_dn = ldap_roles.inject(hash_of_arrays){|h,r| h[r.dn] << r; h }
|
||||
ldap_by_m2m = ldap_roles.inject([]) do |a,r|
|
||||
next a unless r.member_dns
|
||||
a + r.member_dns.map{|dn|
|
||||
if has_member=ldap_by_dn[dn]
|
||||
a + r.member_dns.flat_map do |dn|
|
||||
has_members = ldap_by_dn[dn]
|
||||
log.warn{"ldap member with dn #{dn} is unknown"} if has_members.empty?
|
||||
has_members.map do |has_member|
|
||||
[r.name, has_member.name]
|
||||
else
|
||||
log.warn{"ldap member with dn #{dn} is unknown"}
|
||||
nil
|
||||
end
|
||||
}.compact
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
pg_by_name = pg_roles.inject({}){|h,r| h[r.name] = r; h }
|
||||
pg_by_m2m = pg_roles.inject([]){|a,r|
|
||||
hash_of_arrays = Hash.new { |h, k| h[k] = [] }
|
||||
pg_by_name = pg_roles.inject(hash_of_arrays){|h,r| h[r.name] << r; h }
|
||||
pg_by_m2m = pg_roles.inject([]) do |a,r|
|
||||
next a unless r.member_names
|
||||
a + r.member_names.map{|name|
|
||||
if has_member=pg_by_name[name]
|
||||
a + r.member_names.flat_map do |name|
|
||||
has_members = pg_by_name[name]
|
||||
log.warn{"pg member with name #{name} is unknown"} if has_members.empty?
|
||||
has_members.map do |has_member|
|
||||
[r.name, has_member.name]
|
||||
else
|
||||
log.warn{"pg member with name #{name} is unknown"}
|
||||
nil
|
||||
end
|
||||
}.compact
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
memberships = (ldap_by_m2m & pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :keep }
|
||||
memberships += (ldap_by_m2m - pg_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :grant }
|
||||
memberships += (pg_by_m2m - ldap_by_m2m).map{|r,mo| MatchedMembership.new r, mo, :revoke }
|
||||
|
||||
log.info{
|
||||
log.info do
|
||||
memberships.each do |membership|
|
||||
log.debug{ "#{membership.state} #{membership.role_name} to #{membership.has_member}" }
|
||||
end
|
||||
"membership stat: grant: #{memberships.count{|u| u.state==:grant }} revoke: #{memberships.count{|u| u.state==:revoke }} keep: #{memberships.count{|u| u.state==:keep }}"
|
||||
}
|
||||
end
|
||||
return memberships
|
||||
end
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
module PgLdapSync
|
||||
VERSION = "0.2.0"
|
||||
VERSION = "0.3.0"
|
||||
end
|
||||
|
@ -19,13 +19,14 @@ Gem::Specification.new do |spec|
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ["lib"]
|
||||
spec.rdoc_options = %w[--main README.md --charset=UTF-8]
|
||||
spec.required_ruby_version = ">= 2.4"
|
||||
|
||||
spec.add_runtime_dependency "net-ldap", "~> 0.16"
|
||||
spec.add_runtime_dependency "kwalify", "~> 0.7"
|
||||
spec.add_runtime_dependency "pg", ">= 0.14", "< 2.0"
|
||||
spec.add_development_dependency "ruby-ldapserver", "~> 0.3"
|
||||
spec.add_development_dependency "minitest", "~> 5.0"
|
||||
spec.add_development_dependency "bundler", "~> 1.16"
|
||||
spec.add_development_dependency "rake", "~> 10.0"
|
||||
spec.add_development_dependency "bundler", ">= 1.16", "< 3.0"
|
||||
spec.add_development_dependency "rake", "~> 13.0"
|
||||
spec.add_development_dependency "minitest-hooks", "~> 1.4"
|
||||
end
|
||||
|
34
test/fixtures/config-ldapdb-bothcase.yaml
vendored
Normal file
34
test/fixtures/config-ldapdb-bothcase.yaml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
ldap_connection:
|
||||
host: localhost
|
||||
port: 1389
|
||||
|
||||
ldap_users:
|
||||
base: dc=example,dc=com
|
||||
filter: (sAMAccountName=*)
|
||||
name_attribute: sAMAccountName
|
||||
bothcase_name: true
|
||||
|
||||
ldap_groups:
|
||||
base: dc=example,dc=com
|
||||
filter: (member=*)
|
||||
name_attribute: cn
|
||||
bothcase_name: true
|
||||
member_attribute: member
|
||||
|
||||
pg_connection:
|
||||
dbname: postgres
|
||||
host: localhost
|
||||
port: 54321
|
||||
# needed for postgres-pr:
|
||||
# user: insert_your_username_here
|
||||
# password:
|
||||
|
||||
pg_users:
|
||||
filter: rolcanlogin AND NOT rolsuper AND rolname!='double_user'
|
||||
create_options: LOGIN
|
||||
|
||||
pg_groups:
|
||||
filter: NOT rolcanlogin
|
||||
create_options: NOLOGIN
|
||||
grant_options:
|
4
test/fixtures/ldapdb.yaml
vendored
4
test/fixtures/ldapdb.yaml
vendored
@ -11,14 +11,14 @@ cn=Fred Flintstone,dc=example,dc=com:
|
||||
sn:
|
||||
- Flintstone
|
||||
sAMAccountName:
|
||||
- fred
|
||||
- Fred
|
||||
cn=Wilma Flintstone,dc=example,dc=com:
|
||||
cn:
|
||||
- Wilma Flintstone
|
||||
mail:
|
||||
- wilma@bedrock.org
|
||||
sAMAccountName:
|
||||
- wilma
|
||||
- Wilma
|
||||
cn=Flintstones,dc=example,dc=com:
|
||||
cn:
|
||||
- Flintstones
|
||||
|
@ -83,7 +83,7 @@ class TestPgLdapSync < Minitest::Test
|
||||
end
|
||||
|
||||
def setup
|
||||
@pgconn.exec "DROP ROLE IF EXISTS fred, wilma, \"Flintstones\", \"Wilmas\", \"All Users\", double_user"
|
||||
@pgconn.exec "DROP ROLE IF EXISTS \"Fred\", fred, \"Wilma\", wilma, \"Flintstones\", \"flintstones\", \"Wilmas\", \"wilmas\", \"All Users\", double_user"
|
||||
end
|
||||
|
||||
def assert_role(role_name, attrs, member_of=[])
|
||||
@ -130,12 +130,12 @@ class TestPgLdapSync < Minitest::Test
|
||||
sync_with_config(config)
|
||||
end
|
||||
|
||||
def sync_change
|
||||
sync_to_fixture
|
||||
def sync_change(fixture: "ldapdb", config: "config-ldapdb")
|
||||
sync_to_fixture(fixture: fixture, config: config)
|
||||
|
||||
yield(@directory)
|
||||
|
||||
sync_with_config
|
||||
sync_with_config(config)
|
||||
exec_psql_du if $DEBUG
|
||||
end
|
||||
|
||||
@ -153,8 +153,8 @@ class TestPgLdapSync < Minitest::Test
|
||||
assert_role('All Users', 'Cannot login')
|
||||
assert_role('Flintstones', 'Cannot login')
|
||||
assert_role('Wilmas', 'Cannot login', ['All Users'])
|
||||
assert_role('fred', '', ['All Users', 'Flintstones'])
|
||||
assert_role('wilma', '', ['Flintstones', 'Wilmas'])
|
||||
assert_role('Fred', '', ['All Users', 'Flintstones'])
|
||||
assert_role('Wilma', '', ['Flintstones', 'Wilmas'])
|
||||
end
|
||||
|
||||
def test_add_membership
|
||||
@ -162,7 +162,17 @@ class TestPgLdapSync < Minitest::Test
|
||||
# add 'Fred' to 'Wilmas'
|
||||
@directory[0]['cn=Wilmas,dc=example,dc=com']['member'] << 'cn=Fred Flintstone,dc=example,dc=com'
|
||||
end
|
||||
assert_role('fred', '', ['All Users', 'Flintstones', 'Wilmas'])
|
||||
refute_role('fred')
|
||||
assert_role('Fred', '', ['All Users', 'Flintstones', 'Wilmas'])
|
||||
end
|
||||
|
||||
def test_add_membership_bothcase
|
||||
sync_change(config: "config-ldapdb-bothcase") do |dir|
|
||||
# add 'Fred' to 'Wilmas'
|
||||
@directory[0]['cn=Wilmas,dc=example,dc=com']['member'] << 'cn=Fred Flintstone,dc=example,dc=com'
|
||||
end
|
||||
assert_role('fred', '', ['All Users', 'all users', 'Flintstones', 'flintstones', 'Wilmas', 'wilmas'])
|
||||
assert_role('Fred', '', ['All Users', 'all users', 'Flintstones', 'flintstones', 'Wilmas', 'wilmas'])
|
||||
end
|
||||
|
||||
def test_revoke_membership
|
||||
@ -170,7 +180,7 @@ class TestPgLdapSync < Minitest::Test
|
||||
# revoke membership of 'wilma' to 'Flintstones'
|
||||
dir[0]['cn=Flintstones,dc=example,dc=com']['member'].pop
|
||||
end
|
||||
assert_role('wilma', '', ['Wilmas'])
|
||||
assert_role('Wilma', '', ['Wilmas'])
|
||||
end
|
||||
|
||||
def test_rename_role
|
||||
@ -179,6 +189,7 @@ class TestPgLdapSync < Minitest::Test
|
||||
dir[0]['cn=Wilma Flintstone,dc=example,dc=com']['sAMAccountName'] = ['Wilma Flintstone']
|
||||
end
|
||||
refute_role('wilma')
|
||||
refute_role('Wilma')
|
||||
assert_role('Wilma Flintstone', '', ['Flintstones', 'Wilmas'])
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user