5 Commits

Author SHA1 Message Date
4217ba52e0 Switch service LDAP attribute to serviceEnabled
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Improve internal naming on the way
2024-03-13 16:41:49 +01:00
de20931d30 Add tasks for modifying schema, first custom attributes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
refs #172, #173
2024-03-13 14:30:03 +01:00
8de0a2e26e Improve seed output 2024-03-13 14:28:31 +01:00
06521d1c34 LDAP: add delete_all_users method, use in seeds 2024-03-13 14:27:39 +01:00
38b3d68fd5 LDAP: Rename client method, add modify method 2024-03-13 14:26:44 +01:00
10 changed files with 110 additions and 33 deletions

View File

@@ -168,21 +168,21 @@ class User < ApplicationRecord
end
def services_enabled
ldap_entry[:service] || []
ldap_entry[:services_enabled] || []
end
def enable_service(service)
current_services = services_enabled
new_services = Array(service).map(&:to_s)
services = (current_services + new_services).uniq
ldap.replace_attribute(dn, :service, services)
ldap.replace_attribute(dn, :serviceEnabled, services)
end
def disable_service(service)
current_services = services_enabled
disabled_services = Array(service).map(&:to_s)
services = (current_services - disabled_services).uniq
ldap.replace_attribute(dn, :service, services)
ldap.replace_attribute(dn, :serviceEnabled, services)
end
def disable_all_services

View File

@@ -9,7 +9,7 @@ module LdapManager
attributes = %w{ jpegPhoto }
filter = Net::LDAP::Filter.eq("cn", @cn)
entry = ldap_client.search(base: treebase, filter: filter, attributes: attributes).first
entry = client.search(base: treebase, filter: filter, attributes: attributes).first
entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
end
end

View File

@@ -3,39 +3,49 @@ class LdapService < ApplicationService
@suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
end
def modify(dn, operations=[])
client.modify dn: dn, operations: operations
client.get_operation_result.code
end
def add_attribute(dn, attr, values)
ldap_client.add_attribute dn, attr, values
client.add_attribute dn, attr, values
client.get_operation_result.code
end
def replace_attribute(dn, attr, values)
ldap_client.replace_attribute dn, attr, values
client.replace_attribute dn, attr, values
client.get_operation_result.code
end
def delete_attribute(dn, attr)
ldap_client.delete_attribute dn, attr
client.delete_attribute dn, attr
client.get_operation_result.code
end
def add_entry(dn, attrs, interactive=false)
puts "Adding entry: #{dn}" if interactive
res = ldap_client.add dn: dn, attributes: attrs
puts res.inspect if interactive && !res
res
puts "Add entry: #{dn}" if interactive
client.add dn: dn, attributes: attrs
client.get_operation_result.code
end
def delete_entry(dn, interactive=false)
puts "Deleting entry: #{dn}" if interactive
res = ldap_client.delete dn: dn
puts res.inspect if interactive && !res
res
puts "Delete entry: #{dn}" if interactive
client.delete dn: dn
client.get_operation_result.code
end
def delete_all_entries!
def delete_all_users!
delete_all_entries!(objectclass: "person")
end
def delete_all_entries!(objectclass: "*")
if Rails.env.production?
raise "Mass deletion of entries not allowed in production"
end
filter = Net::LDAP::Filter.eq("objectClass", "*")
entries = ldap_client.search(base: @suffix, filter: filter, attributes: %w{dn})
filter = Net::LDAP::Filter.eq("objectClass", objectclass)
entries = client.search(base: @suffix, filter: filter, attributes: %w{dn})
entries.sort_by!{ |e| e.dn.length }.reverse!
entries.each do |e|
@@ -56,7 +66,7 @@ class LdapService < ApplicationService
]
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries = client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.cn[0] }
entries = entries.collect do |e|
{
@@ -64,9 +74,10 @@ class LdapService < ApplicationService
mail: e.try(:mail) ? e.mail.first : nil,
display_name: e.try(:displayName) ? e.displayName.first : nil,
admin: e.try(:admin) ? 'admin' : nil,
service: e.try(:service),
services_enabled: e.try(:serviceEnabled),
email_maildrop: e.try(:mailRoutingAddress),
email_password: e.try(:mailpassword)
email_password: e.try(:mailpassword),
nostr_key: e.try(:nostrKey)
}
end
end
@@ -77,7 +88,7 @@ class LdapService < ApplicationService
# filter = Net::LDAP::Filter.eq("objectClass", "*")
treebase = "cn=users,#{@suffix}"
entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
entries = client.search(base: treebase, filter: filter, attributes: attributes)
entries.sort_by! { |e| e.ou[0] }
@@ -129,8 +140,8 @@ class LdapService < ApplicationService
private
def ldap_client
ldap_client ||= Net::LDAP.new host: ldap_config['host'],
def client
client ||= Net::LDAP.new host: ldap_config['host'],
port: ldap_config['port'],
# TODO has to be :simple_tls if TLS is enabled
# encryption: ldap_config['ssl'],

View File

@@ -27,7 +27,6 @@ Devise.setup do |config|
config.ldap_auth_password_builder = Proc.new() { |new_password|
salt = SecureRandom.hex(32)
hashed_pw = Base64.strict_encode64(Digest::SHA512.digest(new_password + salt) + salt)
puts '{SSHA512}' + hashed_pw
'{SSHA512}' + hashed_pw
}

View File

@@ -3,6 +3,10 @@ require 'sidekiq/testing'
ldap = LdapService.new
Sidekiq::Testing.inline! do
ldap.delete_all_users!
puts "Create user: admin"
CreateAccount.call(account: {
username: "admin", domain: "kosmos.org", email: "admin@example.com",
password: "admin is admin", confirmed: true
@@ -10,6 +14,7 @@ Sidekiq::Testing.inline! do
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
puts "Create 35 random users"
35.times do |n|
username = Faker::Name.unique.first_name.downcase
email = Faker::Internet.unique.email

View File

@@ -1,6 +1,6 @@
namespace :ldap do
desc "Reset the LDAP directory and set up base entries and default org"
task setup: :environment do |t, args|
task setup: [:environment, :add_custom_attributes] do |t, args|
ldap = LdapService.new
ldap.delete_entry "cn=admin_role,ou=kosmos.org,cn=users,dc=kosmos,dc=org", true
@@ -19,6 +19,42 @@ namespace :ldap do
}, true
end
desc "Add custom attributes to schema"
task add_custom_attributes: :environment do |t, args|
%w[ admin service_enabled nostr_key ].each do |name|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "add")
Rake::Task['ldap:modify_ldap_schema'].reenable
end
end
desc "Delete custom attributes from schema"
task delete_custom_attributes: :environment do |t, args|
%w[ admin service_enabled nostr_key ].each do |name|
Rake::Task["ldap:modify_ldap_schema"].invoke(name, "delete")
Rake::Task['ldap:modify_ldap_schema'].reenable
end
end
desc "Modify LDAP schema"
task :modify_ldap_schema, [:name, :operation] => [:environment] do |t, args|
puts "Modify schema: #{args[:operation]} #{args[:name]}"
filename = "#{Rails.root}/schemas/ldap/#{args[:name]}.ldif"
ldif = YAML.safe_load(File.read(filename))
dn = ldif["dn"]
attribute = ldif["add"]
value = ldif[attribute]
operation = [ args[:operation].to_sym, attribute.to_sym, value ]
ldap = LdapService.new
res = ldap.modify dn, [ operation ]
if res != 0
puts "Result code: #{res}"
exit 1
end
end
desc "List user domains/organizations"
task list_organizations: :environment do |t, args|
ldap = LdapService.new

9
schemas/ldap/admin.ldif Normal file
View File

@@ -0,0 +1,9 @@
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.1
NAME 'admin'
DESC 'Admin flag'
EQUALITY booleanMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.7
SINGLE-VALUE )

View File

@@ -0,0 +1,9 @@
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.21
NAME 'nostrKey'
DESC 'Nostr public key'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )

View File

@@ -0,0 +1,8 @@
dn: cn=schema
changetype: modify
add: attributeTypes
attributeTypes: ( 1.3.6.1.4.1.61554.1.1.2.1.2
NAME 'serviceEnabled'
DESC 'Services enabled for account'
EQUALITY caseExactMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )

View File

@@ -66,7 +66,7 @@ RSpec.describe User, type: :model do
it "returns the entries from the LDAP service attribute" do
expect(user).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
service: ["discourse", "email", "gitea", "wiki", "xmpp"]
services_enabled: ["discourse", "email", "gitea", "wiki", "xmpp"]
})
expect(user.services_enabled).to eq(["discourse", "email", "gitea", "wiki", "xmpp"])
end
@@ -76,21 +76,21 @@ RSpec.describe User, type: :model do
before do
allow(user).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
service: ["discourse", "gitea"]
services_enabled: ["discourse", "gitea"]
})
allow(user).to receive(:dn).and_return(dn)
end
it "adds the service to the LDAP entry" do
expect_any_instance_of(LdapService).to receive(:replace_attribute)
.with(dn, :service, ["discourse", "gitea", "wiki"]).and_return(true)
.with(dn, :serviceEnabled, ["discourse", "gitea", "wiki"]).and_return(true)
user.enable_service(:wiki)
end
it "adds multiple service to the LDAP entry" do
expect_any_instance_of(LdapService).to receive(:replace_attribute)
.with(dn, :service, ["discourse", "gitea", "wiki", "xmpp"]).and_return(true)
.with(dn, :serviceEnabled, ["discourse", "gitea", "wiki", "xmpp"]).and_return(true)
user.enable_service([:wiki, :xmpp])
end
@@ -100,21 +100,21 @@ RSpec.describe User, type: :model do
before do
allow(user).to receive(:ldap_entry).and_return({
uid: user.cn, ou: user.ou, mail: user.email, admin: nil,
service: ["discourse", "gitea", "xmpp"]
services_enabled: ["discourse", "gitea", "xmpp"]
})
allow(user).to receive(:dn).and_return(dn)
end
it "removes the service from the LDAP entry" do
expect_any_instance_of(LdapService).to receive(:replace_attribute)
.with(dn, :service, ["discourse", "gitea"]).and_return(true)
.with(dn, :serviceEnabled, ["discourse", "gitea"]).and_return(true)
user.disable_service(:xmpp)
end
it "removes multiple services from the LDAP entry" do
expect_any_instance_of(LdapService).to receive(:replace_attribute)
.with(dn, :service, ["discourse"]).and_return(true)
.with(dn, :serviceEnabled, ["discourse"]).and_return(true)
user.disable_service([:xmpp, "gitea"])
end