From e9bd9e646fcde7e87ac940014bda3aba2bcea125 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 3 Dec 2014 15:53:46 +0100 Subject: [PATCH 1/4] enable STORMPATH_APPLICATION setting to be name or href this fixes the scenario when the web app blows up if STORMPATH_APPLICAITON is a href. --- flask_stormpath/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flask_stormpath/__init__.py b/flask_stormpath/__init__.py index 4ae984d..6006373 100644 --- a/flask_stormpath/__init__.py +++ b/flask_stormpath/__init__.py @@ -264,9 +264,13 @@ def application(self): ctx = stack.top if ctx is not None: if not hasattr(ctx, 'stormpath_application'): - ctx.stormpath_application = self.client.applications.search( - self.app.config['STORMPATH_APPLICATION'] - )[0] + if self.app.config['STORMPATH_APPLICATION'].startswith('http'): + ctx.stormpath_application = self.client.applications.get( + self.app.config['STORMPATH_APPLICATION']) + else: + ctx.stormpath_application = self.client.applications.search( + self.app.config['STORMPATH_APPLICATION'] + )[0] return ctx.stormpath_application From b9221a8cb11ff4d8d44b8660558b6bc2f7751e30 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 3 Dec 2014 15:56:26 +0100 Subject: [PATCH 2/4] add ID Site support to flask - Added a setting STORMPATH_ENABLE_ID_SITE which basically switches the built in manual views/forms for the ID site ones. Another settings called STORMPATH_ID_SITE_CALLBACK_URL was added as well. - Updated the stormpath sdk dependency to point to the latest version. I was hitting the search issue with '/' and '&' charaters resulting in a bad error message informing me of invalid API credentials, and this has been resolved in the latest sdk version. - Added quickstart docs for this feature --- docs/quickstart.rst | 20 +++++++ flask_stormpath/__init__.py | 107 +++++++++++++++++++++++++----------- flask_stormpath/id_site.py | 39 +++++++++++++ flask_stormpath/models.py | 7 +++ flask_stormpath/views.py | 40 ++++++++++++++ setup.py | 2 +- 6 files changed, 183 insertions(+), 32 deletions(-) create mode 100644 flask_stormpath/id_site.py diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 2ee3642..dc932cd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -98,6 +98,26 @@ walk you through the basics: - Navigate to ``/login``. You will see a login page. You can now re-enter your user credentials and log into the site again. +ID Site +------- + +If you'd like to not worry about using your own registration and login +screens at all, you can use Stormpath's new `ID site feature +`_. This is a hosted login +subdomain which handles authentication for you automatically. + +To make this work, you need to specify a few additional settings: + + app.config['STORMPATH_ENABLE_ID_SITE'] = True + app.config['STORMPATH_ID_SITE_CALLBACK_URL'] = '/id-site-callback' + +.. note:: + Please note that the ID Site callback URL must be a relative path and it must + match the one set in the Stormpath ID Site Dashboard. + For production pruposes your will probably also want to set app.config['SERVER_NAME'] + for the relative callback url to be properly generated to match the absolute URL + specified in the Stormpath ID Site Dashboard. + Wasn't that easy?! .. note:: diff --git a/flask_stormpath/__init__.py b/flask_stormpath/__init__.py index 6006373..7547ed6 100644 --- a/flask_stormpath/__init__.py +++ b/flask_stormpath/__init__.py @@ -55,6 +55,11 @@ login, logout, register, + id_site_login, + id_site_logout, + id_site_register, + id_site_forgot_password, + id_site_callback, ) @@ -155,56 +160,96 @@ def init_routes(self, app): :param obj app: The Flask app. """ - if app.config['STORMPATH_ENABLE_REGISTRATION']: - app.add_url_rule( - app.config['STORMPATH_REGISTRATION_URL'], - 'stormpath.register', - register, - methods = ['GET', 'POST'], - ) - if app.config['STORMPATH_ENABLE_LOGIN']: + if app.config['STORMPATH_ENABLE_ID_SITE']: + app.add_url_rule( app.config['STORMPATH_LOGIN_URL'], 'stormpath.login', - login, - methods = ['GET', 'POST'], + id_site_login, + methods = ['GET'], ) - if app.config['STORMPATH_ENABLE_FORGOT_PASSWORD']: app.add_url_rule( - app.config['STORMPATH_FORGOT_PASSWORD_URL'], - 'stormpath.forgot', - forgot, - methods = ['GET', 'POST'], + app.config['STORMPATH_REGISTRATION_URL'], + 'stormpath.register', + id_site_register, + methods = ['GET'], ) + app.add_url_rule( - app.config['STORMPATH_FORGOT_PASSWORD_CHANGE_URL'], - 'stormpath.forgot_change', - forgot_change, - methods = ['GET', 'POST'], + app.config['STORMPATH_FORGOT_PASSWORD_URL'], + 'stormpath.forgot', + id_site_forgot_password, + methods = ['GET'], ) - if app.config['STORMPATH_ENABLE_LOGOUT']: app.add_url_rule( app.config['STORMPATH_LOGOUT_URL'], 'stormpath.logout', - logout, + id_site_logout, + methods = ['GET'], ) - if app.config['STORMPATH_ENABLE_GOOGLE']: app.add_url_rule( - app.config['STORMPATH_GOOGLE_LOGIN_URL'], - 'stormpath.google_login', - google_login, + app.config['STORMPATH_ID_SITE_CALLBACK_URL'], + 'stormpath.id_site_callback', + id_site_callback, + methods = ['GET'], ) - if app.config['STORMPATH_ENABLE_FACEBOOK']: - app.add_url_rule( - app.config['STORMPATH_FACEBOOK_LOGIN_URL'], - 'stormpath.facebook_login', - facebook_login, - ) + else: + + if app.config['STORMPATH_ENABLE_REGISTRATION']: + app.add_url_rule( + app.config['STORMPATH_REGISTRATION_URL'], + 'stormpath.register', + register, + methods = ['GET', 'POST'], + ) + + if app.config['STORMPATH_ENABLE_LOGIN']: + app.add_url_rule( + app.config['STORMPATH_LOGIN_URL'], + 'stormpath.login', + login, + methods = ['GET', 'POST'], + ) + + if app.config['STORMPATH_ENABLE_FORGOT_PASSWORD']: + app.add_url_rule( + app.config['STORMPATH_FORGOT_PASSWORD_URL'], + 'stormpath.forgot', + forgot, + methods = ['GET', 'POST'], + ) + app.add_url_rule( + app.config['STORMPATH_FORGOT_PASSWORD_CHANGE_URL'], + 'stormpath.forgot_change', + forgot_change, + methods = ['GET', 'POST'], + ) + + if app.config['STORMPATH_ENABLE_LOGOUT']: + app.add_url_rule( + app.config['STORMPATH_LOGOUT_URL'], + 'stormpath.logout', + logout, + ) + + if app.config['STORMPATH_ENABLE_GOOGLE']: + app.add_url_rule( + app.config['STORMPATH_GOOGLE_LOGIN_URL'], + 'stormpath.google_login', + google_login, + ) + + if app.config['STORMPATH_ENABLE_FACEBOOK']: + app.add_url_rule( + app.config['STORMPATH_FACEBOOK_LOGIN_URL'], + 'stormpath.facebook_login', + facebook_login, + ) @property def client(self): diff --git a/flask_stormpath/id_site.py b/flask_stormpath/id_site.py new file mode 100644 index 0000000..a40109c --- /dev/null +++ b/flask_stormpath/id_site.py @@ -0,0 +1,39 @@ +from flask.ext.login import login_user, logout_user +from flask import redirect, current_app, request + +from .models import User + + +ID_SITE_STATUS_AUTHENTICATED = 'AUTHENTICATED' +ID_SITE_STATUS_LOGOUT = 'LOGOUT' +ID_SITE_STATUS_REGISTERED = 'REGISTERED' + + +def _handle_authenticated(id_site_response): + login_user(User.from_id_site(id_site_response.account), + remember=True) + return redirect(request.args.get('next') or current_app.config['STORMPATH_REDIRECT_URL']) + + +def _handle_logout(id_site_response): + logout_user() + return redirect('/') + + +_handle_registered = _handle_authenticated + + +def handle_id_site_callback(id_site_response): + if id_site_response: + action = CALLBACK_ACTIONS[id_site_response.status] + return action(id_site_response) + else: + return None + + +CALLBACK_ACTIONS = { + ID_SITE_STATUS_AUTHENTICATED: _handle_authenticated, + ID_SITE_STATUS_LOGOUT: _handle_logout, + ID_SITE_STATUS_REGISTERED: _handle_registered +} + diff --git a/flask_stormpath/models.py b/flask_stormpath/models.py index 8417ccd..0dc4b55 100644 --- a/flask_stormpath/models.py +++ b/flask_stormpath/models.py @@ -100,6 +100,13 @@ def from_login(self, login, password): return _user + @classmethod + def from_id_site(self, account): + _user = account + _user.__class__ = User + + return _user + @classmethod def from_google(self, code): """ diff --git a/flask_stormpath/views.py b/flask_stormpath/views.py index 0beaa9a..75ca407 100644 --- a/flask_stormpath/views.py +++ b/flask_stormpath/views.py @@ -7,6 +7,7 @@ current_app, flash, redirect, + url_for, render_template, request, ) @@ -21,6 +22,7 @@ RegistrationForm, ) from .models import User +from .id_site import handle_id_site_callback def register(): @@ -399,3 +401,41 @@ def logout(): """ logout_user() return redirect('/') + + +def id_site_login(): + rdr = current_app.stormpath_manager.application.build_id_site_redirect_url( + callback_uri=url_for('stormpath.id_site_callback', _external=True), + state=request.args.get('state')) + return redirect(rdr) + + +def id_site_register(): + rdr = current_app.stormpath_manager.application.build_id_site_redirect_url( + callback_uri=url_for('stormpath.id_site_callback', _external=True), + state=request.args.get('state'), + path="/#/register") + return redirect(rdr) + + +def id_site_forgot_password(): + rdr = current_app.stormpath_manager.application.build_id_site_redirect_url( + callback_uri=url_for('stormpath.id_site_callback', _external=True), + state=request.args.get('state'), + path="/#/forgot") + return redirect(rdr) + + +def id_site_logout(): + rdr = current_app.stormpath_manager.application.build_id_site_redirect_url( + callback_uri=url_for('stormpath.id_site_callback', _external=True), + state=request.args.get('state'), + logout=True) + return redirect(rdr) + + +def id_site_callback(): + ret = current_app.stormpath_manager.application.handle_id_site_callback( + request.url) + return handle_id_site_callback(ret) + diff --git a/setup.py b/setup.py index f74c105..85afeff 100644 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def run(self): 'Flask-WTF>=0.9.5', 'facebook-sdk==0.4.0', 'oauth2client==1.2', - 'stormpath==1.2.4', + 'stormpath==1.2.6', ], classifiers = [ 'Environment :: Web Environment', From 7b20d09bb166e75c43c9d6b5d7bbf4be6897ea74 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 3 Dec 2014 21:11:04 +0100 Subject: [PATCH 3/4] forgot to add default settings for id site --- flask_stormpath/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flask_stormpath/settings.py b/flask_stormpath/settings.py index ab0df17..ac3d2e7 100644 --- a/flask_stormpath/settings.py +++ b/flask_stormpath/settings.py @@ -19,6 +19,8 @@ def init_settings(config): config.setdefault('STORMPATH_API_KEY_SECRET', None) config.setdefault('STORMPATH_API_KEY_FILE', None) config.setdefault('STORMPATH_APPLICATION', None) + config.setdefault('STORMPATH_ENABLE_ID_SITE', False) + config.setdefault('STORMPATH_ID_SITE_CALLBACK_URL', None) # Which fields should be displayed when registering new users? config.setdefault('STORMPATH_ENABLE_FACEBOOK', False) From 75db757daf8e5016a2c2cd84dc6890f85d70b7e9 Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Wed, 3 Dec 2014 16:17:46 +0100 Subject: [PATCH 4/4] another fix for running live tests on PRs --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3eaf011..2001f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ install: - pip install -r requirements.txt - python setup.py develop script: - - python setup.py test + - test -z "$STORMPATH_API_KEY_SECRET" || python setup.py test - cd docs && make html env: global: