commit 5174b503ce8e352baae3100d518158bf23758b87
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Mon Sep 6 13:24:14 2010 +0200

    [evol] added support for multiple worlds (closes #144)

diff --git a/.gitignore b/.gitignore
index 9bb2a8a..69299c1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,5 @@
 lib/ldap_shadows/config_setup.rb
 data/locale
 #
-conf/ldap_shadows/global_private.conf
+conf/ldap_shadows/worlds/*/world_private.conf
 var
diff --git a/bin/shadowwalker b/bin/shadowwalker
index d155714..47391e4 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -41,6 +41,7 @@ $program_options = {
   :debug       => false,
   :expert      => false,
   :handles     => false,
+  :world_name  => nil,
   :shadow_name => nil
 }
 
@@ -55,6 +56,7 @@ cmdparser.options = CmdParse::OptionParserWrapper.new do |opt|
   opt.on("-d", "--debug", "Output debug info without being formated") {|t| $program_options[:debug] = true }
   opt.on("-e", "--expert", "Output extra info for expert users") {|t| $program_options[:expert] = true }
   opt.on("-H", "--handles", "Output with handles (objects/field/... keys used for manipulations)") {|t| $program_options[:handles] = true }
+  opt.on("-w WORLD", "--world WORLD", "Choose world to enter") {|t| $program_options[:world_name] = t }
   opt.on("-s SHADOW", "--shadow SHADOW", "Choose shadow to travel through") {|t| $program_options[:shadow_name] = t }
 end
 
@@ -79,7 +81,7 @@ class Command < CmdParse::Command
 
   def execute(args)
     config = LdapShadows::Config.instance
-    config.load_global_config([File.join(ENV['HOME'], ".shadowwalker")])
+    config.load_world_config($program_options[:world_name], [File.join(ENV['HOME'], ".shadowwalker")])
     begin
       @shadow = config.load_shadow($program_options[:shadow_name])
     rescue ActiveLdap::Error => e
diff --git a/conf/ldap_shadows/global.conf b/conf/ldap_shadows/global.conf
index b10e91b..4068608 100644
--- a/conf/ldap_shadows/global.conf
+++ b/conf/ldap_shadows/global.conf
@@ -1,3 +1,2 @@
 ---
-default_shadow: MilkyPond
-password_hash: ssha
+default_world: MilkyPond
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf
deleted file mode 100644
index 92c5af6..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/fs.conf
+++ /dev/null
@@ -1,21 +0,0 @@
----
-mapping:
-  classes: ['fsUser']
-  associated_relations: ['primaryGroup', 'secondaryGroups']
-relations:
-  primaryGroup:
-    type: belongs_to
-    object: group
-    foreign_key: gidNumber
-    primary_key: gidNumber
-  secondaryGroups:
-    type: belongs_to
-    object: group
-    many: uniqueMember
-    primary_key: dn
-plugin:
-  min_uid: 10000
-  max_uid: 65535
-  step_uid: 100
-  default_group: dc-users
-  default_home_base: '/home'
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/ftp.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/ftp.conf
deleted file mode 100644
index b7e2574..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/ftp.conf
+++ /dev/null
@@ -1,4 +0,0 @@
----
-mapping:
-  classes: ['ftpUser']
-  depend_aspects: ['primary_account', 'fs']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/jabber.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/jabber.conf
deleted file mode 100644
index a9e969d..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/jabber.conf
+++ /dev/null
@@ -1,3 +0,0 @@
----
-mapping:
-  classes: ['jabberUser']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/mail.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/mail.conf
deleted file mode 100644
index 7a027af..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/mail.conf
+++ /dev/null
@@ -1,3 +0,0 @@
----
-mapping:
-  classes: ['emailUser']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/primary_account.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/primary_account.conf
deleted file mode 100644
index 6deb2db..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/primary_account.conf
+++ /dev/null
@@ -1,4 +0,0 @@
----
-mapping:
-  classes: ['primaryAccount']
-  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/shell.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/shell.conf
deleted file mode 100644
index 5f2250d..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/shell.conf
+++ /dev/null
@@ -1,5 +0,0 @@
----
-mapping:
-  classes: ['shellUser']
-  depend_aspects: ['primary_account', 'fs']
-  associated_attributes: ['loginShell']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/aspects/web.conf b/conf/ldap_shadows/shadows/MilkyPond/aspects/web.conf
deleted file mode 100644
index 06388ef..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/aspects/web.conf
+++ /dev/null
@@ -1,4 +0,0 @@
----
-mapping:
-  classes: ['webUser']
-  depend_aspects: ['primary_account']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/bot.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/bot.conf
deleted file mode 100644
index 4dacb5d..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/bot.conf
+++ /dev/null
@@ -1,11 +0,0 @@
----
-mapping:
-  dn_attribute: uid
-  prefix: ''
-  classes: ['bot']
-  sort_by: uid
-  possible_aspects: ['primary_account', 'mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
-  default_parent: "unit/SpecialAccounts"
-presentation:
-  hidden_attributes: ['uid']
-  expert_attributes: ['uidNumber', 'gidNumber', 'gecos']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/domain.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/domain.conf
deleted file mode 100644
index fbbb07b..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/domain.conf
+++ /dev/null
@@ -1,9 +0,0 @@
----
-mapping:
-  dn_attribute: cn
-  prefix: ''
-  classes: ['genericDomain']
-  sort_by: cn
-  parent_handle_restrictions: "^(unit/Domains|(individual|bot)/.+)$"
-  default_parent: "unit/Domains"
-
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
deleted file mode 100644
index d61c16e..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
+++ /dev/null
@@ -1,24 +0,0 @@
----
-mapping:
-  dn_attribute: o
-  prefix: ''
-  classes: ['entity']
-  excluded_classes: ['dcObject']
-  sort_by: o
-  associated_relations: ['foundersIndividuals', 'foundersEntities']
-  parent_handle_restrictions: "^unit/Entities$"
-  default_parent: "unit/Entities"
-presentation:
-  name_attribute: o
-  hidden_attributes: ['founder']
-relations:
-  foundersIndividuals:
-    type: has_many
-    object: individual
-    foreign_key: founder
-    primary_key: dn
-  foundersEntities:
-    type: has_many
-    object: entity
-    foreign_key: founder
-    primary_key: dn
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf
deleted file mode 100644
index 8eebef3..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/group.conf
+++ /dev/null
@@ -1,31 +0,0 @@
----
-mapping:
-  dn_attribute: cn
-  prefix: ''
-  classes: ['posixGroup', 'groupOfMembers']
-  sort_by: cn
-  associated_relations: ['individualsPrimaryMembers', 'botsPrimaryMembers', 'individualsSecondaryMembers', 'botsSecondaryMembers']
-presentation:
-  hidden_attributes: ['uniqueMember']
-  expert_attributes: ['gidNumber']
-relations:
-  individualsPrimaryMembers:
-    type: belongs_to
-    object: individual
-    many: gidNumber
-    primary_key: gidNumber
-  botsPrimaryMembers:
-    type: belongs_to
-    object: bot
-    many: gidNumber
-    primary_key: gidNumber
-  individualsSecondaryMembers:
-    type: has_many
-    object: individual
-    foreign_key: uniqueMember
-    primary_key: dn
-  botsSecondaryMembers:
-    type: has_many
-    object: bot
-    foreign_key: uniqueMember
-    primary_key: dn
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/individual.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/individual.conf
deleted file mode 100644
index e020d5a..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/individual.conf
+++ /dev/null
@@ -1,12 +0,0 @@
----
-mapping:
-  dn_attribute: uid
-  prefix: ''
-  classes: ['individual']
-  sort_by: uid
-  possible_aspects: ['primary_account', 'mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
-  parent_handle_restrictions: "^unit/People$"
-  default_parent: "unit/People"
-presentation:
-  hidden_attributes: ['uid']
-  expert_attributes: ['uidNumber', 'gidNumber', 'gecos']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/secondary_account.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/secondary_account.conf
deleted file mode 100644
index 18e444f..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/secondary_account.conf
+++ /dev/null
@@ -1,10 +0,0 @@
----
-mapping:
-  dn_attribute: uid
-  prefix: ''
-  classes: ['secondaryAccount']
-  sort_by: uid
-  possible_aspects: ['mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
-  parent_handle_restrictions: "^(unit/SpecialAccounts|(individual|bot|domain)/.+)$"
-  default_parent: "unit/SpecialAccounts"
-  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/system_account.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/system_account.conf
deleted file mode 100644
index 2ef7e68..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/system_account.conf
+++ /dev/null
@@ -1,9 +0,0 @@
----
-mapping:
-  dn_attribute: uid
-  prefix: ''
-  classes: ['systemAccount']
-  sort_by: uid
-  possible_aspects: ['mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
-  default_parent: "unit/SpecialAccounts"
-  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/top_entity.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/top_entity.conf
deleted file mode 100644
index a4be6c7..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/top_entity.conf
+++ /dev/null
@@ -1,9 +0,0 @@
----
-mapping:
-  dn_attribute: dc
-  prefix: ''
-  classes: ['entity', 'dcObject']
-  sort_by: o
-  parent_handle_restrictions: "^$"
-presentation:
-  name_attribute: o
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/unit.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/unit.conf
deleted file mode 100644
index 9de9245..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/unit.conf
+++ /dev/null
@@ -1,8 +0,0 @@
----
-mapping:
-  dn_attribute: ou
-  prefix: ''
-  classes: ['organizationalUnit']
-  # no sort_by means sort by DN
-presentation:
-  name_attribute: dn
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/user_service.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/user_service.conf
deleted file mode 100644
index f4a0431..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/user_service.conf
+++ /dev/null
@@ -1,8 +0,0 @@
----
-mapping:
-  dn_attribute: cn
-  prefix: ''
-  classes: ['userService']
-  sort_by: cn
-  possible_aspects: []
-  parent_handle_restrictions: "^unit/ProvidedServices$"
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/fs.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/fs.rb
deleted file mode 100644
index e6dfe6c..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/fs.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-
-def self.hook_before_create(item)
-  hook_common(item)
-end
-
-def self.hook_before_modify(item)
-  hook_common(item)
-end
-
-def self.hook_common(item)
-  pconfig_default = {
-    :default_group     => 'users',
-    :min_uid           => 1000,
-    :max_uid           => 65535,
-    :step_uid          => 100,
-    :default_home_base => "/home"
-  }
-  pconfig = pconfig_default.merge(self.parameters[:plugin])
-
-  unless item.attribute_present?('gidNumber')
-    item.primaryGroup = item.class.shadow.get_object(:group).find(:first, pconfig[:default_group])
-  end
-
-  if item.attribute_present?('uidNumber')
-    user = ActiveLdap::Base.find(:first, :scope => :sub, :filter => "(&(objectClass=posixAccount)(uidNumber=#{item.uidNumber}))", :attributes => ['uidNumber'])
-    unless user.nil? or user.dn == item.dn
-      raise_error "UID already used by another item"
-    end
-  else
-    users = ActiveLdap::Base.find(:all, :scope => :sub, :filter => "(&(objectClass=posixAccount)(uidNumber=*))", :attributes => ['uidNumber'])
-    uidnumbers =  users.collect {|user| user.uidNumber }
-
-    avail_uidnumber = nil
-    min_uidn = pconfig[:min_uid]
-    max_uidn = [min_uidn + pconfig[:step_uid], pconfig[:max_uid]].min
-    while avail_uidnumber.nil?
-      avail = (min_uidn..max_uidn).to_a - uidnumbers
-      unless avail.empty?
-        avail_uidnumber = avail.first
-        break
-      end
-      min_uidn = max_uidn
-      max_uidn = [min_uidn + pconfig[:step_uid], pconfig[:max_uid]].min
-    end
-
-    if avail_uidnumber.nil?
-      raise_error "Available UID range is depleted."
-    else
-      item.uidNumber = avail_uidnumber
-    end
-  end
-
-  unless item.attribute_present?('homeDirectory')
-    item.homeDirectory = File.join(pconfig[:default_home_base], item.uid)
-  end
-end
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/primary_account.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/primary_account.rb
deleted file mode 100644
index 74998fb..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/aspects/primary_account.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-
-require 'shared_attributes'
-
-def self.hook_before_create(item)
-  hook_common(item)
-end
-
-def self.hook_before_modify(item)
-  hook_common(item)
-end
-
-def self.hook_common(item)
-  SharedAttr.check_common_attributes(item)
-end
-
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/includes/shared_attributes.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/includes/shared_attributes.rb
deleted file mode 100644
index 0c7922d..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/includes/shared_attributes.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-module SharedAttr
-  module_function
-
-  def check_common_attributes(item)
-    if item.attribute_present?('allowedServices')
-      user_services = ActiveLdap::Base.find(:all, :scope => :sub, :filter => "(objectClass=userService)", :attributes => ['cn'])
-      srv_names =  user_services.collect {|srv| srv.cn }
-
-      invalid_srv_names = item.allowedServices(true) - (item.allowedServices(true) & srv_names)
-      unless invalid_srv_names.empty?
-        raise PreProcessingError, "Invalid User Service(s) name(s): " + invalid_srv_names.join(", ")
-      end
-    end
-  end
-end
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/individual.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/individual.rb
deleted file mode 100644
index 4bc7a6e..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/individual.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-
-def self.hook_before_create(item)
-  unless item.attribute_present?('cn')
-    cn = item.surname || ""
-    cn = item.givenName + " " + cn if item.givenName
-    item.cn = cn
-  end
-end
-
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/secondary_account.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/secondary_account.rb
deleted file mode 100644
index 74998fb..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/secondary_account.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-
-require 'shared_attributes'
-
-def self.hook_before_create(item)
-  hook_common(item)
-end
-
-def self.hook_before_modify(item)
-  hook_common(item)
-end
-
-def self.hook_common(item)
-  SharedAttr.check_common_attributes(item)
-end
-
diff --git a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/system_account.rb b/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/system_account.rb
deleted file mode 100644
index 74998fb..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/plugins/objects/system_account.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-
-require 'shared_attributes'
-
-def self.hook_before_create(item)
-  hook_common(item)
-end
-
-def self.hook_before_modify(item)
-  hook_common(item)
-end
-
-def self.hook_common(item)
-  SharedAttr.check_common_attributes(item)
-end
-
diff --git a/conf/ldap_shadows/shadows/MilkyPond/shadow.conf b/conf/ldap_shadows/shadows/MilkyPond/shadow.conf
deleted file mode 100644
index 6f9d97b..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/shadow.conf
+++ /dev/null
@@ -1,4 +0,0 @@
----
-presentation:
-  hidden_attributes: ['objectClass', 'userPassword', 'hasSubordinates', 'entryUUID', 'entryDN', 'structuralObjectClass', 'subschemaSubentry']
-  tree_objects: ['unit', 'entity']
diff --git a/conf/ldap_shadows/shadows/MilkyPond/translations/en.yml b/conf/ldap_shadows/shadows/MilkyPond/translations/en.yml
deleted file mode 100644
index 086200f..0000000
--- a/conf/ldap_shadows/shadows/MilkyPond/translations/en.yml
+++ /dev/null
@@ -1,76 +0,0 @@
----
-en:
-  objects:
-    bot: "Bot"
-    domain: "Domain"
-    entity: "Entity"
-    group: "Group"
-    individual: "Individual"
-    secondary_account: "Secondary Account"
-    system_account: "System Account"
-    unit: "Unit"
-    user_service: "User Service"
-  attribute_types:
-    allowGlobalDirectory: "Disclose Own Contact Information in GLobal Directory"
-    cn: "Full Name"
-    createTimestamp: "Item creation time"
-    creatorsName: "Item Creator"
-    birthday: "Birthday"
-    birthlocation: "Birth Location"
-    description: "Description"
-    entryUUID: "Item UUID"
-    foundingDate: "Founding Date"
-    ftpHost: "FTP Allowed Hosts"
-    gecos: "GECOS"
-    gidNumber: "Primary Group (numeric)"
-    givenName: "FirstName"
-    homeDirectory: "Home Directory"
-    homePostalAddress: "Home Postal Address"
-    host: "Shell Allowed Hosts"
-    ipServiceName: "Service Name"
-    ipServiceHost: "Service Host"
-    ipServicePort: "Service Port"
-    ipServiceProtocol: "Service Protocol"
-    jid: "Jabber ID"
-    jpegPhoto: "Photo"
-    keyFingerPrint: "GPG/PGP Key Fingerprint"
-    labeledURI: "Web Site"
-    loginShell: "Shell Interpreter"
-    mail: "eMail address(es)"
-    mailForward: "eMail Forward Adress(es)"
-    mailQuota: "Maximum Mailbox Size"
-    manager: "Manager(s)"
-    mobile: "Mobile Phone"
-    modifiersName: "Item Last Modifier"
-    modifyTimestamp: "Item Last Modification Time"
-    occupation: "Job / Studies"
-    o: "Organization(s)"
-    structuralObjectClass: "Item Structural Classes"
-    owner: "Owner(s)"
-    preferredLanguage: "Language Preference"
-    sn: "Surname"
-    sshAuthKey: "SSH Public Key(s)"
-    uid: "Identifier (Login)"
-    uidNumber: "Identifier (numeric)"
-    uniqueAbbreviation: "Unique Abbreviation"
-    uniqueMember: "Group member"
-    userPassword: "Password"
-    webVirtualHost: "Hosted Web Sites"
-    allowedServices: "Authorized services"
-  relations:
-    primaryGroup: "Primary Group"
-    secondaryGroups: "Secondary Groups"
-    individualsPrimaryMembers: "Primary Member(s) Individual(s)"
-    botsPrimaryMembers: "Primary Member(s) Bot(s)"
-    individualsSecondaryMembers: "Secondary Member(s) Individual(s)"
-    botsSecondaryMembers: "Secondary Member(s) Bot(s)"
-    foundersIndividuals: "Founder(s) Individual(s)"
-    foundersEntities: "Founder(s) Entity(ies)"
-  aspects:
-    fs: "FileSystem Account"
-    ftp: "FTP Account"
-    jabber: "Jabber Account"
-    mail: "eMail Account"
-    primary_account: "Primary Account"
-    shell: "Shell Account"
-    web: "Web Account"
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/fs.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/fs.conf
new file mode 100644
index 0000000..92c5af6
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/fs.conf
@@ -0,0 +1,21 @@
+---
+mapping:
+  classes: ['fsUser']
+  associated_relations: ['primaryGroup', 'secondaryGroups']
+relations:
+  primaryGroup:
+    type: belongs_to
+    object: group
+    foreign_key: gidNumber
+    primary_key: gidNumber
+  secondaryGroups:
+    type: belongs_to
+    object: group
+    many: uniqueMember
+    primary_key: dn
+plugin:
+  min_uid: 10000
+  max_uid: 65535
+  step_uid: 100
+  default_group: dc-users
+  default_home_base: '/home'
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/ftp.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/ftp.conf
new file mode 100644
index 0000000..b7e2574
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/ftp.conf
@@ -0,0 +1,4 @@
+---
+mapping:
+  classes: ['ftpUser']
+  depend_aspects: ['primary_account', 'fs']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/jabber.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/jabber.conf
new file mode 100644
index 0000000..a9e969d
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/jabber.conf
@@ -0,0 +1,3 @@
+---
+mapping:
+  classes: ['jabberUser']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/mail.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/mail.conf
new file mode 100644
index 0000000..7a027af
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/mail.conf
@@ -0,0 +1,3 @@
+---
+mapping:
+  classes: ['emailUser']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/primary_account.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/primary_account.conf
new file mode 100644
index 0000000..6deb2db
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/primary_account.conf
@@ -0,0 +1,4 @@
+---
+mapping:
+  classes: ['primaryAccount']
+  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/shell.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/shell.conf
new file mode 100644
index 0000000..5f2250d
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/shell.conf
@@ -0,0 +1,5 @@
+---
+mapping:
+  classes: ['shellUser']
+  depend_aspects: ['primary_account', 'fs']
+  associated_attributes: ['loginShell']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/web.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/web.conf
new file mode 100644
index 0000000..06388ef
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/aspects/web.conf
@@ -0,0 +1,4 @@
+---
+mapping:
+  classes: ['webUser']
+  depend_aspects: ['primary_account']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/bot.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/bot.conf
new file mode 100644
index 0000000..4dacb5d
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/bot.conf
@@ -0,0 +1,11 @@
+---
+mapping:
+  dn_attribute: uid
+  prefix: ''
+  classes: ['bot']
+  sort_by: uid
+  possible_aspects: ['primary_account', 'mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
+  default_parent: "unit/SpecialAccounts"
+presentation:
+  hidden_attributes: ['uid']
+  expert_attributes: ['uidNumber', 'gidNumber', 'gecos']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/domain.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/domain.conf
new file mode 100644
index 0000000..fbbb07b
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/domain.conf
@@ -0,0 +1,9 @@
+---
+mapping:
+  dn_attribute: cn
+  prefix: ''
+  classes: ['genericDomain']
+  sort_by: cn
+  parent_handle_restrictions: "^(unit/Domains|(individual|bot)/.+)$"
+  default_parent: "unit/Domains"
+
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/entity.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/entity.conf
new file mode 100644
index 0000000..d61c16e
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/entity.conf
@@ -0,0 +1,24 @@
+---
+mapping:
+  dn_attribute: o
+  prefix: ''
+  classes: ['entity']
+  excluded_classes: ['dcObject']
+  sort_by: o
+  associated_relations: ['foundersIndividuals', 'foundersEntities']
+  parent_handle_restrictions: "^unit/Entities$"
+  default_parent: "unit/Entities"
+presentation:
+  name_attribute: o
+  hidden_attributes: ['founder']
+relations:
+  foundersIndividuals:
+    type: has_many
+    object: individual
+    foreign_key: founder
+    primary_key: dn
+  foundersEntities:
+    type: has_many
+    object: entity
+    foreign_key: founder
+    primary_key: dn
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/group.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/group.conf
new file mode 100644
index 0000000..8eebef3
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/group.conf
@@ -0,0 +1,31 @@
+---
+mapping:
+  dn_attribute: cn
+  prefix: ''
+  classes: ['posixGroup', 'groupOfMembers']
+  sort_by: cn
+  associated_relations: ['individualsPrimaryMembers', 'botsPrimaryMembers', 'individualsSecondaryMembers', 'botsSecondaryMembers']
+presentation:
+  hidden_attributes: ['uniqueMember']
+  expert_attributes: ['gidNumber']
+relations:
+  individualsPrimaryMembers:
+    type: belongs_to
+    object: individual
+    many: gidNumber
+    primary_key: gidNumber
+  botsPrimaryMembers:
+    type: belongs_to
+    object: bot
+    many: gidNumber
+    primary_key: gidNumber
+  individualsSecondaryMembers:
+    type: has_many
+    object: individual
+    foreign_key: uniqueMember
+    primary_key: dn
+  botsSecondaryMembers:
+    type: has_many
+    object: bot
+    foreign_key: uniqueMember
+    primary_key: dn
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/individual.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/individual.conf
new file mode 100644
index 0000000..e020d5a
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/individual.conf
@@ -0,0 +1,12 @@
+---
+mapping:
+  dn_attribute: uid
+  prefix: ''
+  classes: ['individual']
+  sort_by: uid
+  possible_aspects: ['primary_account', 'mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
+  parent_handle_restrictions: "^unit/People$"
+  default_parent: "unit/People"
+presentation:
+  hidden_attributes: ['uid']
+  expert_attributes: ['uidNumber', 'gidNumber', 'gecos']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/secondary_account.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/secondary_account.conf
new file mode 100644
index 0000000..18e444f
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/secondary_account.conf
@@ -0,0 +1,10 @@
+---
+mapping:
+  dn_attribute: uid
+  prefix: ''
+  classes: ['secondaryAccount']
+  sort_by: uid
+  possible_aspects: ['mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
+  parent_handle_restrictions: "^(unit/SpecialAccounts|(individual|bot|domain)/.+)$"
+  default_parent: "unit/SpecialAccounts"
+  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/system_account.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/system_account.conf
new file mode 100644
index 0000000..2ef7e68
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/system_account.conf
@@ -0,0 +1,9 @@
+---
+mapping:
+  dn_attribute: uid
+  prefix: ''
+  classes: ['systemAccount']
+  sort_by: uid
+  possible_aspects: ['mail', 'fs', 'shell', 'ftp', 'web', 'jabber']
+  default_parent: "unit/SpecialAccounts"
+  associated_attributes: ['uid', 'allowedServices']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/top_entity.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/top_entity.conf
new file mode 100644
index 0000000..a4be6c7
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/top_entity.conf
@@ -0,0 +1,9 @@
+---
+mapping:
+  dn_attribute: dc
+  prefix: ''
+  classes: ['entity', 'dcObject']
+  sort_by: o
+  parent_handle_restrictions: "^$"
+presentation:
+  name_attribute: o
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/unit.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/unit.conf
new file mode 100644
index 0000000..9de9245
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/unit.conf
@@ -0,0 +1,8 @@
+---
+mapping:
+  dn_attribute: ou
+  prefix: ''
+  classes: ['organizationalUnit']
+  # no sort_by means sort by DN
+presentation:
+  name_attribute: dn
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/user_service.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/user_service.conf
new file mode 100644
index 0000000..f4a0431
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/objects/user_service.conf
@@ -0,0 +1,8 @@
+---
+mapping:
+  dn_attribute: cn
+  prefix: ''
+  classes: ['userService']
+  sort_by: cn
+  possible_aspects: []
+  parent_handle_restrictions: "^unit/ProvidedServices$"
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/fs.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/fs.rb
new file mode 100644
index 0000000..e6dfe6c
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/fs.rb
@@ -0,0 +1,56 @@
+
+def self.hook_before_create(item)
+  hook_common(item)
+end
+
+def self.hook_before_modify(item)
+  hook_common(item)
+end
+
+def self.hook_common(item)
+  pconfig_default = {
+    :default_group     => 'users',
+    :min_uid           => 1000,
+    :max_uid           => 65535,
+    :step_uid          => 100,
+    :default_home_base => "/home"
+  }
+  pconfig = pconfig_default.merge(self.parameters[:plugin])
+
+  unless item.attribute_present?('gidNumber')
+    item.primaryGroup = item.class.shadow.get_object(:group).find(:first, pconfig[:default_group])
+  end
+
+  if item.attribute_present?('uidNumber')
+    user = ActiveLdap::Base.find(:first, :scope => :sub, :filter => "(&(objectClass=posixAccount)(uidNumber=#{item.uidNumber}))", :attributes => ['uidNumber'])
+    unless user.nil? or user.dn == item.dn
+      raise_error "UID already used by another item"
+    end
+  else
+    users = ActiveLdap::Base.find(:all, :scope => :sub, :filter => "(&(objectClass=posixAccount)(uidNumber=*))", :attributes => ['uidNumber'])
+    uidnumbers =  users.collect {|user| user.uidNumber }
+
+    avail_uidnumber = nil
+    min_uidn = pconfig[:min_uid]
+    max_uidn = [min_uidn + pconfig[:step_uid], pconfig[:max_uid]].min
+    while avail_uidnumber.nil?
+      avail = (min_uidn..max_uidn).to_a - uidnumbers
+      unless avail.empty?
+        avail_uidnumber = avail.first
+        break
+      end
+      min_uidn = max_uidn
+      max_uidn = [min_uidn + pconfig[:step_uid], pconfig[:max_uid]].min
+    end
+
+    if avail_uidnumber.nil?
+      raise_error "Available UID range is depleted."
+    else
+      item.uidNumber = avail_uidnumber
+    end
+  end
+
+  unless item.attribute_present?('homeDirectory')
+    item.homeDirectory = File.join(pconfig[:default_home_base], item.uid)
+  end
+end
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/primary_account.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/primary_account.rb
new file mode 100644
index 0000000..74998fb
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/aspects/primary_account.rb
@@ -0,0 +1,15 @@
+
+require 'shared_attributes'
+
+def self.hook_before_create(item)
+  hook_common(item)
+end
+
+def self.hook_before_modify(item)
+  hook_common(item)
+end
+
+def self.hook_common(item)
+  SharedAttr.check_common_attributes(item)
+end
+
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/includes/shared_attributes.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/includes/shared_attributes.rb
new file mode 100644
index 0000000..0c7922d
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/includes/shared_attributes.rb
@@ -0,0 +1,15 @@
+module SharedAttr
+  module_function
+
+  def check_common_attributes(item)
+    if item.attribute_present?('allowedServices')
+      user_services = ActiveLdap::Base.find(:all, :scope => :sub, :filter => "(objectClass=userService)", :attributes => ['cn'])
+      srv_names =  user_services.collect {|srv| srv.cn }
+
+      invalid_srv_names = item.allowedServices(true) - (item.allowedServices(true) & srv_names)
+      unless invalid_srv_names.empty?
+        raise PreProcessingError, "Invalid User Service(s) name(s): " + invalid_srv_names.join(", ")
+      end
+    end
+  end
+end
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/individual.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/individual.rb
new file mode 100644
index 0000000..4bc7a6e
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/individual.rb
@@ -0,0 +1,9 @@
+
+def self.hook_before_create(item)
+  unless item.attribute_present?('cn')
+    cn = item.surname || ""
+    cn = item.givenName + " " + cn if item.givenName
+    item.cn = cn
+  end
+end
+
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/secondary_account.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/secondary_account.rb
new file mode 100644
index 0000000..74998fb
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/secondary_account.rb
@@ -0,0 +1,15 @@
+
+require 'shared_attributes'
+
+def self.hook_before_create(item)
+  hook_common(item)
+end
+
+def self.hook_before_modify(item)
+  hook_common(item)
+end
+
+def self.hook_common(item)
+  SharedAttr.check_common_attributes(item)
+end
+
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/system_account.rb b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/system_account.rb
new file mode 100644
index 0000000..74998fb
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/plugins/objects/system_account.rb
@@ -0,0 +1,15 @@
+
+require 'shared_attributes'
+
+def self.hook_before_create(item)
+  hook_common(item)
+end
+
+def self.hook_before_modify(item)
+  hook_common(item)
+end
+
+def self.hook_common(item)
+  SharedAttr.check_common_attributes(item)
+end
+
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/shadow.conf b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/shadow.conf
new file mode 100644
index 0000000..6f9d97b
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/shadow.conf
@@ -0,0 +1,4 @@
+---
+presentation:
+  hidden_attributes: ['objectClass', 'userPassword', 'hasSubordinates', 'entryUUID', 'entryDN', 'structuralObjectClass', 'subschemaSubentry']
+  tree_objects: ['unit', 'entity']
diff --git a/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/translations/en.yml b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/translations/en.yml
new file mode 100644
index 0000000..086200f
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/shadows/Administration/translations/en.yml
@@ -0,0 +1,76 @@
+---
+en:
+  objects:
+    bot: "Bot"
+    domain: "Domain"
+    entity: "Entity"
+    group: "Group"
+    individual: "Individual"
+    secondary_account: "Secondary Account"
+    system_account: "System Account"
+    unit: "Unit"
+    user_service: "User Service"
+  attribute_types:
+    allowGlobalDirectory: "Disclose Own Contact Information in GLobal Directory"
+    cn: "Full Name"
+    createTimestamp: "Item creation time"
+    creatorsName: "Item Creator"
+    birthday: "Birthday"
+    birthlocation: "Birth Location"
+    description: "Description"
+    entryUUID: "Item UUID"
+    foundingDate: "Founding Date"
+    ftpHost: "FTP Allowed Hosts"
+    gecos: "GECOS"
+    gidNumber: "Primary Group (numeric)"
+    givenName: "FirstName"
+    homeDirectory: "Home Directory"
+    homePostalAddress: "Home Postal Address"
+    host: "Shell Allowed Hosts"
+    ipServiceName: "Service Name"
+    ipServiceHost: "Service Host"
+    ipServicePort: "Service Port"
+    ipServiceProtocol: "Service Protocol"
+    jid: "Jabber ID"
+    jpegPhoto: "Photo"
+    keyFingerPrint: "GPG/PGP Key Fingerprint"
+    labeledURI: "Web Site"
+    loginShell: "Shell Interpreter"
+    mail: "eMail address(es)"
+    mailForward: "eMail Forward Adress(es)"
+    mailQuota: "Maximum Mailbox Size"
+    manager: "Manager(s)"
+    mobile: "Mobile Phone"
+    modifiersName: "Item Last Modifier"
+    modifyTimestamp: "Item Last Modification Time"
+    occupation: "Job / Studies"
+    o: "Organization(s)"
+    structuralObjectClass: "Item Structural Classes"
+    owner: "Owner(s)"
+    preferredLanguage: "Language Preference"
+    sn: "Surname"
+    sshAuthKey: "SSH Public Key(s)"
+    uid: "Identifier (Login)"
+    uidNumber: "Identifier (numeric)"
+    uniqueAbbreviation: "Unique Abbreviation"
+    uniqueMember: "Group member"
+    userPassword: "Password"
+    webVirtualHost: "Hosted Web Sites"
+    allowedServices: "Authorized services"
+  relations:
+    primaryGroup: "Primary Group"
+    secondaryGroups: "Secondary Groups"
+    individualsPrimaryMembers: "Primary Member(s) Individual(s)"
+    botsPrimaryMembers: "Primary Member(s) Bot(s)"
+    individualsSecondaryMembers: "Secondary Member(s) Individual(s)"
+    botsSecondaryMembers: "Secondary Member(s) Bot(s)"
+    foundersIndividuals: "Founder(s) Individual(s)"
+    foundersEntities: "Founder(s) Entity(ies)"
+  aspects:
+    fs: "FileSystem Account"
+    ftp: "FTP Account"
+    jabber: "Jabber Account"
+    mail: "eMail Account"
+    primary_account: "Primary Account"
+    shell: "Shell Account"
+    web: "Web Account"
diff --git a/conf/ldap_shadows/worlds/MilkyPond/world.conf b/conf/ldap_shadows/worlds/MilkyPond/world.conf
new file mode 100644
index 0000000..b6b4dd4
--- /dev/null
+++ b/conf/ldap_shadows/worlds/MilkyPond/world.conf
@@ -0,0 +1,3 @@
+---
+default_shadow: Administration
+password_hash: ssha
diff --git a/data/ldap_shadows/default_config/global.yaml b/data/ldap_shadows/default_config/global.yaml
index 7cf158d..c8f5092 100644
--- a/data/ldap_shadows/default_config/global.yaml
+++ b/data/ldap_shadows/default_config/global.yaml
@@ -1,4 +1,2 @@
 ---
-default_shadow:
-password_hash:
-ldap: {}
+default_world:
diff --git a/data/ldap_shadows/default_config/world.yaml b/data/ldap_shadows/default_config/world.yaml
new file mode 100644
index 0000000..7cf158d
--- /dev/null
+++ b/data/ldap_shadows/default_config/world.yaml
@@ -0,0 +1,4 @@
+---
+default_shadow:
+password_hash:
+ldap: {}
diff --git a/data/ldap_shadows/schema/global.yaml b/data/ldap_shadows/schema/global.yaml
index c94da1a..10661bd 100644
--- a/data/ldap_shadows/schema/global.yaml
+++ b/data/ldap_shadows/schema/global.yaml
@@ -1,13 +1,5 @@
 ---
 type: map
 mapping:
-  "default_shadow": {type: str}
-  "ldap":
-    type: map
-    required: true
-    name: LdapMapping
-    mapping:
-      =:
-        type: any
-  "password_hash": {type: str, enum: [crypt, md5, smd5, sha, ssha]}
+  "default_world": {type: str}
 
diff --git a/data/ldap_shadows/schema/world.yaml b/data/ldap_shadows/schema/world.yaml
new file mode 100644
index 0000000..c94da1a
--- /dev/null
+++ b/data/ldap_shadows/schema/world.yaml
@@ -0,0 +1,13 @@
+---
+type: map
+mapping:
+  "default_shadow": {type: str}
+  "ldap":
+    type: map
+    required: true
+    name: LdapMapping
+    mapping:
+      =:
+        type: any
+  "password_hash": {type: str, enum: [crypt, md5, smd5, sha, ssha]}
+
diff --git a/lib/ldap_shadows/config.rb b/lib/ldap_shadows/config.rb
index 7a1690b..db9c78b 100644
--- a/lib/ldap_shadows/config.rb
+++ b/lib/ldap_shadows/config.rb
@@ -28,12 +28,13 @@ module LdapShadows
 
     def initialize
       @global_config = nil
+      @world_config = nil
       @schemas = {}
       @default_configs = {}
       @shadows = {}
     end
 
-    def load_global_config(extra_private_config_list = [])
+    def load_global_config
       g_default_config = load_default_config('global')
 
       g_config_file = File.join(CFG_DIR, "global.conf")
@@ -42,21 +43,41 @@ module LdapShadows
       end
 
       g_def_partial = YAML.load_file(g_config_file) || {}
+      g_def = parse('config', 'global', YAML.dump(g_def_partial))
+      g_config = g_default_config.recursive_merge(g_def)
+
+      @global_config = g_config
+    end
+
+    def load_world_config(world_name = nil, extra_private_config_list = [])
+      world_name = self.global_config[:default_world] if world_name.nil?
+
+      w_default_config = load_default_config('world')
+
+      w_config_path = File.join(CFG_DIR, "worlds", world_name)
+
+      w_config_file = File.join(w_config_path, "world.conf")
+      unless File.exists? w_config_file
+        raise PreProcessingError, _("General configuration file for World '%s' is missing") % world_name
+      end
+
+      w_def_partial = YAML.load_file(w_config_file) || {}
       extra_private_config_list += [
-        File.join(CFG_DIR, "global_private.conf")
+        File.join(w_config_path, "world_private.conf")
         ]
       extra_private_config_list.each do |file|
         if File.exists?(file)
-          g_def_partial.merge!(YAML.load_file(file) || {})
+          w_def_partial.merge!(YAML.load_file(file) || {})
           break
         end
       end
-      g_def = parse('config', 'global', YAML.dump(g_def_partial))
-      g_config = g_default_config.recursive_merge(g_def)
+      w_def = parse('config', 'world', YAML.dump(w_def_partial))
+      w_config = w_default_config.recursive_merge(w_def)
 
-      ActiveLdap::Base.setup_connection(g_config[:ldap])
+      ActiveLdap::Base.setup_connection(w_config[:ldap])
 
-      @global_config = g_config
+      @world_config = w_config
+      @world_config_path = w_config_path
     end
 
     def global_config
@@ -64,14 +85,19 @@ module LdapShadows
       @global_config
     end
 
+    def world_config
+      load_world_config() if @world_config.nil?
+      @world_config
+    end
+
     # needs 'ldap_shadows/shadow' for this feature to work
     def load_shadow(shadow_name = nil)
-      shadow_name = self.global_config[:default_shadow] if shadow_name.nil?
+      shadow_name = self.world_config[:default_shadow] if shadow_name.nil?
       if shadow_name.nil?
         raise PreProcessingError, _("Could not determine which Shadow to travel through")
       end
 
-      shadow_config_path = File.join(CFG_DIR, "shadows", shadow_name)
+      shadow_config_path = File.join(@world_config_path, "shadows", shadow_name)
       unless File.exists? shadow_config_path
         raise PreProcessingError, _("Configuration directory for Shadow '%s' is missing") % shadow_name
       end
@@ -124,8 +150,8 @@ module LdapShadows
 
       if validate
         validator_klass = case type
-        when 'global'
-          GlobalValidator
+        when 'world'
+          WorldValidator
         when 'object'
           ObjectValidator
         when 'aspect'
@@ -251,7 +277,7 @@ module LdapShadows
       end
     end
 
-    class GlobalValidator < LdapShadowsValidator
+    class WorldValidator < LdapShadowsValidator
       def validate_hook_in(value, rule, path, msg_list)
         case rule.name
         when 'LdapMapping'
