142 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
class LdapService < ApplicationService
 | 
						|
  def initialize
 | 
						|
    @suffix = ENV["LDAP_SUFFIX"] || "dc=kosmos,dc=org"
 | 
						|
  end
 | 
						|
 | 
						|
  def add_attribute(dn, attr, values)
 | 
						|
    ldap_client.add_attribute dn, attr, values
 | 
						|
  end
 | 
						|
 | 
						|
  def replace_attribute(dn, attr, values)
 | 
						|
    ldap_client.replace_attribute dn, attr, values
 | 
						|
  end
 | 
						|
 | 
						|
  def delete_attribute(dn, attr)
 | 
						|
    ldap_client.delete_attribute dn, attr
 | 
						|
  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
 | 
						|
  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
 | 
						|
  end
 | 
						|
 | 
						|
  def delete_all_entries!
 | 
						|
    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})
 | 
						|
    entries.sort_by!{ |e| e.dn.length }.reverse!
 | 
						|
 | 
						|
    entries.each do |e|
 | 
						|
      delete_entry e.dn, true
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def fetch_users(args={})
 | 
						|
    if args[:ou]
 | 
						|
      treebase = "ou=#{args[:ou]},cn=users,#{@suffix}"
 | 
						|
    else
 | 
						|
      treebase = ldap_config["base"]
 | 
						|
    end
 | 
						|
 | 
						|
    attributes = %w{dn cn uid mail admin service}
 | 
						|
    filter     = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
 | 
						|
 | 
						|
    entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
 | 
						|
    entries.sort_by! { |e| e.cn[0] }
 | 
						|
    entries = entries.collect do |e|
 | 
						|
      {
 | 
						|
        uid: e.uid.first,
 | 
						|
        mail: e.try(:mail) ? e.mail.first : nil,
 | 
						|
        admin: e.try(:admin) ? 'admin' : nil,
 | 
						|
        service: e.try(:service)
 | 
						|
      }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def fetch_organizations
 | 
						|
    attributes = %w{dn ou description}
 | 
						|
    filter     = Net::LDAP::Filter.eq("objectClass", "organizationalUnit")
 | 
						|
    # filter     = Net::LDAP::Filter.eq("objectClass", "*")
 | 
						|
    treebase   = "cn=users,#{@suffix}"
 | 
						|
 | 
						|
    entries = ldap_client.search(base: treebase, filter: filter, attributes: attributes)
 | 
						|
 | 
						|
    entries.sort_by! { |e| e.ou[0] }
 | 
						|
 | 
						|
    entries = entries.collect do |e|
 | 
						|
      {
 | 
						|
        dn: e.dn,
 | 
						|
        ou: e.ou.first,
 | 
						|
        description: e.try(:description) ? e.description.first : nil,
 | 
						|
      }
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def add_organization(ou, description, interactive=false)
 | 
						|
    dn = "ou=#{ou},cn=users,#{@suffix}"
 | 
						|
 | 
						|
    aci = <<-EOS
 | 
						|
(target="ldap:///cn=*,ou=#{ou},cn=users,#{@suffix}")(targetattr="cn || sn || uid || mail || userPassword || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{@suffix}";)
 | 
						|
    EOS
 | 
						|
 | 
						|
    attrs = {
 | 
						|
      objectClass: ["top", "organizationalUnit"],
 | 
						|
      description: description,
 | 
						|
      ou: ou,
 | 
						|
      aci: aci
 | 
						|
    }
 | 
						|
 | 
						|
    add_entry dn, attrs, interactive
 | 
						|
  end
 | 
						|
 | 
						|
  def reset_directory!
 | 
						|
    if Rails.env.production?
 | 
						|
      raise "Resetting the directory not allowed in production"
 | 
						|
    end
 | 
						|
 | 
						|
    delete_all_entries!
 | 
						|
 | 
						|
    user_read_aci = <<-EOS
 | 
						|
(target="ldap:///#{@suffix}")(targetattr="*") (version 3.0; acl "user-read-search-own-attributes"; allow (read,search) userdn="ldap:///self";)
 | 
						|
    EOS
 | 
						|
 | 
						|
    add_entry @suffix, {
 | 
						|
      dc: "kosmos", objectClass: ["top", "domain"], aci: user_read_aci
 | 
						|
    }, true
 | 
						|
 | 
						|
    add_entry "cn=users,#{@suffix}", {
 | 
						|
       cn: "users", objectClass: ["top", "organizationalRole"]
 | 
						|
    }, true
 | 
						|
  end
 | 
						|
 | 
						|
  private
 | 
						|
 | 
						|
  def ldap_client
 | 
						|
    ldap_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'],
 | 
						|
      auth: {
 | 
						|
        method: :simple,
 | 
						|
        username: ldap_config['admin_user'],
 | 
						|
        password: ldap_config['admin_password']
 | 
						|
      }
 | 
						|
  end
 | 
						|
 | 
						|
  def ldap_config
 | 
						|
    ldap_config ||= YAML.load(ERB.new(File.read("#{Rails.root}/config/ldap.yml")).result)[Rails.env]
 | 
						|
  end
 | 
						|
end
 |