Example #1
0
    def post(self):
        """
        Authenticate the user, logout the current user, login the new user
        :return: dict containing success message
        """
        try:
            data = get_post_data(request)
            email = data['username']
            password = data['password']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        u = user_manipulator.first(email=email)
        if u is None or not u.validate_password(password):
            abort(401)
        if u.confirmed_at is None:
            return {"error": "account has not been verified"}, 403

        # Logout of previous user (may have been bumblebee)
        if current_user.is_authenticated():
            logout_user()
        login_user(u)  # Login to real user
        user_manipulator.update(
            u,
            last_login_at=datetime.datetime.now(),
            login_count=u.login_count + 1 if u.login_count else 1,
        )
        return {"message": "success"}, 200
Example #2
0
    def test_user_ratelimit(self):
        """
        Set the user's ratelimit_level to a value and confirm that
        """
        go = lambda: self.open('GET', '/test_webservice/LOW_RATE_LIMIT') # 3 per 5 second
        user_manipulator.update(self.user, ratelimit_level=2) # 2*3 per 5 second

        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        time.sleep(0.2)  # Make sure cache is caught up
        self.assertStatus(r, 429)

        # Wait until the ratelimit has expired, then try again.
        time.sleep(5)
        r = go()
        self.assertStatus(r, 200)
Example #3
0
    def test_user_ratelimit(self):
        """
        Set the user's ratelimit_level to a value and confirm that
        """
        go = lambda: self.open('GET', '/test_webservice/LOW_RATE_LIMIT'
                               )  # 3 per 5 second
        user_manipulator.update(self.user,
                                ratelimit_level=2)  # 2*3 per 5 second

        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        self.assertStatus(r, 200)
        r = go()
        time.sleep(0.2)  # Make sure cache is caught up
        self.assertStatus(r, 429)

        # Wait until the ratelimit has expired, then try again.
        time.sleep(5)
        r = go()
        self.assertStatus(r, 200)
Example #4
0
    def post(self):
        """
        Authenticate the user, logout the current user, login the new user
        :return: dict containing success message
        """
        try:
            data = get_post_data(request)
            email = data['username']
            password = data['password']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        u = user_manipulator.first(email=email)
        if u is None or not u.validate_password(password):
            abort(401)
        if u.confirmed_at is None:
            return {"error": "account has not been verified"}, 403

        # Logout of previous user (may have been bumblebee)
        if current_user.is_authenticated():
            logout_user()
        login_user(u)  # Login to real user
        user_manipulator.update(
            u,
            last_login_at=datetime.datetime.now(),
            login_count=u.login_count+1 if u.login_count else 1,
        )
        return {"message": "success"}, 200
Example #5
0
    def test_login_and_logout(self):
        """
        tests a login and logout pattern, including incorrect login
        """
        url = url_for('userauthview')

        payload = {'username': '******', 'password': '******'}
        r = self.client.post(url,
                             data=json.dumps(payload),
                             headers={'content-type': 'application/json'})
        self.assertStatus(r, 400)  # No csrf token = 400

        with self.client as c:
            csrf = self.get_csrf()

            # Incorrect login should return 401
            payload = {'username': '******', 'password': '******'}
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 401)

            # A correct login, but unverified account should return an error
            payload = {
                'username': self.real_user.email,
                'password': self.passwords[self.real_user]
            }
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 403)
            self.assertEqual(r.json['error'], 'account has not been verified')

            # Correct login on a verified account
            user_manipulator.update(self.real_user,
                                    confirmed_at=datetime.datetime.now())
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 200)
            self.assertEqual(current_user.email, self.real_user.email)
            self.assertEqual(current_user.login_count, 1)
            self.assertIsInstance(current_user.last_login_at, datetime.date)
            self.assertAlmostEqual(
                current_user.last_login_at,
                datetime.datetime.now(),
                delta=datetime.timedelta(seconds=1),
            )

            # Test logout
            r = c.post(url_for('logoutview'), headers={'X-CSRFToken': csrf})
            self.assertRaises(AttributeError, lambda: current_user.email)
Example #6
0
    def test_adsws_user_ratelimit_header(self):
        """
        Test that the correct 'adsws-user_ratelimit_level' header is passed to
        remote services
        """
        r = self.open('GET', '/test_webservice/ECHO_HEADERS')
        self.assertNotIn('X-Adsws-Ratelimit-Level', r.json)

        user_manipulator.update(self.user, ratelimit_level=10)
        r = self.open('GET', '/test_webservice/ECHO_HEADERS')
        self.assertEqual('10', r.json['X-Adsws-Ratelimit-Level'])
Example #7
0
    def test_adsws_user_ratelimit_header(self):
        """
        Test that the correct 'adsws-user_ratelimit_level' header is passed to
        remote services
        """
        r = self.open('GET', '/test_webservice/ECHO_HEADERS')
        self.assertNotIn('X-Adsws-Ratelimit-Level', r.json)

        user_manipulator.update(self.user, ratelimit_level=10)
        r = self.open('GET', '/test_webservice/ECHO_HEADERS')
        self.assertEqual('10', r.json['X-Adsws-Ratelimit-Level'])
    def test_user_ratelimit(self):
        """
        Set the user's ratelimit_level to a value and confirm that
        """
        user_manipulator.update(self.user, ratelimit_level=10)

        r = self.open('GET', '/test_webservice/LOW_RATE_LIMIT')
        self.assertStatus(r, 200)
        self.assertEqual(
            g._rate_limit_info.remaining,
            self.user.ratelimit_level*3 - 1,
        )
Example #9
0
class ChangePasswordView(Resource):
    """
    Implements change password functionality
    """
    decorators = [login_required]

    def post(self):
        """
        Verify that the current user's password is correct, that the desired
        new password is valid, and finally update the password in the User
        object
        """
        try:
            data = get_post_data(request)
            old_password = data['old_password']
            new_password1 = data['new_password1']
            new_password2 = data['new_password2']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        if not current_user.validate_password(old_password):
            return {'error': 'please verify your current password'}, 401

        if new_password1 != new_password2:
            return {'error': 'passwords do not match'}, 400
        try:
            validate_password(new_password1)
        except ValidationError, e:
            return {'error': 'validation error'}, 400

        u = user_manipulator.first(email=current_user.email)
        user_manipulator.update(u, password=new_password1)
        return {'message': 'success'}, 200
Example #10
0
    def get(self, token):
        try:
            email = current_app.ts.loads(token,
                                         max_age=86400,
                                         salt=VerificationEmail.salt)
        except:
            current_app.logger.warning(
                "{0} verification token not validated".format(token))
            return {"error": "unknown verification token"}, 404

        # This logic is necessary to de-activate accounts via the change-email
        # workflow. This strong coupling should be deprecated by using signals.
        previous_uid = None
        if " " in email:
            email, previous_uid = email.split()

        u = user_manipulator.first(email=email)
        if u is None:
            return {
                "error": "no user associated "
                "with that verification token"
            }, 404
        if u.confirmed_at is not None:
            return {
                "error": "this user and email "
                "has already been validated"
            }, 400
        if previous_uid:
            # De-activate previous accounts by deleting the account associated
            # with the new email address, then update the old account with the
            # new email address. Again, this should be deprecated with signals.
            user_manipulator.delete(u)
            u = user_manipulator.first(id=previous_uid)
            user_manipulator.update(u,
                                    email=email,
                                    confirmed_at=datetime.datetime.now())
        else:
            user_manipulator.update(u, confirmed_at=datetime.datetime.now())
        login_user(u)
        return {"message": "success", "email": email}
Example #11
0
    def get(self, token):
        try:
            email = current_app.ts.loads(token,
                                         max_age=86400,
                                         salt=VerificationEmail.salt)
        except:
            current_app.logger.warning(
                "{0} verification token not validated".format(token)
            )
            return {"error": "unknown verification token"}, 404

        # This logic is necessary to de-activate accounts via the change-email
        # workflow. This strong coupling should be deprecated by using signals.
        previous_uid = None
        if " " in email:
            email, previous_uid = email.split()

        u = user_manipulator.first(email=email)
        if u is None:
            return {"error": "no user associated "
                             "with that verification token"}, 404
        if u.confirmed_at is not None:
            return {"error": "this user and email "
                             "has already been validated"}, 400
        if previous_uid:
            # De-activate previous accounts by deleting the account associated
            # with the new email address, then update the old account with the
            # new email address. Again, this should be deprecated with signals.
            user_manipulator.delete(u)
            u = user_manipulator.first(id=previous_uid)
            user_manipulator.update(
                u,
                email=email,
                confirmed_at=datetime.datetime.now()
            )
        else:
            user_manipulator.update(u, confirmed_at=datetime.datetime.now())
        login_user(u)
        return {"message": "success", "email": email}
Example #12
0
    def test_users_crud_operations(self):
        """
        perform and test create, read, update, and delete patterns on user
        models using the `user_manipulator` service
        """

        # .new() should not save the User to the database
        joe = user_manipulator.new(email='joe')
        self.assertIsNone(user_manipulator.first(email='joe'))

        # .save() should save the User to the database
        user_manipulator.save(joe)
        u = user_manipulator.first(email='joe')
        self.assertIsNotNone(u)
        self.assertEqual(u.email, 'joe')

        # .create() should create immediately
        elias = user_manipulator.create(email='elias')
        u = user_manipulator.first(email='elias')
        self.assertIsNotNone(u)
        self.assertEqual(elias, u)

        # .update() should update immediately
        user_manipulator.update(elias, confirmed_at=datetime(2000, 1, 1))
        u = user_manipulator.first(email='elias')
        self.assertEqual(u.confirmed_at, datetime(2000, 1, 1))
        self.assertEqual(elias, u)

        # .delete() should delete immediately
        user_manipulator.delete(elias)
        u = user_manipulator.first(email='elias')
        self.assertIsNone(u)

        # even though this object was deleted in the db, we still should
        # have a reference to the python object
        self.assertIsNotNone(elias)
        self.assertEqual(elias.confirmed_at, datetime(2000, 1, 1))
Example #13
0
    def test_users_crud_operations(self):
        """
        perform and test create, read, update, and delete patterns on user
        models using the `user_manipulator` service
        """

        # .new() should not save the User to the database
        joe = user_manipulator.new(email='joe')
        self.assertIsNone(user_manipulator.first(email='joe'))

        # .save() should save the User to the database
        user_manipulator.save(joe)
        u = user_manipulator.first(email='joe')
        self.assertIsNotNone(u)
        self.assertEqual(u.email, 'joe')

        # .create() should create immediately
        elias = user_manipulator.create(email='elias')
        u = user_manipulator.first(email='elias')
        self.assertIsNotNone(u)
        self.assertEqual(elias, u)

        # .update() should update immediately
        user_manipulator.update(elias, confirmed_at=datetime(2000, 1, 1))
        u = user_manipulator.first(email='elias')
        self.assertEqual(u.confirmed_at, datetime(2000, 1, 1))
        self.assertEqual(elias, u)

        # .delete() should delete immediately
        user_manipulator.delete(elias)
        u = user_manipulator.first(email='elias')
        self.assertIsNone(u)

        # even though this object was deleted in the db, we still should
        # have a reference to the python object
        self.assertIsNotNone(elias)
        self.assertEqual(elias.confirmed_at, datetime(2000, 1, 1))
Example #14
0
    def login(self, user, client, csrf):
        """
        Perform the necessary steps required to sucessfully login

        Assumes that the resuling cookie will be saved, i.e. we are in a
        flask.client session context.

        :param user: User object whose confirmed_at attribute to set, and then
        logged in
        :param client: flask.client instance
        :param csrf: csrf token
        """
        user_manipulator.update(
            user,
            confirmed_at=datetime.datetime.now()
        )
        passwd = self.passwords[user]
        payload = {'username': user.email,'password': passwd}
        r = client.post(
            url_for('userauthview'),
            data=json.dumps(payload),
            headers={'content-type': 'application/json', 'X-CSRFToken': csrf},
        )
        self.assertStatus(r, 200)
Example #15
0
    def login(self, user, client, csrf):
        """
        Perform the necessary steps required to sucessfully login

        Assumes that the resuling cookie will be saved, i.e. we are in a
        flask.client session context.

        :param user: User object whose confirmed_at attribute to set, and then
        logged in
        :param client: flask.client instance
        :param csrf: csrf token
        """
        user_manipulator.update(user, confirmed_at=datetime.datetime.now())
        passwd = self.passwords[user]
        payload = {'username': user.email, 'password': passwd}
        r = client.post(
            url_for('userauthview'),
            data=json.dumps(payload),
            headers={
                'content-type': 'application/json',
                'X-CSRFToken': csrf
            },
        )
        self.assertStatus(r, 200)
Example #16
0
class ForgotPasswordView(Resource):
    """
    Implements "reset password" functionality
    """
    def get(self, token):
        """
        Attempts to decode a verification token into an email.
        Responds with the resulting decoded email

        :param token: HMAC encoded string
        :type token: basestring
        """
        try:
            email = current_app.ts.loads(token,
                                         max_age=86400,
                                         salt=PasswordResetEmail.salt)
        except:
            current_app.logger.warning(
                "Invalid Token {0} in ForgotPasswordView".format(token))
            return {"error": "unknown verification token"}, 404

        # Check that the user still exists
        u = user_manipulator.first(email=email)
        if u is None:
            current_app.logger.error(
                "[GET] Reset password validated link,"
                " but no user exists for {email}".format(email=email))
            abort(400)
        return {"email": email}, 200

    def post(self, token):
        """
        Send the password reset email to the specified email address
        (recaptcha protected)
        Note that param "token" represents the raw email address
        of the recipient, and it is not expected to be encoded.

        :param token: email address of the recipient
        :type token: basestring
        """
        if token == current_app.config['BOOTSTRAP_USER_EMAIL']:
            abort(403)
        try:
            data = get_post_data(request)
            reset_url = data['reset_url']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400
        if not verify_recaptcha(request):
            return {'error': 'captcha was not verified'}, 403

        u = user_manipulator.first(email=token)
        if u is None:
            return {'error': 'no such user exists'}, 404

        if not u.confirmed_at:
            return {
                'error':
                'This email was never verified. It will be '
                'deleted from out database within a day'
            }, 403
        send_email(email_addr=token,
                   email_template=PasswordResetEmail,
                   base_url=reset_url,
                   payload=token)
        return {"message": "success"}, 200

    def put(self, token):
        """
        Check if the current user has the same email as the decoded token,
        and, if so, change that user's password to that specified in
        the PUT body

        :param token: HMAC encoded email string
        :type token: basestring
        """
        try:
            email = current_app.ts.loads(token,
                                         max_age=86400,
                                         salt=PasswordResetEmail.salt)
        except:
            current_app.logger.critical(
                "PUT on reset-password with invalid token. "
                "This may indicate a brute-force attack!")
            return {"error": "unknown verification token"}, 404

        try:
            data = get_post_data(request)
            new_password1 = data['password1']
            new_password2 = data['password2']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        if new_password1 != new_password2:
            return {'error': 'passwords do not match'}, 400
        try:
            validate_password(new_password1)
        except ValidationError, e:
            return {'error': 'validation error'}, 400

        u = user_manipulator.first(email=email)
        if u is None:
            current_app.logger.error(
                "[PUT] Reset password validated link,"
                " but no user exists for {email}".format(email=email))
            abort(500)
        user_manipulator.update(u, password=new_password1)
        logout_user()
        login_user(u)
        return {"message": "success"}, 200
Example #17
0
    def test_reset_password(self):
        """
        test the reset password workflow
        """
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()
            user_manipulator.update(self.real_user,confirmed_at=datetime.datetime.now())

            url = url_for(
                'forgotpasswordview',
                token="this_email_wasnt@registered"
            )
            payload = {
                'g-recaptcha-response': 'correct_response',
                'reset_url':'http://not_relevant.com',
            }

            # Attempt to reset the password for an unregistered email address
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken':csrf}
            )
            self.assertStatus(r, 404)
            self.assertEqual(r.json['error'], 'no such user exists')

            # Resetting password for the default user should not be permitted
            url = url_for(
                'forgotpasswordview',
                token=self.bootstrap_user.email
            )
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 403)

            # Test a proper change-email request
            url = url_for('forgotpasswordview', token=self.real_user.email)
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 200)

            # Now let's test GET and PUT requests with the encoded token
            msg, token = utils.send_email(
                email_addr=self.real_user.email,
                email_template=PasswordResetEmail,
                payload=self.real_user.email
            )
            url = url_for('forgotpasswordview', token=token)

            # Test de-coding and verifying of the token
            r = c.get(url)
            self.assertStatus(r, 200)
            self.assertEqual(r.json['email'], self.real_user.email)

            # Change the password, then attempt to log-in with the new password
            payload = {'password1': '123Abc', 'password2': '123Abc'}
            r = c.put(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken':csrf}
            )
            self.assertStatus(r, 200)

            url = url_for('userauthview')
            payload = {'username': self.real_user.email,'password':'******'}
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 200)
            self.assertEqual(current_user.email, self.real_user.email)
Example #18
0
    def test_reset_password(self):
        """
        test the reset password workflow
        """
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()
            user_manipulator.update(self.real_user,
                                    confirmed_at=datetime.datetime.now())

            url = url_for('forgotpasswordview',
                          token="this_email_wasnt@registered")
            payload = {
                'g-recaptcha-response': 'correct_response',
                'reset_url': 'http://not_relevant.com',
            }

            # Attempt to reset the password for an unregistered email address
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 404)
            self.assertEqual(r.json['error'], 'no such user exists')

            # Resetting password for the default user should not be permitted
            url = url_for('forgotpasswordview',
                          token=self.bootstrap_user.email)
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 403)

            # Test a proper change-email request
            url = url_for('forgotpasswordview', token=self.real_user.email)
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)

            # Now let's test GET and PUT requests with the encoded token
            msg, token = utils.send_email(email_addr=self.real_user.email,
                                          email_template=PasswordResetEmail,
                                          payload=self.real_user.email)
            url = url_for('forgotpasswordview', token=token)

            # Test de-coding and verifying of the token
            r = c.get(url)
            self.assertStatus(r, 200)
            self.assertEqual(r.json['email'], self.real_user.email)

            # Change the password, then attempt to log-in with the new password
            payload = {'password1': '123Abc', 'password2': '123Abc'}
            r = c.put(url,
                      data=json.dumps(payload),
                      headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)

            url = url_for('userauthview')
            payload = {'username': self.real_user.email, 'password': '******'}
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)
            self.assertEqual(current_user.email, self.real_user.email)
Example #19
0
    def test_login_and_logout(self):
        """
        tests a login and logout pattern, including incorrect login
        """
        url = url_for('userauthview')

        payload = {'username': '******', 'password': '******'}
        r = self.client.post(
            url,
            data=json.dumps(payload),
            headers={'content-type': 'application/json'}
        )
        self.assertStatus(r, 400)  # No csrf token = 400

        with self.client as c:
            csrf = self.get_csrf()

            # Incorrect login should return 401
            payload = {'username': '******', 'password': '******'}
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 401)

            # A correct login, but unverified account should return an error
            payload = {
                'username': self.real_user.email,
                'password': self.passwords[self.real_user]}
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 403)
            self.assertEqual(r.json['error'], 'account has not been verified')

            # Correct login on a verified account
            user_manipulator.update(
                self.real_user,
                confirmed_at=datetime.datetime.now()
            )
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 200)
            self.assertEqual(current_user.email, self.real_user.email)
            self.assertEqual(current_user.login_count, 1)
            self.assertIsInstance(current_user.last_login_at, datetime.date)
            self.assertAlmostEqual(
               current_user.last_login_at,
               datetime.datetime.now(),
               delta=datetime.timedelta(seconds=1),
            )

            # Test logout
            r = c.post(url_for('logoutview'),headers={'X-CSRFToken': csrf})
            self.assertRaises(AttributeError, lambda: current_user.email)