def delete_trait(self, code): """Remove the entity's trait identified by code value.""" code = _EntityTraitCode(code) # get_traits populates __traits as a side effect if code not in self.get_traits(): raise Errors.NotFoundError(code) if code in self.__trait_updates: if self.__trait_updates[code] == 'INSERT': del self.__trait_updates[code] del self.__traits[code] return del self.__trait_updates[code] params = self._pickle_fixup(self.__traits[code]) binds = {'entity_id': self.entity_id, 'code': int(code)} exists_stmt = """ SELECT EXISTS ( SELECT 1 FROM [:table schema=cerebrum name=entity_trait] WHERE entity_id=:entity_id AND code=:code ) """ if not self.query_1(exists_stmt, binds): # False positive return delete_stmt = """ DELETE FROM [:table schema=cerebrum name=entity_trait] WHERE entity_id=:entity_id AND code=:code """ self.execute(delete_stmt, binds) self._db.log_change(self.entity_id, self.clconst.trait_del, None, change_params=params) del self.__traits[code]
def get_free_uid(self): """Returns the next free uid from ``posix_uid_seq``""" while 1: # We pick an UID uid = self.nextval("posix_uid_seq") # We check if the UID is in any of the reserved ranges. # If it is, we'll skip past the range (call setval), and # pick a new UID that is past the reserved range. for x in sorted(cereconf.UID_RESERVED_RANGE): # TODO: Move this check to some unit-testing stuff sometime if x[1] < x[0]: raise Errors.ProgrammingError( 'Wrong order in cereconf.UID_RESERVED_RANGE') if uid >= x[0] and uid <= x[1]: self._db.setval('posix_uid_seq', x[1]) uid = self.nextval("posix_uid_seq") # If the UID is not in use, we return it, else, we try over. try: self.query_1( """ SELECT posix_uid FROM [:table schema=cerebrum name=posix_user] WHERE posix_uid=:uid""", locals()) except Errors.NotFoundError: return int(uid)
def __init__(self, config=None, *args, **kw): """ Constructs a PasswordGenerator. :param Cerebrum.config.configuration.Configuration config: The Configuration object for the password_generator module. """ try: if config is None: self.config = load_config() else: self.config = load_config(filepath=config) # Create a local random object for increased randomness # "Use os.urandom() or SystemRandom if you require a # cryptographically secure pseudo-random number generator." # docs.python.org/2.7/library/random.html#random.SystemRandom self.lrandom = random.SystemRandom() self.dict_words = [] if self.config.passphrase_dictionary: with open(self.config.passphrase_dictionary) as fp: for line in fp: try: # assume UTF-8 encoded text-file self.dict_words.append( line.strip().decode('utf-8')) except: continue except Exception as e: raise Errors.CerebrumError('Unable to create a PasswordGenerator ' 'instance: {error}'.format(error=e))
def _autopick_homeMDB(self): """Return a valid homeMDB value to be used for the account. If the account has previously had a HomeMDB, this is reused, but only as long the MDB value is valid today, see L{cereconf.EXCHANGE_HOMEMDB_VALID}. Otherwise a random HomeMDB is selected. We don't care about the weight of the MDBs, ØFK wants everyone to be equally assigned. @rtype: string @return: One of the HomeMDB values from L{cereconf.EXCHANGE_HOMEMDB_VALID}. """ mdb_candidates = cereconf.EXCHANGE_HOMEMDB_VALID # Check if account had homeMDB earlier. If so use that, as long as it # is in one of today's valid MDBs. mdb_choice = self._get_old_homeMDB() if mdb_choice and mdb_choice in mdb_candidates: return mdb_choice # Choose a random HomeMDB, and don't care about weights: mdb_choice = random.choice(mdb_candidates.keys()) if mdb_choice is None: raise Errors.CerebrumError("Couldn't assign mdb for %s" % self.entity_id) return mdb_choice
def get_group(db, id_type, group): """Fetch a group by id. :type db: <Cerebrum.Database.Database> :param db: A Cerebrum database object. :type id_type: str :param id_type: The identifier type, 'name' or 'id' :type group: str or int :param group: The group identifier :rtype: Group or None """ gr = Factory.get('Group')(db) # Determine lookup type if id_type == 'name': lookup = gr.find_by_name elif id_type == 'id': lookup = gr.find else: raise Errors.CerebrumRPCException('Invalid id_type.') # Perform actual lookup # TODO: How do we handle NotFoundErrors? Is this correct? try: lookup(group) except Errors.NotFoundError: return None return gr
def get_account(db, id_type, account): """Fetch a group by id. :type db: <Cerebrum.Database.Database> :param db: A Cerebrum database object. :type id_type: str :param id_type: The identifier type, 'name' or 'id' :type id_type: str or int :param group: The account identifier :rtype: Account or None """ ac = Factory.get('Account')(db) if id_type == 'name': lookup = ac.find_by_name elif id_type == 'id': lookup = ac.find else: raise Errors.CerebrumRPCException('Invalid id_type.') try: lookup(account) except Errors.NotFoundError: return None return ac
def create_project(self, project_name): """Create a new project in TSD. Note that this method calls `write_db`. :param str project_name: A unique, short project name to use to identify the project. This is not the *project ID*, which is created automatically. :return str: The generated project ID for the new project. """ # Check if given project name is already in use: if tuple(self.search_tsd_projects(name=project_name, exact_match=True)): raise Errors.CerebrumError( 'Project name already taken: %s' % project_name) self.populate() self.write_db() # Set a generated project ID self.affect_external_id(self.const.system_cached, self.const.externalid_project_id) pid = self.get_next_free_project_id() self.populate_external_id(self.const.system_cached, self.const.externalid_project_id, pid) self.write_db() # Set the project name self.add_name_with_language(name_variant=self.const.ou_name_acronym, name_language=self.const.language_en, name=project_name) self.write_db() return pid
def _get_affiliate_trait(self, etype): """Get trait used to affiliate an entity with this project. :type etype: Constants.EntityType, int, str :param entity_type: The entity type """ type2trait = { self.const.entity_group: self.const.trait_project_group, self.const.entity_dns_owner: self.const.trait_project_host, self.const.entity_dns_subnet: self.const.trait_project_subnet, self.const.entity_dns_ipv6_subnet: self.const.trait_project_subnet6, } entity_type = ( etype if isinstance(etype, self.const.EntityType) else self.const.human2constant(etype, self.const.EntityType)) try: return type2trait[entity_type] except KeyError: raise Errors.CerebrumError( u"Entity type %s (%r) cannot be affilated with project." % (entity_type, etype))
def _get_ou(self, ou_id=None, stedkode=None): """ Fetch a specified OU instance. Either ou_id or stedkode must be provided. @type ou_id: int or None @param ou_id: ou_id (entity_id) if not None. @type stedkode: string (DDDDDD, where D is a digit) @param stedkode: Stedkode for OU if not None. """ ou = self.OU_class(self.db) ou.clear() try: if ou_id is not None: ou.find(ou_id) else: if len(stedkode) != 6 or not stedkode.isdigit(): raise CerebrumError("Expected 6 digits in stedkode %r" % stedkode) ou.find_stedkode(stedkode[:2], stedkode[2:4], stedkode[4:], institusjon=cereconf.DEFAULT_INSTITUSJONSNR) return ou except Errors.NotFoundError: raise CerebrumError("Unknown OU (%s)" % (ou_id and ("id=%r" % ou_id) or ("sko=%r" % stedkode))) raise Errors.UnreachableCodeError("_get_ou")
def search_members_flat(self, groupname): # TODO: add access control for who is allowed to get the members. Only # moderators of the given group? #if not self.ba.is_superuser(self.operator_id): # raise NotAuthorizedError('Only for superusers') # Raises Cerebrum.modules.bofh.errors.PermissionDenied - how to handle # these? #self.ba.can_set_trait(self.operator_id) try: self.grp.clear() self.grp.find_by_name(groupname) except Errors.NotFoundError: raise Errors.CerebrumRPCException("Group %s not found." % groupname) grp_id = self.grp.entity_id self.grp.clear() type_account = str(self.co.entity_account) member_rows = self.grp.search_members(group_id=grp_id, indirect_members=True, include_member_entity_name=True) return [{ 'member_type': type_account, 'member_id': str(row['member_id']), 'uname': row['member_name'] } for row in member_rows if row['member_type'] == self.co.entity_account]
def _get_gid(self): """Returns the next free GID from 'posix_gid_seq'""" while True: # Pick a new GID gid = self.nextval('posix_gid_seq') # We check if the GID is in any of the reserved ranges. # If it is, we'll skip past the range (call setval), and # pick a new GID that is past the reserved range. for x in sorted(cereconf.GID_RESERVED_RANGE): # TODO: Move this check to some unit-testing stuff sometime if x[1] < x[0]: raise Errors.ProgrammingError( 'Wrong order in cereconf.GID_RESERVED_RANGE') if x[0] <= gid <= x[1]: self._db.setval('posix_gid_seq', x[1]) gid = self.nextval('posix_gid_seq') # We check if the GID is in use, if not, return, else start over. try: self.query_1( """ SELECT posix_gid FROM [:table schema=cerebrum name=posix_group] WHERE posix_gid=:gid""", locals()) except Errors.NotFoundError: return gid
def __init__(self, config=None, logger=None, *args, **kw): """ Constructs a PasswordGenerator. :param Cerebrum.config.configuration.Configuration config: The Configuration object for the password_generator module. :param logging.Logger logger: Logger object to use. If `None`, this object will fetch a new logger with `Factory.get_logger('crontab')`. This is the default. """ try: if config is None: self.config = load_config() else: self.config = load_config(filepath=config) # Create a local random object for increased randomness # "Use os.urandom() or SystemRandom if you require a # cryptographically secure pseudo-random number generator." # docs.python.org/2.7/library/random.html#random.SystemRandom self.lrandom = random.SystemRandom() self.logger = logger or Utils.Factory.get_logger('console') self.dict_words = [] if self.config.passphrase_dictionary: with open(self.config.passphrase_dictionary) as fp: for line in fp: try: # assume UTF-8 encoded text-file self.dict_words.append( line.strip().decode('utf-8')) except: continue self.logger.debug('PasswordGenerator initialized') except Exception as e: raise Errors.CerebrumError('Unable to create a PasswordGenerator ' 'instance: {error}'.format(error=e))
def add_relationship(self, relationship_code, target_id): """Add a relationship of given type between this role and a target component (atom or role). @type relationship_code: int @param relationship_code: The relationship constant that defines the kind of relationship the source and target will have. """ if self.illegal_relationship(relationship_code, target_id): # TODO: is ProgrammingError the correct exception to raise here? # Frontends should be able to give better feedbacks raise Errors.ProgrammingError('Illegal relationship') self.execute( """ INSERT INTO [:table schema=cerebrum name=hostpolicy_relationship] (source_policy, relationship, target_policy) VALUES (:source, :rel, :target)""", { 'source': self.entity_id, 'rel': int(relationship_code), 'target': target_id }) self._db.log_change(self.entity_id, self.clconst.hostpolicy_relationship_add, target_id)
def ldif_outfile(tree, filename=None, default=None, explicit_default=False, max_change=None, module=cereconf): """(Open and) return LDIF outfile for <tree>. Use <filename> if specified, otherwise module.LDAP_<tree>['file'] unless <explicit_default>, otherwise return <default> (an open filehandle) if that is not None. (explicit_default should be set if <default> was opened from a <filename> argument and not from module.LDAP*['file'].) When opening a file, use SimilarSizeWriter where close() fails if the resulting file has changed more than <max_change>, or module.LDAP_<tree>['max_change'], or module.LDAP['max_change']. If max_change is unset or >= 100, just open the file normally. """ if not (filename or explicit_default): filename = getattr(module, 'LDAP_' + tree).get('file') if filename: filename = os.path.join(module.LDAP['dump_dir'], filename) if filename: if max_change is None: max_change = ldapconf(tree, 'max_change', default=ldapconf( None, 'max_change', default=100, module=module), module=module) if max_change < 100: f = SimilarSizeWriter(filename, 'w') f.max_pct_change = max_change else: f = AtomicFileWriter(filename, 'w') return f if default: return default raise _Errors.CerebrumError( 'Outfile not specified and LDAP_{0}["file"] not set'.format(tree))
def _validate_project_name(self, name): """Check if a given project name is valid. Project names are used to identify project entities in other systems, so we need to enforce some limits to avoid potential problems. Requirements ------------ Can not contain spaces TODO: Why? Can not contain commas. This is due to AD's way of identifying objects. Example: CN=username,OU=projectname,DC=tsd,DC=uio,DC=no. Can not contain the SQL wildcards ? and %. This is a convenience limit, to be able to use Cerebrum's existing API without modifications. Can not contain control characters. Can not contain characters outside of ASCII. This is to avoid unexpected encoding issues. TBD: A maximum length in the name? AD probably has a limit. As a prefix, it should be a bit less than AD's limit. In practice, we only accept regular alphanumeric characters in ASCII, in addition to some punctuation characters, like colon, dash and question marks. This would need to be extended in the future. :param str name: The name to check. :raise Errors.CerebrumError: If the given project name was not accepted """ m = re.search('[^A-Za-z0-9_\-:;\*"\'\#\&\=!\?]', name) if m: raise Errors.CerebrumError( 'Invalid characters in projectname: %s' % m.group()) if len(name) < 3: raise Errors.CerebrumError('Project name too short') if len(name) > 8: # TBD: or 6? raise Errors.CerebrumError('Project name is too long') return True
def add_cname_record(db, cname_record_name, target_name, fail_on_exists=True): """ Creates a CNAME-record. If fail_on_exists=False, it will simply return without doing anything, if the CNAME-record already exists. This is due to the method being used by OU._setup_project_hosts, which can be run several times for the same project when it is reconfigured. :param db: A Cerebrum-database instance :type db: Cerebrum.database.Database :param cname_record_name: FQDN of the CNAME-record :type cname_record_name: str :param target_name: FQDN of the target domain :type target_name: str :param fail_on_exists: True or False :type fail_on_exists: bool """ cname = CNameRecord(db) dns_owner = DnsOwner(db) constants = Factory.get('Constants') try: dns_owner.find_by_name(target_name) proxy_dns_owner_ref = dns_owner.entity_id except Errors.NotFoundError: raise Errors.NotFoundError('%s does not exist.' % target_name) dns_owner.clear() try: dns_owner.find_by_name(cname_record_name) except Errors.NotFoundError: dns_owner.populate(constants.DnsZone(cereconf.DNS_DEFAULT_ZONE), cname_record_name) dns_owner.write_db() cname_dns_owner_ref = dns_owner.entity_id try: cname.find_by_cname_owner_id(cname_dns_owner_ref) if fail_on_exists: raise Errors.RealityError('CNAME %s already exists.' % cname_record_name) except Errors.NotFoundError: cname.populate(cname_dns_owner_ref, proxy_dns_owner_ref) cname.write_db()
def get_addresses_by_affiliation(ctx, status=None, skos=None, source=None): """Get primary e-mail addresses for persons that match given criterias.""" if not source and not status: raise Errors.CerebrumRPCException('Input needed') return ctx.udc['postmaster'].get_addresses_by_affiliation(status=status, skos=skos, source=source)
def get_project_id(self): """Get the project ID of this project""" ret = self.get_external_id(id_type=self.const.externalid_project_id) if ret: return ret[0]['external_id'] raise Errors.NotFoundError( 'Mandatory project ID not found for {entity}'.format( entity=self.entity_id))
def get_account(self, uname): account = Factory.get('Account')(self.db) try: account.find_by_name(uname) except Errors.NotFoundError: log.info("Couldn't find account %s" % uname) raise Errors.CerebrumRPCException('person_notfound') else: return account
def set_password(self, uname, new_password, token, browser_token): if not self.check_token(uname, token, browser_token): return False account = self.get_account(uname) try: check_password(new_password, account) except PasswordNotGoodEnough as e: m = str(e).decode('utf-8') raise Errors.CerebrumRPCException('password_invalid', m) # All data is good. Set password account.set_password(new_password) try: account.write_db() account._db.commit() log.info("Password for %s altered." % uname) except self.db.DatabaseError, m: log.error("Error when setting password for %s: %s" % (uname, m)) raise Errors.CerebrumRPCException('error_unknown')
def __init__(self, db): """ Override __init__ to set up AD spesific constants. """ self.__super.__init__(db) # Setup AD spread constants if not hasattr(cereconf, 'AD_ACCOUNT_SPREADS'): raise Errors.CerebrumError("Missing AD_ACCOUNT_SPREADS in cereconf") self.ad_account_spreads = {} for spread_str in cereconf.AD_ACCOUNT_SPREADS: c = self.const.Spread(spread_str) self.ad_account_spreads[int(c)] = c # Setup defined ad traits if not hasattr(cereconf, 'AD_TRAIT_TYPES'): raise Errors.CerebrumError("Missing AD_TRAIT_TYPES in cereconf") self.ad_trait_types = [] for trait_str in cereconf.AD_TRAIT_TYPES: self.ad_trait_types.append(self.const.EntityTrait(trait_str))
def check_token(self, uname, token, browser_token): """Check if token and other data from user is correct.""" try: account = self.get_account(uname) except Errors.CerebrumRPCException: # shouldn't tell what went wrong return False # Check browser_token. The given browser_token may be "" but if so # the stored browser_token must be "" as well for the test to pass. bt = account.get_trait(self.co.trait_browser_token) if not bt or bt['strval'] != self.hash_token(browser_token, uname): log.info("Incorrect browser_token %s for user %s" % (browser_token, uname)) return False # Check password token. Keep track of how many times a token is # checked to protect against brute force attack (defaults to 20). pt = account.get_trait(self.co.trait_password_token) no_checks = int(pt['numval']) if no_checks > getattr(cereconf, 'INDIVIDUATION_TOKEN_ATTEMPTS', 20): log.info("No. of token checks exceeded for user %s" % uname) raise Errors.CerebrumRPCException('toomanyattempts_check') # Check if we're within time limit time_limit = now() - RelativeDateTime( minutes=cereconf.INDIVIDUATION_TOKEN_LIFETIME) if pt['date'] < time_limit: log.debug("Password token's timelimit for user %s exceeded" % uname) raise Errors.CerebrumRPCException('timeout_check') if pt and pt['strval'] == self.hash_token(token, uname): # All is fine return True log.debug("Token %s incorrect for user %s" % (token, uname)) account.populate_trait(self.co.trait_password_token, strval=pt['strval'], date=pt['date'], numval=no_checks + 1) account.write_db() account._db.commit() return False
def delete_machine(self): """Demotes Machine to a normal host""" if self.entity_id is None: raise Errors.NoEntityAssociationError( "Unable to determine which entity to delete.") self._db.log_change(self.entity_id, self.const.machine_demote, None) self.execute( """ DELETE FROM [:table schema=cerebrum name=machine_info] WHERE host_id=:e_id""", {'e_id': self.entity_id})
def get_fullname(self): """The GECOS contains the full name the user wants to be associated with POSIX account. This method's return value will also be used to generate an email-address if the posix account is not owned by an actual person.""" if self.owner_type != int(self.const.entity_person): if self.gecos is not None: return self.gecos raise Errors.NotFoundError('Name (GECOS) not set for' 'non-personal PosixUser.') return self.__super.get_fullname()
def __init__(self, db, logger, u_sprd=None, g_sprd=None, n_sprd=None, fd=None): """ Initiate database and import modules. Spreads are given in initiation and general constants which is used in more than one method. """ timer = make_timer(logger, 'Initing PosixLDIF...') from Cerebrum.modules import PosixGroup self.db = db self.logger = logger self.const = Factory.get('Constants')(self.db) self.grp = Factory.get('Group')(self.db) self.posuser = Factory.get('PosixUser')(self.db) self.posgrp = PosixGroup.PosixGroup(self.db) self.user_dn = LDIFutils.ldapconf('USER', 'dn', None) # This is an odd one -- if set to False, then id2uname should be # populated with users exported in the users export -- which makes the # group exports filter group members by *actually* exported users... self.get_name = True self.fd = fd self.spread_d = {} # Validate spread from arg or from cereconf for x, y in zip(['USER', 'FILEGROUP', 'NETGROUP'], [u_sprd, g_sprd, n_sprd]): spread = LDIFutils.map_spreads( y or getattr(cereconf, 'LDAP_' + x).get('spread'), list) if spread: self.spread_d[x.lower()] = spread if 'user' not in self.spread_d: raise Errors.ProgrammingError( "Must specify spread-value as 'arg' or in cereconf") self.account2name = dict() self.group2gid = dict() self.groupcache = defaultdict(dict) self.group2groups = defaultdict(set) self.group2users = defaultdict(set) self.group2persons = defaultdict(list) self.shell_tab = dict() self.quarantines = dict() self.user_exporter = UserExporter(self.db) if len(self.spread_d['user']) > 1: logger.warning('Exporting users with multiple spreads, ' 'ignoring homedirs from %r', self.spread_d['user'][1:]) self.homedirs = HomedirResolver(db, self.spread_d['user'][0]) self.owners = OwnerResolver(db) auth_attr = LDIFutils.ldapconf('USER', 'auth_attr', None) self.user_password = AuthExporter.make_exporter( db, auth_attr['userPassword']) timer('... done initing PosixLDIF.')
def find_by_tsd_projectname(self, project_name): """Finds Project OU by project name. This is a L{find}-method, it will populate this entity with any project it finds. :param str project_name: The short-name of the project to find. :raise NotFoundError: If the project is not found. :raise TooManyRowsError: If multiple projects matches the name (should not be possible). """ matched = self.search_tsd_projects(name=project_name, exact_match=True) if not matched: raise Errors.NotFoundError(u"Unknown project: %s" % project_name) if len(matched) != 1: raise Errors.TooManyRowsError( u"Found several OUs with given name: %s" % project_name) return self.find(matched[0]['entity_id'])
def get_person(self, id_type, ext_id): person = Factory.get('Person')(self.db) person.clear() if not hasattr(self.co, id_type): log.error("Wrong id_type: '%s'" % id_type) raise Errors.CerebrumRPCException('person_notfound') try: person.find_by_external_id(getattr(self.co, id_type), ext_id) return person except Errors.NotFoundError: log.debug("Couldn't find person with %s='%s'" % (id_type, ext_id)) # Try without leading zeros, as FS use that, and which could confuse # students. TODO: Note that this does not help if the external IDs are # stored _with_ leading zeros in the database, i.e. the opposite way. if ext_id.isdigit(): try: person.find_by_external_id(getattr(self.co, id_type), str(int(ext_id))) log.debug( "Found person %s without leading zeros in ext_id: %s" % (person.entity_id, ext_id)) return person except Errors.NotFoundError: pass # Still not found? Try to padd with zeros if it's a student number # with less than 6 digits: if (hasattr(self.co, 'externalid_studentnr') and getattr( self.co, id_type) == self.co.externalid_studentnr and len(ext_id) < 6): try: person.find_by_external_id(getattr(self.co, id_type), '%06d' % int(ext_id)) log.debug( "Found person %s with padded zeros in ext_id: %s" % (person.entity_id, ext_id)) return person except Errors.NotFoundError: pass raise Errors.CerebrumRPCException('person_notfound')
def select_extid(entity_id, id_type): """ Get preferred fnr for a given person_id. """ ext_ids = { int(r['source_system']): r['external_id'] for r in person.search_external_ids(entity_id=entity_id, source_system=sys_lookup_order, id_type=id_type) } for pref in sys_lookup_order: if ext_ids.get(int(pref)): return ext_ids[int(pref)] raise Errors.NotFoundError("No fnr for person_id=%r" % (entity_id, ))
def group_remove_member(self, group_id_type, group_id, member_id_type, member_id): """Remove a member from a group. :type group_id_type: str :param group_id_type: Group identifier type, 'id' or 'group_name' :type group_id: str :param group_id: Group identifier :type member_id_type: str :param member_id_type: Member identifier type, 'id' or 'account_name' :type member_id: str :param member_id: Member identifier :rtype: boolean """ # Get the group gr = Utils.get(self.db, 'group', group_id_type, group_id) if not gr: raise Errors.CerebrumRPCException( 'Group %s:%s does not exist.' % (group_id_type, group_id)) # Perform auth check self.ba.can_alter_group(self.operator_id, gr) # Get the member we want to add member = Utils.get(self.db, 'entity', member_id_type, member_id) if not member: raise Errors.CerebrumRPCException( 'Entity %s:%s does not exist.' % (member_id_type, member_id)) if not gr.has_member(member.entity_id): return False GroupAPI.remove_member(gr, member.entity_id) return True
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"