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
Example #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 "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)}
Example #3
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
Example #4
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),
        }
Example #5
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:
Example #6
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
Example #7
0
        members = list(grp.search_members(group_id=grp.entity_id))
        tmp = {}
        for ret_pfix, entity_type in (
                ('c_group', int(self.const.entity_group)),
                ('c_account', int(self.const.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']
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)}
Example #9
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