Exemplo n.º 1
0
class BofhdExtension(BofhdCommonMethods, BofhdEmailMixin):

    all_commands = {}
    external_id_mappings = {}
    parent_commands = True
    authz = BofhdAuth

    def __init__(self, *args, **kwargs):
        super(BofhdExtension, self).__init__(*args, **kwargs)
        self.external_id_mappings['fnr'] = self.const.externalid_fodselsnr

    @classmethod
    def get_help_strings(cls):
        return bofhd_core_help.get_help_strings()

    #
    # person student_info
    #
    all_commands['person_student_info'] = cmd_param.Command(
        ("person", "student_info"),
        cmd_param.PersonId(),
        fs=cmd_param.FormatSuggestion([
            ("Studieprogrammer: %s, %s, %s, %s, "
             "tildelt=%s->%s privatist: %s",
             ("studprogkode", "studieretningkode", "studierettstatkode",
              "studentstatkode", format_day("dato_tildelt"),
              format_day("dato_gyldig_til"), "privatist")),
            ("Eksamensmeldinger: %s (%s), %s", ("ekskode", "programmer",
                                                format_day("dato"))),
            ("Utd. plan: %s, %s, %d, %s",
             ("studieprogramkode", "terminkode_bekreft", "arstall_bekreft",
              format_day("dato_bekreftet"))),
            ("Semesterreg: %s, %s, FS bet. reg: %s, endret: %s",
             ("regformkode", "betformkode", format_day("dato_endring"),
              format_day("dato_regform_endret"))),
        ]),
        perm_filter='can_get_student_info')

    def person_student_info(self, operator, person_id):
        person = self._get_person(*self._map_person_id(person_id))
        self.ba.can_get_student_info(operator.get_entity_id(), person)
        fnr = person.get_external_id(id_type=self.const.externalid_fodselsnr,
                                     source_system=self.const.system_fs)
        if not fnr:
            raise CerebrumError("No matching fnr from FS")
        fodselsdato, pnum = fodselsnr.del_fnr(fnr[0]['external_id'])
        har_opptak = {}
        ret = []
        try:
            fs_db = make_fs()
        except Database.DatabaseError, e:
            self.logger.warn("Can't connect to FS (%s)" % e)
            raise CerebrumError("Can't connect to FS, try later")
        for row in fs_db.student.get_studierett(fodselsdato, pnum):
            har_opptak[str(row['studieprogramkode'])] = row['status_privatist']
            ret.append({
                'studprogkode':
                row['studieprogramkode'],
                'studierettstatkode':
                row['studierettstatkode'],
                'studentstatkode':
                row['studentstatkode'],
                'studieretningkode':
                row['studieretningkode'],
                'dato_tildelt':
                self._convert_ticks_to_timestamp(
                    row['dato_studierett_tildelt']),
                'dato_gyldig_til':
                self._convert_ticks_to_timestamp(
                    row['dato_studierett_gyldig_til']),
                'privatist':
                row['status_privatist']
            })

        for row in fs_db.student.get_eksamensmeldinger(fodselsdato, pnum):
            programmer = []
            for row2 in fs_db.info.get_emne_i_studieprogram(row['emnekode']):
                if str(row2['studieprogramkode']) in har_opptak:
                    programmer.append(row2['studieprogramkode'])
            ret.append({
                'ekskode':
                row['emnekode'],
                'programmer':
                ",".join(programmer),
                'dato':
                self._convert_ticks_to_timestamp(row['dato_opprettet'])
            })

        for row in fs_db.student.get_utdanningsplan(fodselsdato, pnum):
            ret.append({
                'studieprogramkode':
                row['studieprogramkode'],
                'terminkode_bekreft':
                row['terminkode_bekreft'],
                'arstall_bekreft':
                row['arstall_bekreft'],
                'dato_bekreftet':
                self._convert_ticks_to_timestamp(row['dato_bekreftet'])
            })

        for row in fs_db.student.get_semreg(fodselsdato, pnum):
            ret.append({
                'regformkode':
                row['regformkode'],
                'betformkode':
                row['betformkode'],
                'dato_endring':
                self._convert_ticks_to_timestamp(row['dato_endring']),
                'dato_regform_endret':
                self._convert_ticks_to_timestamp(row['dato_regform_endret'])
            })
        return ret
Exemplo n.º 2
0
class BofhdCommonMethods(BofhdCommandBase):
    """Class with common methods that is used by most, 'normal' instances.

    Instances that requires some special care for some methods could subclass
    those in their own institution-specific class
    (modules.no.<inst>.bofhd_<inst>_cmds.py:BofhdExtension).

    The methods are using the BofhdAuth that is defined in the institution's
    subclass - L{BofhdExtension.authz}.

    """

    @property
    def util(self):
        try:
            return self.__util
        except AttributeError:
            self.__util = BofhdUtils(self.db)
            return self.__util

    # Each subclass defines its own class attribute containing the relevant
    # commands.
    # Any command defined in 'all_commands' or 'hidden_commands' are callable
    # from clients.
    all_commands = {}

    ##
    # User methods

    #
    # user delete
    #
    all_commands['user_delete'] = cmd.Command(
        ("user", "delete"),
        cmd.AccountName(),
        perm_filter='can_delete_user')

    def user_delete(self, operator, accountname):
        account = self._get_account(accountname)
        self.ba.can_delete_user(operator.get_entity_id(), account)
        if account.is_deleted():
            raise CerebrumError("User is already deleted")
        account.deactivate()
        account.write_db()
        return u"User %s is deactivated" % account.account_name

    ##
    # Group methods

    #
    # group create
    #
    all_commands['group_create'] = cmd.Command(
        ("group", "create"),
        cmd.GroupName(help_ref="group_name_new"),
        cmd.SimpleString(help_ref="string_description"),
        cmd.GroupName(optional=True, help_ref="group_name_admin"),
        # cmd.GroupExpireDate(optional=True, help_ref="group_expire_date"),
        fs=cmd.FormatSuggestion(
            "Group created, internal id: %i", ("group_id",)
        ),
        perm_filter='can_create_group')

    def group_create(self, operator, groupname, description, admin_group=None,
                     expire_date=None):
        """ Standard method for creating normal groups.

        BofhdAuth's L{can_create_group} is first checked. The group gets the
        spreads as defined in L{cereconf.BOFHD_NEW_GROUP_SPREADS}.

        :param operator: operator's account object
        :param groupname: str name of new group
        :param description: str description of group
        :param admin_group: str name of admin group, optional for superuser
        :param expire_date: str expire date of group,
        :return: Group id
        """
        self.ba.can_create_group(operator.get_entity_id(),
                                 groupname=groupname)
        g = self.Group_class(self.db)
        roles = GroupRoles(self.db)

        # Check if group name is already in use, raise error if so
        duplicate_test = g.search(name=groupname, filter_expired=False)
        if len(duplicate_test) > 0:
            raise CerebrumError("Group name is already in use")

        # Only superuser is allowed to create new group without specifying group admin
        if not self.ba.is_superuser(operator.get_entity_id()) and not admin_group:
            raise PermissionDenied("Must specify admin group(s)")

        # Populate group
        g.populate(
            creator_id=operator.get_entity_id(),
            visibility=self.const.group_visibility_all,
            name=groupname,
            description=description,
            expire_date=expire_date,
            group_type=self.const.group_type_manual,
        )

        # Set default expire_date if it is not set
        if expire_date is None:
            g.set_default_expire_date()

        g.write_db()

        # Add spread
        for spread in cereconf.BOFHD_NEW_GROUP_SPREADS:
            g.add_spread(self.const.Spread(spread))
            # It is necessary to write changes to db before any calls to
            # add_spread are made: add_spread may in some cases may call find.
            g.write_db()

        # Set admin group(s)
        if admin_group:
            for admin in admin_group.split(' '):
                try:
                    admin_gr = self._get_group(admin, idtype=None,
                                               grtype='Group')
                except Errors.NotFoundError:
                    raise CerebrumError('No admin group with name: {}'
                                        .format(admin_group))
                else:
                    roles.add_admin_to_group(admin_gr.entity_id, g.entity_id)
        return {'group_id': int(g.entity_id)}

    #
    # group rename
    #
    all_commands['group_rename'] = cmd.Command(
        ('group', 'rename'),
        cmd.GroupName(help_ref="group_name"),
        cmd.GroupName(help_ref="group_name_new"),
        fs=cmd.FormatSuggestion(
            "Group renamed to %s. Check integrations!", ("new_name",)
        ),
        perm_filter='is_superuser')

    def group_rename(self, operator, groupname, newname):
        """ Rename a Cerebrum group.

        Warning: This creates issues for fullsyncs that doesn't handle state.
        Normally, the old group would get deleted and lose any data attached to
        it, and a shiny new one would be created. Do not use unless you're
        aware of the consequences!

        """
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Only superusers may rename groups, due "
                                   "to its consequences!")
        gr = self._get_group(groupname)
        self._raise_PermissionDenied_if_not_manual_group(gr)
        gr.group_name = newname
        if self._is_perishable_manual_group(gr):
            gr.set_default_expire_date()
        try:
            gr.write_db()
        except gr._db.IntegrityError as e:
            raise CerebrumError("Couldn't rename group: %s" % e)
        return {
            'new_name': gr.group_name,
            'group_id': int(gr.entity_id),
        }
Exemplo n.º 3
0
class BofhdExtension(BofhdCommonMethods):

    OU_class = Utils.Factory.get('OU')
    Account_class = Factory.get('Account')
    Group_class = Factory.get('Group')

    all_commands = {}
    parent_commands = True
    external_id_mappings = {}
    authz = NihAuth

    def __init__(self, *args, **kwargs):
        super(BofhdExtension, self).__init__(*args, **kwargs)
        self.external_id_mappings['fnr'] = self.const.externalid_fodselsnr

    @classmethod
    def get_help_strings(cls):
        return merge_help_strings(get_help_strings(), ({}, HELP_CMDS, {}))

    #
    # person student_info
    #
    all_commands['person_student_info'] = cmd_param.Command(
        ("person", "student_info"),
        cmd_param.PersonId(),
        fs=cmd_param.FormatSuggestion([
            ("Studieprogrammer: %s, %s, %s, %s, "
             "tildelt=%s->%s privatist: %s", (
                 "studprogkode",
                 "studieretningkode",
                 "studierettstatkode",
                 "studentstatkode",
                 format_day("dato_tildelt"),
                 format_day("dato_gyldig_til"),
                 "privatist",
             )),
            ("Eksamensmeldinger: %s (%s), %s", ("ekskode", "programmer",
                                                format_day("dato"))),
            ("Utd. plan: %s, %s, %d, %s",
             ("studieprogramkode", "terminkode_bekreft", "arstall_bekreft",
              format_day("dato_bekreftet"))),
            ("Semesterreg: %s, %s, FS bet. reg: %s, endret: %s",
             ("regformkode", "betformkode", format_day("dato_endring"),
              format_day("dato_regform_endret")))
        ]),
        perm_filter='can_get_student_info')

    def person_student_info(self, operator, person_id):
        person = self._get_person(*self._map_person_id(person_id))
        self.ba.can_get_student_info(operator.get_entity_id(), person)
        fnr = person.get_external_id(id_type=self.const.externalid_fodselsnr,
                                     source_system=self.const.system_fs)
        if not fnr:
            raise CerebrumError("No matching fnr from FS")
        fodselsdato, pnum = fodselsnr.del_fnr(fnr[0]['external_id'])
        ret = []
        try:
            fs_db = make_fs()
        except database.DatabaseError as e:
            self.logger.warn("Can't connect to FS (%s)" % e)
            raise CerebrumError("Can't connect to FS, try later")

        har_opptak = set()
        for row in fs_db.student.get_studierett(fodselsdato, pnum):
            har_opptak.add(row['studieprogramkode'])
            ret.append({
                'studprogkode':
                row['studieprogramkode'],
                'studierettstatkode':
                row['studierettstatkode'],
                'studentstatkode':
                row['studentstatkode'],
                'studieretningkode':
                row['studieretningkode'],
                'dato_tildelt':
                self._ticks_to_date(row['dato_studierett_tildelt']),
                'dato_gyldig_til':
                self._ticks_to_date(row['dato_studierett_gyldig_til']),
                'privatist':
                row['status_privatist'],
            })

        for row in fs_db.student.get_eksamensmeldinger(fodselsdato, pnum):
            programmer = []
            for row2 in fs_db.info.get_emne_i_studieprogram(row['emnekode']):
                if row2['studieprogramkode'] in har_opptak:
                    programmer.append(row2['studieprogramkode'])
            ret.append({
                'ekskode': row['emnekode'],
                'programmer': ",".join(programmer),
                'dato': self._ticks_to_date(row['dato_opprettet']),
            })

        for row in fs_db.student.get_utdanningsplan(fodselsdato, pnum):
            ret.append({
                'studieprogramkode':
                row['studieprogramkode'],
                'terminkode_bekreft':
                row['terminkode_bekreft'],
                'arstall_bekreft':
                row['arstall_bekreft'],
                'dato_bekreftet':
                self._ticks_to_date(row['dato_bekreftet']),
            })

        for row in fs_db.student.get_semreg(fodselsdato, pnum):
            ret.append({
                'regformkode':
                row['regformkode'],
                'betformkode':
                row['betformkode'],
                'dato_endring':
                self._ticks_to_date(row['dato_endring']),
                'dato_regform_endret':
                self._ticks_to_date(row['dato_regform_endret']),
            })
        return ret

    #
    # user delete
    #
    all_commands['user_delete'] = cmd_param.Command(
        ("user", "delete"),
        cmd_param.AccountName(),
        perm_filter='can_delete_user')

    def user_delete(self, operator, accountname):
        account = self._get_account(accountname)
        self.ba.can_delete_user(operator.get_entity_id(), account)
        if account.is_deleted():
            raise CerebrumError("User is already deleted")
        account.expire_date = mx.DateTime.now()
        for s in account.get_spread():
            account.delete_spread(int(s['spread']))
        account.write_db()
        return "User %s queued for deletion immediately" % account.account_name
Exemplo n.º 4
0
class BofhdExtension(BofhdCommonMethods):
    """ Extends bofhd with a 'ephorte' command group. """

    all_commands = {}
    parent_commands = False
    authz = UiOEphorteAuth

    @property
    def ephorte_role(self):
        try:
            return self.__ephorte_role_util
        except AttributeError:
            self.__ephorte_role_util = EphorteRole(self.db)
            return self.__ephorte_role_util

    @property
    def ephorte_perm(self):
        try:
            return self.__ephorte_perm_util
        except AttributeError:
            self.__ephorte_perm_util = EphortePermission(self.db)
            return self.__ephorte_perm_util

    @classmethod
    def get_help_strings(cls):
        const = Factory.get('Constants')()
        group_help = {
            'ephorte': "Commands for administrating ePhorte data"
        }
        command_help = {
            'ephorte': {
                'ephorte_add_role':
                    'Add an ePhorte role for a person',
                'ephorte_history':
                    'Show the ePhorte related history for a person',
                'ephorte_remove_role':
                    'Remove an ePhorte role from a person',
                'ephorte_list_roles':
                    'List a persons ePhorte roles',
                'ephorte_set_standard_role':
                    'Set given role as standard role',
                'ephorte_add_perm':
                    'Add an ePhorte permission for a person',
                'ephorte_remove_perm':
                    'Remove an ePhorte permission from a person',
                'ephorte_list_perm':
                    'List a persons ePhorte permissions'
            }
        }
        arg_help = {
            'journalenhet': [
                'journalenhet', 'Enter journalenhet',
                'Legal values are: \n%s' % "\n".join([
                    "  %-8s : %s" % (text_type(c), c.description)
                    for c in const.fetch_constants(const.EphorteJournalenhet)
                ])
            ],
            'arkivdel': [
                'arkivdel', 'Enter arkivdel',
                'Legal values are: \n%s' % "\n".join([
                    "  %-8s : %s" % (text_type(c), c.description)
                    for c in const.fetch_constants(const.EphorteArkivdel)
                ])
            ],
            'rolle': [
                'rolle', 'Enter rolle',
                'Legal values are: \n%s' % "\n".join([
                    "  %-8s : %s" % (text_type(c), c.description)
                    for c in const.fetch_constants(const.EphorteRole)
                ])
            ],
            'tilgang': [
                'tilgang', 'Enter perm ("tilgang")',
                'Legal values are: \n%s' % "\n".join([
                    "  %-8s : %s" % (text_type(c), c.description)
                    for c in const.fetch_constants(const.EphortePermission)
                ])
            ],
        }

        # liste lovlige arkivdel/journalenhet
        return (group_help, command_help, arg_help)

    def _get_role_type(self, code_str):
        try:
            c = self.const.EphorteRole(code_str)
            int(c)
            return c
        except Errors.NotFoundError:
            raise CerebrumError("Unknown role")

    def _get_arkivdel(self, code_str):
        try:
            c = self.const.EphorteArkivdel(code_str)
            int(c)
            return c
        except Errors.NotFoundError:
            raise CerebrumError("Unknown arkivdel")

    def _get_journalenhet(self, code_str):
        try:
            c = self.const.EphorteJournalenhet(code_str)
            int(c)
            return c
        except Errors.NotFoundError:
            raise CerebrumError("Unknown journalenhet")

    def _get_tilgang(self, code_str):
        try:
            c = self.const.EphortePermission(code_str)
            int(c)
            return c
        except Errors.NotFoundError:
            raise CerebrumError("Unknown ePhorte auth. permission"
                                " (tilgangskode)")

    #
    # Add, remove or list ePhorte-roles
    #
    all_commands['ephorte_add_role'] = cmd_param.Command(
        ("ephorte", "add_role"),
        cmd_param.PersonId(),
        Rolle(),
        cmd_param.OU(),
        Arkivdel(),
        Journalenhet(),
        perm_filter='can_add_ephorte_role')

    def ephorte_add_role(self, operator,
                         person_id, role, sko, arkivdel, journalenhet):
        not_ephorte_ou = False
        if not self.ba.can_add_ephorte_role(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ou = self._get_ou(stedkode=sko)
        if not ou.has_spread(self.const.spread_ephorte_ou):
            not_ephorte_ou = True
        extra_msg = ""
        if not person.has_spread(self.const.spread_ephorte_person):
            person.add_spread(self.const.spread_ephorte_person)
            extra_msg = " (implicitly added ephorte-spread)"

        arkivdel = self._get_arkivdel(arkivdel)
        journalenhet = self._get_journalenhet(journalenhet)
        role = self._get_role_type(role)
        self.ephorte_role.add_role(person.entity_id, role,
                                   ou.entity_id, arkivdel, journalenhet,
                                   auto_role='F')
        if not_ephorte_ou:
            return ("Warning: Added %s role for %s%s to a"
                    " non-archive OU %r") % (text_type(role), person_id,
                                             extra_msg, sko)
        return "OK, added %s role for %s%s" % (text_type(role), person_id,
                                               extra_msg)

    #
    # ephorte remove_role
    #
    all_commands['ephorte_remove_role'] = cmd_param.Command(
        ("ephorte", "remove_role"),
        cmd_param.PersonId(),
        Rolle(),
        cmd_param.OU(),
        Arkivdel(),
        Journalenhet(),
        perm_filter='can_remove_ephorte_role')

    def ephorte_remove_role(self, operator,
                            person_id, role, sko, arkivdel, journalenhet):
        if not self.ba.can_remove_ephorte_role(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ou = self._get_ou(stedkode=sko)
        arkivdel = self._get_arkivdel(arkivdel)
        journalenhet = self._get_journalenhet(journalenhet)
        role = self._get_role_type(role)
        # Check that the person has the given role.
        if not self.ephorte_role.get_role(
                person.entity_id,
                role,
                ou.entity_id,
                arkivdel,
                journalenhet):
            raise CerebrumError("Person has no such role")
        # Check if role is a standard role
        _list_roles = self.ephorte_role.list_roles
        if (self.ephorte_role.is_standard_role(person.entity_id,
                                               role,
                                               ou.entity_id,
                                               arkivdel,
                                               journalenhet)
                and len(_list_roles(person_id=person.entity_id)) > 1):
            raise CerebrumError("Cannot delete standard role.")
        self.ephorte_role.remove_role(person.entity_id,
                                      role,
                                      ou.entity_id,
                                      arkivdel,
                                      journalenhet)
        return "OK, removed %s role for %s" % (text_type(role), person_id)

    #
    # ephorte list_roles
    #
    all_commands['ephorte_list_roles'] = cmd_param.Command(
        ("ephorte", "list_roles"),
        cmd_param.PersonId(),
        perm_filter='can_list_ephorte_roles',
        fs=cmd_param.FormatSuggestion(
            "%-5s %-30s %-15s %-13s %s",
            ('role', 'adm_enhet', 'arkivdel', 'journalenhet', 'std_role'),
            hdr="%-5s %-30s %-15s %-13s %s" % (
                "Rolle", "Adm enhet", "Arkivdel", "Journalenhet",
                "Standardrolle")
        ))

    def ephorte_list_roles(self, operator, person_id):
        if not self.ba.can_list_ephorte_roles(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ret = []

        def to_text(getter, value):
            if not value:
                return ''
            return text_type(getter(value))

        for row in self.ephorte_role.list_roles(person_id=person.entity_id):
            ou = self._get_ou(ou_id=row['adm_enhet'])
            ret.append({
                'role': to_text(self._get_role_type, row['role_type']),
                'adm_enhet': self._format_ou_name(ou),
                'arkivdel': to_text(self._get_arkivdel, row['arkivdel']),
                'journalenhet': to_text(self._get_journalenhet,
                                        row['journalenhet']),
                'std_role': row['standard_role'] or '',
            })
        return ret

    #
    # ephorte set_standard_role
    #
    all_commands['ephorte_set_standard_role'] = cmd_param.Command(
        ("ephorte", "set_standard_role"),
        cmd_param.PersonId(),
        Rolle(),
        cmd_param.OU(),
        Arkivdel(),
        Journalenhet(),
        perm_filter='can_add_ephorte_role')

    def ephorte_set_standard_role(self, operator, person_id, role, sko,
                                  arkivdel, journalenhet):
        """
        Set given role as standard role.

        There can be only one standard role, thus if another role is
        marked as standard role, it will no longer be the persons
        standard role.
        """
        # Check operators permissions
        if not self.ba.can_add_ephorte_role(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ou = self._get_ou(stedkode=sko)
        # Check that the person has the given role.
        tmp = self.ephorte_role.get_role(person.entity_id,
                                         self._get_role_type(role),
                                         ou.entity_id,
                                         self._get_arkivdel(arkivdel),
                                         self._get_journalenhet(journalenhet))
        # Some sanity checks
        if not tmp:
            raise CerebrumError("Person has no such role")
        elif len(tmp) > 1:
            raise Errors.TooManyRowsError("Unexpectedly found more than one"
                                          " role")
        new_std_role = tmp[0]
        if new_std_role['standard_role'] == 'T':
            return "Role is already standard role"
        # There can be only one standard role
        for row in self.ephorte_role.list_roles(person_id=person.entity_id):
            if row['standard_role'] == 'T':
                self.logger.debug("Unset role %s at %s as standard_role" % (
                    row['role_type'], row['adm_enhet']))
                self.ephorte_role.set_standard_role_val(person.entity_id,
                                                        row['role_type'],
                                                        row['adm_enhet'],
                                                        row['arkivdel'],
                                                        row['journalenhet'],
                                                        'F')
        # Finally set the new standard role
        self.ephorte_role.set_standard_role_val(
            person.entity_id,
            self._get_role_type(role),
            ou.entity_id,
            self._get_arkivdel(arkivdel),
            self._get_journalenhet(journalenhet),
            'T')
        return "OK, set new standard role"

    def _lookup_perm_tr(self, tilgang):
        key = text_type(tilgang)
        res = (cereconf.EPHORTE_NEW2OLD_PERMISSIONS.get(key, None) or
               cereconf.EPHORTE_OLD2NEW_PERMISSIONS.get(key, None))
        if res:
            return self._get_tilgang(res)
        return None

    #
    # Add, remove or list auth. permissions ("tilgangskoder") for ePhorte
    # TBD: we should consider supporting permissions starting in the future
    #
    all_commands['ephorte_add_perm'] = cmd_param.Command(
        ("ephorte", "add_perm"),
        cmd_param.PersonId(),
        Tilgang(),
        cmd_param.OU(repeat=True),
        perm_filter='can_add_ephorte_perm')

    def ephorte_add_perm(self, operator, person_id, tilgang, sko):
        if not self.ba.can_add_ephorte_perm(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        operator_id = operator.get_entity_id()
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        if not person.has_spread(self.const.spread_ephorte_person):
            raise CerebrumError("Person has no ephorte roles")
        ou = self._get_ou(stedkode=sko)
        if not (sko == cereconf.EPHORTE_EGNE_SAKER_SKO or
                ou.has_spread(self.const.spread_ephorte_ou)):
            raise CerebrumError("Cannot assign permission to a non-ephorte OU")

        if self.ephorte_perm.has_permission(person.entity_id,
                                            self._get_tilgang(tilgang),
                                            ou.entity_id):
            raise CerebrumError("Person %r already has perm %r"
                                " (remove first)" % (person_id, tilgang))

        tilgang = self._get_tilgang(tilgang)
        # This is a hack needed by the archivists.
        # If one of the new permissions, defined in
        # EPHORTE_NEW2OLD_PERMISSIONS.values() is to be added, the old
        # (expired) one must be added to. And vice versa.
        corresponding_perm = self._lookup_perm_tr(tilgang)
        if corresponding_perm:
            # Add the corresponding permission
            self.ephorte_perm.add_permission(
                person.entity_id,
                corresponding_perm,
                ou.entity_id,
                operator_id)
            ret_msg_suffix = " Also added 'tilgang' %s" % (
                text_type(corresponding_perm))
        else:
            ret_msg_suffix = ""
        # Add new permission
        self.ephorte_perm.add_permission(person.entity_id,
                                         tilgang,
                                         ou.entity_id,
                                         operator_id)
        return "OK, added 'tilgang' %s for %s.%s" % (text_type(tilgang),
                                                     person_id,
                                                     ret_msg_suffix)

    #
    # ephorte remove_perm
    #
    all_commands['ephorte_remove_perm'] = cmd_param.Command(
        ("ephorte", "remove_perm"),
        cmd_param.PersonId(),
        Tilgang(),
        cmd_param.OU(repeat=True),
        perm_filter='can_remove_ephorte_perm')

    def ephorte_remove_perm(self, operator, person_id, tilgang, sko):
        if not self.ba.can_remove_ephorte_perm(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ou = self._get_ou(stedkode=sko)
        # This is a hack needed by the archivists.
        # If one of the new permissions, defined in
        # EPHORTE_NEW2OLD_PERMISSIONS.values() is to be added, the old
        # (expired) one must be added to. And vice versa.
        tilgang = self._get_tilgang(tilgang)

        corresponding_perm = self._lookup_perm_tr(tilgang)
        if corresponding_perm:
            # Remove old permission
            self.ephorte_perm.remove_permission(
                person.entity_id,
                corresponding_perm,
                ou.entity_id)
            ret_msg_suffix = " Also removed 'tilgang' %s" % (
                text_type(corresponding_perm))
        else:
            ret_msg_suffix = ""
        # Remove new permission
        self.ephorte_perm.remove_permission(person.entity_id,
                                            tilgang,
                                            ou.entity_id)
        return "OK, removed 'tilgang' %s for %s.%s" % (text_type(tilgang),
                                                       person_id,
                                                       ret_msg_suffix)

    #
    # ephorte list_perm
    #
    all_commands['ephorte_list_perm'] = cmd_param.Command(
        ("ephorte", "list_perm"),
        cmd_param.PersonId(),
        perm_filter='can_list_ephorte_perm',
        fs=cmd_param.FormatSuggestion(
            "%-10s %-34s %-18s %-10s",
            ('tilgang', 'adm_enhet', 'requestee', 'end_date'),
            hdr="%-10s %-34s %-18s %-10s" % (
                "Tilgang", "Adm.enhet", "Tildelt av", "Sluttdato")
        ))

    def ephorte_list_perm(self, operator, person_id):
        if not self.ba.can_list_ephorte_perm(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        ret = []
        for row in self.ephorte_perm.list_permission(
                person_id=person.entity_id):
            ou = self._get_ou(ou_id=row['adm_enhet'])
            requestee = self.util.get_target(int(row['requestee_id']))
            if row['end_date']:
                end_date = row['end_date'].date
            else:
                end_date = ''
            ret.append({
                'tilgang': text_type(self._get_tilgang(row['perm_type'])),
                'adm_enhet': self._format_ou_name(ou),
                'requestee': requestee.get_names()[0][0],
                'end_date': end_date,
            })
        return ret

    #
    # ephorte history
    #
    all_commands['ephorte_history'] = cmd_param.Command(
        ("ephorte", "history"),
        cmd_param.PersonId(),
        cmd_param.Integer(optional=True, help_ref="limit_number_of_results"),
        fs=cmd_param.FormatSuggestion(
            "%s [%s]: %s", ("timestamp", "change_by", "message")),
        # perm_filter='can_add_ephorte_perm')
        # Only available for superusers for now.
        perm_filter='is_superuser')

    def ephorte_history(self, operator, person_id, limit=100):
        if not self.ba.can_list_ephorte_perm(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to ephorte admins")
        try:
            person = self.util.get_target(person_id, restrict_to=['Person'])
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")

        try:
            limit = int(limit)
        except ValueError:
            raise CerebrumError("Limit must be a number")

        # Only certain tyeps of changes are relevant for ephorte history
        types = ["ephorte_role_add", "ephorte_role_upd", "ephorte_role_rem",
                 "ephorte_perm_add", "ephorte_perm_rem", "person_aff_add",
                 "person_aff_del", "person_aff_mod", "person_aff_src_add",
                 "person_aff_src_del", "person_aff_src_mod", "person_create"]
        ret = []

        rows = list(self.db.get_log_events(0,
                                           subject_entity=person.entity_id,
                                           types=[getattr(self.clconst, t)
                                                  for t in types]))
        for r in rows[-limit:]:
            ret.append(self._format_changelog_entry(r))
        return ret
Exemplo n.º 5
0
            ret.append(
                {
                    'regformkode': row['regformkode'],
                    'betformkode': row['betformkode'],
                    'dato_endring': self._convert_ticks_to_timestamp(
                        row['dato_endring']),
                    'dato_regform_endret': self._convert_ticks_to_timestamp(
                        row['dato_regform_endret']),
                }
            )
        return ret

    #
    # user delete
    #
    all_commands['user_delete'] = cmd_param.Command(
        ("user", "delete"),
        cmd_param.AccountName(),
        perm_filter='can_delete_user')

    def user_delete(self, operator, accountname):
        account = self._get_account(accountname)
        self.ba.can_delete_user(operator.get_entity_id(), account)
        if account.is_deleted():
            raise CerebrumError("User is already deleted")
        account.expire_date = mx.DateTime.now()
        for s in account.get_spread():
            account.delete_spread(int(s['spread']))
        account.write_db()
        return "User %s queued for deletion immediately" % account.account_name
Exemplo n.º 6
0
class BofhdExtension(BofhdCommonMethods):

    OU_class = Utils.Factory.get('OU')
    Account_class = Factory.get('Account')
    Group_class = Factory.get('Group')

    external_id_mappings = {}
    all_commands = {}
    hidden_commands = {}
    parent_commands = True
    authz = BofhdAuth

    def __init__(self, *args, **kwargs):
        super(BofhdExtension, self).__init__(*args, **kwargs)
        self.external_id_mappings['fnr'] = self.const.externalid_fodselsnr

    @classmethod
    def get_help_strings(cls):
        return bofhd_core_help.get_help_strings()

    #
    # misc check_password
    #
    all_commands['misc_check_password'] = cmd_param.Command(
        ("misc", "check_password"), cmd_param.AccountPassword())

    def misc_check_password(self, operator, password):
        ac = self.Account_class(self.db)
        try:
            check_password(password, ac, structured=False)
        except RigidPasswordNotGoodEnough as e:
            raise CerebrumError('Bad password: {err_msg}'.format(
                err_msg=str(e).decode('utf-8').encode('latin-1')))
        except PhrasePasswordNotGoodEnough as e:
            raise CerebrumError('Bad passphrase: {err_msg}'.format(
                err_msg=str(e).decode('utf-8').encode('latin-1')))
        except PasswordNotGoodEnough as e:
            raise CerebrumError('Bad password: {err_msg}'.format(err_msg=e))
        crypt = ac.encrypt_password(self.const.Authentication("crypt3-DES"),
                                    password)
        md5 = ac.encrypt_password(self.const.Authentication("MD5-crypt"),
                                  password)
        sha256 = ac.encrypt_password(self.const.auth_type_sha256_crypt,
                                     password)
        sha512 = ac.encrypt_password(self.const.auth_type_sha512_crypt,
                                     password)
        return ("OK.\n  crypt3-DES:   %s\n  MD5-crypt:    %s\n"
                "  SHA256-crypt: %s\n  SHA512-crypt: %s") % (crypt, md5,
                                                             sha256, sha512)

    def _person_affiliation_add_helper(self, operator, person, ou, aff,
                                       aff_status):
        """Helper-function for adding an affiliation to a person with
        permission checking.  person is expected to be a person
        object, while ou, aff and aff_status should be the textual
        representation from the client"""
        aff = self._get_affiliationid(aff)
        aff_status = self._get_affiliation_statusid(aff, aff_status)
        ou = self._get_ou(stedkode=ou)

        # Assert that the person already have the affiliation
        has_aff = False
        for a in person.get_affiliations():
            if a['ou_id'] == ou.entity_id and a['affiliation'] == aff:
                if a['status'] == aff_status:
                    has_aff = True
                elif a['source_system'] == self.const.system_manual:
                    raise CerebrumError("Person has conflicting aff_status "
                                        "for this OU/affiliation combination")
        if not has_aff:
            self.ba.can_add_affiliation(operator.get_entity_id(), person, ou,
                                        aff, aff_status)
            person.add_affiliation(ou.entity_id, aff, self.const.system_manual,
                                   aff_status)
            person.write_db()
        return ou, aff, aff_status

    #
    # access list_alterable [group/maildom/host/disk] [username]
    # This command is for listing out what groups an account is a moderator of
    # in Brukerinfo.
    #
    hidden_commands['access_list_alterable'] = cmd_param.Command(
        ('access', 'list_alterable'),
        cmd_param.SimpleString(optional=True),
        cmd_param.AccountName(optional=True),
        fs=cmd_param.FormatSuggestion(
            "%10d %15s     %s", ("entity_id", "entity_type", "entity_name")))

    def access_list_alterable(self,
                              operator,
                              target_type='group',
                              access_holder=None):
        """List entities that access_holder can moderate."""

        if access_holder is None:
            account_id = operator.get_entity_id()
        else:
            account = self._get_account(access_holder, actype="PosixUser")
            account_id = account.entity_id

        if not (account_id == operator.get_entity_id()
                or self.ba.is_superuser(operator.get_entity_id())):
            raise PermissionDenied(
                "You do not have permission for this operation")

        result = list()
        matches = self.ba.list_alterable_entities(account_id, target_type)
        if len(matches) > cereconf.BOFHD_MAX_MATCHES_ACCESS:
            raise CerebrumError(
                "More than %d (%d) matches. Refusing to return"
                " result" % (cereconf.BOFHD_MAX_MATCHES_ACCESS, len(matches)))
        for row in matches:
            entity = self._get_entity(ident=row["entity_id"])
            etype = str(self.const.EntityType(entity.entity_type))
            ename = self._get_entity_name(entity.entity_id, entity.entity_type)
            tmp = {
                "entity_id": row["entity_id"],
                "entity_type": etype,
                "entity_name": ename,
            }
            if entity.entity_type == self.const.entity_group:
                tmp["description"] = entity.description

            result.append(tmp)
        return result

    #
    # get_constant_description <const class> [?]
    #
    hidden_commands['get_constant_description'] = cmd_param.Command(
        ("misc", "get_constant_description"),
        cmd_param.SimpleString(),  # constant class
        cmd_param.SimpleString(optional=True),
        fs=cmd_param.FormatSuggestion("%-15s %s", ("code_str", "description")))

    def get_constant_description(self, operator, code_cls, code_str=None):
        """Fetch constant descriptions.

        There are no permissions checks for this method -- it can be called by
        anyone without any restrictions.

        @type code_cls: basestring
        @param code_cls:
          Class (name) for the constants to fetch.

        @type code_str: basestring or None
        @param code_str:
          code_str for the specific constant to fetch. If None is specified,
          *all* constants of the given type are retrieved.

        @rtype: dict or a sequence of dicts
        @return:
          Description of the specified constants. Each dict has 'code' and
          'description' keys.
        """

        if not hasattr(self.const, code_cls):
            raise CerebrumError("%s is not a constant type" % code_cls)

        kls = getattr(self.const, code_cls)
        if not issubclass(kls, self.const.CerebrumCode):
            raise CerebrumError("%s is not a valid constant class" % code_cls)

        if code_str is not None:
            c = self._get_constant(kls, code_str)
            return {
                "code": int(c),
                "code_str": str(c),
                "description": c.description
            }

        # Fetch all of the constants of the specified type
        return [{
            "code": int(x),
            "code_str": str(x),
            "description": x.description
        } for x in self.const.fetch_constants(kls)]

    def _person_create_externalid_helper(self, person):
        person.affect_external_id(self.const.system_manual,
                                  self.const.externalid_fodselsnr)

    #
    # email info [username]
    #
    all_commands['email_info'] = cmd_param.Command(
        ("email", "info"),
        cmd_param.AccountName(help_ref="account_name", repeat=True),
        perm_filter='can_email_info',
        fs=cmd_param.FormatSuggestion([
            ("Type:             %s", ("target_type", )),
            ("Account:          %s", ("account", )),
            ("Primary address:  %s", ("def_addr", )),
        ]))

    def email_info(self, operator, uname):
        """ email info for an account. """
        acc = self._get_account(uname)
        ret = []
        ret += [
            {
                'target_type': "Account",
            },
        ]
        ret.append({'def_addr': acc.get_primary_mailaddress()})
        return ret
Exemplo n.º 7
0
class BofhdExtension(BofhdEmailMixin, BofhdCommandBase):

    OU_class = Utils.Factory.get('OU')

    all_commands = {}
    authz = BofhdAuth

    @classmethod
    def get_help_strings(cls):
        return (bofhd_hiof_help.group_help, bofhd_hiof_help.command_help,
                bofhd_hiof_help.arg_help)

    def _email_info_basic(self, acc, et):
        """ Basic email info. """
        info = {}
        data = [
            info,
        ]
        if et.email_target_alias is not None:
            info['alias_value'] = et.email_target_alias
        info["account"] = acc.account_name
        if et.email_server_id:
            es = Email.EmailServer(self.db)
            es.find(et.email_server_id)
            info["server"] = es.name
            info["server_type"] = "N/A"
        else:
            info["server"] = "<none>"
            info["server_type"] = "N/A"
        return data

    def _email_info_detail(self, acc):
        """ Get quotas from Cerebrum, and usage from Cyrus. """
        # NOTE: Very similar to ofk/giske and uio

        info = []
        eq = Email.EmailQuota(self.db)

        # Get quota and usage
        try:
            eq.find_by_target_entity(acc.entity_id)
            et = Email.EmailTarget(self.db)
            et.find_by_target_entity(acc.entity_id)
            es = Email.EmailServer(self.db)
            es.find(et.email_server_id)

            if es.email_server_type == self.const.email_server_type_cyrus:
                used = 'N/A'
                limit = None
                pw = self.db._read_password(cereconf.CYRUS_HOST,
                                            cereconf.CYRUS_ADMIN)
                try:
                    cyrus = imaplib.IMAP4(es.name)
                    # IVR 2007-08-29 If the server is too busy, we do not want
                    # to lock the entire bofhd.
                    # 5 seconds should be enough
                    cyrus.socket().settimeout(5)
                    cyrus.login(cereconf.CYRUS_ADMIN, pw)
                    res, quotas = cyrus.getquota("user." + acc.account_name)
                    cyrus.socket().settimeout(None)
                    if res == "OK":
                        for line in quotas:
                            try:
                                folder, qtype, qused, qlimit = line.split()
                                if qtype == "(STORAGE":
                                    used = str(int(qused) / 1024)
                                    limit = int(qlimit.rstrip(")")) / 1024
                            except ValueError:
                                # line.split fails e.g. because quota isn't set
                                # on server
                                folder, junk = line.split()
                                self.logger.warning(
                                    "No IMAP quota set for '%s'" %
                                    acc.account_name)
                                used = "N/A"
                                limit = None
                except (TimeoutException, socket.error):
                    used = 'DOWN'
                except ConnectException as e:
                    used = str(e)
                info.append({
                    'quota_hard': eq.email_quota_hard,
                    'quota_soft': eq.email_quota_soft,
                    'quota_used': used
                })
                if limit is not None and limit != eq.email_quota_hard:
                    info.append({'quota_server': limit})
            else:
                # Just get quotas
                info.append({
                    'dis_quota_hard': eq.email_quota_hard,
                    'dis_quota_soft': eq.email_quota_soft
                })
        except Errors.NotFoundError:
            pass
        return info

    #
    # email replace_server [username] [servername]
    #
    all_commands['email_replace_server'] = cmd_param.Command(
        ('email', 'replace_server'),
        cmd_param.AccountName(help_ref='account_name'),
        cmd_param.SimpleString(),
        fs=cmd_param.FormatSuggestion("Ok, new email server: %s",
                                      ('new_server', )),
        perm_filter='can_email_address_add')

    def email_replace_server(self, operator, user, server_name):
        """ Replace the server for an email target. """
        if not self.ba.is_postmaster(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")
        et = self._get_email_target_for_account(user)
        es = Email.EmailServer(self.db)
        es.clear()
        try:
            es.find_by_name(server_name)
        except Errors.NotFoundError:
            raise CerebrumError("No such server: '%s'" % server_name)
        if et.email_server_id != es.entity_id:
            et.email_server_id = es.entity_id
            try:
                et.write_db()
            except self.db.DatabaseError, m:
                raise CerebrumError("Database error: %s" % m)
        else:
Exemplo n.º 8
0
class BofhdUserCreateMethod(BofhdCommonMethods):
    """Class with 'user create' method that is used by most, 'normal' instances.

    Instances that requires some special care for some methods could subclass
    those in their own institution-specific class
    (modules.no.<inst>.bofhd_<inst>_cmds.py:BofhdExtension).

    The methods are using the BofhdAuth that is defined in the institution's
    subclass - L{BofhdExtension.authz}.

    """

    all_commands = {}
    authz = BofhdAuth

    @classmethod
    def get_help_strings(cls):
        group, _, args = get_help_strings()
        # merge help strings will strip away unused groups
        return merge_help_strings((group, {}, args), ({}, CMD_HELP, {}))

    def _user_create_prompt_func_helper(self, session, *args):
        """A prompt_func on the command level should return
        {'prompt': message_string, 'map': dict_mapping}
        - prompt is simply shown.
        - map (optional) maps the user-entered value to a value that
          is returned to the server, typically when user selects from
          a list."""
        all_args = list(args[:])

        if not all_args:
            return {
                'prompt': "Person identification",
                'help_ref': "user_create_person_id"
            }
        arg = all_args.pop(0)
        if arg.startswith("group:"):
            group_owner = True
        else:
            group_owner = False
        if not all_args or group_owner:
            if group_owner:
                group = self._get_group(arg.split(":")[1])
                if all_args:
                    all_args.insert(0, group.entity_id)
                else:
                    all_args = [group.entity_id]
            else:
                c = self._find_persons(arg)
                map = [(("%-8s %s", "Id", "Name"), None)]
                for i in range(len(c)):
                    person = self._get_person("entity_id", c[i]['person_id'])
                    map.append((("%8i %s", int(c[i]['person_id']),
                                 person.get_name(self.const.system_cached,
                                                 self.const.name_full)),
                                int(c[i]['person_id'])))
                if not len(map) > 1:
                    raise CerebrumError("No persons matched")
                return {
                    'prompt': "Choose person from list",
                    'map': map,
                    'help_ref': 'user_create_select_person'
                }
        owner_id = all_args.pop(0)
        if not group_owner:
            person = self._get_person("entity_id", owner_id)
            existing_accounts = []
            account = self.Account_class(self.db)
            for r in account.list_accounts_by_owner_id(person.entity_id):
                account = self._get_account(r['account_id'], idtype='id')
                if account.expire_date:
                    exp = account.expire_date.strftime('%Y-%m-%d')
                else:
                    exp = '<not set>'
                existing_accounts.append("%-10s %s" %
                                         (account.account_name, exp))
            if existing_accounts:
                existing_accounts = "Existing accounts:\n%-10s %s\n%s\n" % (
                    "uname", "expire", "\n".join(existing_accounts))
            else:
                existing_accounts = ''
            if existing_accounts:
                if not all_args:
                    return {'prompt': "%sContinue? (y/n)" % existing_accounts}
                yes_no = all_args.pop(0)
                if not yes_no == 'y':
                    raise CerebrumError("Command aborted at user request")
            if not all_args:
                map = [(("%-8s %s", "Num", "Affiliation"), None)]
                for aff in person.get_affiliations():
                    ou = self._get_ou(ou_id=aff['ou_id'])
                    name = "%s@%s" % (six.text_type(
                        self.const.PersonAffStatus(
                            aff['status'])), self._format_ou_name(ou))
                    map.append((("%s", name), {
                        'ou_id': int(aff['ou_id']),
                        'aff': int(aff['affiliation'])
                    }))
                if not len(map) > 1:
                    raise CerebrumError("Person has no affiliations. "
                                        "Try person affiliation_add")
                return {'prompt': "Choose affiliation from list", 'map': map}
            all_args.pop(0)  # Affiliation
        else:
            if not all_args:
                return {
                    'prompt': "Enter np_type",
                    'help_ref': 'string_np_type'
                }
            all_args.pop(0)  # np_type
        if not all_args:
            ret = {'prompt': "Username", 'last_arg': True}
            posix_user = Factory.get('PosixUser')(self.db)
            if not group_owner:
                try:
                    person = self._get_person("entity_id", owner_id)
                    sugg = posix_user.suggest_unames(person)
                    if sugg:
                        ret['default'] = sugg[0]
                except ValueError:
                    pass  # Failed to generate a default username
            return ret
        if len(all_args) == 1:
            return {'last_arg': True}
        raise CerebrumError("Too many arguments")

    def _user_create_set_account_type(self,
                                      account,
                                      owner_id,
                                      ou_id,
                                      affiliation,
                                      priority=None):
        person = self._get_person('entity_id', owner_id)
        try:
            affiliation = self.const.PersonAffiliation(affiliation)
            # make sure exist
            int(affiliation)
        except Errors.NotFoundError:
            raise CerebrumError("Invalid affiliation %s" %
                                six.text_type(affiliation))
        for aff in person.get_affiliations():
            if aff['ou_id'] == ou_id and aff['affiliation'] == affiliation:
                break
        else:
            raise CerebrumError("Owner did not have any affiliation %s" %
                                six.text_type(affiliation))
        account.set_account_type(ou_id, affiliation, priority=priority)

    def _user_create_basic(self, operator, owner, uname, np_type=None):
        account_policy = AccountPolicy(self.db)
        try:
            return account_policy.create_basic_account(
                operator.get_entity_id(), owner, uname, np_type=np_type)
        except InvalidAccountCreationArgument as e:
            raise CerebrumError(e)

    def _user_password(self, operator, account, passwd=None):
        """Set new (random) password for account and store in bofhd session"""
        if not passwd:
            passwd = account.make_passwd(account.account_name)
        account.set_password(passwd)
        try:
            account.write_db()
        except self.db.DatabaseError as m:
            raise CerebrumError('Database error: %s' % m)
        operator.store_state('new_account_passwd', {
            'account_id': int(account.entity_id),
            'password': passwd
        })

    #
    # user create ---
    #
    all_commands['user_create'] = cmd.Command(
        ('user', 'create'),
        prompt_func=_user_create_prompt_func_helper,
        fs=cmd.FormatSuggestion("Created account_id=%i", ("account_id", )),
        perm_filter='is_superuser')

    def user_create(self, operator, *args):
        np_type = None
        affiliation = None

        if args[0].startswith('group:'):
            group_id, np_type, uname = args
            owner = self._get_group(group_id.split(":")[1])
            np_type = self._get_constant(self.const.Account, np_type,
                                         "account type")
        else:
            if len(args) == 4:
                idtype, person_id, affiliation, uname = args
            else:
                idtype, person_id, yes_no, affiliation, uname = args
            owner = self._get_person("entity_id", person_id)

        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("only superusers may reserve users")
        account = self._user_create_basic(operator, owner, uname, np_type)
        self._user_password(operator, account)
        for spread in cereconf.BOFHD_NEW_USER_SPREADS:
            account.add_spread(self.const.Spread(spread))
        try:
            account.write_db()
            if affiliation is not None:
                ou_id, affiliation = affiliation['ou_id'], affiliation['aff']
                self._user_create_set_account_type(account, owner.entity_id,
                                                   ou_id, affiliation)
        except self.db.DatabaseError as m:
            raise CerebrumError("Database error: %s" % m)
        return {'account_id': int(account.entity_id)}
Exemplo n.º 9
0
        except self.db.DatabaseError, m:
            raise CerebrumError, "Database error: %s" % m
        return "OK, removed '%s' from '%s'" % (member_name, group.group_name)

    #
    # group info
    #
    all_commands['group_info'] = cmd_param.Command(
        ("group", "info"),
        cmd_param.GroupName(help_ref="id:gid:name"),
        fs=cmd_param.FormatSuggestion(
            [("Name:         %s\n"
              "Spreads:      %s\n"
              "Description:  %s\n"
              "Expire:       %s\n"
              "Entity id:    %i",
              ("name",
               "spread",
               "description",
               format_day("expire_date"),
               "entity_id")),
             ("Moderator:    %s %s (%s)", ('owner_type', 'owner', 'opset')),
             ("Gid:          %i", ('gid',)),
             ("Members:      %s", ("members",))]))

    def group_info(self, operator, groupname):
        grp = self._get_group(groupname)
        co = self.const
        ret = [ self._entity_info(grp) ]
        # find owners
        aot = BofhdAuthOpTarget(self.db)
Exemplo n.º 10
0
class BofhdExtension(BofhdCommonMethods):

    all_commands = {}
    parent_commands = False
    authz = BofhdAuth

    @property
    def bgu(self):
        try:
            return self.__guest_utils
        except AttributeError:
            self.__guest_utils = GuestUtils(self.db, self.logger)
            return self.__guest_utils

    @classmethod
    def get_help_strings(cls):
        group_help = {}

        # The texts in command_help are automatically line-wrapped, and should
        # not contain \n
        command_help = {
            'user': {
                'user_create_guest': cls.user_create_guest.__doc__,
                'user_request_guest': cls.user_request_guest.__doc__,
                'user_release_guest': cls.user_release_guest.__doc__,
                'user_guests': cls.user_guests.__doc__,
                'user_guests_status': cls.user_guests_status.__doc__,
            }
        }

        arg_help = {
            'release_type': [
                'release_type',
                'Enter release type',
                "Enter a guest user name or a range of names, e.g.,"
                " guest007 or guest010-040.",
            ],
            'guest_owner_group': [
                'guest_owner_group',
                'Enter name of owner group',
            ],
            'nr_guests': [
                'nr_guests',
                'Enter number of guests',
            ],
            'comment': [
                'comment',
                'Enter comment',
            ]
        }
        return (group_help, command_help, arg_help)

    def user_create_guest_prompt_func(self, session, *args):
        all_args = list(args[:])
        # get number of new guest users
        if not all_args:
            return {'prompt': 'How many guest users?', 'default': '1'}
        try:
            int(all_args[0])
            all_args.pop(0)
        except ValueError:
            raise CerebrumError("Not a number: %s" % all_args[0])
        # Get group name.
        if not all_args:
            return {
                'prompt': 'Enter owner group name',
                'default': cereconf.GUESTS_OWNER_GROUP
            }
        # get default file_group
        owner_group_name = all_args.pop(0)
        if not all_args:
            return {
                'prompt': "Default filegroup",
                'default': cereconf.GUESTS_DEFAULT_FILEGROUP
            }
        filegroup = all_args.pop(0)
        # get shell
        if not all_args:
            return {'prompt': "Shell", 'default': 'bash'}
        shell = all_args.pop(0)
        # get disk
        if not all_args:
            return {'prompt': "Disk", 'help_ref': 'disk'}
        disk = all_args.pop(0)
        # get prefix
        if not all_args:
            return {
                'prompt': "Name prefix",
                'default': cereconf.GUESTS_PREFIX[-1],
                'last_arg': True
            }
        return {'last_arg': True}

    #
    # user create_guest <nr> <group_id> <filegroup> <shell> <disk>
    #
    all_commands['user_create_guest'] = cmd_param.Command(
        ('user', 'create_guest'),
        prompt_func=user_create_guest_prompt_func,
        perm_filter='can_create_guests')

    def user_create_guest(self, operator, *args):
        """ Create a number of guest users for a certain time. """
        nr, owner_group_name, filegroup, shell, home, prefix = args
        owner_type = self.const.entity_group
        owner_id = self.util.get_target(owner_group_name,
                                        default_lookup="group").entity_id
        np_type = self.const.account_uio_guest
        group = self.util.get_target(filegroup, default_lookup="group")
        posix_user = Factory.get('PosixUser')(self.db)
        shell = self._get_shell(shell)
        disk_id, home = self._get_disk(home)[1:3]
        gecos = None
        expire_date = None
        self.ba.can_create_user(operator.get_entity_id(), owner_id, disk_id)

        ret = []
        # Find the names of the next <nr> of guest users
        for uname in self.bgu.find_new_guestusernames(int(nr), prefix=prefix):
            posix_user.clear()
            # Create the guest_users
            uid = posix_user.get_free_uid()
            posix_user.populate(uid,
                                group.entity_id,
                                gecos,
                                shell,
                                name=uname,
                                owner_type=owner_type,
                                owner_id=owner_id,
                                np_type=np_type,
                                creator_id=operator.get_entity_id(),
                                expire_date=expire_date)
            try:
                posix_user.write_db()
                # For correct ordering of ChangeLog events, new users
                # should be signalled as "exported to" a certain system
                # before the new user's password is set.  Such systems are
                # flawed, and should be fixed.
                for spread in cereconf.GUESTS_USER_SPREADS:
                    posix_user.add_spread(self.const.Spread(spread))
                homedir_id = posix_user.set_homedir(
                    disk_id=disk_id,
                    home=home,
                    status=self.const.home_status_not_created)
                posix_user.set_home(self.const.spread_uio_nis_user, homedir_id)
            except self.db.DatabaseError, m:
                raise CerebrumError("Database error: %s" % m)
            self.bgu.update_group_memberships(posix_user.entity_id)
            posix_user.populate_trait(self.const.trait_uio_guest_owner,
                                      target_id=None)
            # The password must be set _after_ the trait, or else it
            # won't be stored using the 'PGP-guest_acc' method.
            posix_user.set_password(posix_user.make_passwd(uname))
            posix_user.write_db()
            ret.append(uname)
        return "OK, created guest_users:\n %s " % self._pretty_print(ret)
Exemplo n.º 11
0
class BofhdExtension(BofhdCommonMethods):

    OU_class = Utils.Factory.get('OU')
    Account_class = Factory.get('Account')
    Group_class = Factory.get('Group')

    all_commands = {}
    parent_commands = True
    authz = HiofAuth

    @property
    def ou(self):
        try:
            return self.__ou
        except AttributeError:
            self.__ou = Factory.get('OU')(self.db)
            return self.__ou

    @classmethod
    def get_help_strings(cls):
        return merge_help_strings(get_help_strings(), ({}, HELP_CMDS, {}))

    #
    # user remove_ad_attrs
    #
    all_commands['user_remove_ad_attrs'] = cmd_param.Command(
        ('user', 'remove_ad_attrs'),
        cmd_param.AccountName(),
        cmd_param.Spread(),
        perm_filter='is_superuser')

    def user_remove_ad_attrs(self, operator, uname, spread):
        """ Bofh command user remove_ad_attrs

        Delete AD attributes home, profile_path and ou for user by
        adding a BofhdRequests. The actual deletion is governed by
        job_runner when calling process_bofhd_requests. This is done
        to avvoid race condition with AD sync.

        @param operator: operator in bofh session
        @type  uname: string
        @param uname: user name of account which AD values should be
                      deleted
        @type  spread: string
        @param spread: code_str of spread
        @rtype: string
        @return: OK message if success
        """
        account = self._get_account(uname, idtype='name')
        spread = self._get_constant(self.const.Spread, spread)
        br = HiofBofhdRequests(self.db, self.const)
        delete_attrs = account.get_ad_attrs_by_spread(spread)
        if delete_attrs:
            br.add_request(operator.get_entity_id(),
                           br.now,
                           self.const.bofh_ad_attrs_remove,
                           account.entity_id,
                           None,
                           state_data=text_type(spread))
            return "OK, added remove AD attributes request for %s" % uname
        else:
            return "No AD attributes to remove for user %s" % uname

    #
    # user list_ad_attrs
    #
    all_commands['user_list_ad_attrs'] = cmd_param.Command(
        ('user', 'list_ad_attrs'),
        cmd_param.AccountName(),
        perm_filter='is_superuser',
        fs=cmd_param.FormatSuggestion(
            "%-16s %-16s %s", ("spread", "ad_attr", "ad_val"),
            hdr="%-16s %-16s %s" % ("Spread", "AD attribute", "Value")))

    def user_list_ad_attrs(self, operator, uname):
        """
        Bofh command user list_ad_attrs

        List all ad_traits for user

        @param operator: operator in bofh session
        @type  uname: string
        @param uname: user name of account which AD values should be
                      deleted
        @rtype: string
        @return: OK message if success
        """
        account = self._get_account(uname, idtype='name')
        ret = []
        for spread, attr_map in account.get_ad_attrs().iteritems():
            spread = self._get_constant(self.const.Spread, spread, 'spread')
            for attr_type, attr_val in attr_map.items():
                ret.append({
                    'spread': text_type(spread),
                    'ad_attr': attr_type,
                    'ad_val': attr_val,
                })
        return ret

    #
    # user create prompt
    #
    def user_create_prompt_func(self, session, *args):
        return self._user_create_prompt_func_helper('Account', session, *args)

    #
    # user create prompt helper
    #
    def _user_create_prompt_func_helper(self, ac_type, session, *args):
        """A prompt_func on the command level should return
        {'prompt': message_string, 'map': dict_mapping}
        - prompt is simply shown.
        - map (optional) maps the user-entered value to a value that
          is returned to the server, typically when user selects from
          a list."""
        all_args = list(args[:])

        if not all_args:
            return {
                'prompt': "Person identification",
                'help_ref': "user_create_person_id"
            }
        arg = all_args.pop(0)
        if arg.startswith("group:"):
            group_owner = True
        else:
            group_owner = False
        if not all_args or group_owner:
            if group_owner:
                group = self._get_group(arg.split(":")[1])
                if all_args:
                    all_args.insert(0, group.entity_id)
                else:
                    all_args = [group.entity_id]
            else:
                c = self._find_persons(arg)
                map = [(("%-8s %s", "Id", "Name"), None)]
                for i in range(len(c)):
                    person = self._get_person("entity_id", c[i]['person_id'])
                    map.append((("%8i %s", int(c[i]['person_id']),
                                 person.get_name(self.const.system_cached,
                                                 self.const.name_full)),
                                int(c[i]['person_id'])))
                if not len(map) > 1:
                    raise CerebrumError("No persons matched")
                return {
                    'prompt': "Choose person from list",
                    'map': map,
                    'help_ref': 'user_create_select_person'
                }
        owner_id = all_args.pop(0)
        if not group_owner:
            person = self._get_person("entity_id", owner_id)
            existing_accounts = []
            account = self.Account_class(self.db)
            for r in account.list_accounts_by_owner_id(person.entity_id):
                account = self._get_account(r['account_id'], idtype='id')
                if account.expire_date:
                    exp = account.expire_date.strftime('%Y-%m-%d')
                else:
                    exp = '<not set>'
                existing_accounts.append("%-10s %s" %
                                         (account.account_name, exp))
            if existing_accounts:
                existing_accounts = "Existing accounts:\n%-10s %s\n%s\n" % (
                    "uname", "expire", "\n".join(existing_accounts))
            else:
                existing_accounts = ''
            if existing_accounts:
                if not all_args:
                    return {'prompt': "%sContinue? (y/n)" % existing_accounts}
                yes_no = all_args.pop(0)
                if not yes_no == 'y':
                    raise CerebrumError("Command aborted at user request")
            if not all_args:
                map = [(("%-8s %s", "Num", "Affiliation"), None)]
                for aff in person.get_affiliations():
                    ou = self._get_ou(ou_id=aff['ou_id'])
                    name = "%s@%s" % (text_type(
                        self.const.PersonAffStatus(
                            aff['status'])), self._format_ou_name(ou))
                    map.append((("%s", name), {
                        'ou_id': int(aff['ou_id']),
                        'aff': int(aff['affiliation']),
                    }))
                if not len(map) > 1:
                    raise CerebrumError("Person has no affiliations."
                                        " Try person affiliation_add")
                return {'prompt': "Choose affiliation from list", 'map': map}
            all_args.pop(0)
        else:
            if not all_args:
                return {
                    'prompt': "Enter np_type",
                    'help_ref': 'string_np_type'
                }
            all_args.pop(0)
        if not all_args:
            return {'prompt': "Enter spread", 'help_ref': 'string_spread'}
        all_args.pop(0)
        if not all_args:
            return {
                'prompt': "Enter e-mail server name",
                'help_ref': 'string_email_host'
            }
        all_args.pop(0)
        if not all_args:
            ret = {'prompt': "Username", 'last_arg': True}
            if not group_owner:
                try:
                    person = self._get_person("entity_id", owner_id)
                    sugg = account.suggest_unames(person)
                    if sugg:
                        ret['default'] = sugg[0]
                except ValueError:
                    pass  # Failed to generate a default username
            return ret
        if len(all_args) == 1:
            return {'last_arg': True}
        raise CerebrumError("Too many arguments")

    #
    # user create
    #
    all_commands['user_create'] = cmd_param.Command(
        ('user', 'create'),
        prompt_func=user_create_prompt_func,
        fs=cmd_param.FormatSuggestion("Created account_id=%i",
                                      ("account_id", )),
        perm_filter='is_superuser')

    def user_create(self, operator, *args):
        if args[0].startswith('group:'):
            group_id, np_type, spread, email_server, uname = args
            owner_type = self.const.entity_group
            owner_id = self._get_group(group_id.split(":")[1]).entity_id
            np_type = self._get_constant(self.const.Account, np_type,
                                         "account type")
            affiliation = None
            owner_type = self.const.entity_group
        else:
            if len(args) == 6:
                (idtype, person_id, affiliation, spread, email_server,
                 uname) = args
            else:
                (idtype, person_id, yes_no, affiliation, spread, email_server,
                 uname) = args
            person = self._get_person("entity_id", person_id)
            owner_type, owner_id = self.const.entity_person, person.entity_id
            np_type = None
        account = self.Account_class(self.db)
        account.clear()
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("only superusers may reserve users")
        account.populate(
            uname,
            owner_type,  # Owner type
            owner_id,
            np_type,  # np_type
            operator.get_entity_id(),  # creator_id
            None)  # expire_date
        account.write_db()
        for s in cereconf.BOFHD_NEW_USER_SPREADS:
            account.add_spread(self.const.Spread(s))
        if spread:
            try:
                account.add_spread(self.const.Spread(spread))
            except Errors.NotFoundError:
                raise CerebrumError("No such spread: %r" % spread)
        account.write_db()
        try:
            account._update_email_server(email_server)
        except Errors.NotFoundError:
            raise CerebrumError("No such email server: %r" % email_server)
        passwd = account.make_passwd(uname)
        account.set_password(passwd)
        try:
            account.write_db()
            if affiliation is not None:
                ou_id, affiliation = affiliation['ou_id'], affiliation['aff']
                self._user_create_set_account_type(account, person.entity_id,
                                                   ou_id, affiliation)
        except self.db.DatabaseError as m:
            raise CerebrumError("Database error: %s" % m)
        operator.store_state("new_account_passwd", {
            'account_id': int(account.entity_id),
            'password': passwd
        })
        return {'account_id': int(account.entity_id)}
Exemplo n.º 12
0
            posix_user.populate_trait(self.const.trait_uio_guest_owner,
                                      target_id=None)
            # The password must be set _after_ the trait, or else it
            # won't be stored using the 'PGP-guest_acc' method.
            posix_user.set_password(posix_user.make_passwd(uname))
            posix_user.write_db()
            ret.append(uname)
        return "OK, created guest_users:\n %s " % self._pretty_print(ret)

    #
    # user request_guest <nr> <to_from> <entity_name>
    #
    all_commands['user_request_guest'] = cmd_param.Command(
        ('user', 'request_guest'),
        cmd_param.Integer(default="1", help_ref="nr_guests"),
        cmd_param.SimpleString(help_ref="string_from_to"),
        cmd_param.GroupName(help_ref="guest_owner_group"),
        cmd_param.SimpleString(help_ref="comment"),
        perm_filter='can_request_guests')

    def user_request_guest(self, operator, nr, date, groupname, comment):
        """ Request a number of guest users for a certain time. """
        # date checking
        start_date, end_date = self._parse_date_from_to(date)
        today = DateTime.today()
        if start_date < today:
            raise CerebrumError("Start date shouldn't be in the past")
        # end_date in allowed interval?
        if end_date < start_date:
            raise CerebrumError("End date can't be earlier than start_date")
        max_date = start_date + DateTime.RelativeDateTime(
Exemplo n.º 13
0
class BofhdExtension(BofhdCommandBase):

    """ Debug commands. """

    all_commands = {}
    authz = BofhdAuth

    MIN_SLEEP = 0
    MAX_SLEEP = 10

    @classmethod
    def get_help_strings(cls):
        group_help = {'debug': "Debug commands.", }

        # The texts in command_help are automatically line-wrapped, and should
        # not contain \n
        command_help = {
            'debug': {
                'debug_raise_cerebrum_error':
                    'Raise a Cerebrum.Errors.CerebrumError exception',
                'debug_raise_bofhd_cerebrum_error':
                    'Raise a bofhd.errors.CerebrumError exception',
                'debug_raise_exception_multiple_args':
                    'Raise an exception with multiple args',
                'debug_cause_integrity_error':
                    'Cause the database to raise an IntegrityError',
            }
        }

        arg_help = {
            'exc_strval': ['string', 'Enter a string',
                           'Enter a string value for the exception'],
            'exc_intval': ['integer', 'Enter an integer',
                           'Enter an integer value for the exception'],
            'wait_int': ['integer', 'How many seconds?',
                         'How many seconds should this command sleep for?'
                         ' (min={:d} max={:d}'.format(cls.MIN_SLEEP,
                                                      cls.MAX_SLEEP)],
        }
        return (group_help, command_help, arg_help)

    #
    # debug raise_cerebrum_error
    #
    all_commands['debug_raise_cerebrum_error'] = cmd_param.Command(
        ("debug", "raise_cerebrum_error"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
    )

    def debug_raise_cerebrum_error(self, operator, strval="Foo Bar"):
        """ Raise a generic Cerebrum.Errors.CerebrumError. """
        raise Errors.CerebrumError(strval)

    #
    # debug raise_bofhd_cerebrum_error
    #
    all_commands['debug_raise_bofhd_cerebrum_error'] = cmd_param.Command(
        ("debug", "raise_bofhd_cerebrum_error"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
    )

    def debug_raise_bofhd_cerebrum_error(self, operator, strval="Foo Bar"):
        """ Raise an bofhd.errors.CerebrumError. """
        raise CerebrumError(strval)

    #
    # debug raise_exception_multiple_args
    #
    all_commands['debug_raise_exception_multiple_args'] = cmd_param.Command(
        ("debug", "raise_exception_multiple_args"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
        cmd_param.Integer(help_ref='exc_intval', optional=True)
    )

    def debug_raise_exception_multiple_args(self, operator, strval="Foo Bar",
                                            intval=10):
        """ Raise an exception that takes multiple args.

        This is useful when we want to see what gets transferred to the client.

        """
        intval = int(intval)
        raise ExceptionMultipleArgs(strval, intval)

    #
    # debug cause_integrity_error
    #
    all_commands['debug_cause_integrity_error'] = cmd_param.Command(
        ("debug", "cause_integrity_error")
    )

    def debug_cause_integrity_error(self, operator):
        """ Cause the db-driver to raise an IntegrityError.

        This is done by adding an existing spread to the operator account.

        """
        op_acc = self._get_account(operator.get_entity_id(), idtype='id')

        try:
            maybe_spread = self.const.fetch_constants(None)[0]
            for _ in range(2):
                # Will cause IntegrityError because...
                #  - maybe_spread is not a spread
                #  - maybe_spread is not an account spread
                #  - maybe_spread is an account spread, and is added twice
                op_acc.add_spread(maybe_spread)
        except IndexError:
            raise CerebrumError("Unable to cause IntegrityError. "
                                "Check implementation for details "
                                "(debug_cause_integrity_error)")

        # op had spreads, and adding them again did not fail. Something is
        # seriously wrong!
        raise CerebrumError("Should not be reached.")

    #
    # debug wait <n>
    #
    all_commands['debug_wait'] = cmd_param.Command(
        ("debug", "wait"),
        cmd_param.Integer(help_ref='wait_int', optional=True),
        fs=cmd_param.FormatSuggestion('%s seconds passed.', ('wait', ))
    )

    def debug_wait(self, operator, sleep_seconds=1):
        """ Sleep and return.

        This command can be used to simulate long blocks.

        """
        sleep_seconds = min(max(int(sleep_seconds),
                                self.MIN_SLEEP),
                            self.MAX_SLEEP)
        time.sleep(sleep_seconds)
        return {'wait': sleep_seconds, }
Exemplo n.º 14
0
class BofhdExtension(BofhdCommonMethods):

    all_commands = {}
    parent_commands = False
    authz = BofhdAuth

    @property
    def bgu(self):
        try:
            return self.__guest_utils
        except AttributeError:
            self.__guest_utils = GuestUtils(self.db, self.logger)
            return self.__guest_utils

    @classmethod
    def get_help_strings(cls):
        group_help = {}

        # The texts in command_help are automatically line-wrapped, and should
        # not contain \n
        command_help = {
            'user': {
                'user_create_guest': cls.user_create_guest.__doc__,
                'user_request_guest': cls.user_request_guest.__doc__,
                'user_release_guest': cls.user_release_guest.__doc__,
                'user_guests': cls.user_guests.__doc__,
                'user_guests_status': cls.user_guests_status.__doc__,
            }
        }

        arg_help = {
            'release_type': [
                'release_type',
                'Enter release type',
                "Enter a guest user name or a range of names, e.g.,"
                " guest007 or guest010-040.", ],
            'guest_owner_group': [
                'guest_owner_group',
                'Enter name of owner group (those responsible for the guest)',
            ],
            'nr_guests': [
                'nr_guests',
                'Enter number of guests', ],
            'comment': [
                'comment',
                'Enter comment', ]
        }
        return (group_help, command_help, arg_help)

    def user_create_guest_prompt_func(self, session, *args):
        all_args = list(args[:])
        # get number of new guest users
        if not all_args:
            return {'prompt': 'How many guest users?', 'default': '1'}
        try:
            int(all_args[0])
            all_args.pop(0)
        except ValueError:
            raise CerebrumError("Not a number: %r" % all_args[0])
        # Get group name.
        if not all_args:
            return {'prompt': 'Enter owner group name',
                    'default': cereconf.GUESTS_OWNER_GROUP}
        # get default file_group
        all_args.pop(0)
        if not all_args:
            return {'prompt': "Default filegroup",
                    'default': cereconf.GUESTS_DEFAULT_FILEGROUP}
        all_args.pop(0)
        # get shell
        if not all_args:
            return {'prompt': "Shell", 'default': 'bash'}
        all_args.pop(0)
        # get disk
        if not all_args:
            return {'prompt': "Disk", 'help_ref': 'disk'}
        all_args.pop(0)
        # get prefix
        if not all_args:
            return {'prompt': "Name prefix",
                    'default': cereconf.GUESTS_PREFIX[-1],
                    'last_arg': True}
        return {'last_arg': True}

    #
    # user create_guest <nr> <group_id> <filegroup> <shell> <disk>
    #
    all_commands['user_create_guest'] = cmd_param.Command(
        ('user', 'create_guest'),
        prompt_func=user_create_guest_prompt_func,
        perm_filter='can_create_guests')

    def user_create_guest(self, operator, *args):
        """ Create a number of guest users for a certain time. """
        nr, owner_group_name, filegroup, shell, home, prefix = args
        owner_type = self.const.entity_group
        owner_id = self.util.get_target(owner_group_name,
                                        default_lookup="group").entity_id
        np_type = self.const.account_uio_guest
        group = self.util.get_target(filegroup, default_lookup="group")
        posix_user = Factory.get('PosixUser')(self.db)
        shell = self._get_shell(shell)
        disk_id, home = self._get_disk(home)[1:3]
        gecos = None
        expire_date = None
        self.ba.can_create_user(operator.get_entity_id(), owner_id, disk_id)

        ret = []
        # Find the names of the next <nr> of guest users
        for uname in self.bgu.find_new_guestusernames(int(nr), prefix=prefix):
            posix_user.clear()
            # Create the guest_users
            uid = posix_user.get_free_uid()
            posix_user.populate(uid, group.entity_id, gecos, shell, name=uname,
                                owner_type=owner_type,
                                owner_id=owner_id, np_type=np_type,
                                creator_id=operator.get_entity_id(),
                                expire_date=expire_date)
            try:
                posix_user.write_db()
                # For correct ordering of ChangeLog events, new users
                # should be signalled as "exported to" a certain system
                # before the new user's password is set.  Such systems are
                # flawed, and should be fixed.
                for spread in cereconf.GUESTS_USER_SPREADS:
                    posix_user.add_spread(self.const.Spread(spread))
                homedir_id = posix_user.set_homedir(
                    disk_id=disk_id, home=home,
                    status=self.const.home_status_not_created)
                posix_user.set_home(self.const.spread_uio_nis_user, homedir_id)
            except self.db.DatabaseError as m:
                raise CerebrumError("Database error: %s" % m)
            self.bgu.update_group_memberships(posix_user.entity_id)
            posix_user.populate_trait(
                self.const.trait_uio_guest_owner,
                target_id=None)
            # The password must be set _after_ the trait, or else it
            # won't be stored using the 'PGP-guest_acc' method.
            posix_user.set_password(posix_user.make_passwd(uname))
            posix_user.write_db()
            ret.append(uname)
        return "OK, created guest_users:\n %s " % self._pretty_print(ret)

    #
    # user request_guest <nr> <to_from> <entity_name>
    #
    all_commands['user_request_guest'] = cmd_param.Command(
        ('user', 'request_guest'),
        cmd_param.Integer(default="1", help_ref="nr_guests"),
        cmd_param.SimpleString(help_ref="string_from_to"),
        cmd_param.GroupName(help_ref="guest_owner_group"),
        cmd_param.SimpleString(help_ref="comment"),
        perm_filter='can_request_guests')

    def user_request_guest(self, operator, nr, date, groupname, comment):
        """ Request a number of guest users for a certain time. """
        # date checking
        start_date, end_date = self._parse_date_from_to(date)
        today = DateTime.today()
        if start_date < today:
            raise CerebrumError("Start date shouldn't be in the past")
        # end_date in allowed interval?
        if end_date < start_date:
            raise CerebrumError("End date can't be earlier than start_date")
        max_date = start_date + DateTime.RelativeDateTime(
            days=cereconf.GUESTS_MAX_PERIOD)
        if end_date > max_date:
            raise CerebrumError("End date can't be later than %s" %
                                max_date.date)
        if not nr.isdigit():
            raise CerebrumError(
                "'Number of accounts' requested must be a number;"
                " %r isn't." % nr)

        try:
            self.ba.can_request_guests(operator.get_entity_id(), groupname)
        except Errors.NotFoundError:
            raise CerebrumError("Group %r not found" % groupname)
        owner = self.util.get_target(groupname, default_lookup="group")
        try:
            user_list = self.bgu.request_guest_users(
                int(nr),
                end_date,
                comment,
                owner.entity_id,
                operator.get_entity_id())
            for uname, comment, e_id, passwd in user_list:
                operator.store_state("new_account_passwd",
                                     {'account_id': e_id,
                                      'password': passwd})

            ret = "OK, reserved guest users:\n%s\n" % \
                  self._pretty_print([x[0] for x in user_list])
            ret += "Please use misc list_passwords to view the passwords\n"
            ret += "or use misc print_passwords to print the passwords."
            return ret
        except GuestAccountException as e:
            raise CerebrumError(e)

    #
    # user release_guest [<guest> || <range>]+
    #
    all_commands['user_release_guest'] = cmd_param.Command(
        ('user', 'release_guest'),
        cmd_param.SimpleString(help_ref='release_type', repeat=True))

    def user_release_guest(self, operator, *args):
        """ Manually release guest users that was requested earlier. """
        guests = []
        if not args:
            raise CerebrumError(
                "Usage: user release_guest [<guest> || <range>]+")
        # Each arg should be an guest account name or an interval
        for arg in args:
            if '-' in arg:
                first, last = arg.split('-')
                prefix = first.rstrip('0123456789')
                first = int(first[len(prefix):])
                last = int(last)
                for i in range(first, last+1):
                    guests.append('%s%03d' % (prefix, i))
            else:
                guests.append(arg)

        for guest in guests:
            try:
                owner_id = self.bgu.get_owner(guest)
                owner_group = self.util.get_target(owner_id)
                self.ba.can_release_guests(operator.get_entity_id(),
                                           owner_group.group_name)
                self.bgu.release_guest(guest, operator.get_entity_id())
            except Errors.NotFoundError:
                raise CerebrumError(
                    "Could not find guest user with name %s" % guest)
            except PermissionDenied:
                raise CerebrumError(
                    "No permission to release guest user %s" % guest)
            except GuestAccountException as e:
                raise CerebrumError(
                    "Could not release guest user. %s" % e)

        return "OK, released guests:\n%s" % self._pretty_print(guests)

    #
    # user guests <owner>
    #
    all_commands['user_guests'] = cmd_param.Command(
        ('user', 'guests'),
        cmd_param.GroupName(help_ref="guest_owner_group"))

    def user_guests(self, operator, groupname):
        """ Show the guest users that are owned by a group. """
        owner = self.util.get_target(groupname, default_lookup="group")
        users = self.bgu.list_guest_users(owner_id=owner.entity_id,
                                          include_comment=True,
                                          include_date=True)
        if not users:
            return "No guest users are owned by %s" % groupname
        if len(users) == 1:
            verb = 'user is'
            noun = 'Guest user'
        else:
            verb = 'users are'
            noun = 'Guest users'
        return ("The following guest %s owned by %s:\n%s\n%s" %
                (verb, groupname,
                 "%-12s   %-12s  %s" % (noun, "End date", "Comment"),
                 self._pretty_print(users, include_date=True,
                                    include_comment=True)))

    #
    # user guests_status <?>
    #
    all_commands['user_guests_status'] = cmd_param.Command(
        ('user', 'guests_status'),
        cmd_param.SimpleString(optional=True))

    def user_guests_status(self, operator, *args):
        """ Show how many guest users are available. """
        ret = ""
        if (args and args[0] == "verbose" and
                self.ba.is_superuser(operator.get_entity_id())):
            tmp = self.bgu.list_guests_info()
            # Find status for all guests
            ret = "%-12s   %s:\n%s\n" % (
                "Guest users", "Status",
                self._pretty_print(tmp, include_comment=True))
            ret += "%d allocated guest users.\n" % len(
                [1 for x in tmp if x[2].startswith("allocated")])
            ret += "%d guest users in release_quarantine.\n" % len(
                [1 for x in tmp if x[2].startswith("in release_quarantine")])
        ret += "%d guest users available." % self.bgu.num_available_accounts()
        return ret

    def _pretty_print(self, guests, include_comment=False, include_date=False):
        """Return a pretty string of the names in guestlist.

        If the list contains more than 2 consecutive names they are written as
        an interval.

        If comment or date in included they must be equal within an interval.

        guests is either a list of guest names or a list of lists, where the
        inner lists are on the form [username, end_date, comment].
        """
        guests.sort()
        prev = -2
        prev_comment = None
        prev_date = None
        intervals = []
        for guest in guests:
            # Handle simple list of guest user names
            if not type(guest) is list:
                guest = [guest, None, None]
            num = int(guest[0][-3:])
            if (intervals and num - prev == 1 and
                    guest[1] == prev_date and
                    guest[2] == prev_comment):
                intervals[-1][1] = guest
            else:
                intervals.append([guest, guest])
            prev = num
            prev_date = guest[1]
            prev_comment = guest[2]

        ret = []
        for i, j in intervals:
            if i == j:
                tmp = '%-12s   ' % i[0]
                if include_date:
                    tmp += '%-12s  ' % i[1]
                if include_comment:
                    tmp += '%s' % i[2]
            else:
                tmp = '%-8s-%s  ' % (i[0], j[0][-3:])
                if include_date:
                    tmp += ' %-12s  ' % i[1]
                if include_comment:
                    tmp += ' %s' % i[2]
            ret.append(tmp)
        return '\n'.join(ret)
Exemplo n.º 15
0
        """
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Only superusers may rename groups, due "
                                   "to its consequences!")
        gr = self._get_group(groupname)
        gr.group_name = newname
        try:
            gr.write_db()
        except gr._db.IntegrityError, e:
            raise CerebrumError("Couldn't rename group: %s" % e)
        return {'new_name': gr.group_name, 'group_id': int(gr.entity_id)}

    # entity contactinfo_add <entity> <contact type> <contact value>
    all_commands['entity_contactinfo_add'] = cmd.Command(
        ('entity', 'contactinfo_add'),
        cmd.SimpleString(help_ref='id:target:entity'),
        cmd.SimpleString(help_ref='entity_contact_type'),
        cmd.SimpleString(help_ref='entity_contact_value'),
        perm_filter='can_add_contact_info')

    def entity_contactinfo_add(self, operator, entity_target, contact_type,
                               contact_value):
        """Manually add contact info to an entity."""
        co = self.const

        # default values
        contact_pref = 50
        source_system = co.system_manual

        # get entity object
        entity = self.util.get_target(entity_target, restrict_to=[])
Exemplo n.º 16
0
class BofhdExtension(BofhdCommonMethods):

    external_id_mappings = {}
    all_commands = {}
    parent_commands = True
    authz = BofhdAuth

    def __init__(self, *args, **kwargs):
        super(BofhdExtension, self).__init__(*args, **kwargs)
        self.external_id_mappings['fnr'] = self.const.externalid_fodselsnr

        # Quick fix to replace the `person_find_uio` hack
        self.__uio_impl = bofhd_uio_cmds.BofhdExtension(*args, **kwargs)

    @property
    def person(self):
        try:
            return self.__person
        except AttributeError:
            self.__person = Factory.get('Person')(self.db)
            return self.__person

    @property
    def ou(self):
        try:
            return self.__ou
        except AttributeError:
            self.__ou = Factory.get('OU')(self.db)
            return self.__ou

    @staticmethod
    def get_help_strings():
        return (bofhd_go_help.group_help, bofhd_go_help.command_help,
                bofhd_go_help.arg_help)

    # IVR 2007-03-12 We override UiO's behaviour (since there are no
    # PosixUsers in Indigo by default). Ideally, UiO's bofhd should be split
    # into manageable units that can be plugged in on demand
    all_commands['_group_remove_entity'] = None

    def _group_remove_entity(self, operator, member, group):
        self.ba.can_alter_group(operator.get_entity_id(), group)
        member_name = self._get_name_from_object(member)
        if not group.has_member(member.entity_id):
            return ("%s isn't a member of %s" %
                    (member_name, group.group_name))
        try:
            group.remove_member(member.entity_id)
        except self.db.DatabaseError as m:
            raise CerebrumError("Database error: %s" % m)
        return "OK, removed '%s' from '%s'" % (member_name, group.group_name)

    #
    # group info
    #
    all_commands['group_info'] = cmd_param.Command(
        ("group", "info"),
        cmd_param.GroupName(help_ref="id:gid:name"),
        fs=cmd_param.FormatSuggestion([
            ("Name:         %s\n"
             "Spreads:      %s\n"
             "Description:  %s\n"
             "Expire:       %s\n"
             "Entity id:    %i", ("name", "spread", "description",
                                  format_day("expire_date"), "entity_id")),
            ("Admin:        %s %s", ('admin_type', 'admin')),
            ("Moderator:    %s %s", ('mod_type', 'mod')),
            ("Gid:          %i", ('gid', )),
            ("Members:      %s", ("members", ))
        ]))

    def group_info(self, operator, groupname):
        grp = self._get_group(groupname)
        co = self.const
        ret = [
            self._entity_info(grp),
        ]
        roles = GroupRoles(self.db)
        # find admins
        for row in roles.search_admins(group_id=grp.entity_id):
            id = int(row['admin_id'])
            en = self._get_entity(ident=id)
            if en.entity_type == co.entity_account:
                admin = en.account_name
            elif en.entity_type == co.entity_group:
                admin = en.group_name
            else:
                admin = '#%d' % id
            ret.append({
                'admin_type': text_type(co.EntityType(en.entity_type)),
                'admin': admin,
            })
        # find moderators
        for row in roles.search_moderators(group_id=grp.entity_id):
            id = int(row['moderator_id'])
            en = self._get_entity(ident=id)
            if en.entity_type == co.entity_account:
                mod = en.account_name
            elif en.entity_type == co.entity_group:
                mod = en.group_name
            else:
                mod = '#%d' % id
            ret.append({
                'mod_type': text_type(co.EntityType(en.entity_type)),
                'mod': mod,
            })

        # Count group members of different types
        members = list(grp.search_members(group_id=grp.entity_id))
        tmp = {}
        for ret_pfix, entity_type in (('c_group', int(co.entity_group)),
                                      ('c_account', int(co.entity_account))):
            tmp[ret_pfix] = len(
                [x for x in members if int(x["member_type"]) == entity_type])
        ret.append(tmp)
        return ret

    #
    # group user
    #
    all_commands['group_user'] = cmd_param.Command(
        ('group', 'user'),
        cmd_param.AccountName(),
        fs=cmd_param.FormatSuggestion("%-9s %-18s", ("memberop", "group")))

    def group_user(self, operator, accountname):
        return self.group_memberships(operator, 'account', accountname)

    #
    # get_auth_level
    #
    all_commands['get_auth_level'] = None

    def get_auth_level(self, operator):
        if self.ba.is_superuser(operator.get_entity_id()):
            return cereconf.INDIGO_AUTH_LEVEL['super']

        if self.ba.is_schoolit(operator.get_entity_id(), True):
            return cereconf.INDIGO_AUTH_LEVEL['schoolit']

        return cereconf.INDIGO_AUTH_LEVEL['other']

    #
    # list_defined_spreads
    #
    all_commands['list_defined_spreads'] = None

    def list_defined_spreads(self, operator):
        return [{
            'code_str': text_type(y),
            'desc': y.description,
            'entity_type': text_type(self.const.EntityType(y.entity_type)),
        } for y in self.const.fetch_constants(self.const.Spread)]

    #
    # get_entity_spreads
    #
    all_commands['get_entity_spreads'] = None

    def get_entity_spreads(self, operator, entity_id):
        entity = self._get_entity(ident=int(entity_id))
        to_spread = self.const.Spread
        return [{
            'spread': text_type(to_spread(row['spread'])),
            'spread_desc': to_spread(row['spread']).description,
        } for row in entity.get_spread()]

    #
    # get_default_email
    #
    all_commands['get_default_email'] = None

    def get_default_email(self, operator, entity_id):
        account = self._get_account(entity_id)
        try:
            return account.get_primary_mailaddress()
        except Errors.NotFoundError:
            return "No e-mail addresse available for %s" % account.account_name

    #
    # get_create_date
    #
    all_commands['get_create_date'] = None

    def get_create_date(self, operator, entity_id):
        account = self._get_account(entity_id)
        return account.created_at

    #
    # user_get_pwd
    #
    all_commands['user_get_pwd'] = None

    def user_get_pwd(self, operator, id_):
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")
        try:
            account = self._get_account(id_, 'id')
        except Errors.NotFoundError:
            raise CerebrumError('Cannot find user with id:{}'.format(id_))
        try:
            pwd = account.get_account_authentication(
                self.const.auth_type_plaintext)
        except Errors.NotFoundError:
            raise CerebrumError('Cannot get user password, password is hashed')
        return {'password': pwd, 'uname': account.account_name}

    #
    # list_active
    #
    all_commands['list_active'] = None

    def list_active(self, operator):
        active = list()
        # IVR 2007-03-11 fetch the source system, which determines people that
        # are considered 'active'.
        source = int(getattr(self.const, cereconf.INDIGO_ACTIVE_SOURCE_SYSTEM))
        for row in self.person.list_affiliations(source_system=source):
            active.append(row['person_id'])
        return active

    #
    # user info [username]
    #
    all_commands['user_info'] = cmd_param.Command(
        ("user", "info"),
        cmd_param.AccountName(),
        fs=cmd_param.FormatSuggestion(
            "Entity id:      %d\n"
            "Owner id:       %d\n"
            "Owner type:     %d\n", ("entity_id", "owner_id", "owner_type")),
        perm_filter='can_view_user')

    def user_info(self, operator, entity_id):
        """ Account info. """
        account = self._get_account(entity_id)
        self.ba.can_view_user(operator.get_entity_id(), account=account)
        return {
            'entity_id': account.entity_id,
            'owner_id': account.owner_id,
            'owner_type': account.owner_type
        }

    #
    # person_info <id>
    #
    all_commands['person_info'] = cmd_param.Command(
        ("person", "info"),
        cmd_param.PersonId(help_ref="id:target:person"),
        fs=cmd_param.FormatSuggestion([
            ("Name:          %s\n"
             "Entity-id:     %i\n"
             "Export-id:     %s\n"
             "Birth:         %s", ("name", "entity_id", "export_id", "birth")),
            ("Affiliation:   %s@%s (%i) [from %s]",
             ("affiliation", "aff_sted_desc", "ou_id", "source_system")),
            ("Fnr:           %s [from %s]", ("fnr", "fnr_src")),
        ]))

    def person_info(self, operator, person_id):
        """ Person info for Cweb. """
        try:
            person = self._get_person(*self._map_person_id(person_id))
        except Errors.TooManyRowsError:
            raise CerebrumError("Unexpectedly found more than one person")
        data = [{
            'name':
            person.get_name(self.const.system_cached,
                            getattr(self.const, cereconf.DEFAULT_GECOS_NAME)),
            'export_id':
            person.export_id,
            'birth':
            person.birth_date,
            'entity_id':
            person.entity_id
        }]

        for row in person.get_affiliations():
            ou = self._get_ou(ou_id=row['ou_id'])
            data.append({
                'aff_sted_desc':
                ou.get_name_with_language(name_variant=self.const.ou_name,
                                          name_language=self.const.language_nb,
                                          default=""),
                'aff_type':
                text_type(self.const.PersonAffiliation(row['affiliation'])),
                'aff_status':
                text_type(self.const.PersonAffStatus(row['status'])),
                'ou_id':
                row['ou_id'],
                'affiliation':
                text_type(self.const.PersonAffStatus(row['status'])),
                'source_system':
                text_type(self.const.AuthoritativeSystem(
                    row['source_system'])),
            })

        account = self.Account_class(self.db)
        account_ids = [
            int(r['account_id'])
            for r in account.list_accounts_by_owner_id(person.entity_id)
        ]
        if (self.ba.is_schoolit(operator.get_entity_id(), True)
                or operator.get_entity_id() in account_ids):
            for row in person.get_external_id(
                    id_type=self.const.externalid_fodselsnr):
                data.append({
                    'fnr':
                    row['external_id'],
                    'fnr_src':
                    text_type(
                        self.const.AuthoritativeSystem(row['source_system'])),
                })
        return data

    #
    # person_find
    #
    all_commands['person_find'] = None

    def person_find(self, operator, search_type, value, filter=None):
        """Indigo-specific wrapper and filter around UiO's open person_find."""

        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            raise PermissionDenied("Limited to school IT and superusers")

        results = self.__uio_impl.person_find(operator, search_type, value,
                                              filter)
        return self._filter_resultset_by_operator(operator, results, "id")

    #
    # person_accounts
    #
    all_commands['person_accounts'] = None

    def person_accounts(self, operator, id):
        """person_accounts with restrictions for Indigo.

        This is a copy of UiO's method, except for result
        filtering/permission check.
        """

        person = self.util.get_target(id, restrict_to=['Person', 'Group'])
        if not (self.ba.is_schoolit(operator.get_entity_id(), True)
                or operator.get_owner_id() == person.entity_id):
            raise PermissionDenied("Limited to school IT and superusers")

        if not self._operator_sees_person(operator, person.entity_id):
            return []

        account = self.Account_class(self.db)
        ret = []
        for r in account.list_accounts_by_owner_id(
                person.entity_id,
                owner_type=person.entity_type,
                filter_expired=False):
            account = self._get_account(r['account_id'], idtype='id')

            ret.append({
                'account_id': r['account_id'],
                'name': account.account_name,
                'expire': account.expire_date
            })
        ret.sort(key=lambda d: d['name'])
        return ret

    #
    # user_create
    #
    all_commands['user_create'] = None

    def user_create(self, operator, uname, owner_id):
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")
        account = self.Account_class(self.db)
        entity = self._get_entity(ident=int(owner_id))
        if entity.entity_type == int(self.const.entity_person):
            np_type = None
        else:
            # TODO: What value?  Or drop-down?
            np_type = self.const.account_program

        account.populate(uname, entity.entity_type, owner_id, np_type,
                         operator.get_entity_id(), None)
        passwd = account.make_passwd(uname)
        account.set_password(passwd)
        account.write_db()
        operator.store_state("new_account_passwd", {
            'account_id': int(account.entity_id),
            'password': passwd
        })
        return "Ok, user created"

    #
    # user_suggest_name
    #
    all_commands['user_suggest_uname'] = None

    def user_suggest_uname(self, operator, owner_id):
        person = self._get_person("entity_id", owner_id)
        account = self.Account_class(self.db)
        return account.suggest_unames(person)

    #
    # user_find
    #
    all_commands['user_find'] = None

    def user_find(self, operator, search_type, search_value):
        "Locate users whose unames loosely matches 'search_value'."

        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            raise PermissionDenied("Limited to superusers and school IT"
                                   " admins")

        if search_type != 'uname':
            raise CerebrumError("Unknown search type (%s)" % search_type)

        if len(search_value.strip(" \t%_*?")) < 3:
            raise CerebrumError("You must specify at least three non-wildcard"
                                " letters")

        # if there are no wildcards in the pattern, add them
        if not [wildcard for wildcard in "_%?*" if wildcard in search_value]:
            search_value = '*' + search_value.replace(' ', '*') + '*'

        account = Factory.get("Account")(self.db)
        matches = list(
            account.search(name=search_value,
                           owner_type=int(self.const.entity_person)))
        # prepare the return value
        ret = list()
        seen = dict()
        if len(matches) > 250:
            raise CerebrumError("More than 250 (%d) matches, please narrow "
                                "search criteria" % len(matches))

        for row in matches:
            account_id = row['account_id']
            if account_id in seen:
                continue

            seen[account_id] = True
            account.clear()
            account.find(account_id)
            person = self._get_person("entity_id", int(account.owner_id))
            owner_name = person.get_name(
                self.const.system_cached,
                getattr(self.const, cereconf.DEFAULT_GECOS_NAME))
            ret.append({
                'account_id': account_id,
                'name': row['name'],
                'owner_id': account.owner_id,
                'owner_name': owner_name,
                'birth': person.birth_date
            })

        # school lita can see their own schools only!
        ret = self._filter_resultset_by_operator(operator, ret, "owner_id")

        ret.sort(key=lambda d: d['name'])
        return ret

    def _operator_sees_person(self, operator, person_id):
        """Decide if operator can obtain information about person_id.

        Superusers can see information about everyone. People can see their
        own information as well.

        Additionally, school IT may see everyone who is affiliated with an OU
        that they have permissions for.
        """

        # superusers and own information
        if (self.ba.is_superuser(operator.get_entity_id(), True)
                or operator.get_owner_id() == person_id):
            return True

        # non-LITAs cannot see anyone else
        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            return False

        # ... but LITAs can
        operators_ou = set(self._operators_ou(operator))
        targets_ou = set([
            x['ou_id']
            for x in self.person.list_affiliations(person_id=int(person_id))
        ])
        return bool(operators_ou.intersection(targets_ou))

    def _operator_sees_ou(self, operator, ou_id):
        """Decide if operator can obtain information about ou_id.

        Superusers can see information about anything. School IT can only see
        info about the schools where they have school IT permissions.
        """

        if self.ba.is_superuser(operator.get_entity_id(), True):
            return True

        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            return False

        operators_ou = set(self._operators_ou(operator))
        return int(ou_id) in operators_ou

    def _filter_resultset_by_operator(self, operator, results, person_key):
        """Remove elements from results to which operator has no access.

        In general, a school lita should not 'see' any results outside of his
        school. This means that the list of users and people returned to
        him/her has to be filtered.

        operator    operator (person_id)
        results     a sequency of dictionary-like objects where each object
                    represents a database row. These are to be filtered.
        person_key  name of the key in each element of results that
                    designates the owner.

        Caveats:
        * This method is quite costly. It gets more so, the larger the schools
          are.
        * This method will not help with group filtering.
        """

        # never filter superusers' results
        if self.ba.is_superuser(operator.get_entity_id(), True):
            return results

        # The operation is performed in three steps:
        # 1) fetch all OUs where that the operator can "see".
        # 2) fetch all people affiliated with OUs in #1
        # 3) intersect results with #2

        # Find operator's OUs
        operators_ou = self._operators_ou(operator)

        # Find all people affiliated with operator's OUs
        operators_people = set([
            x['person_id']
            for x in self.person.list_affiliations(ou_id=operators_ou)
        ])
        # Filter the results...
        filtered_set = list()
        for element in results:
            if element[person_key] in operators_people:
                filtered_set.append(element)

        return type(results)(filtered_set)

    def _operators_ou(self, operator):
        """Return a sequence of OUs that operator can 'see'.

        Superusers see everything.
        School IT see only the OUs where they have privileges.
        Everyone else sees nothing.
        """
        def grab_all_ous():
            return [
                int(x['ou_id'])
                for x in self.ou.search(filter_quarantined=False)
            ]

        if self.ba.is_superuser(operator.get_entity_id(), True):
            return grab_all_ous()

        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            return []

        group = self.Group_class(self.db)
        # fetch all groups where operator is a member
        op_groups = [
            x['group_id']
            for x in group.search(member_id=operator.get_entity_id(),
                                  indirect_members=False)
        ]
        # fetch all permissions that these groups have
        op_targets = [
            x['op_target_id']
            for x in BofhdAuthRole(self.db).list(entity_ids=op_groups)
        ]

        # Now, finally, the permissions:
        result = list()
        for permission in BofhdAuthOpTarget(self.db).list(
                target_id=op_targets,
                target_type=self.const.auth_target_type_ou,
        ):
            if permission["entity_id"] is not None:
                result.append(int(permission["entity_id"]))
            else:
                # AHA! We have a general OU permission. Grab them all!
                return grab_all_ous()

        return result

    #
    # misc history <num-days>
    #
    all_commands['misc_history'] = cmd_param.Command(('misc', 'history'),
                                                     cmd_param.SimpleString())

    def misc_history(self, operator, days):
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")

        types = (self.clconst.account_create, self.clconst.account_password,
                 self.clconst.ou_create, self.clconst.person_create)
        sdate = mx.DateTime.now() - mx.DateTime.oneDay * int(days)
        # Collect in a dict to remove duplicates etc.
        tmp = {}
        for r in self.db.get_log_events(sdate=sdate, types=types):
            tmp.setdefault(int(r['subject_entity']),
                           {})[int(r['change_type_id'])] = r

        ret = []
        for entity_id, changes in tmp.items():
            if (int(self.clconst.account_password) in changes
                    and int(self.clconst.account_create) not in changes):
                # TBD: naa er det OK aa vise passordet?
                del (changes[int(self.clconst.account_password)])

            for k, v in changes.items():
                change_type = self.clconst.ChangeType(int(k))
                params = ''
                if k == self.const.account_password:
                    if v['change_params']:
                        params = json.loads(v['change_params'])
                        params = params.get('password', '')
                tmp = {
                    'tstamp': v['tstamp'],
                    # 'change_type': str(cl),
                    'change_type': text_type(change_type),
                    'misc': params,
                }
                entity = self._get_entity(ident=int(v['subject_entity']))
                if entity.entity_type == int(self.const.entity_person):
                    person = self._get_person("entity_id", entity.entity_id)
                    name = person.get_name(self.const.system_cached,
                                           self.const.name_full)
                    tmp['person_id'] = int(person.entity_id)
                elif entity.entity_type == int(self.const.entity_account):
                    account = self.Account_class(self.db)
                    account.find(entity.entity_id)
                    name = account.account_name
                    tmp['person_id'] = int(account.owner_id)
                else:
                    self.ou.clear()
                    self.ou.find(entity.entity_id)
                    name = self.ou.get_name_with_language(
                        name_variant=self.const.ou_name,
                        name_language=self.const.language_nb,
                        default="")
                tmp['name'] = name
                ret.append(tmp)
        return ret

    #
    # find_school <name>
    #
    all_commands['find_school'] = None

    def find_school(self, operator, name):

        if not self.ba.is_schoolit(operator.get_entity_id(), True):
            raise PermissionDenied("Currently limited to superusers and"
                                   " school IT")

        # name could be an acronym or a "regular" name
        result = set()
        for name_variant in (self.const.ou_name, self.const.ou_name_acronym):
            result.update(r["entity_id"]
                          for r in self.ou.search_name_with_language(
                              entity_type=self.const.entity_ou,
                              name_variant=name_variant,
                              name=name,
                              name_language=self.const.language_nb,
                              exact_match=False))

        if len(result) == 0:
            raise CerebrumError("Could not find school matching %s" % name)
        elif len(result) > 1:
            raise CerebrumError("Found several schools with matching names")

        # Now there is just one left. But can the operator see it?
        ou_id = result.pop()
        # filter the results for school IT
        if not self._operator_sees_ou(operator, ou_id):
            raise CerebrumError("School information is unavailable for this"
                                " user")
        else:
            return ou_id

    #
    # get_password_information <entity_id>
    #
    all_commands["get_password_information"] = None

    def get_password_information(self, operator, entity_id):
        """Retrieve information about password changes for entity_id.

        This function helps implement a command in Giske's cweb.
        """

        self.logger.debug("Processing for id=%s", entity_id)
        entity_id = int(entity_id)
        result = {}
        for row in operator.get_state():
            if row["state_data"] is None:
                continue
            if entity_id != row["state_data"]["account_id"]:
                continue
            if row["state_type"] not in ("new_account_passwd", "user_passwd"):
                continue

            result = {
                "account_id":
                entity_id,
                "uname":
                self._get_entity_name(entity_id, self.const.entity_account),
                "password":
                row["state_data"]["password"],
            }
            account = self._get_entity(ident=entity_id)
            owner = self._get_entity(ident=account.owner_id)
            result["name"] = self._get_entity_name(owner.entity_id,
                                                   owner.entity_type)
            if owner.entity_type == self.const.entity_person:
                result["birth_date"] = owner.birth_date
                # Main affiliation points to school.
                affs = account.list_accounts_by_type(
                    primary_only=True,
                    person_id=owner.entity_id,
                    account_id=account.entity_id)
                if affs:
                    ou = self._get_entity(ident=affs[0]["ou_id"])
                    ou_name = ou.get_name_with_language(
                        name_variant=self.const.ou_name,
                        name_language=self.const.language_nb,
                        default="")
                    result["institution_name"] = ou_name
                else:
                    result["institution_name"] = "n/a"
            else:
                result["birth_date"] = "n/a"
                result["institution_name"] = "n/a"
        return result

    @classmethod
    def get_format_suggestion(cls, cmd):
        return cls.all_commands[cmd].get_fs()

    def _format_ou_name(self, ou):
        binds = {"name_language": self.const.language_nb, "default": ""}
        return (ou.get_name_with_language(
            name_variant=self.const.ou_name_short, **binds)
                or ou.get_name_with_language(name_variant=self.const.ou_name,
                                             **binds))

    def _email_info_detail(self, acc):
        """ Get quotas from Cerebrum, and usage from Cyrus. """
        # NOTE: Very similar to hiof and uio

        info = []
        eq = Email.EmailQuota(self.db)

        # Get quota and usage
        try:
            eq.find_by_target_entity(acc.entity_id)
            et = Email.EmailTarget(self.db)
            et.find_by_target_entity(acc.entity_id)
            es = Email.EmailServer(self.db)
            es.find(et.email_server_id)

            if es.email_server_type == self.const.email_server_type_cyrus:
                used = 'N/A'
                limit = None
                pw = self.db._read_password(cereconf.CYRUS_HOST,
                                            cereconf.CYRUS_ADMIN)
                try:
                    cyrus = imaplib.IMAP4(es.name)
                    # IVR 2007-08-29 If the server is too busy, we do not want
                    # to lock the entire bofhd.
                    # 5 seconds should be enough
                    cyrus.socket().settimeout(5)
                    cyrus.login(cereconf.CYRUS_ADMIN, pw)
                    res, quotas = cyrus.getquota("user." + acc.account_name)
                    cyrus.socket().settimeout(None)
                    if res == "OK":
                        for line in quotas:
                            try:
                                folder, qtype, qused, qlimit = line.split()
                                if qtype == "(STORAGE":
                                    used = str(int(qused) / 1024)
                                    limit = int(qlimit.rstrip(")")) / 1024
                            except ValueError:
                                # line.split fails e.g. because quota isn't set
                                # on server
                                folder, junk = line.split()
                                self.logger.warning(
                                    "No IMAP quota set for '%s'" %
                                    acc.account_name)
                                used = "N/A"
                                limit = None
                except (bofhd_uio_cmds.TimeoutException, socket.error):
                    used = 'DOWN'
                except bofhd_uio_cmds.ConnectException as e:
                    used = text_type(e)
                except imaplib.IMAP4.error:
                    used = 'DOWN'
                info.append({
                    'quota_hard': eq.email_quota_hard,
                    'quota_soft': eq.email_quota_soft,
                    'quota_used': used
                })
                if limit is not None and limit != eq.email_quota_hard:
                    info.append({'quota_server': limit})
            else:
                # Just get quotas
                info.append({
                    'dis_quota_hard': eq.email_quota_hard,
                    'dis_quota_soft': eq.email_quota_soft
                })
        except Errors.NotFoundError:
            pass
        return info

    # Commands for Exchange migration:

    #
    # user migrate_exchange [username] [mdb]
    #
    all_commands['user_migrate_exchange'] = cmd_param.Command(
        ("user", "migrate_exchange"),
        cmd_param.AccountName(help_ref="account_name", repeat=False),
        cmd_param.SimpleString(help_ref='string_mdb'),
        perm_filter='is_superuser')

    def user_migrate_exchange(self, operator, uname, mdb):
        """Tagging a user as under migration, and setting the new MDB.

        The new MDB value should not be used until the user is tagged as
        successfully migrated.

        """
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")
        account = self._get_account(uname)
        # TODO: check the new MDB value?

        # Set new mdb value
        account.populate_trait(self.const.trait_homedb_info, strval=mdb)
        # Mark that account is being migrated
        account.populate_trait(self.const.trait_exchange_under_migration)
        account.write_db()
        return "OK, mdb stored for user %s" % uname

    #
    # user migrate_exchange_finished
    #
    all_commands['user_migrate_exchange_finished'] = cmd_param.Command(
        ("user", "migrate_exchange_finished"),
        cmd_param.AccountName(help_ref="account_name", repeat=True),
        perm_filter='is_superuser')

    def user_migrate_exchange_finished(self, operator, uname):
        """Tagging a user as migrated to a newer Exchange version."""
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Currently limited to superusers")
        account = self._get_account(uname)
        if not account.get_trait(self.const.trait_exchange_under_migration):
            raise CerebrumError("Account %s not under migration" % uname)
        # Mark that account is successfully migrated to new exchange server
        account.populate_trait(self.const.trait_exchange_migrated)
        account.write_db()
        # Remove trait for being under migration
        account.delete_trait(self.const.trait_exchange_under_migration)
        account.write_db()
        return "OK, deleted trait for user %s" % uname
Exemplo n.º 17
0
class BofhdCommonMethods(BofhdCommandBase):
    """Class with common methods that is used by most, 'normal' instances.

    Instances that requires some special care for some methods could subclass
    those in their own institution-specific class
    (modules.no.<inst>.bofhd_<inst>_cmds.py:BofhdExtension).

    The methods are using the BofhdAuth that is defined in the institution's
    subclass - L{BofhdExtension.authz}.

    """
    @property
    def util(self):
        try:
            return self.__util
        except AttributeError:
            self.__util = BofhdUtils(self.db)
            return self.__util

    # Each subclass defines its own class attribute containing the relevant
    # commands.
    # Any command defined in 'all_commands' or 'hidden_commands' are callable
    # from clients.
    all_commands = {}

    ##
    # User methods

    # user delete
    all_commands['user_delete'] = cmd.Command(("user", "delete"),
                                              cmd.AccountName(),
                                              perm_filter='can_delete_user')

    def user_delete(self, operator, accountname):
        account = self._get_account(accountname)
        self.ba.can_delete_user(operator.get_entity_id(), account)
        if account.is_deleted():
            raise CerebrumError("User is already deleted")
        account.deactivate()
        account.write_db()
        return "User %s is deactivated" % account.account_name

    def _user_create_prompt_func_helper(self, session, *args):
        """A prompt_func on the command level should return
        {'prompt': message_string, 'map': dict_mapping}
        - prompt is simply shown.
        - map (optional) maps the user-entered value to a value that
          is returned to the server, typically when user selects from
          a list."""
        all_args = list(args[:])

        if not all_args:
            return {
                'prompt': "Person identification",
                'help_ref': "user_create_person_id"
            }
        arg = all_args.pop(0)
        if arg.startswith("group:"):
            group_owner = True
        else:
            group_owner = False
        if not all_args or group_owner:
            if group_owner:
                group = self._get_group(arg.split(":")[1])
                if all_args:
                    all_args.insert(0, group.entity_id)
                else:
                    all_args = [group.entity_id]
            else:
                c = self._find_persons(arg)
                map = [(("%-8s %s", "Id", "Name"), None)]
                for i in range(len(c)):
                    person = self._get_person("entity_id", c[i]['person_id'])
                    map.append((("%8i %s", int(c[i]['person_id']),
                                 person.get_name(self.const.system_cached,
                                                 self.const.name_full)),
                                int(c[i]['person_id'])))
                if not len(map) > 1:
                    raise CerebrumError("No persons matched")
                return {
                    'prompt': "Choose person from list",
                    'map': map,
                    'help_ref': 'user_create_select_person'
                }
        owner_id = all_args.pop(0)
        if not group_owner:
            person = self._get_person("entity_id", owner_id)
            existing_accounts = []
            account = self.Account_class(self.db)
            for r in account.list_accounts_by_owner_id(person.entity_id):
                account = self._get_account(r['account_id'], idtype='id')
                if account.expire_date:
                    exp = account.expire_date.strftime('%Y-%m-%d')
                else:
                    exp = '<not set>'
                existing_accounts.append("%-10s %s" %
                                         (account.account_name, exp))
            if existing_accounts:
                existing_accounts = "Existing accounts:\n%-10s %s\n%s\n" % (
                    "uname", "expire", "\n".join(existing_accounts))
            else:
                existing_accounts = ''
            if existing_accounts:
                if not all_args:
                    return {'prompt': "%sContinue? (y/n)" % existing_accounts}
                yes_no = all_args.pop(0)
                if not yes_no == 'y':
                    raise CerebrumError("Command aborted at user request")
            if not all_args:
                map = [(("%-8s %s", "Num", "Affiliation"), None)]
                for aff in person.get_affiliations():
                    ou = self._get_ou(ou_id=aff['ou_id'])
                    name = "%s@%s" % (self.const.PersonAffStatus(
                        aff['status']), self._format_ou_name(ou))
                    map.append((("%s", name), {
                        'ou_id': int(aff['ou_id']),
                        'aff': int(aff['affiliation'])
                    }))
                if not len(map) > 1:
                    raise CerebrumError("Person has no affiliations. "
                                        "Try person affiliation_add")
                return {'prompt': "Choose affiliation from list", 'map': map}
            all_args.pop(0)  # Affiliation
        else:
            if not all_args:
                return {
                    'prompt': "Enter np_type",
                    'help_ref': 'string_np_type'
                }
            all_args.pop(0)  # np_type
        if not all_args:
            ret = {'prompt': "Username", 'last_arg': True}
            posix_user = Factory.get('PosixUser')(self.db)
            if not group_owner:
                try:
                    person = self._get_person("entity_id", owner_id)
                    fname, lname = [
                        person.get_name(self.const.system_cached, v)
                        for v in (self.const.name_first, self.const.name_last)
                    ]
                    sugg = posix_user.suggest_unames(
                        self.const.account_namespace, fname, lname)
                    if sugg:
                        ret['default'] = sugg[0]
                except ValueError:
                    pass  # Failed to generate a default username
            return ret
        if len(all_args) == 1:
            return {'last_arg': True}
        raise CerebrumError("Too many arguments")

    def _user_create_set_account_type(self, account, owner_id, ou_id,
                                      affiliation):
        person = self._get_person('entity_id', owner_id)
        try:
            affiliation = self.const.PersonAffiliation(affiliation)
            # make sure exist
            int(affiliation)
        except Errors.NotFoundError:
            raise CerebrumError("Invalid affiliation {}".format(affiliation))
        for aff in person.get_affiliations():
            if aff['ou_id'] == ou_id and aff['affiliation'] == affiliation:
                break
        else:
            raise CerebrumError(
                "Owner did not have any affiliation {}".format(affiliation))
        account.set_account_type(ou_id, affiliation)

    all_commands['user_create'] = cmd.Command(
        ('user', 'create'),
        prompt_func=_user_create_prompt_func_helper,
        fs=cmd.FormatSuggestion("Created account_id=%i", ("account_id", )),
        perm_filter='is_superuser')

    def user_create(self, operator, *args):
        if args[0].startswith('group:'):
            group_id, np_type, uname = args
            owner_type = self.const.entity_group
            owner_id = self._get_group(group_id.split(":")[1]).entity_id
            np_type = self._get_constant(self.const.Account, np_type,
                                         "account type")
            affiliation = None
            owner_type = self.const.entity_group
        else:
            if len(args) == 4:
                idtype, person_id, affiliation, uname = args
            else:
                idtype, person_id, yes_no, affiliation, uname = args
            person = self._get_person("entity_id", person_id)
            owner_type, owner_id = self.const.entity_person, person.entity_id
            np_type = None
        account = self.Account_class(self.db)
        account.clear()
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("only superusers may reserve users")
        account.populate(uname, owner_type, owner_id, np_type,
                         operator.get_entity_id(), None)
        account.write_db()
        for spread in cereconf.BOFHD_NEW_USER_SPREADS:
            account.add_spread(self.const.Spread(spread))
        passwd = account.make_passwd(uname)
        account.set_password(passwd)
        try:
            account.write_db()
            if affiliation is not None:
                ou_id, affiliation = affiliation['ou_id'], affiliation['aff']
                self._user_create_set_account_type(account, person.entity_id,
                                                   ou_id, affiliation)
        except self.db.DatabaseError as m:
            raise CerebrumError("Database error: %s" % m)
        operator.store_state("new_account_passwd", {
            'account_id': int(account.entity_id),
            'password': passwd
        })
        return {'account_id': int(account.entity_id)}

    ##
    # Group methods

    #
    # group create
    #
    all_commands['group_create'] = cmd.Command(
        ("group", "create"),
        cmd.GroupName(help_ref="group_name_new"),
        cmd.SimpleString(help_ref="string_description"),
        fs=cmd.FormatSuggestion("Group created, internal id: %i",
                                ("group_id", )),
        perm_filter='can_create_group')

    def group_create(self, operator, groupname, description):
        """ Standard method for creating normal groups.

        BofhdAuth's L{can_create_group} is first checked. The group gets the
        spreads as defined in L{cereconf.BOFHD_NEW_GROUP_SPREADS}.
        """
        self.ba.can_create_group(operator.get_entity_id(), groupname=groupname)
        g = self.Group_class(self.db)
        # Check if group name is already in use, raise error if so
        duplicate_test = g.search(name=groupname, filter_expired=False)
        if len(duplicate_test) > 0:
            raise CerebrumError("Group name is already in use")
        g.populate(creator_id=operator.get_entity_id(),
                   visibility=self.const.group_visibility_all,
                   name=groupname,
                   description=description)
        g.write_db()
        for spread in cereconf.BOFHD_NEW_GROUP_SPREADS:
            g.add_spread(self.const.Spread(spread))
            g.write_db()
        return {'group_id': int(g.entity_id)}

    all_commands['group_rename'] = cmd.Command(
        ('group', 'rename'),
        cmd.GroupName(help_ref="group_name"),
        cmd.GroupName(help_ref="group_name_new"),
        fs=cmd.FormatSuggestion("Group renamed to %s. Check integrations!",
                                ("new_name", )),
        perm_filter='is_superuser')

    def group_rename(self, operator, groupname, newname):
        """ Rename a Cerebrum group.

        Warning: This creates issues for fullsyncs that doesn't handle state.
        Normally, the old group would get deleted and lose any data attached to
        it, and a shiny new one would be created. Do not use unless you're aware
        of the consequences!

        """
        if not self.ba.is_superuser(operator.get_entity_id()):
            raise PermissionDenied("Only superusers may rename groups, due "
                                   "to its consequences!")
        gr = self._get_group(groupname)
        gr.group_name = newname
        try:
            gr.write_db()
        except gr._db.IntegrityError, e:
            raise CerebrumError("Couldn't rename group: %s" % e)
        return {'new_name': gr.group_name, 'group_id': int(gr.entity_id)}
Exemplo n.º 18
0
class BofhdExtension(BofhdCommandBase):
    """ Debug commands. """

    all_commands = {}
    authz = BofhdAuth

    MIN_SLEEP = 0
    MAX_SLEEP = 10

    @classmethod
    def get_help_strings(cls):
        group_help = {
            'debug': "Debug commands.",
        }

        # The texts in command_help are automatically line-wrapped, and should
        # not contain \n
        command_help = {
            'debug': {
                'debug_raise_cerebrum_error':
                'Raise a Cerebrum.Errors.CerebrumError exception',
                'debug_raise_bofhd_cerebrum_error':
                'Raise a bofhd.errors.CerebrumError exception',
                'debug_raise_exception_multiple_args':
                'Raise an exception with multiple args',
                'debug_cause_integrity_error':
                'Cause the database to raise an IntegrityError',
                'debug_wait':
                'Wait a specified number of seconds before returning',
                'debug_unicode':
                'Echo string input, and check if unicode',
                'debug_bytes':
                'Echo binary input, and return some binary output.'
                ' NOTE: Input will probably not be binary/bytestring,'
                ' since few clients implement this.',
            }
        }

        arg_help = {
            'exc_strval': [
                'string', 'Enter a string',
                'Enter a string value for the exception'
            ],
            'exc_intval': [
                'integer', 'Enter an integer',
                'Enter an integer value for the exception'
            ],
            'wait_int': [
                'integer', 'How many seconds?',
                'How many seconds should this command sleep for?'
                ' (min={:d} max={:d}'.format(cls.MIN_SLEEP, cls.MAX_SLEEP)
            ],
        }
        return (group_help, command_help, arg_help)

    #
    # debug raise_cerebrum_error
    #
    all_commands['debug_raise_cerebrum_error'] = cmd_param.Command(
        ("debug", "raise_cerebrum_error"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
    )

    def debug_raise_cerebrum_error(self, operator, strval="Foo Bar"):
        """ Raise a generic Cerebrum.Errors.CerebrumError. """
        raise Errors.CerebrumError(strval)

    #
    # debug raise_bofhd_cerebrum_error
    #
    all_commands['debug_raise_bofhd_cerebrum_error'] = cmd_param.Command(
        ("debug", "raise_bofhd_cerebrum_error"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
    )

    def debug_raise_bofhd_cerebrum_error(self, operator, strval="Foo Bar"):
        """ Raise an bofhd.errors.CerebrumError. """
        raise CerebrumError(strval)

    #
    # debug raise_exception_multiple_args
    #
    all_commands['debug_raise_exception_multiple_args'] = cmd_param.Command(
        ("debug", "raise_exception_multiple_args"),
        cmd_param.SimpleString(help_ref='exc_strval', optional=True),
        cmd_param.Integer(help_ref='exc_intval', optional=True))

    def debug_raise_exception_multiple_args(self,
                                            operator,
                                            strval="Foo Bar",
                                            intval=10):
        """ Raise an exception that takes multiple args.

        This is useful when we want to see what gets transferred to the client.

        """
        intval = int(intval)
        raise ExceptionMultipleArgs(strval, intval)

    #
    # debug cause_integrity_error
    #
    all_commands['debug_cause_integrity_error'] = cmd_param.Command(
        ("debug", "cause_integrity_error"))

    def debug_cause_integrity_error(self, operator):
        """ Cause the db-driver to raise an IntegrityError.

        This is done by adding an existing spread to the operator account.

        """
        op_acc = self._get_account(operator.get_entity_id(), idtype='id')

        try:
            maybe_spread = self.const.fetch_constants(None)[0]
            for _ in range(2):
                # Will cause IntegrityError because...
                #  - maybe_spread is not a spread
                #  - maybe_spread is not an account spread
                #  - maybe_spread is an account spread, and is added twice
                op_acc.add_spread(maybe_spread)
        except IndexError:
            raise CerebrumError("Unable to cause IntegrityError. "
                                "Check implementation for details "
                                "(debug_cause_integrity_error)")

        # op had spreads, and adding them again did not fail. Something is
        # seriously wrong!
        raise CerebrumError("Should not be reached.")

    #
    # debug wait <n>
    #
    all_commands['debug_wait'] = cmd_param.Command(
        ("debug", "wait"),
        cmd_param.Integer(help_ref='wait_int', optional=True),
        fs=cmd_param.FormatSuggestion('%s seconds passed.', ('wait', )))

    def debug_wait(self, operator, sleep_seconds=1):
        """ Sleep and return.

        This command can be used to simulate long blocks.

        """
        sleep_seconds = min(max(int(sleep_seconds), self.MIN_SLEEP),
                            self.MAX_SLEEP)
        time.sleep(sleep_seconds)
        return {
            'wait': sleep_seconds,
        }

    #
    # debug unicode <text>
    #
    all_commands['debug_unicode'] = cmd_param.Command(
        ("debug", "unicode"),
        cmd_param.SimpleString(optional=False),
        fs=cmd_param.FormatSuggestion([("text:        '%s'", ('text', )),
                                       ('type:        %s', ('type', )),
                                       ('repr:        %s', ('repr', ))]))

    def debug_unicode(self, operator, text):
        """ Return text. """
        return {
            'type': repr(type(text)),
            'text': text,
            'repr': repr(text),
        }

    #
    # debug bytes <bytestring>
    #
    all_commands['debug_bytes'] = cmd_param.Command(
        ("debug", "bytes"),
        cmd_param.SimpleString(optional=False),
        fs=cmd_param.FormatSuggestion([
            ("bytestring:         '%s'", ('bytestring', )),
            ('type:               %s', ('type', )),
            ('repr:               %s', ('repr', )),
            ('some actual bytes:  %r', ('bytes', )),
        ]))

    def debug_bytes(self, operator, bytestring):
        """ Return text. """
        # Clients don't really implement the binary xmlrpc data type, so
        # `bytestring` will probably be a unicode string.
        return {
            'type': repr(type(bytestring)),
            'bytestring': bytestring,
            'repr': repr(bytestring),
            'bytes': bytearray(b'abcæøå'),
        }