def test_unread_message_count(self):
        session = Session.from_party_id("party")
        session.save()

        self.assertFalse(session.message_count_expired())

        session.set_unread_message_total(5)

        key = session.session_key
        session_to_assert = Session.from_session_key(key)

        self.assertEqual(session_to_assert.get_unread_message_count(), 5)
    def test_from_session_key(self):
        session = Session.from_party_id("party")
        session.save()
        session_key = session.session_key

        session_from_redis = Session.from_session_key(session_key)

        self.assertTrue(session_from_redis is not None)

        decoded_jwt = session_from_redis.get_decoded_jwt()
        self.assertEqual(decoded_jwt["party_id"], "party")
        self.assertEqual(session_from_redis.get_party_id(), "party")
        def extract_session_wrapper(*args, **kwargs):
            session_key = request.cookies.get("authorization")
            redis_session = Session.from_session_key(session_key)
            encoded_jwt = redis_session.get_encoded_jwt()
            if encoded_jwt:
                logger.debug("Attempting to authorize token")
                try:
                    jwt = decode(encoded_jwt, jwt_secret, algorithms="HS256")
                    logger.debug("Token decoded successfully")
                except JWTError:
                    logger.warning("Unable to decode token")
                    raise JWTValidationError
            else:
                logger.warning("No authorization token provided")
                flash("To help protect your information we have signed you out.", "info")
                session["next"] = request.url
                return redirect(url_for("sign_in_bp.login"))

            if app.config["VALIDATE_JWT"]:
                if validate(jwt):
                    redis_session.refresh_session()
                    return original_function(redis_session, *args, **kwargs)
                else:
                    logger.warning("Token is not valid for this request")
                    raise JWTValidationError
    def test_message_count_expired(self, test_patch):
        expired_time = datetime.now() - timedelta(seconds=301)
        test_patch.return_value = expired_time.timestamp()
        session = Session.from_party_id("party")
        session.save()

        self.assertTrue(session.message_count_expired())
        def extract_session_wrapper(*args, **kwargs):
            session_key = request.cookies.get('authorization')
            session = Session.from_session_key(session_key)
            encoded_jwt = session.get_encoded_jwt()
            if encoded_jwt:
                logger.debug('Attempting to authorize token')
                try:
                    jwt = decode(encoded_jwt, jwt_secret, algorithms='HS256')
                    logger.debug('Token decoded successfully')
                except JWTError:
                    logger.warning('Unable to decode token')
                    raise JWTValidationError
            else:
                logger.warning('No authorization token provided')
                flash(
                    'To help protect your information we have signed you out.',
                    'info')
                return redirect(url_for('sign_in_bp.login', next=request.url))

            if app.config['VALIDATE_JWT']:
                if validate(jwt):
                    session.refresh_session()
                    return original_function(session, *args, **kwargs)
                else:
                    logger.warning('Token is not valid for this request')
                    raise JWTValidationError
示例#6
0
    def test_get_message_count_from_session(self):
        session = Session.from_party_id("id")
        session.set_unread_message_total(3)
        with app.app_context():
            count = conversation_controller.try_message_count_from_session(
                session)

            self.assertEqual(3, count)
示例#7
0
 def test_get_message_count_unauthorized(self, headers):
     headers.return_value = {"Authorization": "token"}
     session = Session.from_party_id("id")
     with responses.RequestsMock() as rsps:
         rsps.add(rsps.GET, url_get_conversation_count, status=403)
         with app.app_context():
             with self.assertRaises(IncorrectAccountAccessError):
                 conversation_controller.get_message_count_from_api(session)
示例#8
0
def _create_get_conversation_headers(encoded_jwt=None):
    try:
        if encoded_jwt is None:
            encoded_jwt = Session.from_session_key(
                request.cookies['authorization']).get_encoded_jwt()
    except KeyError:
        logger.error('Authorization token missing in cookie')
        raise AuthorizationTokenMissing
    return {'Authorization': encoded_jwt}
示例#9
0
    def test_get_message_count_other_error_returns_0(self, headers):
        headers.return_value = {"Authorization": "token"}
        session = Session.from_party_id("id")
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.GET, url_get_conversation_count, status=400)
            with app.app_context():
                count = conversation_controller.get_message_count_from_api(
                    session)

                self.assertEqual(0, count)
    def test_delete_session(self):
        # Create session and get session key
        session = Session.from_party_id("party")
        session.save()
        session_key = session.session_key
        session.delete_session()

        session = redis.get(session_key)

        self.assertEqual(session, None)
示例#11
0
def confirm_account_email_change(token):
    logger.info("Attempting to confirm account email change", token=token)
    try:
        party_controller.verify_email(token)
    except ApiError as exc:
        # Handle api errors
        if exc.status_code == 409:
            logger.info(
                "Expired account email change verification token",
                token=token,
                api_url=exc.url,
                api_status_code=exc.status_code,
            )
            session_key = request.cookies.get("authorization")
            session = Session.from_session_key(session_key)
            session.delete_session()
            return render_template(
                "account/account-email-change-confirm-link-expired.html",
                token=token)
        elif exc.status_code == 404:
            logger.warning(
                "Unrecognised account email change verification token",
                token=token,
                api_url=exc.url,
                api_status_code=exc.status_code,
            )
            abort(404)
        else:
            logger.info(
                "Failed to verify account email change verification email",
                token=token,
                api_url=exc.url,
                api_status_code=exc.status_code,
            )
            raise exc

    # Successful account activation therefore redirect back to the login screen
    session_key = request.cookies.get("authorization")
    session = Session.from_session_key(session_key)
    session.delete_session()
    logger.info("Successfully verified email change on your account",
                token=token)
    return render_template("account/account-email-change-confirm.html")
    def test_create_session(self):
        # Create session and get session key
        session = Session.from_party_id("party")

        self.assertFalse(session.is_persisted())
        session.save()
        self.assertTrue(session.is_persisted())
        # Retrieve encoded_jwt from session
        test_jwt = session.get_decoded_jwt()
        self.assertEqual(test_jwt['party_id'], "party")
        self.assertEqual(test_jwt['unread_message_count']['value'], 0)
示例#13
0
    def test_get_message_count_from_session_if_persisted_by_api(self):
        session = Session.from_party_id("id")
        session.set_unread_message_total(1)

        session_key = session.session_key

        session_under_test = Session.from_session_key(session_key)
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.GET,
                     url_get_conversation_count,
                     json=message_count,
                     status=200,
                     headers={'Authorisation': 'token'},
                     content_type='application/json')
            with app.app_context():
                count = conversation_controller.get_message_count_from_api(
                    session_under_test)

                self.assertEqual(3,
                                 session_under_test.get_unread_message_count())
def confirm_account_email_change(token):
    if not app.config['ACCOUNT_EMAIL_CHANGE_ENABLED']:
        abort(404)
    logger.info('Attempting to confirm account email change', token=token)
    try:
        party_controller.verify_email(token)
    except ApiError as exc:
        # Handle api errors
        if exc.status_code == 409:
            logger.info('Expired account email change verification token',
                        token=token,
                        api_url=exc.url,
                        api_status_code=exc.status_code)
            session_key = request.cookies.get('authorization')
            session = Session.from_session_key(session_key)
            session.delete_session()
            return render_template(
                'account/account-email-change-confirm-link-expired.html',
                token=token)
        elif exc.status_code == 404:
            logger.warning(
                'Unrecognised account email change verification token',
                token=token,
                api_url=exc.url,
                api_status_code=exc.status_code)
            abort(404)
        else:
            logger.info(
                'Failed to verify account email change verification email',
                token=token,
                api_url=exc.url,
                api_status_code=exc.status_code)
            raise exc

    # Successful account activation therefore redirect back to the login screen
    session_key = request.cookies.get('authorization')
    session = Session.from_session_key(session_key)
    session.delete_session()
    logger.info('Successfully verified email change on your account',
                token=token)
    return render_template('account/account-email-change-confirm.html')
示例#15
0
def _create_send_message_headers() -> dict:
    try:
        encoded_jwt = Session.from_session_key(
            request.cookies["authorization"]).get_encoded_jwt()
    except KeyError:
        logger.error("Authorization token missing in cookie")
        raise AuthorizationTokenMissing
    return {
        "Authorization": encoded_jwt,
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
示例#16
0
def _create_send_message_headers():
    try:
        encoded_jwt = Session.from_session_key(
            request.cookies['authorization']).get_encoded_jwt()
    except KeyError:
        logger.error('Authorization token missing in cookie')
        raise AuthorizationTokenMissing
    return {
        'Authorization': encoded_jwt,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }
    def test_refresh_session(self):
        # Create session and get session key
        session = Session.from_party_id("party")
        session.save()
        session_key = session.session_key

        # Wait 3 seconds and update the session
        time.sleep(1)
        session.refresh_session()

        # Check that the session expiry time has been reset
        expires_in = redis.ttl(session_key)
        self.assertEqual(expires_in, 3600)
示例#18
0
def handle_csrf_error(error):
    logger.warning("CSRF token has expired",
                   error_message=error.description,
                   status_code=error.code)

    session_key = request.cookies.get("authorization")
    session_handler = Session.from_session_key(session_key)
    encoded_jwt = session_handler.get_encoded_jwt()
    if not encoded_jwt:
        return render_template("errors/400-error.html"), 400
    else:
        session["next"] = request.url
        return redirect(url_for("sign_in_bp.logout", csrf_error=True))
示例#19
0
def logout():
    # Delete user session in redis
    session_key = request.cookies.get('authorization')
    session = Session.from_session_key(session_key)
    session.delete_session()
    if request.args.get('csrf_error'):
        flash('To help protect your information we have signed you out.',
              'info')
    # Delete session cookie
    response = make_response(
        redirect(url_for('sign_in_bp.login', next=request.args.get('next'))))
    response.set_cookie('authorization', value='', expires=0)
    return response
示例#20
0
def handle_csrf_error(error):
    logger.warning('CSRF token has expired',
                   error_message=error.description,
                   status_code=error.code)

    session_key = request.cookies.get('authorization')
    session_handler = Session.from_session_key(session_key)
    encoded_jwt = session_handler.get_encoded_jwt()
    if not encoded_jwt:
        return render_template('errors/400-error.html'), 400
    else:
        return redirect(
            url_for('sign_in_bp.logout', csrf_error=True, next=request.url))
示例#21
0
    def test_get_message_count_from_api(self, headers):
        headers.return_value = {'Authorization': "token"}
        session = Session.from_party_id("id")
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.GET,
                     url_get_conversation_count,
                     json=message_count,
                     status=200,
                     headers={'Authorisation': 'token'},
                     content_type='application/json')
            with app.app_context():
                count = conversation_controller.get_message_count_from_api(
                    session)

                self.assertEqual(3, count)
示例#22
0
def logout():
    flashed_messages = get_flashed_messages(with_categories=True)
    # Delete user session in redis
    session_key = request.cookies.get("authorization")
    session = Session.from_session_key(session_key)
    session.delete_session()
    if len(flashed_messages) > 0:
        for category, message in flashed_messages:
            flash(message=message, category=category)
    if request.args.get("csrf_error"):
        flash("To help protect your information we have signed you out.",
              "info")
    # Delete session cookie
    response = make_response(
        redirect(url_for("sign_in_bp.login", next=request.args.get("next"))))
    response.delete_cookie("authorization")
    return response
示例#23
0
    def test_get_message_count_from_api_when_expired(self, headers):
        headers.return_value = {'Authorization': "token"}
        session = Session.from_party_id("id")
        decoded = session.get_decoded_jwt()
        decoded['unread_message_count']['refresh_in'] = (
            datetime.fromtimestamp(
                decoded['unread_message_count']['refresh_in']) -
            timedelta(seconds=301)).timestamp()
        encoded = jwt.encode(decoded)
        session.encoded_jwt_token = encoded
        with responses.RequestsMock() as rsps:
            rsps.add(rsps.GET,
                     url_get_conversation_count,
                     json=message_count,
                     status=200,
                     headers={'Authorisation': 'token'},
                     content_type='application/json')
            with app.app_context():
                count = conversation_controller.try_message_count_from_session(
                    session)

                self.assertEqual(3, count)
示例#24
0
def login():  # noqa: C901
    form = LoginForm(request.form)
    form.username.data = form.username.data.strip()
    account_activated = request.args.get('account_activated', None)

    secure = app.config['WTF_CSRF_ENABLED']

    if request.method == 'POST' and form.validate():
        username = form.username.data
        password = request.form.get('password')
        bound_logger = logger.bind(email=obfuscate_email(username))
        bound_logger.info("Attempting to find user in auth service")
        try:
            auth_controller.sign_in(username, password)
        except AuthError as exc:
            error_message = exc.auth_error
            party_json = party_controller.get_respondent_by_email(username)
            party_id = party_json.get('id') if party_json else None
            bound_logger = bound_logger.bind(party_id=party_id)

            if USER_ACCOUNT_LOCKED in error_message:  # pylint: disable=no-else-return
                if not party_id:
                    bound_logger.error("Respondent account locked in auth but doesn't exist in party")
                    return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}})
                bound_logger.info('User account is locked on the Auth server', status=party_json['status'])
                if party_json['status'] == 'ACTIVE' or party_json['status'] == 'CREATED':
                    notify_party_and_respondent_account_locked(respondent_id=party_id,
                                                               email_address=username,
                                                               status='SUSPENDED')
                return render_template('sign-in/sign-in.account-locked.html', form=form)
            elif NOT_VERIFIED_ERROR in error_message:
                bound_logger.info('User account is not verified on the Auth server')
                return render_template('sign-in/sign-in.account-not-verified.html', party_id=party_id)
            elif BAD_AUTH_ERROR in error_message:
                bound_logger.info('Bad credentials provided')
            elif UNKNOWN_ACCOUNT_ERROR in error_message:
                bound_logger.info('User account does not exist in auth service')
            else:
                bound_logger.error('Unexpected error was returned from Auth service', auth_error=error_message)

            return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}}, next=request.args.get('next'))

        bound_logger.info("Successfully found user in auth service.  Attempting to find user in party service")
        party_json = party_controller.get_respondent_by_email(username)
        if not party_json or 'id' not in party_json:
            bound_logger.error("Respondent has an account in auth but not in party")
            return render_template('sign-in/sign-in.html', form=form, data={"error": {"type": "failed"}})
        party_id = party_json['id']
        bound_logger = bound_logger.bind(party_id=party_id)

        if request.args.get('next'):
            response = make_response(redirect(request.args.get('next')))
        else:
            response = make_response(redirect(url_for('surveys_bp.get_survey_list', tag='todo', _external=True,
                                                      _scheme=getenv('SCHEME', 'http'))))

        bound_logger.info("Successfully found user in party service")
        bound_logger.info('Creating session')
        session = Session.from_party_id(party_id)
        response.set_cookie('authorization',
                            value=session.session_key,
                            expires=session.get_expires_in(),
                            secure=secure,
                            httponly=secure)
        count = conversation_controller.get_message_count_from_api(session)
        session.set_unread_message_total(count)
        bound_logger.info('Successfully created session', session_key=session.session_key)
        return response

    template_data = {
        "error": {
            "type": form.errors,
            "logged_in": "False"
        },
        'account_activated': account_activated
    }
    if request.args.get('next'):
        return render_template('sign-in/sign-in.html', form=form, data=template_data,
                               next=request.args.get('next'))
    return render_template('sign-in/sign-in.html', form=form, data=template_data)
示例#25
0
def login():  # noqa: C901
    form = LoginForm(request.form)
    if form.username.data is not None:
        form.username.data = form.username.data.strip()

    if request.method == "POST" and form.validate():
        username = form.username.data
        password = request.form.get("password")
        bound_logger = logger.bind(email=obfuscate_email(username))
        bound_logger.info("Attempting to find user in auth service")
        try:
            auth_controller.sign_in(username, password)
        except AuthError as exc:
            error_message = exc.auth_error
            party_json = party_controller.get_respondent_by_email(username)
            party_id = party_json.get("id") if party_json else None
            bound_logger = bound_logger.bind(party_id=party_id)

            if USER_ACCOUNT_LOCKED in error_message:
                if not party_id:
                    bound_logger.error(
                        "Respondent account locked in auth but doesn't exist in party"
                    )
                    return render_template("sign-in/sign-in.html",
                                           form=form,
                                           data={"error": {
                                               "type": "failed"
                                           }})
                bound_logger.info("User account is locked on the Auth server",
                                  status=party_json["status"])
                if party_json["status"] == "ACTIVE" or party_json[
                        "status"] == "CREATED":
                    notify_party_and_respondent_account_locked(
                        respondent_id=party_id,
                        email_address=username,
                        status="SUSPENDED")
                return render_template("sign-in/sign-in.account-locked.html",
                                       form=form)
            elif NOT_VERIFIED_ERROR in error_message:
                bound_logger.info(
                    "User account is not verified on the Auth server")
                return render_template(
                    "sign-in/sign-in.account-not-verified.html",
                    party_id=party_id)
            elif BAD_AUTH_ERROR in error_message:
                bound_logger.info("Bad credentials provided")
            elif UNKNOWN_ACCOUNT_ERROR in error_message:
                bound_logger.info(
                    "User account does not exist in auth service")
            elif USER_ACCOUNT_DELETED in error_message:
                bound_logger.info("User account is marked for deletion")
            else:
                bound_logger.error(
                    "Unexpected error was returned from Auth service",
                    auth_error=error_message)

            logger.unbind("email")
            return render_template("sign-in/sign-in.html",
                                   form=form,
                                   data={"error": {
                                       "type": "failed"
                                   }})

        bound_logger.info(
            "Successfully found user in auth service.  Attempting to find user in party service"
        )
        party_json = party_controller.get_respondent_by_email(username)
        if not party_json or "id" not in party_json:
            bound_logger.error(
                "Respondent has an account in auth but not in party")
            return render_template("sign-in/sign-in.html",
                                   form=form,
                                   data={"error": {
                                       "type": "failed"
                                   }})
        party_id = party_json["id"]
        bound_logger = bound_logger.bind(party_id=party_id)

        if session.get("next"):
            response = make_response(redirect(session.get("next")))
            session.pop("next")
        else:
            response = make_response(
                redirect(
                    url_for("surveys_bp.get_survey_list",
                            tag="todo",
                            _external=True,
                            _scheme=getenv("SCHEME", "http"))))

        bound_logger.info("Successfully found user in party service")
        bound_logger.info("Creating session")
        redis_session = Session.from_party_id(party_id)
        secure = app.config["WTF_CSRF_ENABLED"]
        response.set_cookie(
            "authorization",
            value=redis_session.session_key,
            expires=redis_session.get_expires_in(),
            secure=secure,
            httponly=secure,
            samesite="strict",
        )
        count = conversation_controller.get_message_count_from_api(
            redis_session)
        redis_session.set_unread_message_total(count)
        bound_logger.info("Successfully created session",
                          session_key=redis_session.session_key)
        bound_logger.unbind("email")
        return response

    account_activated = request.args.get("account_activated", None)
    template_data = {
        "error": {
            "type": form.errors,
            "logged_in": "False"
        },
        "account_activated": account_activated
    }
    return render_template("sign-in/sign-in.html",
                           form=form,
                           data=template_data)
示例#26
0
 def setUp(self):
     self.app = app.test_client()
     self.app.testing = True
     self.session = Session.from_party_id("test")