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 login_oauth2_user(valid, oauth):
    """
    Login a user after having been verified
    """
    if valid:
        login_user(user_manipulator.first(id=oauth.user.id))
    return valid, oauth
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
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 #7
0
    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
Example #8
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 #9
0
def login_oauth2_user(valid, oauth):
    """
    Login a user after having been verified
    """
    if valid:
        login_user(user_manipulator.first(id=oauth.user.id))
    return valid, oauth
Example #10
0
    def get(self):
        """
        If the current user is unauthenticated, or the current user
        is the "bootstrap" (anon) user, return/create a "BB Client" OAuthClient
        and token depending if "oauth_client" is encoded into their
        session cookie

        If the user is a authenticated as a real user, return/create
        a "BB Client" OAuthClient and token depending if that user already has
        one in the database
        """

        # rca: I'd like to register here my distaste for Flask-Restful and
        # how it divorces parameters; it was a big mistake to go with that framework
        # and the decision shouldn't have been left to inexperienced developers
        # this is not recommended solution, but even the recommended solution
        # is just awful: http://stackoverflow.com/questions/30779584/flask-restful-passing-parameters-to-get-request
        parser = reqparse.RequestParser()
        parser.add_argument('redirect_uri', type=str)
        parser.add_argument('scope', type=str)
        parser.add_argument('client_name', type=str)
        parser.add_argument('ratelimit', type=float)
        parser.add_argument('create_new', type=inputs.boolean)
        parser.add_argument('expires', type=str)
        
        kwargs = parser.parse_args()

        client_name = kwargs.get('client_name', None)
        redirect_uri = kwargs.get('redirect_uri', None)
        ratelimit = kwargs.get('ratelimit', 1.0)
        create_new = kwargs.get('create_new', False)
        expires = kwargs.get('expires', None)
        
        if ratelimit is None:
            ratelimit = 1.0
        
        assert ratelimit >= 0.0
        
        if expires is not None:
            expires = get_date(expires) # throwing error on parse OK
        else:
            expires = datetime.datetime(2500, 1, 1)
            
        # If we visit this endpoint and are unauthenticated, then login as
        # our anonymous user
        if not current_user.is_authenticated():            
            login_user(user_manipulator.first(
                email=current_app.config['BOOTSTRAP_USER_EMAIL']
            ))
        
        if current_user.email == current_app.config['BOOTSTRAP_USER_EMAIL']:
            if 'scopes' in kwargs or client_name or redirect_uri:
                abort(401, "Sorry, you cant change scopes/name/redirect_uri when creating temporary OAuth application")

        try:
            scopes = self._sanitize_scopes(kwargs.get('scope', None))
        except ValidationError, e:
            return {'error': e.value}, 400
Example #11
0
    def post(self):
        """
        POST desired email and password to change the current user's email.
        Checks that the desired new email isn't already registerd

        This will create the new user. The encoded email payload will have
        a second argument `id` which is the id of the user making this
        request. We assume that the view responsible for verifying emails
        knows what to do with this extra argument. This should be deprecated
        by using signals in the future.
        """
        try:
            data = get_post_data(request)
            email = data['email']
            password = data['password']
            verify_url = data['verify_url']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        u = user_manipulator.first(email=current_user.email)
        if not u.validate_password(password):
            abort(401)

        if user_manipulator.first(email=email) is not None:
            return {
                "error": "{0} has already been registered".format(email)
            }, 403
        send_email(
            email_addr=email,
            base_url=verify_url,
            email_template=VerificationEmail,
            payload=[email, u.id]
        )
        send_email(
            email_addr=current_user.email,
            email_template=EmailChangedNotification
        )
        user_manipulator.create(
            email=email,
            password=password,
            active=True,
            registered_at=datetime.datetime.now(),
            login_count=0,
        )
        return {"message": "success"}, 200
Example #12
0
    def post(self):
        """
        POST desired email and password to change the current user's email.
        Checks that the desired new email isn't already registerd

        This will create the new user. The encoded email payload will have
        a second argument `id` which is the id of the user making this
        request. We assume that the view responsible for verifying emails
        knows what to do with this extra argument. This should be deprecated
        by using signals in the future.
        """
        try:
            data = get_post_data(request)
            email = data['email']
            password = data['password']
            verify_url = data['verify_url']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        u = user_manipulator.first(email=current_user.email)
        if not u.validate_password(password):
            abort(401)

        if user_manipulator.first(email=email) is not None:
            return {
                "error": "{0} has already been registered".format(email)
            }, 403
        send_email(
            email_addr=email,
            base_url=verify_url,
            email_template=VerificationEmail,
            payload=[email, u.id]
        )
        send_email(
            email_addr=current_user.email,
            email_template=EmailChangedNotification
        )
        user_manipulator.create(
            email=email,
            password=password,
            active=True,
            registered_at=datetime.datetime.now(),
            login_count=0,
        )
        return {"message": "success"}, 200
Example #13
0
 def post(self):
     """
     Delete the current user's account
     use POST instead of GET to enable csrf validation
     """
     u = user_manipulator.first(email=current_user.email)
     logout_user()
     user_manipulator.delete(u)
     return {"message": "success"}, 200
Example #14
0
 def post(self):
     """
     Delete the current user's account
     use POST instead of GET to enable csrf validation
     """
     u = user_manipulator.first(email=current_user.email)
     logout_user()
     user_manipulator.delete(u)
     return {"message": "success"}, 200
Example #15
0
    def test_register_user(self):
        """
        test user registration
        """
        url = url_for('userregistrationview')
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()

            # posting without a csrf token should return 400
            r = c.post(url)
            self.assertStatus(r, 400)

            # Giving incorrect input should return 400
            payload = {'email': 'me@email'}
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 400)

            # Test that registering an already registered email returns 409
            payload = {
                'email': self.real_user.email,
                'password1': 'Password1',
                'password2': 'Password1',
                'g-recaptcha-response': 'correct_response',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 409)

            # Test a valid new user registration
            payload = {
                'email': 'me@email',
                'password1': 'Password1',
                'password2': 'Password1',
                'g-recaptcha-response': 'correct_response',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 200)
            u = user_manipulator.first(email="me@email")
            self.assertIsNotNone(u)
            self.assertIsNone(u.confirmed_at)
Example #16
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 #17
0
class UserRegistrationView(Resource):
    """
    Implements new user registration
    """

    decorators = [
        ratelimit.shared_limit_and_check("50/600 second", scope=scope_func)
    ]

    def post(self):
        """
        Standard user registration workflow;
        verifies that the email is available, creates a de-activated accounts,
        and sends verification email that serves to activate said account
        """
        try:
            data = get_post_data(request)
            email = data['email']
            password = data['password1']
            repeated = data['password2']
            verify_url = data['verify_url']
        except (AttributeError, KeyError):
            return {'error': 'malformed request'}, 400

        if not verify_recaptcha(request):
            return {'error': 'captcha was not verified'}, 403
        if password != repeated:
            return {'error': 'passwords do not match'}, 400
        try:
            validate_email(email)
            validate_password(password)
        except ValidationError, e:
            return {'error': 'validation error'}, 400

        if user_manipulator.first(email=email) is not None:
            return {
                'error':
                'an account is already'
                ' registered for {0}'.format(email)
            }, 409
        send_email(email_addr=email,
                   base_url=verify_url,
                   email_template=WelcomeVerificationEmail,
                   payload=email)
        u = user_manipulator.create(
            email=email,
            password=password,
            active=True,
            registered_at=datetime.datetime.now(),
            login_count=0,
        )
        return {"message": "success"}, 200
Example #18
0
def load_user(username, password, *args, **kwargs):
    """
    Loads the user (resource owner)

    User getter is optional. It is only required if you need password
    credential authorization:

    Needed for grant type 'password'. Note, grant type password is by default
    disabled.
    """
    user = user_manipulator.first(email=username)
    if user.validate_password(password):
        return user
Example #19
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 #20
0
def load_user(username, password, *args, **kwargs):
    """
    Loads the user (resource owner)

    User getter is optional. It is only required if you need password
    credential authorization:

    Needed for grant type 'password'. Note, grant type password is by default
    disabled.
    """
    user = user_manipulator.first(email=username)
    if user.validate_password(password):
        return user
Example #21
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 #22
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 #23
0
    def test_register_user(self):
        """
        test user registration
        """
        url = url_for('userregistrationview')
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()

            # posting without a csrf token should return 400
            r = c.post(url)
            self.assertStatus(r, 400)

            # Giving incorrect input should return 400
            payload = {'email': 'me@email'}
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 400)

            # Test that registering an already registered email returns 409
            payload = {
                'email': self.real_user.email,
                'password1': 'Password1',
                'password2': 'Password1',
                'g-recaptcha-response': 'correct_response',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 409)

            # Test a valid new user registration
            payload = {
                'email': 'me@email',
                'password1': 'Password1',
                'password2': 'Password1',
                'g-recaptcha-response': 'correct_response',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)
            u = user_manipulator.first(email="me@email")
            self.assertIsNotNone(u)
            self.assertIsNone(u.confirmed_at)
Example #24
0
    def get(self):
        """
        If the current user is unauthenticated, or the current user
        is the "bootstrap" (anon) user, return/create a "BB Client" OAuthClient
        and token depending if "oauth_client" is encoded into their
        session cookie

        If the user is a authenticated as a real user, return/create
        a "BB Client" OAuthClient and token depending if that user already has
        one in the database
        """

        # If we visit this endpoint and are unauthenticated, then login as
        # our anonymous user
        if not current_user.is_authenticated():
            login_user(user_manipulator.first(
                email=current_app.config['BOOTSTRAP_USER_EMAIL']
            ))

        if current_user.email == current_app.config['BOOTSTRAP_USER_EMAIL']:
            try:

                if 'oauth_client' in session:
                    client, token = Bootstrap.load_client(
                        session['oauth_client']
                    )
                else:
                    raise NoClientError('client/user mismatch')

                if client.user_id != int(current_user.get_id()):
                    raise NoClientError("client/user mistmatch")

            except (NoTokenError, NoClientError):
                client, token = Bootstrap.bootstrap_bumblebee()
                session['oauth_client'] = client.client_id
        else:
            client, token = Bootstrap.bootstrap_user()

        client.last_activity = datetime.datetime.now()
        db.session.commit()
        return print_token(token)
Example #25
0
    def test_delete_account(self):
        """
        Test the delete account workflow
        """
        url = url_for('deleteaccountview')
        with self.client as c:
            csrf = self.get_csrf()

            # CSRF not passed; should return 400
            r = c.post(url)
            self.assertStatus(r, 400)

            # Not authenticated; should 401
            r = c.post(url, headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 401)

            # Login and passing the correct csrf should delete the acct
            self.login(self.real_user, c, csrf)
            r = c.post(url, headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)
            u = user_manipulator.first(email=self.real_user.email)
            self.assertIsNone(u)
Example #26
0
    def test_delete_account(self):
        """
        Test the delete account workflow
        """
        url = url_for('deleteaccountview')
        with self.client as c:
            csrf = self.get_csrf()

            # CSRF not passed; should return 400
            r = c.post(url)
            self.assertStatus(r, 400)

            # Not authenticated; should 401
            r = c.post(url,headers={'X-CSRFToken':csrf})
            self.assertStatus(r,401)

            # Login and passing the correct csrf should delete the acct
            self.login(self.real_user, c, csrf)
            r = c.post(url,headers={'X-CSRFToken':csrf})
            self.assertStatus(r, 200)
            u = user_manipulator.first(email=self.real_user.email)
            self.assertIsNone(u)
Example #27
0
    def test_change_email(self):
        """
        Test the change email workflow.

        The workflow is tightly coupled with the verify-email workflow, which
        should be de-coupled by using signals in the future
        """
        url = url_for('changeemailview')
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()

            # Unauthenticated should return 401
            r = c.post(url, headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 401)

            self.login(self.real_user, c, csrf)

            # incorrect password, even though we're logged in should return 401
            payload = {
                'email': self.real_user.email,
                'password': '******',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 401)

            # correct password, but that email is already registered
            payload = {
                'email': self.bootstrap_user.email,
                'password': self.passwords[self.real_user],
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 403)

            # valid end-to-end workflow
            previous_email = self.real_user.email
            payload = {
                'email': 'changed@email',
                'password': self.passwords[self.real_user],
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(url,
                       data=json.dumps(payload),
                       headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 200)

            u = user_manipulator.first(email='changed@email')
            self.assertIsNotNone(u)
            self.assertIsNone(u.confirmed_at)
            self.assertIsNotNone(user_manipulator.first(email=previous_email))
            self.assertIsNotNone(
                user_manipulator.first(email=previous_email).confirmed_at)
            self.assertNotEqual(self.real_user.email, "changed@email")

            # Get the token that this view will send, send it to the
            # verfication email endpoint, and check that the user's email
            # was correctly updated
            msg, token = utils.send_email(
                email_addr="changed@email",
                email_template=VerificationEmail,
                payload=["changed@email", self.real_user.id],
            )

            url = url_for('verifyemailview', token=token)
            r = self.client.get(url)
            self.assertStatus(r, 200)
            self.assertIsNone(user_manipulator.first(email=previous_email))
            self.assertEqual(self.real_user.email, "changed@email")
Example #28
0
    def get(self):
        """
        If the current user is unauthenticated, or the current user
        is the "bootstrap" (anon) user, return/create a "BB Client" OAuthClient
        and token depending if "oauth_client" is encoded into their
        session cookie

        If the user is a authenticated as a real user, return/create
        a "BB Client" OAuthClient and token depending if that user already has
        one in the database
        """

        # rca: I'd like to register here my distaste for Flask-Restful and
        # how it divorces parameters; it was a big mistake to go with that framework
        # and the decision shouldn't have been left to inexperienced developers
        # this is not recommended solution, but even the recommended solution
        # is just awful: http://stackoverflow.com/questions/30779584/flask-restful-passing-parameters-to-get-request
        parser = reqparse.RequestParser()
        parser.add_argument('redirect_uri', type=str)
        parser.add_argument('scope', type=str)
        parser.add_argument('client_name', type=str)
        kwargs = parser.parse_args()

        scopes = kwargs.get('scope', None)
        client_name = kwargs.get('client_name', None)
        redirect_uri = kwargs.get('redirect_uri', None)

        # If we visit this endpoint and are unauthenticated, then login as
        # our anonymous user
        if not current_user.is_authenticated():
            login_user(user_manipulator.first(
                email=current_app.config['BOOTSTRAP_USER_EMAIL']
            ))

            if scopes or client_name:
                abort(401, "Sorry, you cant change scopes/name/redirect_uri of this user")

        if current_user.email == current_app.config['BOOTSTRAP_USER_EMAIL']:
            try:

                if 'oauth_client' in session:
                    client, token = Bootstrap.load_client(
                        session['oauth_client']
                    )
                else:
                    raise NoClientError('client/user mismatch')

                if client.user_id != int(current_user.get_id()):
                    raise NoClientError("client/user mistmatch")

            except (NoTokenError, NoClientError):
                client, token = Bootstrap.bootstrap_bumblebee()
                session['oauth_client'] = client.client_id
        else:
            client, token = Bootstrap.bootstrap_user()

            if scopes:
                client._default_scopes = scopes
            if redirect_uri:
                client._redirect_uris = redirect_uri
            if client_name:
                client.client_name = client_name

        client.last_activity = datetime.datetime.now()
        db.session.commit()
        output = print_token(token)

        output['client_id'] = client.client_id
        output['client_secret'] = client.client_secret

        return output
Example #29
0
    def get(self):
        """
        If the current user is unauthenticated, or the current user
        is the "bootstrap" (anon) user, return/create a "BB Client" OAuthClient
        and token depending if "oauth_client" is encoded into their
        session cookie

        If the user is a authenticated as a real user, return/create
        a "BB Client" OAuthClient and token depending if that user already has
        one in the database
        """

        # rca: I'd like to register here my distaste for Flask-Restful and
        # how it divorces parameters; it was a big mistake to go with that framework
        # and the decision shouldn't have been left to inexperienced developers
        # this is not recommended solution, but even the recommended solution
        # is just awful: http://stackoverflow.com/questions/30779584/flask-restful-passing-parameters-to-get-request
        parser = reqparse.RequestParser()
        parser.add_argument('redirect_uri', type=str)
        parser.add_argument('scope', type=str)
        parser.add_argument('client_name', type=str)
        kwargs = parser.parse_args()

        scopes = kwargs.get('scope', None)
        client_name = kwargs.get('client_name', None)
        redirect_uri = kwargs.get('redirect_uri', None)

        # If we visit this endpoint and are unauthenticated, then login as
        # our anonymous user
        if not current_user.is_authenticated():
            login_user(
                user_manipulator.first(
                    email=current_app.config['BOOTSTRAP_USER_EMAIL']))

            if scopes or client_name:
                abort(
                    401,
                    "Sorry, you cant change scopes/name/redirect_uri of this user"
                )

        if current_user.email == current_app.config['BOOTSTRAP_USER_EMAIL']:
            try:

                if 'oauth_client' in session:
                    client, token = Bootstrap.load_client(
                        session['oauth_client'])
                else:
                    raise NoClientError('client/user mismatch')

                if client.user_id != int(current_user.get_id()):
                    raise NoClientError("client/user mistmatch")

            except (NoTokenError, NoClientError):
                client, token = Bootstrap.bootstrap_bumblebee()
                session['oauth_client'] = client.client_id
        else:
            client, token = Bootstrap.bootstrap_user()

            if scopes:
                client._default_scopes = scopes
            if redirect_uri:
                client._redirect_uris = redirect_uri
            if client_name:
                client.client_name = client_name

        client.last_activity = datetime.datetime.now()
        db.session.commit()
        output = print_token(token)

        output['client_id'] = client.client_id
        output['client_secret'] = client.client_secret

        return output
Example #30
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 #31
0
    def test_change_email(self):
        """
        Test the change email workflow.

        The workflow is tightly coupled with the verify-email workflow, which
        should be de-coupled by using signals in the future
        """
        url = url_for('changeemailview')
        with self.client as c:
            csrf = self.get_csrf()
            self.setup_google_recaptcha_response()

            # Unauthenticated should return 401
            r = c.post(url, headers={'X-CSRFToken': csrf})
            self.assertStatus(r, 401)

            self.login(self.real_user, c, csrf)

            # incorrect password, even though we're logged in should return 401
            payload = {
                'email': self.real_user.email,
                'password': '******',
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 401)

            # correct password, but that email is already registered
            payload = {
                'email': self.bootstrap_user.email,
                'password': self.passwords[self.real_user],
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf},
            )
            self.assertStatus(r, 403)

            # valid end-to-end workflow
            previous_email = self.real_user.email
            payload = {
                'email': 'changed@email',
                'password': self.passwords[self.real_user],
                'verify_url': 'http://not_relevant.com'
            }
            r = c.post(
                url,
                data=json.dumps(payload),
                headers={'X-CSRFToken': csrf}
            )
            self.assertStatus(r, 200)

            u = user_manipulator.first(email='changed@email')
            self.assertIsNotNone(u)
            self.assertIsNone(u.confirmed_at)
            self.assertIsNotNone(
                user_manipulator.first(email=previous_email)
            )
            self.assertIsNotNone(
                user_manipulator.first(email=previous_email).confirmed_at
            )
            self.assertNotEqual(self.real_user.email, "changed@email")

            # Get the token that this view will send, send it to the
            # verfication email endpoint, and check that the user's email
            # was correctly updated
            msg, token = utils.send_email(
                email_addr="changed@email",
                email_template=VerificationEmail,
                payload = ["changed@email", self.real_user.id],
            )

            url = url_for('verifyemailview', token=token)
            r = self.client.get(url)
            self.assertStatus(r, 200)
            self.assertIsNone(
                user_manipulator.first(email=previous_email)
            )
            self.assertEqual(self.real_user.email, "changed@email")