def update_email_addresses(self): # 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(): 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 # 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) ed.find(self.get_primary_maildomain()) domains = [ed.email_domain_name] # Add the default domains if missing for domain in Email.get_default_email_domains(): if domain not in domains: domains.append(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. primary_set = False epat = Email.EmailPrimaryAddressTarget(self._db) for domain in domains: if ed.email_domain_name != domain: ed.clear() ed.find_by_domain(domain) # Check for 'uidaddr' category before 'cnaddr', to prefer # 'uidaddr'-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_uidaddr) in ctgs: local_parts.append(self.account_name) elif int(self.const.email_domain_category_cnaddr) in ctgs: local_parts.append(self.get_email_cn_local_part()) 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
# Cerebrum is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Cerebrum; if not, write to the Free Software Foundation, # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. from Cerebrum import Errors from Cerebrum.Utils import Factory from Cerebrum.modules import Email db = Factory.get('Database')() co = Factory.get('Constants')(db) dom = Email.EmailDomain(db) def is_uio_domain(domain): domain = "." + domain return (domain.endswith('.uio.no') and not domain.endswith('.ifi.uio.no')) def main(): for row in dom.list_email_domains(): domain = row['domain'] if is_uio_domain(domain): try: dom.clear() dom.find_by_domain(row['domain']) for ctg in dom.get_categories():
def update_email_addresses(self, set_primary=False): # check if an e-mail spread is registered yet, if not don't # update email_spreads = (self.const.spread_exchange_account, self.const.spread_exchange_acc_old, self.const.spread_hia_email, self.const.spread_uia_office_365, self.const.spread_uia_forward) if not any([self.has_spread(spread) for spread in email_spreads]): # CRB-742: If spread_uia_office_365 is removed # MailTarget targettype should be set as "deleted" try: et = Email.EmailTarget(self._db) et.find_by_email_target_attrs(target_entity_id=self.entity_id) if et.email_target_type != self.const.email_target_deleted: et.email_target_type = self.const.email_target_deleted et.write_db() except Errors.NotFoundError: pass return # Find, create or update a proper EmailTarget for this # account. et = Email.EmailTarget(self._db) target_type = self.const.email_target_account if self.has_spread(self.const.spread_uia_forward): target_type = self.const.email_target_forward 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 * 1) 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 email_target without email_server is found assign # the appropriate server based on spread and account_type spread = None if not et.email_server_id: if self.get_account_types() or \ self.owner_type == self.const.entity_group: for s in self.get_spread(): if s['spread'] in (int(self.const.spread_exchange_account), int(self.const.spread_exchange_acc_old), int(self.const.spread_hia_email)): spread = s['spread'] et = self._update_email_server(spread) 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 ed.email_domain_name == cereconf.EMAIL_DEFAULT_DOMAIN: if not self.owner_type == self.const.entity_group: primary_set = True 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. # Never change any existing email addresses try: self.get_primary_mailaddress() primary_set = True 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() 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 self.update_email_quota()
def add_spread(self, spread): # guest accounts: if (hasattr(self.const, 'trait_guest_owner') and self.get_trait(self.const.trait_guest_owner)): if spread not in (self.const.spread_ad_guest, self.const.spread_radius_guest): raise self._db.IntegrityError( "Guest accounts are not allowed other than guest spreads.") if spread == self.const.spread_uia_forward: email_spreads = (self.const.spread_exchange_account, self.const.spread_exchange_acc_old, self.const.spread_hia_email, self.const.spread_uia_office_365) if any([self.has_spread(s) for s in email_spreads]): raise self._db.IntegrityError( "Can't add spread {spread} to an account with the " "following spreads: {illegal_spreads}".format( spread=self.const.spread_uia_forward.str, illegal_spreads=map(lambda x: x.str, email_spreads))) if spread == self.const.spread_nis_user: if self.illegal_name(self.account_name): raise self._db.IntegrityError( "Can't add NIS spread to an account with illegal name.") if spread in (self.const.spread_exchange_account, self.const.spread_exchange_acc_old): if self.has_spread(self.const.spread_hia_email): raise self._db.IntegrityError( 'Cannot add Exchange-spread to an IMAP-account, ' 'use email exchange_migrate') # To be available in Exchange, you need to be in AD if not self.has_spread(self.const.spread_hia_ad_account): self.add_spread(self.const.spread_hia_ad_account) if spread == self.const.spread_exchange_acc_old: if self.has_spread(self.const.spread_exchange_account): raise self._db.IntegrityError("User already has new " "exchange spread") mdb = self._autopick_homeMDB() self.populate_trait(self.const.trait_exchange_mdb, strval=mdb) elif spread == self.const.spread_exchange_account: if self.has_spread(self.const.spread_exchange_acc_old): raise self._db.IntegrityError( "User has old exchange " "spread, cannot add new spread") self._update_email_server(spread, force=True) self.write_db() if spread == self.const.spread_hia_email: if (self.has_spread(self.const.spread_exchange_account) or self.has_spread(self.const.spread_exchange_acc_old)): # Accounts with Exchange can't have IMAP too. Should raise an # exception, but process_students tries to add IMAP spreads, # which would then fail, so it just returns instead. return et = Email.EmailTarget(self._db) try: et.find_by_email_target_attrs(target_entity_id=self.entity_id) except Errors.NotFoundError: # the user has no previosly assigned e-mail target to # fix, disregard the process pass else: # a target was found. make sure that the assigned server # is 'mail-imap2' es = Email.EmailServer(self._db) server_name = 'mail-imap2' es.find_by_name(server_name) et.email_server_id = es.entity_id et.write_db() if spread == self.const.spread_uia_office_365: # We except that the user already has an AD spread before setting # the spread for Office 365. This is handled by UiA. # Update the users email-server (and create EmailTarget, if it is # non-existent). self._update_email_server(spread, force=True) self.write_db() # Look up the domains that the user must have an email-address in, # for the cloud stuff to work. ed = Email.EmailDomain(self._db) ea = Email.EmailAddress(self._db) for domain in cereconf.EMAIL_OFFICE_365_DOMAINS: ed.clear() ed.find_by_domain(domain) # Ensure that the <uname>@thedomainfoundabove.uia.no address # exists. try: ea.clear() ea.find_by_local_part_and_domain(self.account_name, ed.entity_id) except Errors.NotFoundError: et = Email.EmailTarget(self._db) et.find_by_target_entity(self.entity_id) ea.populate(self.account_name, ed.entity_id, et.entity_id) ea.write_db() # (Try to) perform the actual spread addition. ret = self.__super.add_spread(spread) return ret
def access_list(self, operator, owner, target_type=None): """ List everything an account or group can operate on. Only direct ownership is reported: the entities an account can access due to group memberships will not be listed. This does not include unpersonal users owned by groups. :param operator: operator in bofh session :param owner: str name of owner object :param target_type: the type of the target :return: List of everything an account or group can operate on """ ar = BofhdAuthRole(self.db) aot = BofhdAuthOpTarget(self.db) aos = BofhdAuthOpSet(self.db) co = self.const owner_id = self.util.get_target(owner, default_lookup="group", restrict_to=[]).entity_id ret = [] for role in ar.list(owner_id): aos.clear() aos.find(role['op_set_id']) for r in aot.list(target_id=role['op_target_id']): if target_type is not None and r['target_type'] != target_type: continue if r['entity_id'] is None: target_name = "N/A" elif r['target_type'] == co.auth_target_type_maildomain: # FIXME: EmailDomain is not an Entity. ed = Email.EmailDomain(self.db) try: ed.find(r['entity_id']) except (Errors.NotFoundError, ValueError): self.logger.warn("Non-existing entity (e-mail domain) " "in auth_op_target {}:{:d}".format( r['target_type'], r['entity_id'])) continue target_name = ed.email_domain_name elif r['target_type'] == co.auth_target_type_ou: ou = self.OU_class(self.db) try: ou.find(r['entity_id']) except (Errors.NotFoundError, ValueError): self.logger.warn("Non-existing entity (ou) in " "auth_op_target %s:%d" % (r['target_type'], r['entity_id'])) continue target_name = "%02d%02d%02d (%s)" % ( ou.fakultet, ou.institutt, ou.avdeling, ou.short_name) elif r['target_type'] == co.auth_target_type_dns: s = Subnet(self.db) # TODO: should Subnet.find() support ints as input? try: s.find('entity_id:%s' % r['entity_id']) except (Errors.NotFoundError, ValueError, SubnetError): self.logger.warn("Non-existing entity (subnet) in " "auth_op_target %s:%d" % (r['target_type'], r['entity_id'])) continue target_name = "%s/%s" % (s.subnet_ip, s.subnet_mask) else: try: ety = self._get_entity(ident=r['entity_id']) target_name = self._get_name_from_object(ety) except (Errors.NotFoundError, ValueError): self.logger.warn("Non-existing entity in " "auth_op_target %s:%d" % (r['target_type'], r['entity_id'])) continue ret.append({ 'opset': aos.name, 'target_type': r['target_type'], 'target': target_name, 'attr': r['attr'] or "", }) ret.sort(lambda a, b: (cmp(a['target_type'], b['target_type']) or cmp( a['target'], b['target']))) return ret
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, target_entity_id=int(account_id), target_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 get_report(exclude_empty): """Returns a list of OUs with no email domain""" db = Factory.get("Database")() co = Factory.get("Constants")(db) ou = Factory.get("OU")(db) ac = Factory.get("Account")(db) email = Email.EmailDomain(db) eed = Email.EntityEmailDomain(db) # Count the number of accounts in each OU ou_to_num_accounts = {} for acc in ac.list_accounts_by_type(): ou_id = acc['ou_id'] if ou_to_num_accounts.has_key(ou_id): ou_to_num_accounts[ou_id] += 1 else: ou_to_num_accounts[ou_id] = 1 # Map OU ids to email domain ou_to_domain = {} for dom in email.list_email_domains(): for affs in eed.list_affiliations(domain_id=dom['domain_id']): ou_to_domain[affs[0]] = dom['domain_id'] # Iterate through OUs report = [] for sko in ou.get_stedkoder(): ou_id = sko['ou_id'] if exclude_empty and not ou_to_num_accounts.has_key(ou_id): continue if not ou_to_domain.has_key(ou_id): ou.clear() ou.find(ou_id) ou_quarantine = ou.get_entity_quarantine() # Skip if OU is in quarantine if len(ou_quarantine) is 0: if not ou_to_num_accounts.has_key(ou_id): ou_num_accounts = 0 else: ou_num_accounts = ou_to_num_accounts[ou_id] ou_name = ou.get_name_with_language( name_variant=co.ou_name, name_language=co.language_nb, default="") report.append({ 'stedkode': '%02d%02d%02d' % (sko['fakultet'], sko['institutt'], sko['avdeling']), 'ou_id': ou_id, 'ou_num_accounts': ou_num_accounts, 'ou_name': ou_name }) return sorted(report, key=lambda k: k['stedkode'])
def get_distgroup_attributes_and_targetdata(self, display_name_lang='nb', roomlist=False): all_data = {} ea = Email.EmailAddress(self._db) ed = Email.EmailDomain(self._db) et = Email.EmailTarget(self._db) epat = Email.EmailPrimaryAddressTarget(self._db) primary_address = "" display_name = "" name_language = "" addrs = [] name_variant = self.const.dl_group_displ_name if display_name_lang == 'nb' or \ not hasattr(self.const, 'dl_group_displ_name'): # code that uses this methos should probably take # care of getting the language right? name_language = self.const.language_nb else: name_language = int(_LanguageCode(display_name_lang)) display_name = self.get_name_with_language(name_variant, name_language, default=self.group_name) # in roomlists we only care about name, description, # displayname and the roomlist-status, the other attributes # don't need to be set in Exchange if roomlist: all_data = { 'name': self.group_name, 'description': self.description, 'displayname': display_name, 'group_id': self.entity_id, 'roomlist': self.roomlist } return all_data try: et.find_by_target_entity(self.entity_id) except Errors.NotFoundError: # could not find e-mail target for group. this should # normally not happen return None try: epat.find(et.entity_id) except Errors.NotFoundError: # could not find primary address for the e-mail target # this happens from time to time, and we should be able # to identify the error raise self._db.IntegrityError( "No primary address registered for {}".format(self.group_name)) ea.clear() ea.find(epat.email_primaddr_id) ed.clear() ed.find(ea.email_addr_domain_id) primary_address = "%s@%s" % (ea.email_addr_local_part, ed.email_domain_name) for r in et.get_addresses(special=True): ad = "%s@%s" % (r['local_part'], r['domain']) addrs.append(ad) # name is expanded with prefix 'dl-' by the export all_data = { 'name': self.group_name, 'description': self.description, 'displayname': display_name, 'group_id': self.entity_id, 'roomlist': self.roomlist, 'hidden': self.hidden, 'primary': primary_address, 'aliases': addrs } return all_data
def entity_contactinfo_add(self, operator, entity_target, contact_type, contact_value): """Manually add contact info to an entity.""" # default values contact_pref = 50 source_system = self.const.system_manual contact_type = contact_type.upper() # get entity object entity = self.util.get_target(entity_target, restrict_to=[]) entity_type = self.const.EntityType(int(entity.entity_type)) if not hasattr(entity, 'get_contact_info'): raise CerebrumError("No support for contact info in %s entity" % six.text_type(entity_type)) # validate contact info type contact_type = self._get_constant(self.const.ContactInfo, contact_type) # check permissions self.ba.can_add_contact_info(operator.get_entity_id(), entity=entity, contact_type=contact_type) # validate email if contact_type == self.const.contact_email: # validate localpart and extract domain. if contact_value.count('@') != 1: raise CerebrumError("Email address (%r) must be on form" "<localpart>@<domain>" % contact_value) localpart, domain = contact_value.split('@') domain = domain.lower() # normalize domain-part ea = Email.EmailAddress(self.db) ed = Email.EmailDomain(self.db) try: if not ea.validate_localpart(localpart): raise AttributeError('Invalid local part') ed._validate_domain_name(domain) # If checks are okay, reassemble email with normalised domain contact_value = localpart + "@" + domain except AttributeError as e: raise CerebrumError(e) # validate phone numbers if contact_type in (self.const.contact_phone, self.const.contact_phone_private, self.const.contact_mobile_phone, self.const.contact_private_mobile): # allows digits and a prefixed '+' if not re.match(r"^\+?\d+$", contact_value): raise CerebrumError( "Invalid phone number: %r. " "The number can contain only digits " "with possible '+' for prefix." % contact_value) # get existing contact info for this entity and contact type contacts = entity.get_contact_info(source=source_system, type=contact_type) existing_prefs = [int(row["contact_pref"]) for row in contacts] for row in contacts: # if the same value already exists, don't add it # case-insensitive check for existing email address if contact_value.lower() == row["contact_value"].lower(): raise CerebrumError("Contact value already exists") # if the value is different, add it with a lower (=greater number) # preference for the new value if contact_pref == row["contact_pref"]: contact_pref = max(existing_prefs) + 1 logger.debug( 'Incremented preference, new value = %d' % contact_pref) logger.debug('Adding contact info: %r, %r, %r, %r', entity.entity_id, six.text_type(contact_type), contact_value, contact_pref) entity.add_contact_info(source_system, type=contact_type, value=contact_value, pref=int(contact_pref), description=None, alias=None) return { 'source_system': six.text_type(source_system), 'contact_type': six.text_type(contact_type), 'contact_value': six.text_type(contact_value), 'entity_type': six.text_type(entity_type), 'entity_id': int(entity.entity_id), }