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 89d3fc6..ab0ebef 100644 --- a/flask_stormpath/__init__.py +++ b/flask_stormpath/__init__.py @@ -63,6 +63,11 @@ login, logout, register, + id_site_login, + id_site_logout, + id_site_register, + id_site_forgot_password, + id_site_callback, ) @@ -163,56 +168,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): @@ -274,9 +319,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 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/settings.py b/flask_stormpath/settings.py index c9441ca..21a87e2 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) 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) +