mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 15:40:01 -06:00
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).
This commit is contained in:
parent
83fd9ef7cc
commit
ba59d9d648
@ -31,6 +31,7 @@ app_DATA = \
|
|||||||
preferences.html.template \
|
preferences.html.template \
|
||||||
referint-conf.ldif \
|
referint-conf.ldif \
|
||||||
dna-posix.ldif \
|
dna-posix.ldif \
|
||||||
|
dna-upg.ldif \
|
||||||
master-entry.ldif \
|
master-entry.ldif \
|
||||||
memberof-task.ldif \
|
memberof-task.ldif \
|
||||||
memberof-conf.ldif \
|
memberof-conf.ldif \
|
||||||
@ -39,6 +40,7 @@ app_DATA = \
|
|||||||
schema_compat.uldif \
|
schema_compat.uldif \
|
||||||
ldapi.ldif \
|
ldapi.ldif \
|
||||||
wsgi.py \
|
wsgi.py \
|
||||||
|
user_private_groups.ldif \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
EXTRA_DIST = \
|
EXTRA_DIST = \
|
||||||
|
16
install/share/dna-upg.ldif
Normal file
16
install/share/dna-upg.ldif
Normal file
@ -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
|
||||||
|
|
19
install/share/user_private_groups.ldif
Normal file
19
install/share/user_private_groups.ldif
Normal file
@ -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
|
||||||
|
|
@ -145,6 +145,8 @@ class group_add(LDAPCreate):
|
|||||||
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
|
||||||
if options['posix'] or 'gidnumber' in options:
|
if options['posix'] or 'gidnumber' in options:
|
||||||
entry_attrs['objectclass'].append('posixgroup')
|
entry_attrs['objectclass'].append('posixgroup')
|
||||||
|
if not 'gidnumber' in options:
|
||||||
|
entry_attrs['gidnumber'] = 999
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
|
|
||||||
@ -200,6 +202,8 @@ class group_mod(LDAPUpdate):
|
|||||||
else:
|
else:
|
||||||
old_entry_attrs['objectclass'].append('posixgroup')
|
old_entry_attrs['objectclass'].append('posixgroup')
|
||||||
entry_attrs['objectclass'] = old_entry_attrs['objectclass']
|
entry_attrs['objectclass'] = old_entry_attrs['objectclass']
|
||||||
|
if not 'gidnumber' in options:
|
||||||
|
entry_attrs['gidnumber'] = 999
|
||||||
return dn
|
return dn
|
||||||
|
|
||||||
api.register(group_mod)
|
api.register(group_mod)
|
||||||
|
@ -122,6 +122,8 @@ class user(LDAPObject):
|
|||||||
cli_name='uid',
|
cli_name='uid',
|
||||||
label=_('UID'),
|
label=_('UID'),
|
||||||
doc=_('User ID Number (system will assign one if not provided)'),
|
doc=_('User ID Number (system will assign one if not provided)'),
|
||||||
|
autofill=True,
|
||||||
|
default=999,
|
||||||
),
|
),
|
||||||
Str('street?',
|
Str('street?',
|
||||||
cli_name='street',
|
cli_name='street',
|
||||||
@ -169,16 +171,20 @@ class user_add(LDAPCreate):
|
|||||||
home_dir = home_dir.replace('//', '/').rstrip('/')
|
home_dir = home_dir.replace('//', '/').rstrip('/')
|
||||||
entry_attrs['homedirectory'] = home_dir
|
entry_attrs['homedirectory'] = home_dir
|
||||||
|
|
||||||
# we're adding new users to a default group, get its gidNumber
|
if ldap.has_upg():
|
||||||
# get default group name from config
|
# User Private Groups - uidNumber == gidNumber
|
||||||
def_primary_group = config.get('ipadefaultprimarygroup')
|
entry_attrs['gidnumber'] = entry_attrs['uidnumber']
|
||||||
group_dn = self.api.Object['group'].get_dn(def_primary_group)
|
else:
|
||||||
try:
|
# we're adding new users to a default group, get its gidNumber
|
||||||
(group_dn, group_attrs) = ldap.get_entry(group_dn, ['gidnumber'])
|
# get default group name from config
|
||||||
except errors.NotFound:
|
def_primary_group = config.get('ipadefaultprimarygroup')
|
||||||
error_msg = 'Default group for new users not found.'
|
group_dn = self.api.Object['group'].get_dn(def_primary_group)
|
||||||
raise errors.NotFound(reason=error_msg)
|
try:
|
||||||
entry_attrs['gidnumber'] = group_attrs['gidnumber']
|
(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
|
return dn
|
||||||
|
|
||||||
|
@ -38,7 +38,8 @@ from ldap.dn import escape_dn_chars
|
|||||||
from ipaserver import ipaldap
|
from ipaserver import ipaldap
|
||||||
from ipaserver.install import ldapupdate
|
from ipaserver.install import ldapupdate
|
||||||
from ipaserver.install import httpinstance
|
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_64 = "/usr/lib64/dirsrv"
|
||||||
SERVER_ROOT_32 = "/usr/lib/dirsrv"
|
SERVER_ROOT_32 = "/usr/lib/dirsrv"
|
||||||
@ -114,6 +115,25 @@ def is_ds_running():
|
|||||||
ret = False
|
ret = False
|
||||||
return ret
|
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 = """
|
INF_TEMPLATE = """
|
||||||
[General]
|
[General]
|
||||||
@ -179,6 +199,8 @@ class DsInstance(service.Service):
|
|||||||
self.step("enabling memberof plugin", self.__add_memberof_module)
|
self.step("enabling memberof plugin", self.__add_memberof_module)
|
||||||
self.step("enabling referential integrity plugin", self.__add_referint_module)
|
self.step("enabling referential integrity plugin", self.__add_referint_module)
|
||||||
self.step("enabling winsync plugin", self.__add_winsync_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("configuring replication version plugin", self.__config_version_module)
|
||||||
self.step("enabling IPA enrollment plugin", self.__add_enrollment_module)
|
self.step("enabling IPA enrollment plugin", self.__add_enrollment_module)
|
||||||
self.step("enabling ldapi", self.__enable_ldapi)
|
self.step("enabling ldapi", self.__enable_ldapi)
|
||||||
@ -331,7 +353,11 @@ class DsInstance(service.Service):
|
|||||||
self._ldap_mod("unique-attributes.ldif", self.sub_dict)
|
self._ldap_mod("unique-attributes.ldif", self.sub_dict)
|
||||||
|
|
||||||
def __config_uidgid_gen_first_master(self):
|
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):
|
def __add_master_entry_first_master(self):
|
||||||
self._ldap_mod("master-entry.ldif", self.sub_dict)
|
self._ldap_mod("master-entry.ldif", self.sub_dict)
|
||||||
@ -342,6 +368,10 @@ class DsInstance(service.Service):
|
|||||||
def __config_version_module(self):
|
def __config_version_module(self):
|
||||||
self._ldap_mod("ipa-version-conf.ldif")
|
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):
|
def __add_enrollment_module(self):
|
||||||
self._ldap_mod("enrollment-conf.ldif", self.sub_dict)
|
self._ldap_mod("enrollment-conf.ldif", self.sub_dict)
|
||||||
|
|
||||||
|
@ -103,9 +103,12 @@ def _handle_errors(e, **kw):
|
|||||||
raise errors.DatabaseError(desc=desc, info=info)
|
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
|
Bind using kerberos credentials. If in the context of the
|
||||||
in-tree "lite" server then use the current ccache. If in the context of
|
in-tree "lite" server then use the current ccache. If in the context of
|
||||||
@ -113,10 +116,11 @@ def load_schema(url):
|
|||||||
principal.
|
principal.
|
||||||
"""
|
"""
|
||||||
tmpdir = None
|
tmpdir = None
|
||||||
|
upg = False
|
||||||
|
|
||||||
if not api.env.in_server or api.env.context not in ['lite', 'server']:
|
if not api.env.in_server or api.env.context not in ['lite', 'server']:
|
||||||
# The schema is only needed on the server side
|
# The schema is only needed on the server side
|
||||||
return
|
return (None, None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if api.env.context == 'server':
|
if api.env.context == 'server':
|
||||||
@ -139,9 +143,17 @@ def load_schema(url):
|
|||||||
'cn=schema', _ldap.SCOPE_BASE,
|
'cn=schema', _ldap.SCOPE_BASE,
|
||||||
attrlist=['attributetypes', 'objectclasses']
|
attrlist=['attributetypes', 'objectclasses']
|
||||||
)[0]
|
)[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()
|
conn.unbind_s()
|
||||||
except _ldap.SERVER_DOWN:
|
except _ldap.SERVER_DOWN:
|
||||||
return None
|
return (None, upg)
|
||||||
except _ldap.LDAPError, e:
|
except _ldap.LDAPError, e:
|
||||||
# TODO: raise a more appropriate exception
|
# TODO: raise a more appropriate exception
|
||||||
_handle_errors(e, **{})
|
_handle_errors(e, **{})
|
||||||
@ -154,13 +166,14 @@ def load_schema(url):
|
|||||||
if tmpdir:
|
if tmpdir:
|
||||||
shutil.rmtree(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:
|
try:
|
||||||
_schema = load_schema(api.env.ldap_uri)
|
(_schema, _upg) = global_init(api.env.ldap_uri)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
_schema = None
|
_schema = None
|
||||||
|
_upg = None
|
||||||
|
|
||||||
|
|
||||||
def get_syntax(attr, value):
|
def get_syntax(attr, value):
|
||||||
@ -524,6 +537,16 @@ class ldap2(CrudBackend, Encoder):
|
|||||||
"""Returns a copy of the current LDAP schema."""
|
"""Returns a copy of the current LDAP schema."""
|
||||||
return copy.deepcopy(self.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)
|
@encode_args(1, 2)
|
||||||
def get_effective_rights(self, dn, entry_attrs):
|
def get_effective_rights(self, dn, entry_attrs):
|
||||||
"""Returns the rights the currently bound user has for the given DN.
|
"""Returns the rights the currently bound user has for the given DN.
|
||||||
|
Loading…
Reference in New Issue
Block a user