Exemple #1
0
class otptoken_sync(Local):
    __doc__ = _('Synchronize an OTP token.')

    header = 'X-IPA-TokenSync-Result'

    takes_options = (
        Str('user', label=_('User ID')),
        Password('password', label=_('Password'), confirm=False),
        Password('first_code', label=_('First Code'), confirm=False),
        Password('second_code', label=_('Second Code'), confirm=False),
    )

    takes_args = (Str('token?', label=_('Token ID')), )

    def forward(self, *args, **kwargs):
        status = {'result': {self.header: 'unknown'}}

        # Get the sync URI.
        segments = list(urllib.parse.urlparse(self.api.env.xmlrpc_uri))
        assert segments[0] == 'https'  # Ensure encryption.
        segments[2] = segments[2].replace('/xml', '/session/sync_token')
        # urlunparse *can* take one argument
        # pylint: disable=too-many-function-args
        sync_uri = urllib.parse.urlunparse(segments)

        # Prepare the query.
        query = {
            k: v
            for k, v in kwargs.items()
            if k in {x.name
                     for x in self.takes_options}
        }
        if args and args[0] is not None:
            obj = self.api.Object.otptoken
            query['token'] = DN((obj.primary_key.name, args[0]),
                                obj.container_dn, self.api.env.basedn)
        query = urllib.parse.urlencode(query)

        # Sync the token.
        # pylint: disable=E1101
        handler = HTTPSHandler(dbdir=api.env.nss_dir,
                               tls_version_min=api.env.tls_version_min,
                               tls_version_max=api.env.tls_version_max)
        rsp = urllib.request.build_opener(handler).open(sync_uri, query)
        if rsp.getcode() == 200:
            status['result'][self.header] = rsp.info().get(
                self.header, 'unknown')
        rsp.close()

        return status

    def output_for_cli(self, textui, result, *keys, **options):
        textui.print_plain({
            'ok': 'Token synchronized.',
            'error': 'Error contacting server!',
            'invalid-credentials': 'Invalid Credentials!',
        }.get(result['result'][self.header], 'Unknown Error!'))
Exemple #2
0
class otptoken_sync(Local):
    __doc__ = _('Synchronize an OTP token.')

    header = 'X-IPA-TokenSync-Result'

    takes_options = (
        Str('user', label=_('User ID')),
        Password('password', label=_('Password'), confirm=False),
        Password('first_code', label=_('First Code'), confirm=False),
        Password('second_code', label=_('Second Code'), confirm=False),
    )

    takes_args = (Str('token?', label=_('Token ID')), )

    def forward(self, *args, **kwargs):
        status = {'result': {self.header: 'unknown'}}

        # Get the sync URI.
        segments = list(urllib.parse.urlparse(self.api.env.xmlrpc_uri))
        assert segments[0] == 'https'  # Ensure encryption.
        segments[2] = segments[2].replace('/xml', '/session/sync_token')
        sync_uri = urllib.parse.urlunparse(segments)

        # Prepare the query.
        options = {x.name for x in self.takes_options}
        query = {k: v for k, v in kwargs.items() if k in options}
        if args and args[0] is not None:
            # sync_token converts token name to token DN
            query['token'] = args[0]
        query = urllib.parse.urlencode(query)
        query = query.encode('utf-8')

        # Sync the token.
        handler = HTTPSHandler(cafile=api.env.tls_ca_cert,
                               tls_version_min=api.env.tls_version_min,
                               tls_version_max=api.env.tls_version_max)
        rsp = urllib.request.build_opener(handler).open(sync_uri, query)
        if rsp.getcode() == 200:
            status['result'][self.header] = rsp.info().get(
                self.header, 'unknown')
        rsp.close()

        return status

    def output_for_cli(self, textui, result, *keys, **options):
        textui.print_plain({
            'ok': 'Token synchronized.',
            'error': 'Error contacting server!',
            'invalid-credentials': 'Invalid Credentials!',
        }.get(result['result'][self.header], 'Unknown Error!'))
Exemple #3
0
class radiusproxy(LDAPObject):
    """
    RADIUS Server object.
    """
    container_dn = api.env.container_radiusproxy
    object_name = _('RADIUS proxy server')
    object_name_plural = _('RADIUS proxy servers')
    object_class = ['ipatokenradiusconfiguration']
    default_attributes = ['cn', 'description', 'ipatokenradiusserver',
        'ipatokenradiustimeout', 'ipatokenradiusretries', 'ipatokenusermapattribute'
    ]
    search_attributes = ['cn', 'description', 'ipatokenradiusserver']
    allow_rename = True
    label = _('RADIUS Servers')
    label_singular = _('RADIUS Server')

    takes_params = (
        Str('cn',
            cli_name='name',
            label=_('RADIUS proxy server name'),
            primary_key=True,
        ),
        Str('description?',
            cli_name='desc',
            label=_('Description'),
            doc=_('A description of this RADIUS proxy server'),
        ),
        Str('ipatokenradiusserver+', validate_radiusserver,
            cli_name='server',
            label=_('Server'),
            doc=_('The hostname or IP (with or without port)'),
        ),
        Password('ipatokenradiussecret',
            cli_name='secret',
            label=_('Secret'),
            doc=_('The secret used to encrypt data'),
            confirm=True,
        ),
        Int('ipatokenradiustimeout?',
            cli_name='timeout',
            label=_('Timeout'),
            doc=_('The total timeout across all retries (in seconds)'),
            minvalue=1,
        ),
        Int('ipatokenradiusretries?',
            cli_name='retries',
            label=_('Retries'),
            doc=_('The number of times to retry authentication'),
            minvalue=0,
            maxvalue=10,
        ),
        Str('ipatokenusermapattribute?', validate_attributename,
            cli_name='userattr',
            label=_('User attribute'),
            doc=_('The username attribute on the user object'),
        ),
    )
Exemple #4
0
class baseuser(LDAPObject):
    """
    baseuser object.
    """

    stage_container_dn        = api.env.container_stageuser
    active_container_dn       = api.env.container_user
    delete_container_dn       = api.env.container_deleteuser
    object_class = ['posixaccount']
    object_class_config = 'ipauserobjectclasses'
    possible_objectclasses = [
        'meporiginentry', 'ipauserauthtypeclass', 'ipauser',
        'ipatokenradiusproxyuser', 'ipacertmapobject'
    ]
    disallow_object_classes = ['krbticketpolicyaux']
    permission_filter_objectclasses = ['posixaccount']
    search_attributes_config = 'ipausersearchfields'
    default_attributes = [
        'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
        'uidnumber', 'gidnumber', 'mail', 'ou',
        'telephonenumber', 'title', 'memberof', 'nsaccountlock',
        'memberofindirect', 'ipauserauthtype', 'userclass',
        'ipatokenradiusconfiglink', 'ipatokenradiususername',
        'krbprincipalexpiration', 'usercertificate;binary',
        'krbprincipalname', 'krbcanonicalname',
        'ipacertmapdata'
    ]
    search_display_attributes = [
        'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname',
        'krbprincipalname', 'loginshell',
        'mail', 'telephonenumber', 'title', 'nsaccountlock',
        'uidnumber', 'gidnumber', 'sshpubkeyfp',
    ]
    uuid_attribute = 'ipauniqueid'
    attribute_members = {
        'manager': ['user'],
        'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
        'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
    }
    allow_rename = True
    bindable = True
    password_attributes = [('userpassword', 'has_password'),
                           ('krbprincipalkey', 'has_keytab')]
    label = _('Users')
    label_singular = _('User')

    takes_params = (
        Str('uid',
            pattern=PATTERN_GROUPUSER_NAME,
            pattern_errmsg='may only include letters, numbers, _, -, . and $',
            maxlength=255,
            cli_name='login',
            label=_('User login'),
            primary_key=True,
            default_from=lambda givenname, sn: givenname[0] + sn,
            normalizer=lambda value: value.lower(),
        ),
        Str('givenname',
            cli_name='first',
            label=_('First name'),
        ),
        Str('sn',
            cli_name='last',
            label=_('Last name'),
        ),
        Str('cn',
            label=_('Full name'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('displayname?',
            label=_('Display name'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('initials?',
            label=_('Initials'),
            default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]),
            autofill=True,
        ),
        Str('homedirectory?',
            cli_name='homedir',
            label=_('Home directory'),
        ),
        Str('gecos?',
            label=_('GECOS'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('loginshell?',
            cli_name='shell',
            label=_('Login shell'),
        ),
        Principal(
            'krbcanonicalname?',
            validate_realm,
            label=_('Principal name'),
            flags={'no_option', 'no_create', 'no_update', 'no_search'},
            normalizer=normalize_user_principal
        ),
        Principal(
            'krbprincipalname*',
            validate_realm,
            cli_name='principal',
            label=_('Principal alias'),
            default_from=lambda uid: kerberos.Principal(
                uid.lower(), realm=api.env.realm),
            autofill=True,
            normalizer=normalize_user_principal,
        ),
        DateTime('krbprincipalexpiration?',
            cli_name='principal_expiration',
            label=_('Kerberos principal expiration'),
        ),
        DateTime('krbpasswordexpiration?',
            cli_name='password_expiration',
            label=_('User password expiration'),
        ),
        Str('mail*',
            cli_name='email',
            label=_('Email address'),
        ),
        Password('userpassword?',
            cli_name='password',
            label=_('Password'),
            doc=_('Prompt to set the user password'),
            # FIXME: This is temporary till bug is fixed causing updates to
            # bomb out via the webUI.
            exclude='webui',
        ),
        Flag('random?',
            doc=_('Generate a random user password'),
            flags=('no_search', 'virtual_attribute'),
            default=False,
        ),
        Str('randompassword?',
            label=_('Random password'),
            flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
        ),
        Int('uidnumber?',
            cli_name='uid',
            label=_('UID'),
            doc=_('User ID Number (system will assign one if not provided)'),
            minvalue=1,
        ),
        Int('gidnumber?',
            label=_('GID'),
            doc=_('Group ID Number'),
            minvalue=1,
        ),
        Str('street?',
            cli_name='street',
            label=_('Street address'),
        ),
        Str('l?',
            cli_name='city',
            label=_('City'),
        ),
        Str('st?',
            cli_name='state',
            label=_('State/Province'),
        ),
        Str('postalcode?',
            label=_('ZIP'),
        ),
        Str('telephonenumber*',
            cli_name='phone',
            label=_('Telephone Number')
        ),
        Str('mobile*',
            label=_('Mobile Telephone Number')
        ),
        Str('pager*',
            label=_('Pager Number')
        ),
        Str('facsimiletelephonenumber*',
            cli_name='fax',
            label=_('Fax Number'),
        ),
        Str('ou?',
            cli_name='orgunit',
            label=_('Org. Unit'),
        ),
        Str('title?',
            label=_('Job Title'),
        ),
        # keep backward compatibility using single value manager option
        Str('manager?',
            label=_('Manager'),
        ),
        Str('carlicense*',
            label=_('Car License'),
        ),
        Str('ipasshpubkey*', validate_sshpubkey,
            cli_name='sshpubkey',
            label=_('SSH public key'),
            normalizer=normalize_sshpubkey,
            flags=['no_search'],
        ),
        Str('sshpubkeyfp*',
            label=_('SSH public key fingerprint'),
            flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'},
        ),
        StrEnum('ipauserauthtype*',
            cli_name='user_auth_type',
            label=_('User authentication types'),
            doc=_('Types of supported user authentication'),
            values=(u'password', u'radius', u'otp'),
        ),
        Str('userclass*',
            cli_name='class',
            label=_('Class'),
            doc=_('User category (semantics placed on this attribute are for '
                  'local interpretation)'),
        ),
        Str('ipatokenradiusconfiglink?',
            cli_name='radius',
            label=_('RADIUS proxy configuration'),
        ),
        Str('ipatokenradiususername?',
            cli_name='radius_username',
            label=_('RADIUS proxy username'),
        ),
        Str('departmentnumber*',
            label=_('Department Number'),
        ),
        Str('employeenumber?',
            label=_('Employee Number'),
        ),
        Str('employeetype?',
            label=_('Employee Type'),
        ),
        Str('preferredlanguage?',
            label=_('Preferred Language'),
            pattern='^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' \
             + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$',
            pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"',
        ),
        Bytes('usercertificate*', validate_certificate,
            cli_name='certificate',
            label=_('Certificate'),
            doc=_('Base-64 encoded user certificate'),
        ),
        Str(
            'ipacertmapdata*',
            cli_name='certmapdata',
            label=_('Certificate mapping data'),
            doc=_('Certificate mapping data'),
            flags=['no_create', 'no_update', 'no_search'],
        ),
    )

    def normalize_and_validate_email(self, email, config=None):
        if not config:
            config = self.backend.get_ipa_config()

        # check if default email domain should be added
        defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
        if email:
            norm_email = []
            if not isinstance(email, (list, tuple)):
                email = [email]
            for m in email:
                if isinstance(m, six.string_types):
                    if '@' not in m and defaultdomain:
                        m = m + u'@' + defaultdomain
                    if not Email(m):
                        raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
                    norm_email.append(m)
                else:
                    if not Email(m):
                        raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
                    norm_email.append(m)
            return norm_email

        return email

    def normalize_manager(self, manager, container):
        """
        Given a userid verify the user's existence (in the appropriate containter) and return the dn.
        """
        if not manager:
            return None

        if not isinstance(manager, list):
            manager = [manager]

        try:
            container_dn = DN(container, api.env.basedn)
            for i, mgr in enumerate(manager):
                if isinstance(mgr, DN) and mgr.endswith(container_dn):
                    continue
                entry_attrs = self.backend.find_entry_by_attr(
                        self.primary_key.name, mgr, self.object_class, [''],
                        container_dn
                    )
                manager[i] = entry_attrs.dn
        except errors.NotFound:
            raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=mgr))

        return manager

    def _user_status(self, user, container):
        assert isinstance(user, DN)
        return user.endswith(container)

    def active_user(self, user):
        assert isinstance(user, DN)
        return self._user_status(user, DN(self.active_container_dn, api.env.basedn))

    def stage_user(self, user):
        assert isinstance(user, DN)
        return self._user_status(user, DN(self.stage_container_dn, api.env.basedn))

    def delete_user(self, user):
        assert isinstance(user, DN)
        return self._user_status(user, DN(self.delete_container_dn, api.env.basedn))

    def convert_usercertificate_pre(self, entry_attrs):
        if 'usercertificate' in entry_attrs:
            entry_attrs['usercertificate;binary'] = entry_attrs.pop(
                'usercertificate')

    def convert_usercertificate_post(self, entry_attrs, **options):
        if 'usercertificate;binary' in entry_attrs:
            entry_attrs['usercertificate'] = entry_attrs.pop(
                'usercertificate;binary')

    def convert_attribute_members(self, entry_attrs, *keys, **options):
        super(baseuser, self).convert_attribute_members(
            entry_attrs, *keys, **options)

        if options.get("raw", False):
            return

        # due the backward compatibility, managers have to be returned in
        # 'manager' attribute instead of 'manager_user'
        try:
            entry_attrs['failed_manager'] = entry_attrs.pop('manager')
        except KeyError:
            pass

        try:
            entry_attrs['manager'] = entry_attrs.pop('manager_user')
        except KeyError:
            pass
Exemple #5
0
class passwd(Command):
    __doc__ = _("Set a user's password.")

    takes_args = (
        Principal(
            'principal',
            validate_realm,
            cli_name='user',
            label=_('User name'),
            primary_key=True,
            autofill=True,
            default_from=lambda: kerberos.Principal(krb_utils.get_principal()),
            normalizer=lambda value: normalize_user_principal(value),
        ),
        Password(
            'password',
            label=_('New Password'),
        ),
        Password(
            'current_password',
            label=_('Current Password'),
            confirm=False,
            default_from=lambda principal: get_current_password(principal),
            autofill=True,
        ),
    )

    takes_options = (Password(
        'otp?',
        label=_('OTP'),
        doc=_('The OTP if the user has a token configured'),
        confirm=False,
    ), )

    has_output = output.simple_value
    msg_summary = _('Changed password for "%(value)s"')

    def execute(self, principal, password, current_password, **options):
        """
        Execute the passwd operation.

        The dn should not be passed as a keyword argument as it is constructed
        by this method.

        Returns the entry

        :param principal: The login name or principal of the user
        :param password: the new password
        :param current_password: the existing password, if applicable
        """
        ldap = self.api.Backend.ldap2

        principal = unicode(principal)

        entry_attrs = ldap.find_entry_by_attr(
            'krbprincipalname', principal, 'posixaccount', [''],
            DN(api.env.container_user, api.env.basedn))

        if principal == getattr(context, 'principal') and \
            current_password == MAGIC_VALUE:
            # No cheating
            logger.warning('User attempted to change password using magic '
                           'value')
            raise errors.ACIError(info=_('Invalid credentials'))

        if current_password == MAGIC_VALUE:
            ldap.modify_password(entry_attrs.dn, password)
        else:
            otp = options.get('otp')
            ldap.modify_password(entry_attrs.dn, password, current_password,
                                 otp)

        return dict(
            result=True,
            value=principal,
        )
Exemple #6
0
class migrate_ds(Command):
    __doc__ = _('Migrate users and groups from DS to IPA.')

    migrate_objects = {
        # OBJECT_NAME: (search_filter, pre_callback, post_callback)
        #
        # OBJECT_NAME - is the name of an LDAPObject subclass
        # search_filter - is the filter to retrieve objects from DS
        # pre_callback - is called for each object just after it was
        #                retrieved from DS and before being added to IPA
        # post_callback - is called for each object after it was added to IPA
        # exc_callback - is called when adding entry to IPA raises an exception
        #
        # {pre, post}_callback parameters:
        #  ldap - ldap2 instance connected to IPA
        #  pkey - primary key value of the object (uid for users, etc.)
        #  dn - dn of the object as it (will be/is) stored in IPA
        #  entry_attrs - attributes of the object
        #  failed - a list of so-far failed objects
        #  config - IPA config entry attributes
        #  ctx - object context, used to pass data between callbacks
        #
        # If pre_callback return value evaluates to False, migration
        # of the current object is aborted.
        'user': {
            'filter_template': '(&(|%s)(uid=*))',
            'oc_option': 'userobjectclass',
            'oc_blocklist_option': 'userignoreobjectclass',
            'attr_blocklist_option': 'userignoreattribute',
            'pre_callback': _pre_migrate_user,
            'post_callback': _post_migrate_user,
            'exc_callback': None
        },
        'group': {
            'filter_template': '(&(|%s)(cn=*))',
            'oc_option': 'groupobjectclass',
            'oc_blocklist_option': 'groupignoreobjectclass',
            'attr_blocklist_option': 'groupignoreattribute',
            'pre_callback': _pre_migrate_group,
            'post_callback': None,
            'exc_callback': _group_exc_callback,
        },
    }
    migrate_order = ('user', 'group')

    takes_args = (
        Str(
            'ldapuri',
            validate_ldapuri,
            cli_name='ldap_uri',
            label=_('LDAP URI'),
            doc=_('LDAP URI of DS server to migrate from'),
        ),
        Password(
            'bindpw',
            cli_name='password',
            label=_('Password'),
            confirm=False,
            doc=_('bind password'),
        ),
    )

    takes_options = (
        DNParam('binddn?',
            cli_name='bind_dn',
            label=_('Bind DN'),
            default=DN(('cn', 'directory manager')),
            autofill=True,
        ),
        DNParam('usercontainer',
            cli_name='user_container',
            label=_('User container'),
            doc=_('DN of container for users in DS relative to base DN'),
            default=DN(('ou', 'people')),
            autofill=True,
        ),
        DNParam('groupcontainer',
            cli_name='group_container',
            label=_('Group container'),
            doc=_('DN of container for groups in DS relative to base DN'),
            default=DN(('ou', 'groups')),
            autofill=True,
        ),
        Str('userobjectclass+',
            cli_name='user_objectclass',
            label=_('User object class'),
            doc=_('Objectclasses used to search for user entries in DS'),
            default=(u'person',),
            autofill=True,
        ),
        Str('groupobjectclass+',
            cli_name='group_objectclass',
            label=_('Group object class'),
            doc=_('Objectclasses used to search for group entries in DS'),
            default=(u'groupOfUniqueNames', u'groupOfNames'),
            autofill=True,
        ),
        Str('userignoreobjectclass*',
            cli_name='user_ignore_objectclass',
            label=_('Ignore user object class'),
            doc=_('Objectclasses to be ignored for user entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('userignoreattribute*',
            cli_name='user_ignore_attribute',
            label=_('Ignore user attribute'),
            doc=_('Attributes to be ignored for user entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreobjectclass*',
            cli_name='group_ignore_objectclass',
            label=_('Ignore group object class'),
            doc=_('Objectclasses to be ignored for group entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreattribute*',
            cli_name='group_ignore_attribute',
            label=_('Ignore group attribute'),
            doc=_('Attributes to be ignored for group entries in DS'),
            default=tuple(),
            autofill=True,
        ),
        Flag('groupoverwritegid',
            cli_name='group_overwrite_gid',
            label=_('Overwrite GID'),
            doc=_('When migrating a group already existing in IPA domain overwrite the '\
                  'group GID and report as success'),
        ),
        StrEnum('schema?',
            cli_name='schema',
            label=_('LDAP schema'),
            doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'),
            values=_supported_schemas,
            default=_supported_schemas[0],
            autofill=True,
        ),
        Flag('continue?',
            label=_('Continue'),
            doc=_('Continuous operation mode. Errors are reported but the process continues'),
            default=False,
        ),
        DNParam('basedn?',
            cli_name='base_dn',
            label=_('Base DN'),
            doc=_('Base DN on remote LDAP server'),
        ),
        Flag('compat?',
            cli_name='with_compat',
            label=_('Ignore compat plugin'),
            doc=_('Allows migration despite the usage of compat plugin'),
            default=False,
        ),
        Str('cacertfile?',
            cli_name='ca_cert_file',
            label=_('CA certificate'),
            doc=_('Load CA certificate of LDAP server from FILE'),
            default=None,
            noextrawhitespace=False,
            ),
        Bool('use_def_group?',
             cli_name='use_default_group',
             label=_('Add to default group'),
             doc=_('Add migrated users without a group to a default group '
                   '(default: true)'),
             default=True,
             autofill=True,
             ),
        StrEnum('scope',
                cli_name='scope',
                label=_('Search scope'),
                doc=_('LDAP search scope for users and groups: base, '
                      'onelevel, or subtree. Defaults to onelevel'),
                values=sorted(_supported_scopes),
                default=_default_scope,
                autofill=True,
                ),
    )

    has_output = (
        output.Output(
            'result',
            type=dict,
            doc=_('Lists of objects migrated; categorized by type.'),
        ),
        output.Output(
            'failed',
            type=dict,
            doc=
            _('Lists of objects that could not be migrated; categorized by type.'
              ),
        ),
        output.Output(
            'enabled',
            type=bool,
            doc=_('False if migration mode was disabled.'),
        ),
        output.Output(
            'compat',
            type=bool,
            doc=
            _('False if migration fails because the compatibility plug-in is enabled.'
              ),
        ),
    )

    exclude_doc = _('%s to exclude from migration')

    truncated_err_msg = _('''\
search results for objects to be migrated
have been truncated by the server;
migration process might be incomplete\n''')

    def get_options(self):
        """
        Call get_options of the baseclass and add "exclude" options
        for each type of object being migrated.
        """
        for option in super(migrate_ds, self).get_options():
            yield option
        for ldap_obj_name in self.migrate_objects:
            ldap_obj = self.api.Object[ldap_obj_name]
            name = 'exclude_%ss' % to_cli(ldap_obj_name)
            doc = self.exclude_doc % ldap_obj.object_name_plural
            yield Str('%s*' % name,
                      cli_name=name,
                      doc=doc,
                      default=tuple(),
                      autofill=True)

    def normalize_options(self, options):
        """
        Convert all "exclude" option values to lower-case.

        Also, empty List parameters are converted to None, but the migration
        plugin doesn't like that - convert back to empty lists.
        """
        names = [
            'userobjectclass', 'groupobjectclass', 'userignoreobjectclass',
            'userignoreattribute', 'groupignoreobjectclass',
            'groupignoreattribute'
        ]
        names.extend('exclude_%ss' % to_cli(n) for n in self.migrate_objects)
        for name in names:
            if options[name]:
                options[name] = tuple(v.lower() for v in options[name])
            else:
                options[name] = tuple()

    def _get_search_bases(self, options, ds_base_dn, migrate_order):
        search_bases = dict()
        for ldap_obj_name in migrate_order:
            container = options.get('%scontainer' % to_cli(ldap_obj_name))
            if container:
                # Don't append base dn if user already appended it in the container dn
                if container.endswith(ds_base_dn):
                    search_base = container
                else:
                    search_base = DN(container, ds_base_dn)
            else:
                search_base = ds_base_dn
            search_bases[ldap_obj_name] = search_base
        return search_bases

    def migrate(self, ldap, config, ds_ldap, ds_base_dn, options):
        """
        Migrate objects from DS to LDAP.
        """
        assert isinstance(ds_base_dn, DN)
        migrated = {}  # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
        failed = {}  # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
        search_bases = self._get_search_bases(options, ds_base_dn,
                                              self.migrate_order)
        migration_start = datetime.datetime.now()

        scope = _supported_scopes[options.get('scope')]

        for ldap_obj_name in self.migrate_order:
            ldap_obj = self.api.Object[ldap_obj_name]

            template = self.migrate_objects[ldap_obj_name]['filter_template']
            oc_list = options[to_cli(
                self.migrate_objects[ldap_obj_name]['oc_option'])]
            search_filter = construct_filter(template, oc_list)

            exclude = options['exclude_%ss' % to_cli(ldap_obj_name)]
            context = dict(ds_ldap=ds_ldap)

            migrated[ldap_obj_name] = []
            failed[ldap_obj_name] = {}

            try:
                entries, truncated = ds_ldap.find_entries(
                    search_filter, ['*'],
                    search_bases[ldap_obj_name],
                    scope,
                    time_limit=0,
                    size_limit=-1)
            except errors.NotFound:
                if not options.get('continue', False):
                    raise errors.NotFound(
                        reason=
                        _('%(container)s LDAP search did not return any result '
                          '(search base: %(search_base)s, '
                          'objectclass: %(objectclass)s)') % {
                              'container': ldap_obj_name,
                              'search_base': search_bases[ldap_obj_name],
                              'objectclass': ', '.join(oc_list)
                          })
                else:
                    truncated = False
                    entries = []
            if truncated:
                logger.error('%s: %s', ldap_obj.name, self.truncated_err_msg)

            blocklists = {}
            for blocklist in ('oc_blocklist', 'attr_blocklist'):
                blocklist_option = (
                    self.migrate_objects[ldap_obj_name][blocklist + '_option'])
                if blocklist_option is not None:
                    blocklists[blocklist] = options.get(
                        blocklist_option, tuple())
                else:
                    blocklists[blocklist] = tuple()

            # get default primary group for new users
            if 'def_group_dn' not in context and options.get('use_def_group'):
                def_group = config.get('ipadefaultprimarygroup')
                context['def_group_dn'] = api.Object.group.get_dn(def_group)
                try:
                    ldap.get_entry(context['def_group_dn'],
                                   ['gidnumber', 'cn'])
                except errors.NotFound:
                    error_msg = _('Default group for new users not found')
                    raise errors.NotFound(reason=error_msg)

            context['has_upg'] = ldap.has_upg()

            valid_gids = set()
            invalid_gids = set()
            migrate_cnt = 0
            context['migrate_cnt'] = 0
            for entry_attrs in entries:
                context['migrate_cnt'] = migrate_cnt
                s = datetime.datetime.now()

                ava = entry_attrs.dn[0][0]
                if ava.attr == ldap_obj.primary_key.name:
                    # In case if pkey attribute is in the migrated object DN
                    # and the original LDAP is multivalued, make sure that
                    # we pick the correct value (the unique one stored in DN)
                    pkey = ava.value.lower()
                else:
                    pkey = entry_attrs[ldap_obj.primary_key.name][0].lower()

                if pkey in exclude:
                    continue

                entry_attrs.dn = ldap_obj.get_dn(pkey)
                entry_attrs['objectclass'] = list(
                    set(
                        config.get(ldap_obj.object_class_config,
                                   ldap_obj.object_class) +
                        [o.lower() for o in entry_attrs['objectclass']]))
                entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[
                    ldap_obj.primary_key.name][0].lower()

                callback = self.migrate_objects[ldap_obj_name]['pre_callback']
                if callable(callback):
                    try:
                        entry_attrs.dn = callback(ldap,
                                                  pkey,
                                                  entry_attrs.dn,
                                                  entry_attrs,
                                                  failed[ldap_obj_name],
                                                  config,
                                                  context,
                                                  schema=options['schema'],
                                                  search_bases=search_bases,
                                                  valid_gids=valid_gids,
                                                  invalid_gids=invalid_gids,
                                                  **blocklists)
                        if not entry_attrs.dn:
                            continue
                    except errors.NotFound as e:
                        failed[ldap_obj_name][pkey] = unicode(e.reason)
                        continue

                try:
                    ldap.add_entry(entry_attrs)
                except errors.ExecutionError as e:
                    callback = self.migrate_objects[ldap_obj_name][
                        'exc_callback']
                    if callable(callback):
                        try:
                            callback(ldap, entry_attrs.dn, entry_attrs, e,
                                     options)
                        except errors.ExecutionError as e2:
                            failed[ldap_obj_name][pkey] = unicode(e2)
                            continue
                    else:
                        failed[ldap_obj_name][pkey] = unicode(e)
                        continue

                migrated[ldap_obj_name].append(pkey)

                callback = self.migrate_objects[ldap_obj_name]['post_callback']
                if callable(callback):
                    callback(ldap, pkey, entry_attrs.dn, entry_attrs,
                             failed[ldap_obj_name], config, context)
                e = datetime.datetime.now()
                d = e - s
                total_dur = e - migration_start
                migrate_cnt += 1
                if migrate_cnt > 0 and migrate_cnt % 100 == 0:
                    logger.info("%d %ss migrated. %s elapsed.", migrate_cnt,
                                ldap_obj_name, total_dur)
                logger.debug("%d %ss migrated, duration: %s (total %s)",
                             migrate_cnt, ldap_obj_name, d, total_dur)

        if 'def_group_dn' in context:
            _update_default_group(ldap, context, True)

        return (migrated, failed)

    def execute(self, ldapuri, bindpw, **options):
        ldap = self.api.Backend.ldap2
        self.normalize_options(options)
        config = ldap.get_ipa_config()

        ds_base_dn = options.get('basedn')
        if ds_base_dn is not None:
            assert isinstance(ds_base_dn, DN)

        # check if migration mode is enabled
        if config.get('ipamigrationenabled', ('FALSE', ))[0] == 'FALSE':
            return dict(result={}, failed={}, enabled=False, compat=True)

        # connect to DS
        if options.get('cacertfile') is not None:
            # store CA cert into file
            tmp_ca_cert_f = write_tmp_file(options['cacertfile'])
            cacert = tmp_ca_cert_f.name

            # start TLS connection or STARTTLS
            ds_ldap = LDAPClient(ldapuri, cacert=cacert, start_tls=True)
            ds_ldap.simple_bind(options['binddn'], bindpw)

            tmp_ca_cert_f.close()
        else:
            ds_ldap = LDAPClient(ldapuri)
            ds_ldap.simple_bind(options['binddn'], bindpw, insecure_bind=True)

        # check whether the compat plugin is enabled
        if not options.get('compat'):
            try:
                ldap.get_entry(
                    DN(('cn', 'users'), ('cn', 'compat'), (api.env.basedn)))
                return dict(result={}, failed={}, enabled=True, compat=False)
            except errors.NotFound:
                pass

        if not ds_base_dn:
            # retrieve base DN from remote LDAP server
            entries, _truncated = ds_ldap.find_entries(
                '',
                ['namingcontexts', 'defaultnamingcontext'],
                DN(''),
                ds_ldap.SCOPE_BASE,
                size_limit=-1,
                time_limit=0,
            )
            if 'defaultnamingcontext' in entries[0]:
                ds_base_dn = DN(entries[0]['defaultnamingcontext'][0])
                assert isinstance(ds_base_dn, DN)
            else:
                try:
                    ds_base_dn = DN(entries[0]['namingcontexts'][0])
                    assert isinstance(ds_base_dn, DN)
                except (IndexError, KeyError) as e:
                    raise Exception(str(e))

        # migrate!
        (migrated, failed) = self.migrate(ldap, config, ds_ldap, ds_base_dn,
                                          options)

        return dict(result=migrated, failed=failed, enabled=True, compat=True)
Exemple #7
0
class migrate_ds(Command):
    __doc__ = _('Migrate users and groups from DS to IPA.')

    migrate_objects = {
        # OBJECT_NAME: (search_filter, pre_callback, post_callback)
        #
        # OBJECT_NAME - is the name of an LDAPObject subclass
        # search_filter - is the filter to retrieve objects from DS
        # pre_callback - is called for each object just after it was
        #                retrieved from DS and before being added to IPA
        # post_callback - is called for each object after it was added to IPA
        # exc_callback - is called when adding entry to IPA raises an exception
        #
        # {pre, post}_callback parameters:
        #  ldap - ldap2 instance connected to IPA
        #  pkey - primary key value of the object (uid for users, etc.)
        #  dn - dn of the object as it (will be/is) stored in IPA
        #  entry_attrs - attributes of the object
        #  failed - a list of so-far failed objects
        #  config - IPA config entry attributes
        #  ctx - object context, used to pass data between callbacks
        #
        # If pre_callback return value evaluates to False, migration
        # of the current object is aborted.
        'user': {
            'filter_template': '(&(|%s)(uid=*))',
            'oc_option': 'userobjectclass',
            'oc_blacklist_option': 'userignoreobjectclass',
            'attr_blacklist_option': 'userignoreattribute',
            'pre_callback': _pre_migrate_user,
            'post_callback': _post_migrate_user,
            'exc_callback': None
        },
        'group': {
            'filter_template': '(&(|%s)(cn=*))',
            'oc_option': 'groupobjectclass',
            'oc_blacklist_option': 'groupignoreobjectclass',
            'attr_blacklist_option': 'groupignoreattribute',
            'pre_callback': _pre_migrate_group,
            'post_callback': None,
            'exc_callback': _group_exc_callback,
        },
    }
    migrate_order = ('user', 'group')

    takes_args = (
        Str(
            'ldapuri',
            validate_ldapuri,
            cli_name='ldap_uri',
            label=_('LDAP URI'),
            doc=_('LDAP URI of DS server to migrate from'),
        ),
        Password(
            'bindpw',
            cli_name='password',
            label=_('Password'),
            confirm=False,
            doc=_('bind password'),
        ),
    )

    takes_options = (
        DNParam('binddn?',
            cli_name='bind_dn',
            label=_('Bind DN'),
            default=DN(('cn', 'directory manager')),
            autofill=True,
        ),
        DNParam('usercontainer',
            cli_name='user_container',
            label=_('User container'),
            doc=_('DN of container for users in DS relative to base DN'),
            default=DN(('ou', 'people')),
            autofill=True,
        ),
        DNParam('groupcontainer',
            cli_name='group_container',
            label=_('Group container'),
            doc=_('DN of container for groups in DS relative to base DN'),
            default=DN(('ou', 'groups')),
            autofill=True,
        ),
        Str('userobjectclass+',
            cli_name='user_objectclass',
            label=_('User object class'),
            doc=_('Comma-separated list of objectclasses used to search for user entries in DS'),
            csv=True,
            default=(u'person',),
            autofill=True,
        ),
        Str('groupobjectclass+',
            cli_name='group_objectclass',
            label=_('Group object class'),
            doc=_('Comma-separated list of objectclasses used to search for group entries in DS'),
            csv=True,
            default=(u'groupOfUniqueNames', u'groupOfNames'),
            autofill=True,
        ),
        Str('userignoreobjectclass*',
            cli_name='user_ignore_objectclass',
            label=_('Ignore user object class'),
            doc=_('Comma-separated list of objectclasses to be ignored for user entries in DS'),
            csv=True,
            default=tuple(),
            autofill=True,
        ),
        Str('userignoreattribute*',
            cli_name='user_ignore_attribute',
            label=_('Ignore user attribute'),
            doc=_('Comma-separated list of attributes to be ignored for user entries in DS'),
            csv=True,
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreobjectclass*',
            cli_name='group_ignore_objectclass',
            label=_('Ignore group object class'),
            doc=_('Comma-separated list of objectclasses to be ignored for group entries in DS'),
            csv=True,
            default=tuple(),
            autofill=True,
        ),
        Str('groupignoreattribute*',
            cli_name='group_ignore_attribute',
            label=_('Ignore group attribute'),
            doc=_('Comma-separated list of attributes to be ignored for group entries in DS'),
            csv=True,
            default=tuple(),
            autofill=True,
        ),
        Flag('groupoverwritegid',
            cli_name='group_overwrite_gid',
            label=_('Overwrite GID'),
            doc=_('When migrating a group already existing in IPA domain overwrite the '\
                  'group GID and report as success'),
        ),
        StrEnum('schema?',
            cli_name='schema',
            label=_('LDAP schema'),
            doc=_('The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis'),
            values=_supported_schemas,
            default=_supported_schemas[0],
            autofill=True,
        ),
        Flag('continue?',
            label=_('Continue'),
            doc=_('Continuous operation mode. Errors are reported but the process continues'),
            default=False,
        ),
        DNParam('basedn?',
            cli_name='base_dn',
            label=_('Base DN'),
            doc=_('Base DN on remote LDAP server'),
        ),
        Flag('compat?',
            cli_name='with_compat',
            label=_('Ignore compat plugin'),
            doc=_('Allows migration despite the usage of compat plugin'),
            default=False,
        ),
    )

    has_output = (
        output.Output(
            'result',
            type=dict,
            doc=_('Lists of objects migrated; categorized by type.'),
        ),
        output.Output(
            'failed',
            type=dict,
            doc=
            _('Lists of objects that could not be migrated; categorized by type.'
              ),
        ),
        output.Output(
            'enabled',
            type=bool,
            doc=_('False if migration mode was disabled.'),
        ),
        output.Output(
            'compat',
            type=bool,
            doc=
            _('False if migration fails because the compatibility plug-in is enabled.'
              ),
        ),
    )

    exclude_doc = _('comma-separated list of %s to exclude from migration')

    truncated_err_msg = _('''\
search results for objects to be migrated
have been truncated by the server;
migration process might be incomplete\n''')

    migration_disabled_msg = _('''\
Migration mode is disabled. Use \'ipa config-mod\' to enable it.''')

    pwd_migration_msg = _('''\
Passwords have been migrated in pre-hashed format.
IPA is unable to generate Kerberos keys unless provided
with clear text passwords. All migrated users need to
login at https://your.domain/ipa/migration/ before they
can use their Kerberos accounts.''')

    def get_options(self):
        """
        Call get_options of the baseclass and add "exclude" options
        for each type of object being migrated.
        """
        for option in super(migrate_ds, self).get_options():
            yield option
        for ldap_obj_name in self.migrate_objects:
            ldap_obj = self.api.Object[ldap_obj_name]
            name = 'exclude_%ss' % to_cli(ldap_obj_name)
            doc = self.exclude_doc % ldap_obj.object_name_plural
            yield Str('%s*' % name,
                      cli_name=name,
                      doc=doc,
                      csv=True,
                      default=tuple(),
                      autofill=True)

    def normalize_options(self, options):
        """
        Convert all "exclude" option values to lower-case.

        Also, empty List parameters are converted to None, but the migration
        plugin doesn't like that - convert back to empty lists.
        """
        for p in self.params():
            if p.csv:
                if options[p.name]:
                    options[p.name] = tuple(v.lower() for v in options[p.name])
                else:
                    options[p.name] = tuple()

    def _get_search_bases(self, options, ds_base_dn, migrate_order):
        search_bases = dict()
        for ldap_obj_name in migrate_order:
            container = options.get('%scontainer' % to_cli(ldap_obj_name))
            if container:
                # Don't append base dn if user already appended it in the container dn
                if container.endswith(ds_base_dn):
                    search_base = container
                else:
                    search_base = DN(container, ds_base_dn)
            else:
                search_base = ds_base_dn
            search_bases[ldap_obj_name] = search_base
        return search_bases

    def migrate(self, ldap, config, ds_ldap, ds_base_dn, options):
        """
        Migrate objects from DS to LDAP.
        """
        assert isinstance(ds_base_dn, DN)
        migrated = {}  # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
        failed = {}  # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
        search_bases = self._get_search_bases(options, ds_base_dn,
                                              self.migrate_order)
        migration_start = datetime.datetime.now()
        for ldap_obj_name in self.migrate_order:
            ldap_obj = self.api.Object[ldap_obj_name]

            template = self.migrate_objects[ldap_obj_name]['filter_template']
            oc_list = options[to_cli(
                self.migrate_objects[ldap_obj_name]['oc_option'])]
            search_filter = construct_filter(template, oc_list)

            exclude = options['exclude_%ss' % to_cli(ldap_obj_name)]
            context = dict(ds_ldap=ds_ldap)

            migrated[ldap_obj_name] = []
            failed[ldap_obj_name] = {}

            try:
                (entries, truncated) = ds_ldap.find_entries(
                    search_filter,
                    ['*'],
                    search_bases[ldap_obj_name],
                    _ldap.SCOPE_ONELEVEL,
                    time_limit=0,
                    size_limit=-1,
                    search_refs=True  # migrated DS may contain search references
                )
            except errors.NotFound:
                if not options.get('continue', False):
                    raise errors.NotFound(
                        reason=
                        _('%(container)s LDAP search did not return any result '
                          '(search base: %(search_base)s, '
                          'objectclass: %(objectclass)s)') % {
                              'container': ldap_obj_name,
                              'search_base': search_bases[ldap_obj_name],
                              'objectclass': ', '.join(oc_list)
                          })
                else:
                    truncated = False
                    entries = []
            if truncated:
                self.log.error('%s: %s' %
                               (ldap_obj.name, self.truncated_err_msg))

            blacklists = {}
            for blacklist in ('oc_blacklist', 'attr_blacklist'):
                blacklist_option = self.migrate_objects[ldap_obj_name][
                    blacklist + '_option']
                if blacklist_option is not None:
                    blacklists[blacklist] = options.get(
                        blacklist_option, tuple())
                else:
                    blacklists[blacklist] = tuple()

            # get default primary group for new users
            if 'def_group_dn' not in context:
                def_group = config.get('ipadefaultprimarygroup')
                context['def_group_dn'] = api.Object.group.get_dn(def_group)
                try:
                    (g_dn, g_attrs) = ldap.get_entry(context['def_group_dn'],
                                                     ['gidnumber', 'cn'])
                except errors.NotFound:
                    error_msg = _('Default group for new users not found')
                    raise errors.NotFound(reason=error_msg)
                if 'gidnumber' in g_attrs:
                    context['def_group_gid'] = g_attrs['gidnumber'][0]

            context['has_upg'] = ldap.has_upg()

            valid_gids = []
            invalid_gids = []
            context['migrate_cnt'] = 0
            migrate_cnt = 0
            for (dn, entry_attrs) in entries:
                context['migrate_cnt'] = migrate_cnt
                s = datetime.datetime.now()
                if dn is None:  # LDAP search reference
                    failed[ldap_obj_name][entry_attrs[0]] = unicode(
                        _ref_err_msg)
                    continue

                try:
                    dn = DN(dn)
                except ValueError:
                    failed[ldap_obj_name][dn] = unicode(_dn_err_msg)
                    continue

                ava = dn[0][0]
                if ava.attr == ldap_obj.primary_key.name:
                    # In case if pkey attribute is in the migrated object DN
                    # and the original LDAP is multivalued, make sure that
                    # we pick the correct value (the unique one stored in DN)
                    pkey = ava.value.lower()
                else:
                    pkey = entry_attrs[ldap_obj.primary_key.name][0].lower()

                if pkey in exclude:
                    continue

                dn = ldap_obj.get_dn(pkey)
                assert isinstance(dn, DN)
                entry_attrs['objectclass'] = list(
                    set(
                        config.get(ldap_obj.object_class_config,
                                   ldap_obj.object_class) +
                        [o.lower() for o in entry_attrs['objectclass']]))
                entry_attrs[ldap_obj.primary_key.name][0] = entry_attrs[
                    ldap_obj.primary_key.name][0].lower()

                callback = self.migrate_objects[ldap_obj_name]['pre_callback']
                if callable(callback):
                    try:
                        dn = callback(ldap,
                                      pkey,
                                      dn,
                                      entry_attrs,
                                      failed[ldap_obj_name],
                                      config,
                                      context,
                                      schema=options['schema'],
                                      search_bases=search_bases,
                                      valid_gids=valid_gids,
                                      invalid_gids=invalid_gids,
                                      **blacklists)
                        assert isinstance(dn, DN)
                        if not dn:
                            continue
                    except errors.NotFound, e:
                        failed[ldap_obj_name][pkey] = unicode(e.reason)
                        continue

                try:
                    ldap.add_entry(dn, entry_attrs)
                except errors.ExecutionError, e:
                    callback = self.migrate_objects[ldap_obj_name][
                        'exc_callback']
                    if callable(callback):
                        try:
                            callback(ldap, dn, entry_attrs, e, options)
                        except errors.ExecutionError, e:
                            failed[ldap_obj_name][pkey] = unicode(e)
                            continue
                    else:
                        failed[ldap_obj_name][pkey] = unicode(e)
                        continue

                migrated[ldap_obj_name].append(pkey)

                callback = self.migrate_objects[ldap_obj_name]['post_callback']
                if callable(callback):
                    callback(
                        ldap,
                        pkey,
                        dn,
                        entry_attrs,
                        failed[ldap_obj_name],
                        config,
                        context,
                    )
                e = datetime.datetime.now()
                d = e - s
                total_dur = e - migration_start
                migrate_cnt += 1
                if migrate_cnt > 0 and migrate_cnt % 100 == 0:
                    api.log.info("%d %ss migrated. %s elapsed." %
                                 (migrate_cnt, ldap_obj_name, total_dur))
                api.log.debug("%d %ss migrated, duration: %s (total %s)" %
                              (migrate_cnt, ldap_obj_name, d, total_dur))
Exemple #8
0
class user(LDAPObject):
    """
    User object.
    """
    container_dn = api.env.container_user
    object_name = _('user')
    object_name_plural = _('users')
    object_class = ['posixaccount']
    object_class_config = 'ipauserobjectclasses'
    possible_objectclasses = ['meporiginentry']
    disallow_object_classes = ['krbticketpolicyaux']
    search_attributes_config = 'ipausersearchfields'
    default_attributes = [
        'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
        'uidnumber', 'gidnumber', 'mail', 'ou',
        'telephonenumber', 'title', 'memberof', 'nsaccountlock',
        'memberofindirect',
    ]
    search_display_attributes = [
        'uid', 'givenname', 'sn', 'homedirectory', 'loginshell',
        'mail', 'telephonenumber', 'title', 'nsaccountlock',
        'uidnumber', 'gidnumber', 'sshpubkeyfp',
    ]
    uuid_attribute = 'ipauniqueid'
    attribute_members = {
        'memberof': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
        'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'],
    }
    rdn_is_primary_key = True
    bindable = True
    password_attributes = [('userpassword', 'has_password'),
                           ('krbprincipalkey', 'has_keytab')]

    label = _('Users')
    label_singular = _('User')

    takes_params = (
        Str('uid',
            pattern='^[a-zA-Z0-9_.][a-zA-Z0-9_.-]{0,252}[a-zA-Z0-9_.$-]?$',
            pattern_errmsg='may only include letters, numbers, _, -, . and $',
            maxlength=255,
            cli_name='login',
            label=_('User login'),
            primary_key=True,
            default_from=lambda givenname, sn: givenname[0] + sn,
            normalizer=lambda value: value.lower(),
        ),
        Str('givenname',
            cli_name='first',
            label=_('First name'),
        ),
        Str('sn',
            cli_name='last',
            label=_('Last name'),
        ),
        Str('cn',
            label=_('Full name'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('displayname?',
            label=_('Display name'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('initials?',
            label=_('Initials'),
            default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]),
            autofill=True,
        ),
        Str('homedirectory?',
            cli_name='homedir',
            label=_('Home directory'),
        ),
        Str('gecos?',
            label=_('GECOS field'),
            default_from=lambda givenname, sn: '%s %s' % (givenname, sn),
            autofill=True,
        ),
        Str('loginshell?',
            cli_name='shell',
            label=_('Login shell'),
        ),
        Str('krbprincipalname?', validate_principal,
            cli_name='principal',
            label=_('Kerberos principal'),
            default_from=lambda uid: '%s@%s' % (uid.lower(), api.env.realm),
            autofill=True,
            flags=['no_update'],
            normalizer=lambda value: normalize_principal(value),
        ),
        Str('mail*',
            cli_name='email',
            label=_('Email address'),
        ),
        Password('userpassword?',
            cli_name='password',
            label=_('Password'),
            doc=_('Prompt to set the user password'),
            # FIXME: This is temporary till bug is fixed causing updates to
            # bomb out via the webUI.
            exclude='webui',
        ),
        Flag('random?',
            doc=_('Generate a random user password'),
            flags=('no_search', 'virtual_attribute'),
            default=False,
        ),
        Str('randompassword?',
            label=_('Random password'),
            flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'),
        ),
        Int('uidnumber',
            cli_name='uid',
            label=_('UID'),
            doc=_('User ID Number (system will assign one if not provided)'),
            autofill=True,
            default=DNA_MAGIC,
            minvalue=1,
        ),
        Int('gidnumber',
            label=_('GID'),
            doc=_('Group ID Number'),
            minvalue=1,
            default=DNA_MAGIC,
            autofill=True,
        ),
        Str('street?',
            cli_name='street',
            label=_('Street address'),
        ),
        Str('l?',
            cli_name='city',
            label=_('City'),
        ),
        Str('st?',
            cli_name='state',
            label=_('State/Province'),
        ),
        Str('postalcode?',
            label=_('ZIP'),
        ),
        Str('telephonenumber*',
            cli_name='phone',
            label=_('Telephone Number')
        ),
        Str('mobile*',
            label=_('Mobile Telephone Number')
        ),
        Str('pager*',
            label=_('Pager Number')
        ),
        Str('facsimiletelephonenumber*',
            cli_name='fax',
            label=_('Fax Number'),
        ),
        Str('ou?',
            cli_name='orgunit',
            label=_('Org. Unit'),
        ),
        Str('title?',
            label=_('Job Title'),
        ),
        Str('manager?',
            label=_('Manager'),
        ),
        Str('carlicense?',
            label=_('Car License'),
        ),
        Bool('nsaccountlock?',
            label=_('Account disabled'),
            flags=['no_option'],
        ),
        Str('ipasshpubkey*', validate_sshpubkey,
            cli_name='sshpubkey',
            label=_('SSH public key'),
            normalizer=normalize_sshpubkey,
            csv=True,
            flags=['no_search'],
        ),
    )

    def _normalize_and_validate_email(self, email, config=None):
        if not config:
            config = self.backend.get_ipa_config()[1]

        # check if default email domain should be added
        defaultdomain = config.get('ipadefaultemaildomain', [None])[0]
        if email:
            norm_email = []
            if not isinstance(email, (list, tuple)):
                email = [email]
            for m in email:
                if isinstance(m, basestring):
                    if '@' not in m and defaultdomain:
                        m = m + u'@' + defaultdomain
                    if not Email(m):
                        raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
                    norm_email.append(m)
                else:
                    if not Email(m):
                        raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m))
                    norm_email.append(m)
            return norm_email

        return email

    def _normalize_manager(self, manager):
        """
        Given a userid verify the user's existence and return the dn.
        """
        if not manager:
            return None

        if not isinstance(manager, list):
            manager = [manager]
        try:
            container_dn = DN(self.container_dn, api.env.basedn)
            for m in xrange(len(manager)):
                if isinstance(manager[m], DN) and manager[m].endswith(container_dn):
                    continue
                (dn, entry_attrs) = self.backend.find_entry_by_attr(
                        self.primary_key.name, manager[m], self.object_class, [''],
                        self.container_dn
                    )
                manager[m] = dn
        except errors.NotFound:
            raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=manager[m]))

        return manager

    def _convert_manager(self, entry_attrs, **options):
        """
        Convert a manager dn into a userid
        """
        if options.get('raw', False):
             return

        if 'manager' in entry_attrs:
            for m in xrange(len(entry_attrs['manager'])):
                entry_attrs['manager'][m] = self.get_primary_key_from_dn(entry_attrs['manager'][m])
Exemple #9
0
class trust_add(LDAPCreate):
    __doc__ = _('''
Add new trust to use.

This command establishes trust relationship to another domain
which becomes 'trusted'. As result, users of the trusted domain
may access resources of this domain.

Only trusts to Active Directory domains are supported right now.

The command can be safely run multiple times against the same domain,
this will cause change to trust relationship credentials on both
sides.
    ''')

    takes_options = LDAPCreate.takes_options + (
        StrEnum(
            'trust_type',
            cli_name='type',
            label=_('Trust type (ad for Active Directory, default)'),
            values=(u'ad', ),
            default=u'ad',
            autofill=True,
        ),
        Str(
            'realm_admin?',
            cli_name='admin',
            label=_("Active Directory domain administrator"),
        ),
        Password(
            'realm_passwd?',
            cli_name='password',
            label=_("Active directory domain administrator's password"),
            confirm=False,
        ),
        Str(
            'realm_server?',
            cli_name='server',
            label=_(
                'Domain controller for the Active Directory domain (optional)'
            ),
        ),
        Password(
            'trust_secret?',
            cli_name='trust_secret',
            label=_('Shared secret for the trust'),
            confirm=False,
        ),
        Int(
            'base_id?',
            cli_name='base_id',
            label=_(
                'First Posix ID of the range reserved for the trusted domain'),
        ),
        Int('range_size?',
            cli_name='range_size',
            label=_('Size of the ID range reserved for the trusted domain'),
            default=200000,
            autofill=True),
    )

    msg_summary = _('Added Active Directory trust for realm "%(value)s"')
    has_output_params = LDAPCreate.has_output_params + trust_output_params

    def execute(self, *keys, **options):
        if not _murmur_installed and 'base_id' not in options:
            raise errors.ValidationError(name=_('missing base_id'),
                error=_('pysss_murmur is not available on the server ' \
                        'and no base-id is given.'))

        if 'trust_type' in options:
            if options['trust_type'] == u'ad':
                result = self.execute_ad(*keys, **options)
            else:
                raise errors.ValidationError(name=_('trust type'),
                                             error=_('only "ad" is supported'))
        else:
            raise errors.RequirementError(name=_('trust type'))

        self.add_range(*keys, **options)

        trust_filter = "cn=%s" % result['value']
        ldap = self.obj.backend
        (trusts,
         truncated) = ldap.find_entries(base_dn=DN(api.env.container_trusts,
                                                   api.env.basedn),
                                        filter=trust_filter)

        result['result'] = trusts[0][1]
        result['result']['trusttype'] = [
            trust_type_string(result['result']['ipanttrusttype'][0])
        ]
        result['result']['trustdirection'] = [
            trust_direction_string(result['result']['ipanttrustdirection'][0])
        ]
        result['result']['truststatus'] = [
            trust_status_string(result['verified'])
        ]
        del result['verified']

        return result

    def add_range(self, *keys, **options):
        new_obj = api.Command['trust_show'](keys[-1])
        dom_sid = new_obj['result']['ipanttrusteddomainsid'][0]

        range_name = keys[-1].upper() + '_id_range'

        try:
            old_range = api.Command['idrange_show'](range_name)
        except errors.NotFound, e:
            old_range = None

        if old_range:
            old_dom_sid = old_range['result']['ipanttrusteddomainsid'][0]

            if old_dom_sid == dom_sid:
                return

            raise errors.ValidationError(name=_('range exists'),
                    error=_('ID range with the same name but different ' \
                            'domain SID already exists. The ID range for ' \
                            'the new trusted domain must be created manually.'))

        if 'base_id' in options:
            base_id = options['base_id']
        else:
            base_id = 200000 + (pysss_murmur.murmurhash3(
                dom_sid, len(dom_sid), 0xdeadbeef) % 10000) * 200000

        try:
            new_range = api.Command['idrange_add'](
                range_name,
                ipabaseid=base_id,
                ipaidrangesize=options['range_size'],
                ipabaserid=0,
                ipanttrusteddomainsid=dom_sid)
        except Exception, e:
            raise errors.ValidationError(
                name=_('ID range exists'),
                error=_('ID range already exists, must be added manually'))
Exemple #10
0
class passwd(Command):
    __doc__ = _("Set a user's password.")

    takes_args = (
        Str(
            'principal',
            validate_principal,
            cli_name='user',
            label=_('User name'),
            primary_key=True,
            autofill=True,
            default_from=lambda: util.get_current_principal(),
            normalizer=lambda value: normalize_principal(value),
        ),
        Password(
            'password',
            label=_('New Password'),
        ),
        Password(
            'current_password',
            label=_('Current Password'),
            confirm=False,
            default_from=lambda principal: get_current_password(principal),
            autofill=True,
            sortorder=-1,
        ),
    )

    has_output = output.standard_value
    msg_summary = _('Changed password for "%(value)s"')

    def execute(self, principal, password, current_password):
        """
        Execute the passwd operation.

        The dn should not be passed as a keyword argument as it is constructed
        by this method.

        Returns the entry

        :param principal: The login name or principal of the user
        :param password: the new password
        :param current_password: the existing password, if applicable
        """
        ldap = self.api.Backend.ldap2

        (dn, entry_attrs) = ldap.find_entry_by_attr(
            'krbprincipalname', principal, 'posixaccount', [''],
            DN(api.env.container_user, api.env.basedn))

        if principal == getattr(context, 'principal') and \
            current_password == MAGIC_VALUE:
            # No cheating
            self.log.warn(
                'User attempted to change password using magic value')
            raise errors.ACIError(info=_('Invalid credentials'))

        if current_password == MAGIC_VALUE:
            ldap.modify_password(dn, password)
        else:
            ldap.modify_password(dn, password, current_password)

        return dict(
            result=True,
            value=principal,
        )
Exemple #11
0
class idp(LDAPObject):
    """
    Identity Provider object.
    """
    container_dn = api.env.container_idp
    object_name = _('Identity Provider server')
    object_name_plural = _('Identity Provider servers')
    object_class = ['ipaidp']
    default_attributes = [
        'cn',
        'ipaidpauthendpoint',
        'ipaidpdevauthendpoint',
        'ipaidpuserinfoendpoint',
        'ipaidpkeysendpoint',
        'ipaidptokenendpoint',
        'ipaidpissuerurl',
        'ipaidpclientid',
        'ipaidpscope',
        'ipaidpsub',
    ]
    search_attributes = [
        'cn',
        'ipaidpauthendpoint',
        'ipaidpdevauthendpoint',
        'ipaidptokenendpoint',
        'ipaidpuserinfoendpoint',
        'ipaidpkeysendpoint',
        'ipaidpscope',
        'ipaidpsub',
    ]
    allow_rename = True
    label = _('Identity Provider servers')
    label_singular = _('Identity Provider server')

    takes_params = (
        Str(
            'cn',
            cli_name='name',
            label=_('Identity Provider server name'),
            primary_key=True,
        ),
        Str(
            'ipaidpauthendpoint?',
            validate_uri,
            cli_name='auth_uri',
            label=_('Authorization URI'),
            doc=_('OAuth 2.0 authorization endpoint'),
        ),
        Str(
            'ipaidpdevauthendpoint?',
            validate_uri,
            cli_name='dev_auth_uri',
            label=_('Device authorization URI'),
            doc=_('Device authorization endpoint'),
        ),
        Str(
            'ipaidptokenendpoint?',
            validate_uri,
            cli_name='token_uri',
            label=_('Token URI'),
            doc=_('Token endpoint'),
        ),
        Str(
            'ipaidpuserinfoendpoint?',
            validate_uri,
            cli_name='userinfo_uri',
            label=_('User info URI'),
            doc=_('User information endpoint'),
        ),
        Str(
            'ipaidpkeysendpoint?',
            validate_uri,
            cli_name='keys_uri',
            label=_('JWKS URI'),
            doc=_('JWKS endpoint'),
        ),
        Str(
            'ipaidpissuerurl?',
            cli_name='issuer_url',
            label=_('OIDC URL'),
            doc=_('The Identity Provider OIDC URL'),
        ),
        Str(
            'ipaidpclientid',
            cli_name='client_id',
            label=_('Client identifier'),
            doc=_('OAuth 2.0 client identifier'),
        ),
        Password(
            'ipaidpclientsecret?',
            cli_name='secret',
            label=_('Secret'),
            doc=_('OAuth 2.0 client secret'),
            confirm=True,
            flags={'no_display'},
        ),
        Str(
            'ipaidpscope?',
            cli_name='scope',
            label=_('Scope'),
            doc=_('OAuth 2.0 scope. Multiple scopes separated by space'),
        ),
        Str(
            'ipaidpsub?',
            cli_name='idp_user_id',
            label=_('External IdP user identifier attribute'),
            doc=_('Attribute for user identity in OAuth 2.0 userinfo'),
        ),
    )

    permission_filter_objectclasses = ['ipaidp']
    managed_permissions = {
        'System: Add External IdP server': {
            'ipapermright': {'add'},
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermtargetfilter': {'(objectclass=ipaidp)'},
            'default_privileges': {'External IdP server Administrators'}
        },
        'System: Read External IdP server': {
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'cn',
                'objectclass',
                'ipaidpauthendpoint',
                'ipaidpdevauthendpoint',
                'ipaidpuserinfoendpoint',
                'ipaidptokenendpoint',
                'ipaidpkeysendpoint',
                'ipaidpissuerurl',
                'ipaidpclientid',
                'ipaidpscope',
                'ipaidpsub',
            },
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermtargetfilter': {'(objectclass=ipaidp)'},
            'default_privileges': {'External IdP server Administrators'}
        },
        'System: Modify External IdP server': {
            'ipapermright': {'write'},
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermdefaultattr': {
                'cn',
                'objectclass',
                'ipaidpauthendpoint',
                'ipaidpdevauthendpoint',
                'ipaidpuserinfoendpoint',
                'ipaidptokenendpoint',
                'ipaidpkeysendpoint',
                'ipaidpissuerurl',
                'ipaidpclientid',
                'ipaidpscope',
                'ipaidpclientsecret',
                'ipaidpsub',
            },
            'default_privileges': {'External IdP server Administrators'}
        },
        'System: Delete External IdP server': {
            'ipapermright': {'delete'},
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermtargetfilter': {'(objectclass=ipaidp)'},
            'default_privileges': {'External IdP server Administrators'}
        },
        'System: Read External IdP server client secret': {
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermdefaultattr': {
                'cn',
                'objectclass',
                'ipaidpauthendpoint',
                'ipaidpdevauthendpoint',
                'ipaidpuserinfoendpoint',
                'ipaidptokenendpoint',
                'ipaidpissuerurl',
                'ipaidpkeysendpoint',
                'ipaidpclientid',
                'ipaidpscope',
                'ipaidpclientsecret',
                'ipaidpsub',
            },
            'ipapermtargetfilter': {'(objectclass=ipaidp)'},
        }
    }
Exemple #12
0
class radiusproxy(LDAPObject):
    """
    RADIUS Server object.
    """
    container_dn = api.env.container_radiusproxy
    object_name = _('RADIUS proxy server')
    object_name_plural = _('RADIUS proxy servers')
    object_class = ['ipatokenradiusconfiguration']
    default_attributes = [
        'cn', 'description', 'ipatokenradiusserver', 'ipatokenradiustimeout',
        'ipatokenradiusretries', 'ipatokenusermapattribute'
    ]
    search_attributes = ['cn', 'description', 'ipatokenradiusserver']
    allow_rename = True
    label = _('RADIUS Servers')
    label_singular = _('RADIUS Server')

    takes_params = (
        Str(
            'cn',
            cli_name='name',
            label=_('RADIUS proxy server name'),
            primary_key=True,
        ),
        Str(
            'description?',
            cli_name='desc',
            label=_('Description'),
            doc=_('A description of this RADIUS proxy server'),
        ),
        Str(
            'ipatokenradiusserver',
            validate_radiusserver,
            cli_name='server',
            label=_('Server'),
            doc=_('The hostname or IP (with or without port)'),
        ),
        Password(
            'ipatokenradiussecret',
            cli_name='secret',
            label=_('Secret'),
            doc=_('The secret used to encrypt data'),
            confirm=True,
        ),
        Int(
            'ipatokenradiustimeout?',
            cli_name='timeout',
            label=_('Timeout'),
            doc=_('The total timeout across all retries (in seconds)'),
            minvalue=1,
        ),
        Int(
            'ipatokenradiusretries?',
            cli_name='retries',
            label=_('Retries'),
            doc=_('The number of times to retry authentication'),
            minvalue=0,
            maxvalue=10,
        ),
        Str(
            'ipatokenusermapattribute?',
            validate_attributename,
            cli_name='userattr',
            label=_('User attribute'),
            doc=_('The username attribute on the user object'),
        ),
    )

    managed_permissions = {
        'System: Read Radius Servers': {
            'replaces_global_anonymous_aci': True,
            'ipapermright': {'read', 'search', 'compare'},
            'ipapermdefaultattr': {
                'cn', 'objectclass', 'ipatokenradiusserver', 'description',
                'ipatokenradiustimeout', 'ipatokenradiusretries',
                'ipatokenusermapattribute'
            },
            'ipapermlocation': DN(container_dn, api.env.basedn),
            'ipapermtargetfilter':
            {'(objectclass=ipatokenradiusconfiguration)'},
            'default_privileges':
            {'User Administrators', 'Stage User Administrators'},
        }
    }