コード例 #1
0
ファイル: Entity.py プロジェクト: chrnux/cerebrum
    def in_system(self, id_type, entity_id, system):
        """Check if a user is represented in a system.

        :type id_type: basestring
        :param id_type: The id-type to look-up by.

        :type entity_id: basestring
        :param entity_id: The entitys id.

        :type system: basestring
        :param system: The system to check."""
        co = Factory.get('Constants')(self.db)

        # Fetch entity
        e = Utils.get(self.db, 'entity', id_type, entity_id)
        if not e:
            # TODO: Should this be raised in the Utils-module/class/whatever?
            raise Errors.CerebrumRPCException('Entity does not exist')

        try:
            sys = co.Spread(system)
            int(sys)
        except Errors.NotFoundError:
            raise Errors.CerebrumRPCException('System does not exist')

        try:
            return bool(e.get_subclassed_object().has_spread(sys))
        except AttributeError:
            # If we wind up here, the entity does not have the EntitySpread
            # class mixed into itself. When the EntitySpread-class is not mixed
            # in, we get an AttributeError since has_spread() is not defined.
            # TBD: Return false, or raise something?
            return False
コード例 #2
0
ファイル: Group.py プロジェクト: chrnux/cerebrum
    def group_create(self, name, description,
                     expire_date=None, visibility='A'):
        """Create a group.

        :type name: str
        :param name: The groups name.

        :type description: str
        :param description: The groups description.

        :type expire_date: DateTime
        :param expire_date: The groups expiration date.

        :type visibility: str
        :param visibility: The groups visibility. Can one of:
            'A'  All
            'I'  Internal
            'N'  None
        """
        # Perform auth-check
        self.ba.can_create_group(self.operator_id, groupname=name)

        # Check if group exists
        if Utils.get_group(self.db, 'name', name):
            raise Errors.CerebrumRPCException('Group already exists.')

        co = Factory.get('Constants')(self.db)
        gr = Factory.get('Group')(self.db)

        # Test if visibility is sane
        vis = co.GroupVisibility(visibility)
        try:
            int(vis)
        except (Errors.NotFoundError, TypeError):
            raise Errors.CerebrumRPCException('Invalid visibility.')

        # Create the group
        # TODO: Moar try/except?
        GroupAPI.group_create(gr, self.operator_id, vis, name,
                              description, expire_date)

        # Set moderator if appropriate.
        # TODO: use the commented version when we pass the config as a dict.
        if getattr(self.config, 'GROUP_OWNER_OPSET', None):
            # Fetch operator.
            en = Utils.get_entity_by_id(self.db, self.operator_id)
            # Grant auth
            GroupAPI.grant_auth(en, gr, getattr(self.config,
                                'GROUP_OWNER_OPSET'))
        return gr.entity_id
コード例 #3
0
    def get_addresses_by_affiliation(self, status, source, skos=None):
        """Find persons that has the given affiliations/statuses from the given
        source systems and at the given stedkoder (SKOs), if any. Return a list
        of all the persons' primary e-mail addresses.

        Note that some persons might not have any user affiliations, thus having
        no *primary* affiliation, even if they have user accounts with e-mail
        addresses.

        """
        affs = stats = ou_ids = None
        if status:
            affs, stats = self._get_aff_status(status)
        if source:
            source = [self.co.AuthoritativeSystem(s) for s in source]
        if skos:
            ou_ids = self._get_ous(skos)
            if not ou_ids:
                raise Errors.CerebrumRPCException('OUs not found')

        pe = Factory.get('Person')(self.db)

        pe2email = dict(pe.list_primary_email_address(self.co.entity_person))
        rows = []
        if affs:
            rows += pe.list_affiliations(affiliation=affs, ou_id=ou_ids)
        if stats:
            rows += pe.list_affiliations(status=stats, ou_id=ou_ids)

        ret = set(pe2email[row['person_id']] for row in rows
                  if pe2email.has_key(row['person_id']))
        print 'DEBUG: Returning %d e-mail addresses' % len(ret)
        return ret
コード例 #4
0
    def check_too_many_attempts(self, account):
        """
        Checks if a user has tried to use the service too many times. Creates
        the trait if it doesn't exist, and increments the numval. Raises an
        exception when too many attempts occur in the block period.

        """
        attempts = 0
        trait = account.get_trait(self.co.trait_password_failed_attempts)
        block_period = now() - RelativeDateTime(
            seconds=cereconf.INDIVIDUATION_ATTEMPTS_BLOCK_PERIOD)
        if trait and trait['date'] > block_period:
            attempts = int(trait['numval'])
        logger.debug('User %r has tried %r times', account.account_name,
                     attempts)
        if attempts > cereconf.INDIVIDUATION_ATTEMPTS:
            logger.info("User %r too many attempts, temporarily blocked",
                        account.account_name)
            raise Errors.CerebrumRPCException('toomanyattempts')
        account.populate_trait(code=self.co.trait_password_failed_attempts,
                               target_id=account.entity_id,
                               date=now(),
                               numval=attempts + 1)
        account.write_db()
        account._db.commit()
コード例 #5
0
ファイル: core.py プロジェクト: chrnux/cerebrum
    def get_group(db, id_type, group):
        """Fetch a group by id.

        :type db: <Cerebrum.Database.Database>
        :param db: A Cerebrum database object.

        :type id_type: str
        :param id_type: The identifier type, 'name' or 'id'

        :type group: str or int
        :param group: The group identifier

        :rtype: Group or None
        """
        gr = Factory.get('Group')(db)

        # Determine lookup type
        if id_type == 'name':
            lookup = gr.find_by_name
        elif id_type == 'id':
            lookup = gr.find
        else:
            raise Errors.CerebrumRPCException('Invalid id_type.')

        # Perform actual lookup
        # TODO: How do we handle NotFoundErrors? Is this correct?
        try:
            lookup(group)
        except Errors.NotFoundError:
            return None
        return gr
コード例 #6
0
ファイル: GroupInfo.py プロジェクト: chrnux/cerebrum
 def search_members_flat(self, groupname):
     # TODO: add access control for who is allowed to get the members. Only
     # moderators of the given group?
     #if not self.ba.is_superuser(self.operator_id):
     #    raise NotAuthorizedError('Only for superusers')
     # Raises Cerebrum.modules.bofh.errors.PermissionDenied - how to handle
     # these?
     #self.ba.can_set_trait(self.operator_id)
     try:
         self.grp.clear()
         self.grp.find_by_name(groupname)
     except Errors.NotFoundError:
         raise Errors.CerebrumRPCException("Group %s not found." % groupname)
     grp_id = self.grp.entity_id
     self.grp.clear()
     type_account = str(self.co.entity_account)
     member_rows = self.grp.search_members(group_id=grp_id,
                                         indirect_members=True,
                                         include_member_entity_name=True)
     return [{   'member_type': type_account,
                 'member_id': str(row['member_id']),
                 'uname': row['member_name']
             }
                 for row in member_rows
                 if row['member_type'] == self.co.entity_account]
コード例 #7
0
    def check_phone(self, phone_no, numbers, person, account):
        """Check if given phone_no belongs to person. The phone number is only
        searched for in source systems that the person has active affiliations
        from and contact types as defined in INDIVIDUATION_PHONE_TYPES. Other
        numbers are ignored. Set delays are also checked, to avoid that changed
        phone numbers are used for some period.

        """
        is_fresh = self.entity_is_fresh(person, account)
        for num in numbers:
            if not self.number_match(stored=num['number'], given=phone_no):
                continue
            if is_fresh:
                # delay is ignored for fresh entities
                return True
            delay = self.get_delay(num['system_name'], num['type'])

            for row in self.db.get_log_events(types=self.co.entity_cinfo_add,
                                              any_entity=person.entity_id,
                                              sdate=delay):
                data = pickle.loads(row['change_params'])
                if num['number'] == data['value']:
                    log.info('person_id=%s recently changed phoneno' %
                             person.entity_id)
                    self.mail_warning(
                        person=person,
                        account=account,
                        reason=("Your phone number has recently been" +
                                " changed. Due to security reasons, it" +
                                " can not be used by the password service" +
                                " for a few days."))
                    raise Errors.CerebrumRPCException('fresh_phonenumber')
            return True
        return False
コード例 #8
0
ファイル: core.py プロジェクト: chrnux/cerebrum
    def get_account(db, id_type, account):
        """Fetch a group by id.

        :type db: <Cerebrum.Database.Database>
        :param db: A Cerebrum database object.

        :type id_type: str
        :param id_type: The identifier type, 'name' or 'id'

        :type id_type: str or int
        :param group: The account identifier

        :rtype: Account or None
        """
        ac = Factory.get('Account')(db)

        if id_type == 'name':
            lookup = ac.find_by_name
        elif id_type == 'id':
            lookup = ac.find
        else:
            raise Errors.CerebrumRPCException('Invalid id_type.')

        try:
            lookup(account)
        except Errors.NotFoundError:
            return None
        return ac
コード例 #9
0
 def get_addresses_by_affiliation(ctx, status=None, skos=None, source=None):
     """Get primary e-mail addresses for persons that match given
     criterias."""
     if not source and not status:
         raise Errors.CerebrumRPCException('Input needed')
     return ctx.udc['postmaster'].get_addresses_by_affiliation(status=status,
                                                               skos=skos,
                                                               source=source)
コード例 #10
0
 def set_password(self, uname, new_password, token, browser_token):
     if not self.check_token(uname, token, browser_token):
         return False
     account = self.get_account(uname)
     try:
         check_password(new_password, account)
     except PasswordNotGoodEnough as e:
         m = str(e).decode('utf-8')
         raise Errors.CerebrumRPCException('password_invalid', m)
     # All data is good. Set password
     account.set_password(new_password)
     try:
         account.write_db()
         account._db.commit()
         log.info("Password for %s altered." % uname)
     except self.db.DatabaseError, m:
         log.error("Error when setting password for %s: %s" % (uname, m))
         raise Errors.CerebrumRPCException('error_unknown')
コード例 #11
0
 def get_account(self, uname):
     account = Factory.get('Account')(self.db)
     try:
         account.find_by_name(uname)
     except Errors.NotFoundError:
         log.info("Couldn't find account %s" % uname)
         raise Errors.CerebrumRPCException('person_notfound')
     else:
         return account
コード例 #12
0
    def check_token(self, uname, token, browser_token):
        """Check if token and other data from user is correct."""
        try:
            account = self.get_account(uname)
        except Errors.CerebrumRPCException:
            # shouldn't tell what went wrong
            return False

        # Check browser_token. The given browser_token may be "" but if so
        # the stored browser_token must be "" as well for the test to pass.

        bt = account.get_trait(self.co.trait_browser_token)
        if not bt or bt['strval'] != self.hash_token(browser_token, uname):
            log.info("Incorrect browser_token %s for user %s" %
                     (browser_token, uname))
            return False

        # Check password token. Keep track of how many times a token is
        # checked to protect against brute force attack (defaults to 20).
        pt = account.get_trait(self.co.trait_password_token)
        no_checks = int(pt['numval'])
        if no_checks > getattr(cereconf, 'INDIVIDUATION_TOKEN_ATTEMPTS', 20):
            log.info("No. of token checks exceeded for user %s" % uname)
            raise Errors.CerebrumRPCException('toomanyattempts_check')
        # Check if we're within time limit
        time_limit = now() - RelativeDateTime(
            minutes=cereconf.INDIVIDUATION_TOKEN_LIFETIME)
        if pt['date'] < time_limit:
            log.debug("Password token's timelimit for user %s exceeded" %
                      uname)
            raise Errors.CerebrumRPCException('timeout_check')

        if pt and pt['strval'] == self.hash_token(token, uname):
            # All is fine
            return True
        log.debug("Token %s incorrect for user %s" % (token, uname))
        account.populate_trait(self.co.trait_password_token,
                               strval=pt['strval'],
                               date=pt['date'],
                               numval=no_checks + 1)
        account.write_db()
        account._db.commit()
        return False
コード例 #13
0
    def get_person(self, id_type, ext_id):
        person = Factory.get('Person')(self.db)
        person.clear()
        if not hasattr(self.co, id_type):
            log.error("Wrong id_type: '%s'" % id_type)
            raise Errors.CerebrumRPCException('person_notfound')
        try:
            person.find_by_external_id(getattr(self.co, id_type), ext_id)
            return person
        except Errors.NotFoundError:
            log.debug("Couldn't find person with %s='%s'" % (id_type, ext_id))

        # Try without leading zeros, as FS use that, and which could confuse
        # students. TODO: Note that this does not help if the external IDs are
        # stored _with_ leading zeros in the database, i.e. the opposite way.
        if ext_id.isdigit():
            try:
                person.find_by_external_id(getattr(self.co, id_type),
                                           str(int(ext_id)))
                log.debug(
                    "Found person %s without leading zeros in ext_id: %s" %
                    (person.entity_id, ext_id))
                return person
            except Errors.NotFoundError:
                pass

            # Still not found? Try to padd with zeros if it's a student number
            # with less than 6 digits:
            if (hasattr(self.co, 'externalid_studentnr') and getattr(
                    self.co, id_type) == self.co.externalid_studentnr
                    and len(ext_id) < 6):
                try:
                    person.find_by_external_id(getattr(self.co, id_type),
                                               '%06d' % int(ext_id))
                    log.debug(
                        "Found person %s with padded zeros in ext_id: %s" %
                        (person.entity_id, ext_id))
                    return person
                except Errors.NotFoundError:
                    pass
        raise Errors.CerebrumRPCException('person_notfound')
コード例 #14
0
ファイル: Group.py プロジェクト: chrnux/cerebrum
    def group_remove_member(self, group_id_type, group_id,
                            member_id_type, member_id):
        """Remove a member from a group.

        :type group_id_type: str
        :param group_id_type: Group identifier type, 'id' or 'group_name'

        :type group_id: str
        :param group_id: Group identifier

        :type member_id_type: str
        :param member_id_type: Member identifier type, 'id' or 'account_name'

        :type member_id: str
        :param member_id: Member identifier

        :rtype: boolean
        """
        # Get the group
        gr = Utils.get(self.db, 'group', group_id_type, group_id)

        if not gr:
            raise Errors.CerebrumRPCException(
                'Group %s:%s does not exist.' % (group_id_type, group_id))

        # Perform auth check
        self.ba.can_alter_group(self.operator_id, gr)

        # Get the member we want to add
        member = Utils.get(self.db, 'entity', member_id_type, member_id)

        if not member:
            raise Errors.CerebrumRPCException(
                'Entity %s:%s does not exist.' % (member_id_type, member_id))

        if not gr.has_member(member.entity_id):
            return False

        GroupAPI.remove_member(gr, member.entity_id)
        return True
コード例 #15
0
    def validate_password(self, password, account_name, structured):
        """
        Validate any password

        :param password: the password to be validated
        :type password: str
        :param account_name: the account name to be used or ''
        :type account_name: str
        :param structured: whether to ask for a strctured (json) output
        :type structured: bool
        """
        account = None
        if account_name:
            try:
                account = Factory.get('Account')(self.db)
                account.find_by_name(account_name)
            except Errors.NotFoundError:
                raise Errors.CerebrumRPCException('unknown_error')
        try:
            result = check_password(password, account, structured)
            # exceptions are obsolete and used only for backward
            # compatibility here (f.i. old brukerinfo clients)
        except PhrasePasswordNotGoodEnough as e:
            # assume that structured is False
            m = str(e).decode('utf-8')
            # separate exception for phrases on the client??
            # no point of having separate except block otherwise
            raise Errors.CerebrumRPCException('password_invalid', m)
        except PasswordNotGoodEnough as e:
            # assume that structured is False
            m = str(e).decode('utf-8')
            raise Errors.CerebrumRPCException('password_invalid', m)
        else:
            if structured:
                # success or error data sent to the caller
                return json.dumps(result, indent=4)
            else:
                # no PasswordNotGoodEnough exception thrown
                return 'OK'
コード例 #16
0
    def get_person_accounts(self, id_type, ext_id):
        """Find Person given by id_type and external id and return a list of
        dicts with username, status and priority. Note that if the person is
        reserved from publication, it will get an SMS with its usernames
        instead.

        @param id_type: type of external id
        @type  id_type: string
        @param ext_id: external id
        @type  ext_id: string
        @return: list of dicts with username, status and priority, sorted
        by priority
        @rtype: list of dicts

        """
        # Check if person exists
        try:
            person = self.get_person(id_type, ext_id)
        except Errors.CerebrumRPCException:
            raise Errors.CerebrumRPCException('person_notfound_usernames')

        # Check reservation
        if self.is_reserved_publication(person):
            log.info("Person id=%s is reserved from publication" %
                     person.entity_id)
            # if person has a phone number, we could send the usernames by SMS:
            phone_nos = self.get_phone_numbers(person,
                                               only_first_affiliation=False)
            if phone_nos:
                accounts = [a['uname'] for a in self.get_account_list(person)]
                log.debug('Sending SMS with usernames: %s' %
                          ', '.join(accounts))
                self.send_sms(phone_nos[0]['number'],
                              cisconf.SMS_MSG_USERNAMES % '\n'.join(accounts))
            else:
                log.debug('No phone number for person %s' % person.entity_id)
            raise Errors.CerebrumRPCException('person_notfound_usernames')
        return self.get_account_list(person)
コード例 #17
0
ファイル: Entity.py プロジェクト: chrnux/cerebrum
    def add_to_system(self, id_type, entity_id, system):
        """Add an entity to a system.

        :type id_type: basestring
        :param id_type: The id-type to look-up by.

        :type entity_id: basestring
        :param entity_id: The entitys id.

        :type system: basestring
        :param system: The system the entity should be added to."""
        # Fetch entity
        en = Utils.get(self.db, 'entity', id_type, entity_id)
        if not en:
            # TODO: Should this be raised in the Utils-module/class/whatever?
            raise Errors.CerebrumRPCException('Entity does not exist')

        co = Factory.get('Constants')(self.db)

        try:
            sys = co.Spread(system)
            int(sys)
        except Errors.NotFoundError:
            raise Errors.CerebrumRPCException('System does not exist')

        self.ba.can_add_spread(self.operator_id, en, sys)

        try:
            if en.get_subclassed_object().has_spread(sys):
                return

            en.get_subclassed_object().add_spread(sys)
        except AttributeError:
            raise Errors.CerebrumRPCException('Can\'t add entity to system.')
        except self.db.IntegrityError:
            # TODO: This seems correct?
            raise Errors.CerebrumRPCException(
                'Entity not applicable for system.')
コード例 #18
0
 def set_password(self, uname, new_password, token, browser_token):
     if not self.check_token(uname, token, browser_token):
         return False
     account = self.get_account(uname)
     try:
         check_password(new_password, account)
     except PasswordNotGoodEnough as e:
         m = text_type(e)
         raise Errors.CerebrumRPCException('password_invalid', m)
     # All data is good. Set password
     account.set_password(new_password)
     try:
         account.write_db()
         account._db.commit()
         logger.info("Password for %r altered", uname)
     except self.db.DatabaseError as m:
         logger.error("Error when setting password for %r: %s", uname, m)
         raise Errors.CerebrumRPCException('error_unknown')
     # Remove "weak password" quarantine
     for r in account.get_entity_quarantine():
         for qua in (self.co.quarantine_autopassord,
                     self.co.quarantine_svakt_passord):
             if int(r['quarantine_type']) == qua:
                 account.delete_entity_quarantine(qua)
                 account.write_db()
                 account._db.commit()
     # TODO: move these checks up and raise exceptions? Wouldn't happen,
     # since generate_token() checks this already, but might get other
     # authentication methods later.
     if account.is_deleted():
         logger.warning("user %r is deleted", uname)
     elif account.is_expired():
         logger.warning("user %r is expired", uname)
     elif QuarantineHandler.check_entity_quarantines(
             self.db, account.entity_id).is_locked():
         logger.info("user %r has an active quarantine", uname)
     return True
コード例 #19
0
ファイル: Group.py プロジェクト: chrnux/cerebrum
    def group_info(self, group_id_type, group_id):
        """Get information about a group.

        :type group_id_type: str
        :param group_id_type: Group identifier type, 'id' or 'group_name'

        :type group_id: str
        :param group_id: Group identifier
        """
        gr = Utils.get(self.db, 'group', group_id_type, group_id)

        # Check if group exists
        if not gr:
            raise Errors.CerebrumRPCException(
                'Group %s:%s does not exist.' % (group_id_type, group_id))

        return GroupAPI.group_info(gr)
コード例 #20
0
ファイル: Group.py プロジェクト: chrnux/cerebrum
    def group_list(self, group_id_type, group_id):
        """Get list of group members

        :type group_id_type: str
        :param group_id_type: Group identifier type, 'id' or 'group_name'

        :type group_id: unicode or str
        :param group_id: Group identifier

        :rtype: list(dict{'name': name or id, 'type': type})
        """
        gr = Utils.get(self.db, 'group', group_id_type, group_id)

        # Check if group exists
        if not gr:
            raise Errors.CerebrumRPCException(
                'Group %s:%s does not exist.' % (group_id_type, group_id))

        lst = [{'name': x[1], 'type': x[0]} for x in
               map(Utils.get_entity_designator, GroupAPI.group_list(gr))]
        return lst
コード例 #21
0
ファイル: Entity.py プロジェクト: chrnux/cerebrum
    def spread_list(self, id_type, entity_id):
        """List account's spreads.

        :type id_type: basestring
        :param id_type: The id-type to look-up by.

        :type entity_id: basestring
        :param entity_id: The entitys id."""
        co = Factory.get('Constants')(self.db)
        e = Utils.get(self.db, 'entity', id_type, entity_id)
        spreads = dict()

        def fixer(id):
            if id[0] in spreads:
                return str(spreads[id[0]])
            s = spreads[id[0]] = co.map_const(id[0])
            return str(s)

        try:
            return map(fixer, e.get_subclassed_object().get_spread())
        except NameError:
            raise Errors.CerebrumRPCException("No spreads in entity")
コード例 #22
0
ファイル: Group.py プロジェクト: chrnux/cerebrum
    def group_set_expire(self, group_id_type, group_id, expire_date=None):
        """Set an expire-date on a group.

        :type group_id_type: str
        :param group_id_type: Group identifier type, 'id' or 'group_name'

        :type group_id: str
        :param group_id: Group identifier

        :type expire_date: <mx.DateTime>
        :param expire_date: The expire-date to set, or None.
        """
        # Get group
        gr = Utils.get(self.db, 'group', group_id_type, group_id)

        if not gr:
            raise Errors.CerebrumRPCException(
                'Group %s:%s does not exist.' % (group_id_type, group_id))

        # Perform auth check
        self.ba.can_alter_group(self.operator_id, gr)

        GroupAPI.set_expire_date(gr, expire_date)
コード例 #23
0
 def validate_password(self, password):
     """Overwriting this to return data in an exception, for checking data"""
     raise Errors.CerebrumRPCException(password)
コード例 #24
0
    def generate_token(self,
                       id_type,
                       ext_id,
                       uname,
                       phone_no,
                       browser_token=''):
        """Generate a token that functions as a short time password for the user
        and send it by SMS.

        @param id_type: type of external id
        @type  id_type: string
        @param ext_id: external id
        @type  ext_id: string
        @param uname: username
        @type  uname: string
        @param phone_no: phone number
        @type  phone_no: string
        @param browser_token: browser id
        @type  browser_token: string
        @return: True if success, False otherwise
        @rtype: bool

        """
        # Check if account exists
        account = self.get_account(uname)
        # Check if account has been checked too many times
        self.check_too_many_attempts(account)
        # Check if person exists
        person = self.get_person(id_type, ext_id)
        if not account.owner_id == person.entity_id:
            log.info("Account %s doesn't belong to person %d" %
                     (uname, person.entity_id))
            raise Errors.CerebrumRPCException('person_notfound')
        # Check if account is blocked
        if not self.check_account(account):
            log.info("Account %s is blocked" % (account.account_name))
            raise Errors.CerebrumRPCException('account_blocked')
        # Check if person/account is reserved
        if self.is_reserved(account=account, person=person):
            log.info("Account %s (or person) is reserved" %
                     (account.account_name))
            raise Errors.CerebrumRPCException('account_reserved')
        # Check if person/account is self reserved
        if self.is_self_reserved(account=account, person=person):
            log.info("Account %s (or person) is self reserved" %
                     (account.account_name))
            raise Errors.CerebrumRPCException('account_self_reserved')
        # Check phone_no
        phone_nos = self.get_phone_numbers(person)
        if not phone_nos:
            log.info("No relevant affiliation or phone registered for %s" %
                     account.account_name)
            raise Errors.CerebrumRPCException('person_miss_info')
        if not self.check_phone(
                phone_no, numbers=phone_nos, person=person, account=account):
            log.info("phone_no %s not found for %s" %
                     (phone_no, account.account_name))
            raise Errors.CerebrumRPCException('person_notfound')
        # Create and send token
        token = self.create_token()
        log.debug("Generated token %s for %s" %
                  (token, uname))  # TODO: remove when done testing
        if not self.send_token(phone_no, token):
            log.error("Couldn't send token to %s for %s" % (phone_no, uname))
            raise Errors.CerebrumRPCException('token_notsent')
        account._db.log_change(account.entity_id,
                               self.co.account_password_token,
                               None,
                               change_params={'phone_to': phone_no})
        # store password token as a trait
        account.populate_trait(self.co.trait_password_token,
                               date=now(),
                               numval=0,
                               strval=self.hash_token(token, uname))
        # store browser token as a trait
        if type(browser_token) is not str:
            log.err("Invalid browser_token, type='%s', value='%s'" %
                    (type(browser_token), browser_token))
            browser_token = ''
        account.populate_trait(self.co.trait_browser_token,
                               date=now(),
                               strval=self.hash_token(browser_token, uname))
        account.write_db()
        account._db.commit()
        return True