def delete(self): """Delete the group, along with its EmailTarget.""" et = Email.EmailTarget(self._db) ea = Email.EmailAddress(self._db) epat = Email.EmailPrimaryAddressTarget(self._db) # If there is not associated an EmailTarget with the group, call delete # from the superclass. try: et.find_by_target_entity(self.entity_id) except Errors.NotFoundError: return super(GroupUiOMixin, self).delete() # An EmailTarget exists, so we try to delete its primary address. try: epat.find(et.entity_id) epat.delete() except Errors.NotFoundError: pass # We remove all the EmailTargets addresses. try: for row in et.get_addresses(): ea.clear() ea.find(row['address_id']) ea.delete() except Errors.NotFoundError: pass # Delete the EmailTarget et.delete() # Finally! Delete the group! super(GroupUiOMixin, self).delete()
def _create_distgroup_mailaddr(self, et): ea = Email.EmailAddress(self._db) # move this to a variable # no need to wash the address, group will not be created # if the name is not valid for Exchange lp = "%s%s" % (cereconf.DISTGROUP_PRIMARY_ADDR_PREFIX, self.group_name) dom = Email.EmailDomain(self._db) dom.find_by_domain(cereconf.DISTGROUP_DEFAULT_DOMAIN) addr_str = lp + '@' + cereconf.DISTGROUP_DEFAULT_DOMAIN try: ea.find_by_local_part_and_domain(lp, dom.entity_id) if ea.email_addr_target_id != et.entity_id: raise self._db.IntegrityError("Address %s is already taken!" % addr_str) except Errors.NotFoundError: ea.populate(lp, dom.entity_id, et.entity_id, expire=None) ea.write_db() epat = Email.EmailPrimaryAddressTarget(self._db) try: epat.find(ea.email_addr_target_id) epat.populate(ea.entity_id) except Errors.NotFoundError: epat.clear() epat.populate(ea.entity_id, parent=et) epat.write_db()
def email_rt_delete(self, operator, queuename): """ Delete RT list. """ queue, host = self._resolve_rt_name(queuename) rt_dom = self._get_email_domain_from_str(host) self.ba.can_rt_delete(operator.get_entity_id(), domain=rt_dom) et = Email.EmailTarget(self.db) ea = Email.EmailAddress(self.db) epat = Email.EmailPrimaryAddressTarget(self.db) result = [] for target_id in self.__get_all_related_rt_targets(queuename): try: et.clear() et.find(target_id) except Errors.NotFoundError: continue epat.clear() try: epat.find(et.entity_id) except Errors.NotFoundError: pass else: epat.delete() for r in et.get_addresses(): addr = '%(local_part)s@%(domain)s' % r ea.clear() ea.find_by_address(addr) ea.delete() result.append({'address': addr}) et.delete() return result
def _get_person_owner_attributes(self, owner): """Return a dict with person tidbits for LDAP. FIXME: THIS MUST NOT FAIL with NotFoundError. """ def extract_email_from_account(account_id): try: et = Email.EmailTarget(self._db) et.find_by_target_entity(account_id) return [ "%s@%s" % (r['local_part'], r['domain']) for r in et.get_addresses(special=False) ] except Errors.NotFoundError: return [] # end extract_email_from_account result = dict() # uid try: account_id = owner.get_primary_account() acc = Factory.get("Account")(self._db) acc.find(account_id) result["uid"] = acc.account_name except Errors.NotFoundError: result["uid"] = None # ALL unames must go into 'voipSipUri'. And so must all e-mail # addresses. result["voipSipUri"] = list() for row in acc.search(owner_id=owner.entity_id): result["voipSipUri"].append(self._voipify(row["name"], None)) for address in extract_email_from_account(row["account_id"]): mangled = self._voipify(address, None) result["voipSipUri"].append(mangled) # mail - primary e-mail address. if result['uid']: try: et = Email.EmailTarget(self._db) et.find_by_target_entity(acc.entity_id) epat = Email.EmailPrimaryAddressTarget(self._db) epat.find(et.entity_id) ea = Email.EmailAddress(self._db) ea.find(epat.get_address_id()) result["mail"] = ea.get_address() except Errors.NotFoundError: result["mail"] = None # cn - grab system cached try: p_name = owner.get_name( self.const.system_cached, getattr(self.const, cereconf.DEFAULT_GECOS_NAME)) except Errors.NotFoundError: p_name = None result["cn"] = p_name return result
def _split_email_address(self, addr, with_checks=True): """Split an e-mail address into local part and domain. Additionally, perform certain basic checks to ensure that the address looks sane. @type addr: basestring @param addr: E-mail address to split, spelled as 'foo@domain'. @type with_checks: bool @param with_checks: Controls whether to perform local part checks on the address. Occasionally we may want to sidestep this (e.g. when *removing* things from the database). @rtype: tuple of (basestring, basestring) @return: A pair, local part and domain extracted from the L{addr}. """ from Cerebrum.modules import Email if addr.count('@') == 0: raise CerebrumError("E-mail address (%s) must include domain" % addr) lp, dom = addr.split('@') if addr != addr.lower() and \ dom not in cereconf.LDAP['rewrite_email_domain']: raise CerebrumError( "E-mail address (%s) can't contain upper case letters" % addr) if not with_checks: return lp, dom ea = Email.EmailAddress(self.db) if not ea.validate_localpart(lp): raise CerebrumError("Invalid localpart '%s'" % lp) return lp, dom
def sympa_create_list_alias(self, operator, listname, address, yes_no_force='No'): """ Create a secondary name for an existing Sympa list. """ force = self._is_yes(yes_no_force) # The first thing we have to do is to locate the delivery # host. Postmasters do NOT want to allow people to specify a different # delivery host for alias than for the list that is being aliased. So, # find the ml's ET and fish out the server_id. self._validate_sympa_list(listname) local_part, domain = self._split_email_address(listname) ed = self._get_email_domain_from_str(domain) email_address = Email.EmailAddress(self.db) email_address.find_by_local_part_and_domain(local_part, ed.entity_id) try: delivery_host = self._get_server_from_address(email_address) except CerebrumError: raise CerebrumError("Cannot alias list %s (missing delivery host)", listname) # TODO: Look at perms (are now done by _register) self._create_sympa_list_alias(operator, listname, address, delivery_host, force_alias=force) return { 'target': listname, 'alias': address, }
def update_email_address(address, group): et, ea = get_email_target_and_address(address) if et: if et.email_target_type != co.email_target_multi: logger.error("Wrong existing target type for <%s>", address) return if et.email_target_entity_id == group.entity_id: logger.debug("Up-to-date <%s>", address) return et.email_target_entity_id = group.entity_id logger.info("Updating <%s>", address) et.write_db() else: et = Email.EmailTarget(db) try: et.find_by_target_entity(group.entity_id) logger.info("Added address <%s>", address) except Errors.NotFoundError: et.populate(co.email_target_multi, target_entity_type=co.entity_group, target_entity_id=group.entity_id) et.write_db() logger.info("Created <%s>", address) ed = Email.EmailDomain(db) lp, dom = address.split('@') ed.find_by_domain(dom) ea = Email.EmailAddress(db) ea.populate(lp, ed.entity_id, et.entity_id) ea.write_db()
def get_address(address_id): ea = Email.EmailAddress(db) ea.find(address_id) ed = Email.EmailDomain(db) ed.find(ea.email_addr_domain_id) return "%s@%s" % (ea.email_addr_local_part, ed.rewrite_special_domains(ed.email_domain_name))
def validate_new_uname(self, domain, uname): """Check that the requested username is legal and free""" # TBD: will domain ever be anything else? if domain == self.const.account_namespace: ea = Email.EmailAddress(self._db) for row in ea.search(local_part=uname, filter_expired=False): return False return self.__super.validate_new_uname(domain, uname)
def read_addr(self): mail_addr = Email.EmailAddress(self._db) for row in mail_addr.list_email_addresses_ext(): a_id, t_id = int(row['address_id']), int(row['target_id']) lp, dom = row['local_part'], row['domain'] addr = self._build_addr(lp, dom) self.targ2addr[t_id].add(addr) self.aid2addr[a_id] = addr
def get_email_target_and_address(address): ea = Email.EmailAddress(db) try: ea.find_by_address(address) except Errors.NotFoundError: return (None, None) et = Email.EmailTarget(db) et.find(ea.email_addr_target_id) return (et, ea)
def _request_list_localpart_domain(self, operator, listname, force=False): """ Request localpart and domain for new listname. This method checks if the given user can create a list with the given name. It will raise an error if the list cannot be created for the following reasons: - Mailing list exists - Mailing list name is illegal - Local part is a user, and the operator is not permitted to override this @param operator: The calling operator @param listname: The listname to validate and split into localpart and domain. @param force: True if the operation should be forced, even if a user exists with the same localpart. @return: tuple(str(localpart), str(somain)) """ local_part, domain = self._split_email_address(listname) ed = self._get_email_domain_from_str(domain) operator_id = operator.get_entity_id() self.ba.can_email_list_create(operator_id, ed) email_address = Email.EmailAddress(self.db) # First, check whether the address already exists try: email_address.find_by_local_part_and_domain( local_part, ed.entity_id) except Errors.NotFoundError: pass else: raise CerebrumError("Mail address %s already exists" % listname) # Then check whether the mailing list name is a legal one. if not (self._is_ok_mailing_list_name(local_part) or self.ba.is_postmaster(operator_id)): raise CerebrumError("Illegal mailing list name '%s'" % listname) # Then, check whether there is a user name equal to local_part. try: self._get_account(local_part, idtype='name') except CerebrumError: pass else: if not (local_part in ('drift', ) or (self.ba.is_postmaster(operator_id) and force)): # TBD: This exception list should probably not be hardcoded # here -- but it's not obvious whether it should be a cereconf # value (implying that only site admins can modify the list) # or a database table. raise CerebrumError("%s is an existing username" % local_part) return (local_part, domain)
def test_phonenumberchange_delay(self): """Fresh phone numbers should not be used if config says so.""" cereconf.INDIVIDUATION_PHONE_TYPES['system_fs']['types']['contact_mobile_phone']['delay'] = 7 cereconf.INDIVIDUATION_PHONE_TYPES['system_fs']['types']['contact_private_mobile']['delay'] = 7 ou = Factory.get('OU')(self.db) co = Factory.get('Constants')(self.db) ou.find_stedkode(0, 0, 0, 0) pe = self.createPerson(first_name='Miss', last_name='Test', studnr='007') ac = self.createAccount(pe, 'mstest') pe.populate_affiliation(source_system=co.system_fs, ou_id=ou.entity_id, affiliation=co.affiliation_student, status=co.affiliation_status_student_aktiv) pe.write_db() pe.populate_contact_info(source_system=co.system_fs, type=co.contact_mobile_phone, value='98012345') pe.write_db() # Create e-mail address for user ed = Email.EmailDomain(self.db) ed.populate('example.com', 'For testing') ed.write_db() et = Email.EmailTarget(self.db) et.populate(co.email_target_account, ac.entity_id, co.entity_account) et.write_db() ea = Email.EmailAddress(self.db) ea.populate('test', ed.entity_id, et.entity_id) ea.write_db() epa = Email.EmailPrimaryAddressTarget(self.db) epa.populate(ea.entity_id, et) epa.write_db() self.db.commit() # hack the create_date in change_log self.db.execute(""" UPDATE [:table schema=cerebrum name=change_log] SET tstamp = :tid WHERE subject_entity = :s_id AND change_type_id = :ct_id""", {'s_id': pe.entity_id, 'ct_id': co.person_create, 'tid': DateTime(2009, 10, 4),}) self.db.commit() d = self.client.callRemote('generate_token', id_type="externalid_studentnr", ext_id='007', username='******', phone_no='98012345', browser_token='a') d = self.assertFailure(d, error.Error) # TODO: test that sendmail is called with the correct to-address return d
def read_addr(self): mail_addr = Email.EmailAddress(self._db) # Handle "magic" domains. # local_part@magic_domain # defines # local_part@[all domains with category magic_domains], # overriding any entry for that local_part in any of those # domains. glob_addr = {} for domain_category in ( self.const.email_domain_category_uio_globals, ): domain = str(domain_category) lp_dict = {} glob_addr[domain] = lp_dict # Fill glob_addr[magic domain][local_part] for row in mail_addr.list_email_addresses_ext(domain): lp_dict[row['local_part']] = row for row in self.mail_dom.list_email_domains_with_category( domain_category): # Alias glob_addr[domain name] to glob_addr[magic domain], # for later "was local_part@domain overridden" check. glob_addr[row['domain']] = lp_dict # Update dicts 'targ2addr' and 'aid2addr' with the # override addresses. for lp, row2 in lp_dict.items(): # Use 'row', and not 'row2', for domain. Using 'dom2' # would give us 'UIO_GLOBALS'. addr = self._build_addr(lp, row['domain']) t_id = int(row2['target_id']) self.targ2addr[t_id].add(addr) # Note: We don't need to update aid2addr here, as # addresses @UIO_GLOBALS aren't allowed to be primary # addresses. for row in mail_addr.list_email_addresses_ext(): lp, dom = row['local_part'], row['domain'] if (dom == "ulrik.uio.no"): self.targ2ulrik_addr[int(row["target_id"])] = lp + "@" + dom # If this address is in a domain that is subject to overrides # from "magic" domains, and the local_part was overridden, skip # this row from being added to targ2addr. addr = self._build_addr(lp, dom) a_id, t_id = [int(row[x]) for x in ('address_id', 'target_id')] self.aid2addr[a_id] = addr if dom in glob_addr and lp in glob_addr[dom]: continue self.targ2addr[t_id].add(addr) # look for primary email changes that are still pending in the event # log self.read_pending_primary_email()
def find_email_address(address): """Looks up an email address. :param int/str address: Email address entity ID or FQDA :rtype Email.EmailAddress: :return: the email address object """ ea = Email.EmailAddress(db.connection) lookup = ea.find_by_address if isinstance(address, (int, long)): lookup = ea.find try: lookup(address) except (Errors.NotFoundError, ValueError): abort(404, message=u"No such email address {}".format(address)) return ea
def update_email_addresses(self): # Overriding default update_email_addresses as NIH does not require # email_server, Jazz, 2011-05-22 # Find, create or update a proper EmailTarget for this # account. et = Email.EmailTarget(self._db) target_type = self.const.email_target_account if self.is_expired() or self.is_reserved(): target_type = self.const.email_target_deleted changed = False try: et.find_by_email_target_attrs(target_entity_id=self.entity_id) if et.email_target_type != target_type: changed = True et.email_target_type = target_type except Errors.NotFoundError: # We don't want to create e-mail targets for reserved or # deleted accounts, but we do convert the type of existing # e-mail targets above. if target_type == self.const.email_target_deleted: return et.populate(target_type, self.entity_id, self.const.entity_account) et.write_db() # For deleted/reserved users, set expire_date for all of the # user's addresses, and don't allocate any new addresses. ea = Email.EmailAddress(self._db) if changed and cereconf.EMAIL_EXPIRE_ADDRESSES is not False: if target_type == self.const.email_target_deleted: seconds = cereconf.EMAIL_EXPIRE_ADDRESSES * 86400 expire_date = self._db.DateFromTicks(time.time() + seconds) else: expire_date = None for row in et.get_addresses(): ea.clear() ea.find(row['address_id']) ea.email_addr_expire_date = expire_date ea.write_db() # Active accounts shouldn't have an alias value (it is used # for failure messages) if changed and target_type == self.const.email_target_account: if et.email_target_alias is not None: et.email_target_alias = None et.write_db() if target_type == self.const.email_target_deleted: return self._update_email_address_domains(et)
def _get_email_target_and_address(self, address): # Support DistributionGroup email target lookup try: return super(EmailCommands, self)._get_email_target_and_address(address) except CerebrumError as e: # Not found, maybe distribution group? try: dlgroup = Utils.Factory.get("DistributionGroup")(self.db) dlgroup.find_by_name(address) et = Email.EmailTarget(self.db) et.find_by_target_entity(dlgroup.entity_id) epa = Email.EmailPrimaryAddressTarget(self.db) epa.find(et.entity_id) ea = Email.EmailAddress(self.db) ea.find(epa.email_primaddr_id) return et, ea except Errors.NotFoundError: raise e
def update_email_addresses(self): """Update an accounts email addresses and quotas.""" spreads = [r['spread'] for r in self.get_spread()] if self.const.spread_uio_imap in spreads: # Make sure the email target of this account is associated # with an appropriate email server. We must do this before # super, since without an email server, no target or address # will be created. self._UiO_update_email_server(self.const.email_server_type_cyrus) self.update_email_quota(spread=self.const.spread_uio_imap) return self.__super.update_email_addresses() elif self.const.spread_exchange_account in spreads: self.__super.update_email_addresses() # Append the default domain for exchange accounts! This should # probably be done elsewhere, but the code is so complex, that # we'll have to live with this solution, until we redesign the # email-module, or force the postmasters to write their own # address-management system. et = Factory.get('EmailTarget')(self._db) ea = Email.EmailAddress(self._db) ed = Email.EmailDomain(self._db) try: ed.find_by_domain( cereconf.EXCHANGE_DEFAULT_ADDRESS_PLACEHOLDER) et.find_by_target_entity(self.entity_id) except Errors.NotFoundError: return else: try: ea.find_by_local_part_and_domain(self.account_name, ed.entity_id) except Errors.NotFoundError: ea.populate(self.account_name, ed.entity_id, et.entity_id, expire=None) ea.write_db() return self.update_email_quota( spread=self.const.spread_exchange_account)
def sympa_remove_list_alias(self, operator, alias): """ Remove Sympa list aliases. """ lp, dom = self._split_email_address(alias, with_checks=False) ed = self._get_email_domain_from_str(dom) self.ba.can_email_list_create(operator.get_entity_id(), ed) ea = Email.EmailAddress(self.db) et = Email.EmailTarget(self.db) for addr_format, pipe in self._sympa_addr2alias: addr = addr_format % { "local_part": lp, "domain": dom, } try: ea.clear() ea.find_by_address(addr) except Errors.NotFoundError: # Even if one of the addresses is missing, it does not matter # -- we are removing the alias anyway. The right thing to do # here is to continue, as if deletion worked fine. Note that # the ET belongs to the original address, not the alias, so if # we don't delete it when the *alias* is removed, we should # still be fine. continue try: et.clear() et.find(ea.email_addr_target_id) except Errors.NotFoundError: raise CerebrumError("Could not find e-mail target for %s" % addr) # nuke the address, and, if it's the last one, nuke the target as # well. self._remove_email_address(et, addr) return {'alias': alias}
def email_rt_add_address(self, operator, queuename, address): """ RT add address. """ queue, host = self._resolve_rt_name(queuename) rt_dom = self._get_email_domain_from_str(host) self.ba.can_rt_address_add(operator.get_entity_id(), domain=rt_dom) et = self._get_rt_email_target(queue, host) lp, dom = self._split_email_address(address) ed = self._get_email_domain_from_str(dom) if host != dom: self.ba.can_email_address_add(operator.get_entity_id(), domain=ed) ea = Email.EmailAddress(self.db) try: ea.find_by_local_part_and_domain(lp, ed.entity_id) raise CerebrumError("Address already exists (%s)" % address) except Errors.NotFoundError: pass if not (self._is_ok_mailing_list_name(lp) or self.ba.is_postmaster(operator.get_entity_id())): raise CerebrumError("Illegal queue address: %s" % address) ea.clear() ea.populate(lp, ed.entity_id, et.entity_id) ea.write_db() return ("OK, added '%s' as e-mail address for '%s'" % (address, queuename))
def _move_email_address(self, address, reassigned_addresses, dest_et): ea = Email.EmailAddress(self.db) ea.find(address['address_id']) ea.email_addr_target_id = dest_et.entity_id ea.write_db() reassigned_addresses.append(ea.get_address())
def destroy_group(group_id, max_recurse): if max_recurse is None: logger.fatal("destroy_group(%r) vil ikke slette permanent gruppe.", group_id) sys.exit(1) gr = get_group(group_id) logger.debug("destroy_group(%s/%d, %d) [After get_group]", gr.group_name, gr.entity_id, max_recurse) if max_recurse < 0: logger.fatal("destroy_group(%s): Recursion too deep", gr.group_name) sys.exit(3) if gr.get_extensions(): logger.fatal("destroy_group(%s): Group is %r", gr.group_name, gr.get_extensions()) sys.exit(4) # If this group is a member of other groups, remove those # memberships. for r in gr.search(member_id=gr.entity_id, indirect_members=False): parent = get_group(r['group_id']) logger.debug("removing %s from group %s", gr.group_name, parent.group_name) parent.remove_member(gr.entity_id) # If a e-mail target is of type multi and has this group as its # destination, delete the e-mail target and any associated # addresses. There can only be one target per group. et = Email.EmailTarget(db) try: et.find_by_email_target_attrs(target_type=const.email_target_multi, target_entity_id=gr.entity_id) except Errors.NotFoundError: pass else: logger.debug("found email target referencing %s", gr.group_name) ea = Email.EmailAddress(db) for r in et.get_addresses(): ea.clear() ea.find(r['address_id']) logger.debug("deleting address %s@%s", r['local_part'], r['domain']) ea.delete() et.delete() # Fetch group's members (*before* the group is deleted) members = [ int(x["member_id"]) for x in gr.search_members(group_id=gr.entity_id, member_type=const.entity_group) ] logger.debug("destroy_group() subgroups: %r", members) # Remove any spreads the group has for row in gr.get_spread(): gr.delete_spread(row['spread']) # Delete the parent group (which implicitly removes all membership # entries representing direct members of the parent group) gr.delete() # Destroy any subgroups (down to level max_recurse). This needs # to be done after the parent group has been deleted, in order for # the subgroups not to be members of the parent anymore. for subg_id in members: destroy_group(subg_id, max_recurse - 1)
def _update_email_address_domains(self, et): # Figure out which domain(s) the user should have addresses # in. Primary domain should be at the front of the resulting # list. ed = Email.EmailDomain(self._db) ea = Email.EmailAddress(self._db) try: ed.find(self.get_primary_maildomain(use_default_domain=False)) except Errors.NotFoundError: # no appropriate primary domain was found, no valid address may # be generated return domains = self.get_prospect_maildomains(use_default_domain=False) # Iterate over the available domains, testing various # local_parts for availability. Set user's primary address to # the first one found to be available. primary_set = False # Never change any existing email addresses try: self.get_primary_mailaddress() primary_set = True except Errors.NotFoundError: pass epat = Email.EmailPrimaryAddressTarget(self._db) if not domains: # no valid domain has been found and no e-mail address # can be assigned return for domain in domains: if ed.entity_id != domain: ed.clear() ed.find(domain) # Check for 'cnaddr' category before 'uidaddr', to prefer # 'cnaddr'-style primary addresses for users in # maildomains that have both categories. ctgs = [int(r['category']) for r in ed.get_categories()] local_parts = [] if int(self.const.email_domain_category_cnaddr) in ctgs: local_parts.append(self.get_email_cn_local_part()) local_parts.append(self.account_name) elif int(self.const.email_domain_category_uidaddr) in ctgs: local_parts.append(self.account_name) for lp in local_parts: lp = self.wash_email_local_part(lp) # Is the address taken? ea.clear() try: ea.find_by_local_part_and_domain(lp, ed.entity_id) if ea.email_addr_target_id != et.entity_id: # Address already exists, and points to a # target not owned by this Account. # # TODO: An expired address gets removed by a # database cleaning job, and when it's gone, # the address will eventually be recreated # connected to this target. continue except Errors.NotFoundError: # Address doesn't exist; create it. ea.populate(lp, ed.entity_id, et.entity_id, expire=None) ea.write_db() if not primary_set: epat.clear() try: epat.find(ea.email_addr_target_id) epat.populate(ea.entity_id) except Errors.NotFoundError: epat.clear() epat.populate(ea.entity_id, parent=et) epat.write_db() primary_set = True
def _get_entity_name(self, entity_id, entity_type=None): """Fetch a human-friendly name for the specified entity. @type entity_id: int @param entity_id: entity_id we are looking for. @type entity_type: const.EntityType instance (or None) @param entity_type: Restrict the search to the specifide entity. This parameter is really a speed-up only -- entity_id in Cerebrum uniquely determines the entity_type. However, should we know it, we save 1 db lookup. @rtype: str @return: Entity's name, obviously :) If none is found a magic string 'notfound:<entity id>' is returned (it's not perfect, but it's better than nothing at all). """ if entity_type is None: ety = Entity.Entity(self.db) try: ety.find(entity_id) entity_type = self.const.EntityType(ety.entity_type) except Errors.NotFoundError: return "notfound:%d" % entity_id if entity_type == self.const.entity_account: acc = self._get_account(entity_id, idtype='id') return acc.account_name elif entity_type in (self.const.entity_group, ): group = self._get_group(entity_id, idtype='id') return group.group_name elif entity_type == self.const.entity_disk: disk = Factory.get('Disk')(self.db) disk.find(entity_id) return disk.path elif entity_type == self.const.entity_host: host = Factory.get('Host')(self.db) host.find(entity_id) return host.name elif entity_type == self.const.entity_person: person = Factory.get('Person')(self.db) person.find(entity_id) return person.get_name(self.const.system_cached, self.const.name_full) # TODO: This should NOT be put in bofhd_core, as it should not require # the Email module! Subclassing? This is only a quickfix: if hasattr(self.const, 'entity_email_target'): if entity_type == self.const.entity_email_target: etarget = Factory.get('EmailTarget')(self.db) etarget.find(entity_id) return '%s:%s' % (str(etarget.get_target_type_name()), self._get_entity_name( etarget.get_target_entity_id())) elif entity_type == self.const.entity_email_address: ea = Email.EmailAddress(self.db) ea.find(entity_id) return ea.get_address() # Okey, we've run out of good options. Let's try a sensible fallback: # many entities have a generic name in entity_name. Let's use that: try: etname = type("entity_with_name", (Entity.EntityName, Entity.Entity), dict()) etname = etname(self.db) etname.find(entity_id) if etname.get_names(): # just grab any name. We don't really have a lot of choice. return etname.get_names()[0]["name"] else: # ... and if it does not exist -- return the id. We are out of # options at this point. return "%s:%s" % (entity_type, entity_id) except Errors.NotFoundError: return "notfound:%d" % entity_id # NOTREACHED assert False
def process_mail(account_id, type, addr): et = Email.EmailTarget(db) ea = Email.EmailAddress(db) edom = Email.EmailDomain(db) epat = Email.EmailPrimaryAddressTarget(db) addr = string.lower(addr) fld = addr.split('@') if len(fld) != 2: logger.error("Bad address: %s. Skipping", addr) return None # fi lp, dom = fld try: edom.find_by_domain(dom) logger.debug("Domain found: %s: %d", dom, edom.entity_id) except Errors.NotFoundError: edom.populate(dom, "Generated by import_uname_mail.") edom.write_db() logger.debug("Domain created: %s: %d", dom, edom.entity_id) # yrt try: et.find_by_target_entity(int(account_id)) logger.debug("EmailTarget found(accound): %s: %d", account_id, et.entity_id) except Errors.NotFoundError: et.populate(constants.email_target_account, entity_id=int(account_id), entity_type=constants.entity_account) et.write_db() logger.debug("EmailTarget created: %s: %d", account_id, et.entity_id) # yrt try: ea.find_by_address(addr) logger.debug("EmailAddress found: %s: %d", addr, ea.entity_id) except Errors.NotFoundError: ea.populate(lp, edom.entity_id, et.entity_id) ea.write_db() logger.debug("EmailAddress created: %s: %d", addr, ea.entity_id) # yrt if type == "defaultmail": try: epat.find(et.entity_id) logger.debug("EmailPrimary found: %s: %d", addr, epat.entity_id) except Errors.NotFoundError: if ea.email_addr_target_id == et.entity_id: epat.clear() epat.populate(ea.entity_id, parent=et) epat.write_db() logger.debug("EmailPrimary created: %s: %d", addr, epat.entity_id) else: logger.error("EmailTarget mismatch: ea: %d, et: %d", ea.email_addr_target_id, et.entity_id) # fi # yrt # fi et.clear() ea.clear() edom.clear() epat.clear()
def update_email_addresses(self): # Find, create or update a proper EmailTarget for this # account. et = Email.EmailTarget(self._db) old_server = None target_type = self.const.email_target_account if self.is_deleted() or self.is_reserved(): target_type = self.const.email_target_deleted try: et.find_by_email_target_attrs(target_entity_id = self.entity_id) et.email_target_type = target_type except Errors.NotFoundError: # We don't want to create e-mail targets for reserved or # deleted accounts, but we do convert the type of existing # e-mail targets above. if target_type == self.const.email_target_deleted: return et.populate(target_type, self.entity_id, self.const.entity_account) et.write_db() # For deleted/reserved users, set expire_date for all of the # user's addresses, and don't allocate any new addresses. ea = Email.EmailAddress(self._db) if target_type == self.const.email_target_deleted: expire_date = self._db.DateFromTicks(time.time() + 60 * 60 * 24 * 180) for row in et.get_addresses(): ea.clear() ea.find(row['address_id']) if ea.email_addr_expire_date is None: ea.email_addr_expire_date = expire_date ea.write_db() return # if an account without email_server_target is found assign # the appropriate server old_server = et.email_server_id acc_types = self.get_account_types() entity = Factory.get("Entity")(self._db) try: entity.clear() entity.find(self.owner_id) except Errors.NotFoundError: pass if not old_server: # we should update servers for employees as well, but we # cannot do that for now as there are no clear criteria # for when we should consider someone av f*g-employee or # adm-employee. we will therefore update servers for students # only # if self.is_fag_employee(): # self._update_email_server('mail.f*g.hiof.no') # elif self.is_adm_employee(): # self._update_email_server('mail.adm.hiof.no') if entity.entity_type != self.const.entity_group: if self.is_student(): self._update_email_server('epost.hiof.no') else: # do not set email_server_target until account_type is registered return # Figure out which domain(s) the user should have addresses # in. Primary domain should be at the front of the resulting # list. # if the only address found is in EMAIL_DEFAULT_DOMAIN # don't set default address. This is done in order to prevent # adresses in default domain being sat as primary # TODO: account_types affiliated to OU's without connected # email domain don't get a default address primary_set = False ed = Email.EmailDomain(self._db) ed.find(self.get_primary_maildomain()) domains = [ed.email_domain_name] if cereconf.EMAIL_DEFAULT_DOMAIN not in domains: domains.append(cereconf.EMAIL_DEFAULT_DOMAIN) # Iterate over the available domains, testing various # local_parts for availability. Set user's primary address to # the first one found to be available. try: self.get_primary_mailaddress() except Errors.NotFoundError: pass epat = Email.EmailPrimaryAddressTarget(self._db) for domain in domains: if ed.email_domain_name != domain: ed.clear() ed.find_by_domain(domain) # Check for 'cnaddr' category before 'uidaddr', to prefer # 'cnaddr'-style primary addresses for users in # maildomains that have both categories. ctgs = [int(r['category']) for r in ed.get_categories()] local_parts = [] if int(self.const.email_domain_category_cnaddr) in ctgs: local_parts.append(self.get_email_cn_local_part(given_names=1, max_initials=1)) local_parts.append(self.account_name) elif int(self.const.email_domain_category_uidaddr) in ctgs: local_parts.append(self.account_name) for lp in local_parts: lp = self.wash_email_local_part(lp) # Is the address taken? ea.clear() try: ea.find_by_local_part_and_domain(lp, ed.entity_id) if ea.email_addr_target_id != et.entity_id: # Address already exists, and points to a # target not owned by this Account. continue # Address belongs to this account; make sure # there's no expire_date set on it. ea.email_addr_expire_date = None except Errors.NotFoundError: # Address doesn't exist; create it. ea.populate(lp, ed.entity_id, et.entity_id, expire=None) ea.write_db() # HiØ do not want the primary adress to change automatically if not primary_set: epat.clear() try: epat.find(ea.email_addr_target_id) except Errors.NotFoundError: epat.clear() epat.populate(ea.entity_id, parent=et) epat.write_db() primary_set = True
def _get_sympa_list(self, listname): """ Try to return the 'official' sympa mailing list name, if it can at all be derived from listname. The problem here is that some lists are actually called foo-admin@domain (and their admin address is foo-admin-admin@domain). Since the 'official' names are not tagged in any way, we try to guess. The guesswork proceeds as follows: 1) if listname points to a sympa ET that has a primary address, we are done, listname *IS* the official list name 2) if not, then there must be a prefix/suffix (like -request) and if we chop it off, we can checked the chopped off part for being an official sympa list. The chopping off continues until we run out of special prefixes/suffixes. """ ea = Email.EmailAddress(self.db) et = Email.EmailTarget(self.db) epat = Email.EmailPrimaryAddressTarget(self.db) def has_prefix(address): local_part, domain = self._split_email_address(address) return True in [ local_part.startswith(x) for x in self._sympa_address_prefixes ] def has_suffix(address): local_part, domain = self._split_email_address(address) return True in [ local_part.endswith(x) for x in self._sympa_address_suffixes ] def has_primary_to_me(address): try: ea.clear() ea.find_by_address(address) epat.clear() epat.find(ea.get_target_id()) return True except Errors.NotFoundError: return False def I_am_sympa(address, check_suffix_prefix=True): try: ea.clear() ea.find_by_address(address) except Errors.NotFoundError: # If it does not exist, it cannot be sympa return False et.clear() et.find(ea.get_target_id()) if ((not et.email_target_alias) or et.email_target_type != self.const.email_target_Sympa): # if it's not a Sympa ET, address cannot be sympa return False return True not_sympa_error = CerebrumError("%s is not a Sympa list" % listname) # Simplest case -- listname is actually a sympa ML directly. It does # not matter whether it has a funky prefix/suffix. if I_am_sympa(listname) and has_primary_to_me(listname): return listname # However, if listname does not have a prefix/suffix AND it is not a # sympa address with a primary address, them it CANNOT be a sympa # address. if not (has_prefix(listname) or has_suffix(listname)): raise not_sympa_error # There is a funky suffix/prefix. Is listname actually such a # secondary address? Try to chop off the funky part and test. local_part, domain = self._split_email_address(listname) for prefix in self._sympa_address_prefixes: if not local_part.startswith(prefix): continue lp_tmp = local_part[len(prefix):] addr_to_test = lp_tmp + "@" + domain try: self._get_sympa_list(addr_to_test) return addr_to_test except CerebrumError: pass for suffix in self._sympa_address_suffixes: if not local_part.endswith(suffix): continue lp_tmp = local_part[:-len(suffix)] addr_to_test = lp_tmp + "@" + domain try: self._get_sympa_list(addr_to_test) return addr_to_test except CerebrumError: pass raise not_sympa_error
def _register_sympa_list_addresses(self, listname, local_part, domain, delivery_host): """ Register all neccessary sympa addresses. Add list, request, editor, owner, subscribe and unsubscribe addresses to a sympa mailing list. @type listname: basestring @param listname: Sympa listname that the operation is about. listname is typically different from local_part@domain when we are creating an alias. local_part@domain is the alias, listname is the original listname. And since aliases should point to the 'original' ETs, we have to use listname to locate the ETs. @type local_part: basestring @param local_part: See domain @type domain: basestring @param domain: L{local_part} and domain together represent a new list address that we want to create. @type delivery_host: EmailServer instance. @param delivery_host: EmailServer where e-mail to L{listname} is to be delivered through. """ if (delivery_host.email_server_type != self.const.email_server_type_sympa): raise CerebrumError( "Delivery host %s has wrong type (%s) for sympa list %s" % (delivery_host.get_name(self.const.host_namespace), self.const.EmailServerType( delivery_host.email_server_type), listname)) ed = Email.EmailDomain(self.db) ed.find_by_domain(domain) et = Email.EmailTarget(self.db) ea = Email.EmailAddress(self.db) epat = Email.EmailPrimaryAddressTarget(self.db) try: ea.find_by_local_part_and_domain(local_part, ed.entity_id) except Errors.NotFoundError: pass else: raise CerebrumError("The address %s@%s is already in use" % (local_part, domain)) sympa = self._get_account('sympa', idtype='name', actype='PosixUser') primary_ea_created = False listname_lp, listname_domain = listname.split("@") # For each of the addresses we are supposed to create... for pattern, pipe_destination in self._sympa_addr2alias: address = pattern % locals() address_lp, address_domain = address.split("@") # pipe has to be derived from the original listname, since it's # used to locate the ET. pipe = pipe_destination % { 'local_part': listname_lp, 'domain': listname_domain, 'listname': listname } # First check whether the address already exist. It should not. try: ea.clear() ea.find_by_local_part_and_domain(address_lp, ed.entity_id) raise CerebrumError("Can't add list %s as the address %s " "is already in use" % (listname, address)) except Errors.NotFoundError: pass # Then find the target for this particular email address. The # target may already exist, though. et.clear() try: et.find_by_alias_and_account(pipe, sympa.entity_id) except Errors.NotFoundError: et.populate(self.const.email_target_Sympa, alias=pipe, using_uid=sympa.entity_id, server_id=delivery_host.entity_id) et.write_db() # Then create the email address and associate it with the ET. ea.clear() ea.populate(address_lp, ed.entity_id, et.entity_id) ea.write_db() # And finally, the primary address. The first entry in # _sympa_addr2alias will match. Do not reshuffle that tuple! if not primary_ea_created: epat.clear() try: epat.find(et.entity_id) except Errors.NotFoundError: epat.clear() epat.populate(ea.entity_id, parent=et) epat.write_db() primary_ea_created = True
def sympa_create_list(self, operator, run_host, delivery_host, listname, admins, list_profile, list_description, yes_no_force="No"): """ Create a sympa list in Cerebrum and on the sympa server(s). Registers all the necessary cerebrum information and make a bofhd request for the actual list creation. """ # Check that the profile is legal if list_profile not in cereconf.SYMPA_PROFILES: raise CerebrumError("Profile %s for sympa list %s is not valid" % (list_profile, listname)) # Check that the command exec host is sane if run_host not in cereconf.SYMPA_RUN_HOSTS: raise CerebrumError("run-host '%s' for list '%s' is not valid" % (run_host, listname)) metachars = "'\"$&()*;<>?[\\]`{|}~\n" def has_meta(s1, s2=metachars): """Check if any char of s1 is in s2""" for c in s1: if c in s2: return True return False # Sympa list creation command will be passed through multiple # exec/shells. Better be restrictive. if True in [ has_meta(x) for x in (run_host, delivery_host, listname, admins, list_profile, list_description) ]: raise CerebrumError( "Illegal metacharacter in list parameter. Allowed: '%s'" % metachars) delivery_host = self._get_email_server(delivery_host) force = self._is_yes(yes_no_force) self._create_sympa_list(operator, listname, delivery_host, force=force) # Now make a bofhd request to create the list itself admin_list = list() for item in admins.split(","): # it's a user name. That username must exist in Cerebrum if "@" not in item: self._get_account(item) # TODO: Not good, this is in use by UIA item = item + "@ulrik.uio.no" admin_list.append(item) # Make the request. lp, dom = self._split_email_address(listname) ed = self._get_email_domain_from_str(dom) ea = Email.EmailAddress(self.db) ea.clear() ea.find_by_local_part_and_domain(lp, ed.entity_id) list_id = ea.entity_id # IVR 2008-08-01 TBD: this is a big ugly. We need to pass several # arguments to p_b_r, but we cannot really store them anywhere :( The # idea is then to take a small dict, pickle it, shove into state_data, # unpickle in p_b_r and be on our merry way. It is at the very best # suboptimal. state = { "runhost": run_host, # IVR 2008-08-01 FIXME: non-fqdn? force? # check? "admins": admin_list, "profile": list_profile, "description": list_description, } br = BofhdRequests(self.db, self.const) # IVR 2009-04-17 +30 minute delay to allow changes to spread to # LDAP. The postmasters are nagging for that delay. All questions # should be directed to them (this is similar to delaying a delete # request). br.add_request(operator.get_entity_id(), DateTime.now() + DateTime.DateTimeDelta(0, 0, 30), self.const.bofh_sympa_create, list_id, ea.entity_id, state_data=pickle.dumps(state)) return {'listname': listname}
def _email_info_sympa(self, operator, et, addr): """ Collect Sympa-specific information for a ML L{addr}. """ def fish_information(suffix, local_part, domain, listname): """Generate an entry for sympa info for the specified address. @type address: basestring @param address: Is the address we are looking for (we locate ETs based on the alias value in _sympa_addr2alias). @type et: EmailTarget instance @rtype: sequence (of dicts of basestring to basestring) @return: A sequence of dicts suitable for merging into return value from email_info_sympa. """ result = [] address = "%(local_part)s-%(suffix)s@%(domain)s" % locals() target_alias = None for a, alias in self._sympa_addr2alias: a = a % locals() if a == address: target_alias = alias % locals() break # IVR 2008-08-05 TBD Is this an error? All sympa ETs must have an # alias in email_target. if target_alias is None: return result try: # Do NOT change et's (parameter's) state. et_tmp = Email.EmailTarget(self.db) et_tmp.clear() et_tmp.find_by_alias(target_alias) except Errors.NotFoundError: return result addrs = et_tmp.get_addresses() if not addrs: return result pattern = '%(local_part)s@%(domain)s' result.append({'sympa_' + suffix + '_1': pattern % addrs[0]}) for idx in range(1, len(addrs)): result.append({'sympa_' + suffix: pattern % addrs[idx]}) return result # end fish_information # listname may be one of the secondary addresses. # email info sympatest@domain MUST be equivalent to # email info sympatest-admin@domain. listname = self._get_sympa_list(addr) ret = [{"sympa_list": listname}] if listname.count('@') == 0: lp, dom = listname, addr.split('@')[1] else: lp, dom = listname.split('@') ed = Email.EmailDomain(self.db) ed.find_by_domain(dom) ea = Email.EmailAddress(self.db) try: ea.find_by_local_part_and_domain(lp, ed.entity_id) except Errors.NotFoundError: raise CerebrumError( "Address %s exists, but the list it points to, %s, does not" % (addr, listname)) # now find all e-mail addresses et_sympa = Email.EmailTarget(self.db) et_sympa.clear() et_sympa.find(ea.email_addr_target_id) addrs = self._get_valid_email_addrs(et_sympa, sort=True) # IVR 2008-08-21 According to postmasters, only superusers should see # forwarding and delivery host information if self.ba.is_postmaster(operator.get_entity_id()): if et_sympa.email_server_id is None: delivery_host = "N/A (this is an error)" else: delivery_host = self._get_email_server( et_sympa.email_server_id).name ret.append({"sympa_delivery_host": delivery_host}) ret += self._email_info_forwarding(et_sympa, addrs) aliases = [] for row in et_sympa.get_addresses(): a = "%(local_part)s@%(domain)s" % row if a == listname: continue aliases.append(a) if aliases: ret.append({"sympa_alias_1": aliases[0]}) for next_alias in aliases[1:]: ret.append({"sympa_alias": next_alias}) for suffix in ("owner", "request", "editor", "subscribe", "unsubscribe"): ret.extend(fish_information(suffix, lp, dom, listname)) return ret