Пример #1
0
    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
Пример #2
0
# 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():
Пример #3
0
 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()
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
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'])
Пример #8
0
    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
Пример #9
0
    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),
        }