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 x[0] <= 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 get_sito_uname(self, fnr, name): """ Generate a SITO account_name. SITO accounts have their own namespace. SITO usernames are named "XXXNNN-s", where: XXX = letters generated based on *name* NNN = unique numeric identifier s = The letter s """ cstart = 0 step = 1 co = Factory.get('Constants')(self._db) try: self._get_person_by_extid(co.externalid_fodselsnr, fnr) except Errors.NotFoundError: raise Errors.ProgrammingError( "Trying to create account for person:%s that does not exist!" % fnr) # getting here implies that person does not have a previous account in # BAS create a new username inits = self.get_initials(name) return self.get_serial(inits, cstart, step=step, postfix=self.sito_postfix)
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 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 __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 __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) 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.groupcache = defaultdict(dict) self.group2groups = defaultdict(set) self.group2users = defaultdict(set) self.group2persons = defaultdict(list) timer('... done initing PosixLDIF.')
def _find_affiliations(cerebrum_person, hr_affs, affiliation_map, source_system, mode): consider_affiliations = filter(lambda x: x, affiliation_map().values()) cerebrum_affiliations = cerebrum_person.list_affiliations( person_id=cerebrum_person.entity_id, status=consider_affiliations, source_system=source_system) in_hr = map( lambda d: tuple( sorted(filter(lambda (k, v): k != u'precedence', d.items()))), hr_affs) in_cerebrum = map( lambda x: tuple( sorted( filter_elements( translate_keys( x, { u'ou_id': u'ou_id', u'affiliation': u'affiliation', u'status': u'status' })))), cerebrum_affiliations) if mode == u'remove': return [ dict( filter(lambda (k, v): k in (u'ou_id', u'affiliation'), x) + ((u'source', source_system), )) for x in set(in_cerebrum) - set(in_hr) ] elif mode == u'add': to_add = set(in_hr) - set(in_cerebrum) to_ensure = set(in_hr) & set(in_cerebrum) return [dict(x) for x in to_add | to_ensure] else: from Cerebrum import Errors raise Errors.ProgrammingError( 'Invalid mode {} supplied to _find_affiliations'.format( repr(mode)))
def get_email_target_info(self, target_id=None, target_entity=None): """Return the entity_type and entity_id of the entity the EmailTarget points at. Look up by _either_ target_id or target_entity. :type target_id: int :param target_id: The EmailTargets id. :type target_entity: int :param target_entity: The targets target entity id. :rtype: typle :return: (entity_id, target_entity_id, target_entity_type, hard_quota, soft_quota).""" self.et.clear() self.eq.clear() if target_id: self.et.find(target_id) try: self.eq.find(target_id) except Errors.NotFoundError: self.eq.clear() elif target_entity: self.et.find_by_target_entity(target_entity) try: self.eq.find(self.et.entity_id) except Errors.NotFoundError: self.eq.clear() else: self.db.rollback() raise Errors.ProgrammingError( 'Must define either target_id og target_entity') ret = (self.et.entity_id, self.et.email_target_entity_id, self.et.email_target_entity_type, self.eq.get_quota_hard(), self.eq.get_quota_soft()) self.db.rollback() return ret
def process_person(self, person): """Sync spreads between a person and it's accounts.""" # str2const is something we need if we create new accounts. self._make_str2const() if not self._ac: self._ac = Factory.get('Account')(self.db) def _diff_aff(new_aff, old_aff): """Return two lists. One with affiliations to remove and one with affiliations to add.""" remove = list() add = list() for tpl in new_aff: if tpl not in old_aff: add.append(tpl) for tpl in old_aff: if tpl not in new_aff: remove.append(tpl) return remove, add # Find the person's affiliations person_affiliations = [] for row in person.get_affiliations(): person_affiliations.append((row['ou_id'], row['affiliation'])) # If the person in question doesn't have any affiliations, we # don't create any account. accounts = person.get_accounts(filter_expired=False) if len(accounts) == 0: if person_affiliations: ac = self._maybe_create_account(person) if ac: self.logger.info("Person '%d' got new account '%s'." % (person.entity_id, ac.account_name)) # Take care of person affiliations too else: self.logger.info( "Person '%d' have no affiliations. No account created." % person.entity_id) # Clean up in spreads change = False for spread in procconf.PERSON_SPREADS: if person_affiliations: if not person.has_spread(int(self.str2const[spread])): person.add_spread(int(self.str2const[spread])) self.logger.info("Person '%d' got spread '%s'." % (person.entity_id, spread)) change = True else: if person.has_spread(int(self.str2const[spread])): person.delete_spread(int(self.str2const[spread])) self.logger.info("Person '%d' have no affiliations. " "Spread '%s' deleted." % (person.entity_id, spread)) change = True if change: person.write_db() # Loop over the person's account(s) and correct affiliations # and spreads for account in person.get_accounts(filter_expired=False): account_affiliations = [] self._ac.clear() self._ac.find(account['account_id']) # Update affiliations for row in self._ac.get_account_types(filter_expired=False): account_affiliations.append((row['ou_id'], row['affiliation'])) rem, add = _diff_aff(person_affiliations, account_affiliations) change = False for r in rem: self._ac.del_account_type(r[0], r[1]) change = True self.logger.info("Account '%s' removed type '%s', '%s'." % (self._ac.account_name, r[0], r[1])) for a in add: self._ac.set_account_type(a[0], a[1]) change = True self.logger.info("Account '%s' added type '%s', '%s'." % (self._ac.account_name, a[0], a[1])) # Set expire_date if no account_types if not person_affiliations: # Don't reset expire_date on an already expired account if not self._ac.expire_date: self._ac.expire_date = DateTime.now() change = True self.logger.info("Account '%s' set to expired." % self._ac.account_name) else: # The account is about to get account_types so we restore it # by removing expire_date if self._ac.expire_date: self._ac.expire_date = None change = True self.logger.info("Account '%s' is restored." % self._ac.account_name) self._ac_add_new_traits(self._ac) # TODO: Limit the removal of spreads to types known by proc_entity # Update account spreads (if set in the config) if (hasattr(procconf, 'ACCOUNT_SPREADS') and hasattr(procconf, 'OU2ACCOUNT_SPREADS')): raise Errors.ProgrammingError( "Both ACCOUNT_SPREADS and OU2ACCOUNT_SPREADS in procconf.") elif (hasattr(procconf, 'ACCOUNT_SPREADS') and procconf.ACCOUNT_SPREADS): acc_spreads = [] for i in person_affiliations: aff_str = str(_PersonAffiliationCode(i[1])) if aff_str not in procconf.ACCOUNT_SPREADS: continue spreads = [ int(self.str2const[s]) for s in procconf.ACCOUNT_SPREADS[aff_str] ] acc_spreads += [s for s in spreads if s not in acc_spreads] for row in self._ac.get_spread(): # Annoying "feature". get_spread() return a tuple of # one-element tuples. if int(row[0]) not in acc_spreads: self._ac.delete_spread(row[0]) self.logger.info("Account '%s' removed spread '%s'." % (self._ac.account_name, str(_SpreadCode(int(row[0]))))) change = True for spread in acc_spreads: if not self._ac.has_spread(spread): self._ac.add_spread(spread) self.logger.info("Account '%s' added spread '%s'." % (self._ac.account_name, str(_SpreadCode(int(spread))))) change = True elif (hasattr(procconf, 'OU2ACCOUNT_SPREADS') and procconf.OU2ACCOUNT_SPREADS): self._populate_ou2spread() acc_spreads = [] for ou, aff in person_affiliations: for s in self.ou2spread[ou]: if s not in acc_spreads: acc_spreads.append(s) for row in self._ac.get_spread(): # Annoying "feature". get_spread() return a tuple of # one-element tuples. if int(row[0]) not in acc_spreads: self._ac.delete_spread(row[0]) self.logger.info("Account '%s' removed spread '%s'." % (self._ac.account_name, str(_SpreadCode(int(row[0]))))) change = True for spread in acc_spreads: if not self._ac.has_spread(spread): self._ac.add_spread(spread) self.logger.info("Account '%s' added spread '%s'." % (self._ac.account_name, str(_SpreadCode(int(spread))))) change = True if change: self._ac.write_db()
def search( self, group_id=None, member_id=None, indirect_members=False, admin_id=None, admin_by_membership=False, moderator_id=None, moderator_by_membership=False, spread=None, name=None, description=None, group_type=None, filter_expired=True, creator_id=None, expired_only=False, fetchall=True, ): """Search for groups satisfying various filters. Search **for groups** where the results are filtered by a number of criteria. There are many filters that can be specified; the result returned by this method satisfies all of the filters. Not all of the filters are compatible (check the documentation) If a filter is None, it means that it will not be applied. Calling this method without any arguments will return all non-expired groups registered in group_info. :type group_id: int or sequence thereof or None. :param group_id: Group ids to look for. This is the most specific filter that can be given. With this filter, only the groups matching the specified id(s) will be returned. This filter cannot be combined with L{member_id}. :type member_id: int or sequence thereof or None. :param member_id: The resulting group list will be filtered by membership - only groups that have members specified by member_id will be returned. If member_id is a sequence, then a group g1 is returned if any of the ids in the sequence are a member of g1. This filter cannot be combined with L{group_id}. :type indirect_members: bool :param indirect_members: This parameter controls how the L{member_id} filter is applied. When False, only groups where L{member_id} is a/are direct member(s) will be returned. When True, the membership of L{member_id} does not have to be direct; if group g2 is a member of group g1, and member_id m1 is a member of g2, specifying indirect_members=True will return g1 as well as g2. Be careful, for some situations this can drastically increase the result size. This filter makes sense only when L{member_id} is set. :type admin_id: int or sequence thereof or None. :param admin_id: The resulting group list will be filtered by adminship - only groups that have admins specified by admin_id will be returned. If admin_id is a sequence, then a group g1 is returned if any of the ids in the sequence are a admin of g1. :type admin_by_membership: bool :param admin_by_membership: This parameter controls how the L{admin_id} filter is applied. When False, only groups where L{admin_id} is a/are direct admin(s) will be returned. When True, the adminship of L{admin_id} does not have to be direct; if group g2 is a admin of group g1, and admin_id m1 is a member of g2, specifying indirect_admins=True will return g1. This filter makes sense only when L{admin_id} is set. :type moderator_id: int or sequence thereof or None. :param moderator_id: The resulting group list will be filtered by moderatorship - only groups that have moderators specified by moderator_id will be returned. If moderator_id is a sequence, then a group g1 is returned if any of the ids in the sequence are a moderator of g1. :type moderator_by_membership: bool :param moderator_by_membership: This parameter controls how the L{moderator_id} filter is applied. When False, only groups where L{moderator_id} is a/are direct moderator(s) will be returned. When True, the moderatorship of L{moderator_id} does not have to be direct; if group g2 is a moderator of group g1, and moderator_id m1 is a member of g2, specifying indirect_moderators=True will return g1. This filter makes sense only when L{moderator_id} is set. :type spread: int or SpreadCode or sequence thereof or None. :param spread: Filter the resulting group list by spread. I.e. only groups with specified spread(s) will be returned. :type name: basestring :param name: Filter the resulting group list by name. The name may contain SQL wildcard characters. :type description: basestring :param description: Filter the resulting group list by group description. The description may contain SQL wildcard characters. :type group_type: int or GroupTypeCode or sequence thereof or None :param description: Filter the resulting group list by group type - i.e. only return groups with one of the given group types. :type filter_expired: bool :param filter_expired: Filter the resulting group list by expiration date. If set, do NOT return groups that have expired (i.e. have group_info.expire_date in the past relative to the call time). :type expired_only: bool :param expired_only: Filter the resulting group list by expiration date. If set, return ONLY groups that have expired_date set and expired (relative to the call time). N.B. filter_expired and filter_expired are mutually exclusive :rtype: iterable (yielding db-rows with group information) :return: An iterable (sequence or a generator) that yields successive db-rows matching all of the specified filters. Regardless of the filters, any given group_id is guaranteed to occur at most once in the result. The keys available in db_rows are the content of the group_info table and group's name (if it does not exist, None is assigned to the 'name' key). """ # Sanity check: if indirect members is specified, then at least we # need one id to go on. if indirect_members and not member_id: raise Errors.ProgrammingError( 'Cannot use indirect_members without member_id') if admin_by_membership and not admin_id: raise Errors.ProgrammingError( 'Cannot use indirect_admins without admin_id') if moderator_by_membership and not moderator_id: raise Errors.ProgrammingError( 'Cannot use indirect_moderators without moderator_id') # Sanity check: it is probably a bad idea to allow specifying both. if member_id and group_id: raise Errors.ProgrammingError( 'member_id and group_id cannot be used simultaneously') def search_transitive_closure(member_id): """Return all groups where member_id is/are indirect member(s). :type member_id: int or sequence thereof. :param member_id: We are looking for groups where L{member_id} is/are indirect member(s). :rtype: set (of group_ids (ints)) :result: Set of group_ids where member_id is/are indirect members. """ result = set() if not isinstance(member_id, (tuple, set, list)): member_id = (member_id, ) # workset contains ids of the entities that are members. in each # iteration we are looking for direct parents of whatever is in # workset. workset = set(int(x) for x in member_id) while workset: tmp = workset workset = set() for row in self.search( member_id=tmp, indirect_members=False, # We need to be *least* restrictive # here. Final filtering will take care # of 'expiredness'. filter_expired=False): group_id = int(row["group_id"]) if group_id in result: continue result.add(group_id) if group_id not in workset: workset.add(group_id) return result stmt = """ SELECT DISTINCT gi.group_id AS group_id, en.entity_name AS name, gi.description AS description, gi.visibility AS visibility, gi.creator_id AS creator_id, ei.created_at AS created_at, gi.expire_date AS expire_date, gi.group_type AS group_type FROM [:table schema=cerebrum name=group_info] gi LEFT OUTER JOIN [:table schema=cerebrum name=entity_name] en ON en.entity_id = gi.group_id AND en.value_domain = :vdomain LEFT OUTER JOIN [:table schema=cerebrum name=entity_info] ei ON ei.entity_id = gi.group_id AND ei.entity_type = :entity_type {extra_tables} {where} """ extra_tables = [] where = [] binds = { "vdomain": int(self.const.group_namespace), "entity_type": int(self.const.entity_group), } # # group_id filter if group_id is not None: where.append(argument_to_sql(group_id, "gi.group_id", binds, int)) # # member_id filters (all of them) if member_id is not None: if indirect_members: # NB! This can be a very large group set. group_ids = search_transitive_closure(member_id) if not group_ids: return [] where.append( argument_to_sql(group_ids, "gi.group_id", binds, int)) else: extra_tables.append( "[:table schema=cerebrum name=group_member] gm") where.append("(gi.group_id = gm.group_id)") where.append( argument_to_sql(member_id, "gm.member_id", binds, int)) # # admin_id filter (all of them) if admin_id is not None: extra_tables.append("[:table schema=cerebrum name=group_admin] ga") where.append("(gi.group_id = ga.group_id)") if admin_by_membership: admin_ids = [admin_id] for group in self.search(member_id=admin_id): admin_ids.append(group['group_id']) where.append( argument_to_sql(admin_ids, "ga.admin_id", binds, int)) else: where.append( argument_to_sql(admin_id, "ga.admin_id", binds, int)) # # moderator_id filter (all of them) if moderator_id is not None: extra_tables.append( "[:table schema=cerebrum name=group_moderator] gmod") where.append("(gi.group_id = gmod.group_id)") if moderator_by_membership: moderator_ids = [moderator_id] for group in self.search(member_id=moderator_id): moderator_ids.append(group['group_id']) where.append( argument_to_sql(moderator_ids, "gmod.moderator_id", binds, int)) else: where.append( argument_to_sql(moderator_id, "gmod.moderator_id", binds, int)) # # spread filter if spread is not None: extra_tables.append( "[:table schema=cerebrum name=entity_spread] es") where.append("(gi.group_id = es.entity_id)") where.append(argument_to_sql(spread, "es.spread", binds, int)) # # name filter if name is not None: name = prepare_string(name) where.append("(LOWER(en.entity_name) LIKE :name)") binds["name"] = name # description filter if description is not None: description = prepare_string(description) where.append("(LOWER(gi.description) LIKE :description)") binds["description"] = description # # group type filter if group_type is not None: where.append( argument_to_sql(group_type, "gi.group_type", binds, int)) # # expired filter if filter_expired: where.append("(gi.expire_date IS NULL OR gi.expire_date > [:now])") # # creator_id filter if creator_id is not None: where.append( argument_to_sql(creator_id, "gi.creator_id", binds, int)) # # expired_only filter if expired_only: where.append("(gi.expire_date IS NOT NULL AND gi.expire_date < " "[:now])") prepared = stmt.format( extra_tables=(', ' + ', '.join(extra_tables) if extra_tables else ''), where='WHERE ' + ' AND '.join(where) if where else '', ) return self.query(prepared, binds, fetchall=fetchall)