From ba59d9d648d7ee9f3e5b03ede9aeccab97f13a13 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Jul 06 2010 19:39:34 +0000 Subject: Add support for User-Private Groups This uses a new 389-ds plugin, Managed Entries, to automatically create a group entry when a user is created. The DNA plugin ensures that the group has a gidNumber that matches the users uidNumber. When the user is removed the group is automatically removed as well. If the managed entries plugin is not available or if a specific, separate range for gidNumber is passed in at install time then User-Private Groups will not be configured. The code checking for the Managed Entries plugin may be removed at some point. This is there because this plugin is only available in a 389-ds alpha release currently (1.2.6-a4). --- diff --git a/install/share/Makefile.am b/install/share/Makefile.am index cb7d983..5ff62ba 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -31,6 +31,7 @@ app_DATA = \ preferences.html.template \ referint-conf.ldif \ dna-posix.ldif \ + dna-upg.ldif \ master-entry.ldif \ memberof-task.ldif \ memberof-conf.ldif \ @@ -39,6 +40,7 @@ app_DATA = \ schema_compat.uldif \ ldapi.ldif \ wsgi.py \ + user_private_groups.ldif \ $(NULL) EXTRA_DIST = \ diff --git a/install/share/dna-upg.ldif b/install/share/dna-upg.ldif new file mode 100644 index 0000000..c4edcfa --- /dev/null +++ b/install/share/dna-upg.ldif @@ -0,0 +1,16 @@ +# add plugin configuration for user private groups + +dn: cn=User Private Groups,cn=Distributed Numeric Assignment Plugin,cn=plugins,cn=config +changetype: add +objectclass: top +objectclass: extensibleObject +cn: Posix Accounts +dnaType: uidNumber +dnaType: gidNumber +dnaNextValue: eval($UIDSTART+1) +dnaInterval: 1 +dnaMaxValue: eval($UIDSTART+100000) +dnaMagicRegen: 999 +dnaFilter: (|(objectclass=posixAccount)(objectClass=posixGroup)) +dnaScope: $SUFFIX + diff --git a/install/share/user_private_groups.ldif b/install/share/user_private_groups.ldif new file mode 100644 index 0000000..070d6e0 --- /dev/null +++ b/install/share/user_private_groups.ldif @@ -0,0 +1,19 @@ +dn: cn=UPG Template,$SUFFIX +changetype: add +objectclass: mepTemplateEntry +cn: UPG Template +mepRDNAttr: cn +mepStaticAttr: objectclass: posixGroup +mepMappedAttr: cn: $$uid +mepMappedAttr: gidNumber: $$uidNumber +mepMappedAttr: description: User private group for $$uid + +dn: cn=UPG Definition,cn=Managed Entries,cn=plugins,cn=config +changetype: add +objectclass: extensibleObject +cn: UPG Definition +originScope: cn=users,cn=accounts,$SUFFIX +originFilter: objectclass=posixAccount +managedBase: cn=groups,cn=accounts,$SUFFIX +managedTemplate: cn=UPG Template,$SUFFIX + diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py index 0f37437..9da4fe5 100644 --- a/ipalib/plugins/group.py +++ b/ipalib/plugins/group.py @@ -145,6 +145,8 @@ class group_add(LDAPCreate): def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): if options['posix'] or 'gidnumber' in options: entry_attrs['objectclass'].append('posixgroup') + if not 'gidnumber' in options: + entry_attrs['gidnumber'] = 999 return dn @@ -200,6 +202,8 @@ class group_mod(LDAPUpdate): else: old_entry_attrs['objectclass'].append('posixgroup') entry_attrs['objectclass'] = old_entry_attrs['objectclass'] + if not 'gidnumber' in options: + entry_attrs['gidnumber'] = 999 return dn api.register(group_mod) diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py index d72b3bb..610d85a 100644 --- a/ipalib/plugins/user.py +++ b/ipalib/plugins/user.py @@ -122,6 +122,8 @@ class user(LDAPObject): cli_name='uid', label=_('UID'), doc=_('User ID Number (system will assign one if not provided)'), + autofill=True, + default=999, ), Str('street?', cli_name='street', @@ -169,16 +171,20 @@ class user_add(LDAPCreate): home_dir = home_dir.replace('//', '/').rstrip('/') entry_attrs['homedirectory'] = home_dir - # we're adding new users to a default group, get its gidNumber - # get default group name from config - def_primary_group = config.get('ipadefaultprimarygroup') - group_dn = self.api.Object['group'].get_dn(def_primary_group) - try: - (group_dn, group_attrs) = ldap.get_entry(group_dn, ['gidnumber']) - except errors.NotFound: - error_msg = 'Default group for new users not found.' - raise errors.NotFound(reason=error_msg) - entry_attrs['gidnumber'] = group_attrs['gidnumber'] + if ldap.has_upg(): + # User Private Groups - uidNumber == gidNumber + entry_attrs['gidnumber'] = entry_attrs['uidnumber'] + else: + # we're adding new users to a default group, get its gidNumber + # get default group name from config + def_primary_group = config.get('ipadefaultprimarygroup') + group_dn = self.api.Object['group'].get_dn(def_primary_group) + try: + (group_dn, group_attrs) = ldap.get_entry(group_dn, ['gidnumber']) + except errors.NotFound: + error_msg = 'Default group for new users not found.' + raise errors.NotFound(reason=error_msg) + entry_attrs['gidnumber'] = group_attrs['gidnumber'] return dn diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 92a8cf0..e1ddef3 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -38,7 +38,8 @@ from ldap.dn import escape_dn_chars from ipaserver import ipaldap from ipaserver.install import ldapupdate from ipaserver.install import httpinstance -from ipalib import util, uuid +from ipalib import util, uuid, errors +from ipaserver.plugins.ldap2 import ldap2 SERVER_ROOT_64 = "/usr/lib64/dirsrv" SERVER_ROOT_32 = "/usr/lib/dirsrv" @@ -114,6 +115,25 @@ def is_ds_running(): ret = False return ret +def has_managed_entries(host_name, dm_password): + """Check to see if the Managed Entries plugin is available""" + ldapuri = 'ldap://%s' % host_name + conn = None + try: + conn = ldap2(shared_instance=False, ldap_uri=ldapuri, base_dn='cn=config') + conn.connect(bind_dn='cn=Directory Manager', bind_pw=dm_password) + (dn, attrs) = conn.get_entry('cn=Managed Entries,cn=plugins', + ['*']) + return True + except errors.NotFound: + return False + except errors.ExecutionError, e: + logging.critical("Could not connect to the Directory Server on %s" % host_name) + raise e + finally: + if conn: + conn.disconnect() + INF_TEMPLATE = """ [General] @@ -179,6 +199,8 @@ class DsInstance(service.Service): self.step("enabling memberof plugin", self.__add_memberof_module) self.step("enabling referential integrity plugin", self.__add_referint_module) self.step("enabling winsync plugin", self.__add_winsync_module) + if self.uidstart == self.gidstart: + self.step("configuring user private groups", self.__user_private_groups) self.step("configuring replication version plugin", self.__config_version_module) self.step("enabling IPA enrollment plugin", self.__add_enrollment_module) self.step("enabling ldapi", self.__enable_ldapi) @@ -331,7 +353,11 @@ class DsInstance(service.Service): self._ldap_mod("unique-attributes.ldif", self.sub_dict) def __config_uidgid_gen_first_master(self): - self._ldap_mod("dna-posix.ldif", self.sub_dict) + if (self.uidstart == self.gidstart and + has_managed_entries(self.host_name, self.dm_password)): + self._ldap_mod("dna-upg.ldif", self.sub_dict) + else: + self._ldap_mod("dna-posix.ldif", self.sub_dict) def __add_master_entry_first_master(self): self._ldap_mod("master-entry.ldif", self.sub_dict) @@ -342,6 +368,10 @@ class DsInstance(service.Service): def __config_version_module(self): self._ldap_mod("ipa-version-conf.ldif") + def __user_private_groups(self): + if has_managed_entries(self.host_name, self.dm_password): + self._ldap_mod("user_private_groups.ldif", self.sub_dict) + def __add_enrollment_module(self): self._ldap_mod("enrollment-conf.ldif", self.sub_dict) diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index d1c083f..987203c 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -103,9 +103,12 @@ def _handle_errors(e, **kw): raise errors.DatabaseError(desc=desc, info=info) -def load_schema(url): +def global_init(url): """ - Retrieve the LDAP schema from the provided url. + Perform global initialization when the module is loaded. + + Retrieve the LDAP schema from the provided url and determine if + User-Private Groups (upg) are configured. Bind using kerberos credentials. If in the context of the in-tree "lite" server then use the current ccache. If in the context of @@ -113,10 +116,11 @@ def load_schema(url): principal. """ tmpdir = None + upg = False if not api.env.in_server or api.env.context not in ['lite', 'server']: # The schema is only needed on the server side - return + return (None, None) try: if api.env.context == 'server': @@ -139,9 +143,17 @@ def load_schema(url): 'cn=schema', _ldap.SCOPE_BASE, attrlist=['attributetypes', 'objectclasses'] )[0] + try: + upg_entry = conn.search_s( + 'cn=UPG Template, %s' % api.env.basedn, _ldap.SCOPE_BASE, + attrlist=['*'] + )[0] + upg = True + except _ldap.NO_SUCH_OBJECT, e: + upg = False conn.unbind_s() except _ldap.SERVER_DOWN: - return None + return (None, upg) except _ldap.LDAPError, e: # TODO: raise a more appropriate exception _handle_errors(e, **{}) @@ -154,13 +166,14 @@ def load_schema(url): if tmpdir: shutil.rmtree(tmpdir) - return _ldap.schema.SubSchema(schema_entry[1]) + return (_ldap.schema.SubSchema(schema_entry[1]), upg) -# cache schema when importing module +# cache schema and User-Private Groups when importing module try: - _schema = load_schema(api.env.ldap_uri) + (_schema, _upg) = global_init(api.env.ldap_uri) except AttributeError: _schema = None + _upg = None def get_syntax(attr, value): @@ -524,6 +537,16 @@ class ldap2(CrudBackend, Encoder): """Returns a copy of the current LDAP schema.""" return copy.deepcopy(self.schema) + def has_upg(self): + """Returns True/False whether User-Private Groups are enabled. + This is determined based on whether the UPG Template exists. + We determine this at module load so we don't have to test for + it every time. + """ + global _upg + + return _upg + @encode_args(1, 2) def get_effective_rights(self, dn, entry_attrs): """Returns the rights the currently bound user has for the given DN.