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
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)}
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), }
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
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:
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
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) targets = [] for row in aot.list(target_type='group', entity_id=grp.entity_id):
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
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)}
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)}
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, }
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
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æøå'), }