예제 #1
0
def change_respondent(payload, session):
    """
    Modify an existing respondent's email address, identified by their current email address.
    """
    v = Validator(Exists('email_address', 'new_email_address'))
    if not v.validate(payload):
        logger.info("Payload for change respondent was invalid",
                    errors=v.errors)
        raise BadRequest(v.errors, 400)

    email_address = payload['email_address']
    new_email_address = payload['new_email_address']

    respondent = query_respondent_by_email(email_address, session)

    if not respondent:
        logger.info("Respondent does not exist")
        raise NotFound("Respondent does not exist")

    if new_email_address == email_address:
        return respondent.to_respondent_dict()

    respondent_with_new_email = query_respondent_by_email(
        new_email_address, session)
    if respondent_with_new_email:
        logger.info("Respondent with email already exists")
        raise Conflict("New email address already taken")

    respondent.pending_email_address = new_email_address

    # check if respondent has initiated this request
    if 'change_requested_by_respondent' in payload:
        verification_url = PublicWebsite().confirm_account_email_change_url(
            new_email_address)
        personalisation = {
            'CONFIRM_EMAIL_URL': verification_url,
            'FIRST_NAME': respondent.first_name
        }
        logger.info('Account change email URL for party_id',
                    party_id=str(respondent.party_uuid),
                    url=verification_url)
        _send_account_email_change_email(
            personalisation,
            template='verify_account_email_change',
            email=new_email_address,
            party_id=respondent.party_uuid)
    else:
        _send_email_verification(respondent.party_uuid, new_email_address)

    logger.info('Verification email sent for changing respondents email',
                respondent_id=str(respondent.party_uuid))

    return respondent.to_respondent_dict()
예제 #2
0
def change_respondent_password(payload, tran, session):
    _is_valid(payload, attribute='new_password')

    respondent = query_respondent_by_email(payload['email_address'], session)
    email_address = respondent.email_address
    if not respondent:
        logger.info("Respondent does not exist")
        raise NotFound("Respondent does not exist")

    new_password = payload['new_password']

    # Check and see if the account is active, if not we can now set to active
    if respondent.status != RespondentStatus.ACTIVE:
        # Checking enrolment status, if PENDING we will change it to ENABLED
        logger.info('Checking enrolment status',
                    respondent_id=respondent.party_uuid)
        if respondent.pending_enrolment:
            enrol_respondent_for_survey(respondent, session)

        # We set the party as ACTIVE in this service
        respondent.status = RespondentStatus.ACTIVE
        oauth_response = OauthClient().update_account(username=email_address,
                                                      password=new_password,
                                                      account_locked='False')
    else:
        oauth_response = OauthClient().update_account(username=email_address,
                                                      password=new_password)

    if oauth_response.status_code != 201:
        logger.error(
            "Unexpected response from auth service, unable to change user password",
            respondent_id=str(respondent.party_uuid),
            status=oauth_response.status_code)
        raise InternalServerError("Failed to change respondent password")

    personalisation = {'FIRST_NAME': respondent.first_name}

    party_id = respondent.party_uuid

    try:
        NotifyGateway(current_app.config).request_to_notify(
            email=email_address,
            template_name='confirm_password_change',
            personalisation=personalisation,
            reference=str(party_id))
    except RasNotifyError as ras_error:
        logger.error(ras_error)

    # This ensures the log message is only written once the DB transaction is committed
    tran.on_success(lambda: logger.info(
        'Respondent has changed their password', respondent_id=party_id))

    return {'response': "Ok"}
def get_respondent_by_email(email, session):
    """
    Get a verified respondent by its email address.
    Returns either the unique respondent identified by the supplied email address, or otherwise raises
    a RasError to indicate the email address doesn't exist.

    :param email: Email of respondent to lookup
    :rtype: Respondent
    """
    respondent = query_respondent_by_email(email, session)
    if not respondent:
        logger.info("Respondent does not exist")
        raise NotFound("Respondent does not exist")

    return respondent.to_respondent_dict()
예제 #4
0
def verify_token(token, session):
    try:
        duration = current_app.config["EMAIL_TOKEN_EXPIRY"]
        email_address = decode_email_token(token, duration)
    except SignatureExpired:
        logger.info("Expired email verification token")
        raise Conflict("Expired email verification token")
    except (BadSignature, BadData):
        logger.exception("Bad token in verify_token")
        raise NotFound("Unknown email verification token")

    respondent = query_respondent_by_email(email_address, session)
    if not respondent:
        logger.info("Respondent with Email from token does not exist")
        raise NotFound("Respondent does not exist")

    return {'response': "Ok"}
예제 #5
0
def resend_password_email_expired_token(token, session):
    """
    Check and resend an email verification email using the expired token
    :param token: the expired token
    :param session: database session
    :return: response
    """
    email_address = decode_email_token(token)
    respondent = query_respondent_by_email(email_address, session)

    if not respondent:
        logger.info("Respondent does not exist")
        raise NotFound("Respondent does not exist")

    payload = {'email_address': email_address}

    response = request_password_change(payload)
    return response
예제 #6
0
def resend_verification_email_expired_token(token, session):
    """
    Check and resend an email verification email using the expired token
    :param token: the expired token
    :param session: database session
    :return: response
    """
    logger.info('Attempting to resend verification email with expired token',
                token=token)
    email_address = decode_email_token(token)
    respondent = query_respondent_by_email(email_address, session)

    if not respondent:
        logger.info("Respondent does not exist", token=token)
        raise NotFound("Respondent does not exist")

    response = _resend_verification_email(respondent)
    logger.info('Successfully resent verification email with expired token',
                token=token)
    return response
def update_respondent_mark_for_deletion(email, session):
    """
    Update respondent flag mark_for_deletion

    :param email: email of Respondent to be marked for deletion
    :type email: str
    :param session:
    :return: On Success it returns None, on failure will raise exceptions
    """
    respondent = query_respondent_by_email(email, session)
    if respondent:
        try:
            session.query(Respondent).filter(Respondent.party_uuid == respondent.party_uuid) \
                .update({Respondent.mark_for_deletion: True})
            return 'respondent successfully marked for deletion', 202
        except (SQLAlchemyError, Exception) as error:
            logger.error('error with update respondent mark for deletion',
                         error)
            return 'something went wrong', 500
    else:
        return 'respondent does not exist', 404
예제 #8
0
def request_password_change(payload, session):
    _is_valid(payload, attribute='email_address')

    logger.info("Verifying user exists before sending password reset email")
    respondent = query_respondent_by_email(payload['email_address'], session)
    if not respondent:
        logger.info("Respondent does not exist")
        raise NotFound("Respondent does not exist")

    logger.info("Requesting password change", party_id=respondent.party_uuid)

    email_address = respondent.email_address

    verification_url = PublicWebsite().reset_password_url(email_address)

    personalisation = {
        'RESET_PASSWORD_URL': verification_url,
        'FIRST_NAME': respondent.first_name
    }

    party_id = str(respondent.party_uuid)

    logger.info('Reset password url', url=verification_url, party_id=party_id)

    try:
        NotifyGateway(current_app.config).request_to_notify(
            email=email_address,
            template_name='request_password_change',
            personalisation=personalisation,
            reference=party_id)
    except RasNotifyError:
        # Note: intentionally suppresses exception
        logger.error('Error sending request to Notify Gateway',
                     respondent_id=party_id)

    logger.info('Password reset email successfully sent', party_id=party_id)

    return {'response': "Ok"}
예제 #9
0
def put_email_verification(token, tran, session):
    """
    Verify email address, this method can be reached when registering or updating email address
    :param token:
    :param tran:
    :param session: db session
    :return: Verified respondent details
    """
    logger.info('Attempting to verify email', token=token)
    try:
        duration = current_app.config["EMAIL_TOKEN_EXPIRY"]
        email_address = decode_email_token(token, duration)
    except SignatureExpired:
        logger.info("Expired email verification token")
        raise Conflict("Expired email verification token")
    except (BadSignature, BadData):
        logger.exception("Bad token in put_email_verification")
        raise NotFound("Unknown email verification token")

    respondent = query_respondent_by_email(email_address, session)

    if not respondent:
        logger.info("Attempting to find respondent by pending email address")
        # When changing contact details, unverified new email is in pending_email_address
        respondent = query_respondent_by_pending_email(email_address, session)

        if respondent:
            # Get old email address
            old_email_address = respondent.email_address
            update_verified_email_address(respondent, tran)
            # send confirmation email to old email address
            personalisation = {
                'FIRST_NAME': respondent.first_name,
                'NEW_EMAIL': respondent.email_address
            }
            logger.info(
                "Sending change of email on account to previously held email address"
            )
            _send_account_email_change_email(
                personalisation=personalisation,
                template='confirm_change_to_account_email',
                email=old_email_address,
                party_id=respondent.party_uuid)
        else:
            logger.info("Unable to find respondent by pending email")
            raise NotFound(
                "Unable to find user while checking email verification token")

    if respondent.status != RespondentStatus.ACTIVE:
        # We set the party as ACTIVE in this service
        respondent.status = RespondentStatus.ACTIVE

        # Next we check if this respondent has a pending enrolment (there WILL be only one, set during registration)
        if respondent.pending_enrolment:
            enrol_respondent_for_survey(respondent, session)
        else:
            logger.info(
                'No pending enrolment for respondent while checking email verification token',
                party_uuid=str(respondent.party_uuid))

        # We set the user as verified on the OAuth2 server.
        set_user_verified(email_address)

    return respondent.to_respondent_dict()
예제 #10
0
def post_respondent(party, session):
    """
    Register respondent and set up pending enrolment before account verification
    :param party: respondent to be created details
    :param session
    :return: created respondent
    """

    # Validation, curation and checks
    expected = ('emailAddress', 'firstName', 'lastName', 'password',
                'telephone', 'enrolmentCode')

    v = Validator(Exists(*expected))
    if 'id' in party:
        # Note: there's not strictly a requirement to be able to pass in a UUID, this is currently supported to
        # aid with testing.
        logger.info("'id' in respondent post message")
        try:
            uuid.UUID(party['id'])
        except ValueError:
            logger.info("Invalid respondent id type",
                        respondent_id=party['id'])
            raise BadRequest(
                f"'{party['id']}' is not a valid UUID format for property 'id'"
            )

    if not v.validate(party):
        logger.debug(v.errors)
        raise BadRequest(v.errors)

    iac = request_iac(party['enrolmentCode'])
    if not iac.get('active'):
        logger.info("Inactive enrolment code")
        raise BadRequest("Enrolment code is not active")

    existing = query_respondent_by_email(party['emailAddress'].lower(),
                                         session)
    if existing:
        logger.info("Email already exists",
                    party_uuid=str(existing.party_uuid))
        raise BadRequest("Email address already exists")

    case_context = request_case(party['enrolmentCode'])
    case_id = case_context['id']
    business_id = case_context['partyId']
    collection_exercise_id = case_context['caseGroup']['collectionExerciseId']
    collection_exercise = request_collection_exercise(collection_exercise_id)
    survey_id = collection_exercise['surveyId']

    business = query_business_by_party_uuid(business_id, session)
    if not business:
        logger.error(
            "Could not locate business when creating business association",
            business_id=business_id,
            case_id=case_id,
            collection_exercise_id=collection_exercise_id)
        raise InternalServerError(
            "Could not locate business when creating business association")

    # Chain of enrolment processes
    translated_party = {
        'party_uuid': party.get('id') or str(uuid.uuid4()),
        'email_address': party['emailAddress'].lower(),
        'first_name': party['firstName'],
        'last_name': party['lastName'],
        'telephone': party['telephone'],
        'status': RespondentStatus.CREATED
    }

    # This might look odd but it's done in the interest of keeping the code working in the same way.
    # If raise_for_status in the function raises an error, it would've been caught by @with_db_session,
    # rolled back the db and raised it.  Whether that's something we want is another question.
    try:
        respondent = _add_enrolment_and_auth(business, business_id, case_id,
                                             party, session, survey_id,
                                             translated_party)
    except HTTPError:
        logger.error("add_enrolment_and_auth raised an HTTPError",
                     exc_info=True)
        session.rollback()
        raise

    # If the disabling of the enrolment code fails we log an error and continue anyway.  In the interest of keeping
    # the code working in the same way (which may itself be wrong...) we'll handle the ValueError that can be raised
    # in the same way as before (rollback the session and raise) but it's not clear whether this is the desired
    # behaviour.
    try:
        disable_iac(party['enrolmentCode'], case_id)
    except ValueError:
        logger.error("disable_iac didn't return json in its response",
                     exc_info=True)
        session.rollback()
        raise

    _send_email_verification(respondent.party_uuid,
                             party['emailAddress'].lower())

    return respondent.to_respondent_dict()