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 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(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 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
def user_info(self, operator, entity_id): """ Account info. """ account = self._get_account(entity_id) return {'entity_id': account.entity_id, 'owner_id': account.owner_id, 'owner_type': account.owner_type} # TODO: Interessant... FormatSuggestion stemmer overhode ikke overens med # data som returneres fra person_info ... # Denne vil gi NullPointerException i jBofh - fhl # # 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" + "Birth: %s\n" + "Spreads: %s\n" + "Affiliations: %s [from %s]", ("name", "entity_id", "birth", "spreads", "affiliation_1", "source_system_1")), (" %s [from %s]", ("affiliation", "source_system")), ("Names: %s[from %s]", ("names", "name_src")), ("Fnr: %s [from %s]", ("fnr", "fnr_src")), ("Contact: %s: %s [from %s]", ("contact_type", "contact", "contact_src")), ("External id: %s [from %s]", ("extid", "extid_src")) ]))