Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Id site #37

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
<http://docs.stormpath.com/guides/using-id-site/>`_. 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::
Expand Down
107 changes: 76 additions & 31 deletions flask_stormpath/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@
login,
logout,
register,
id_site_login,
id_site_logout,
id_site_register,
id_site_forgot_password,
id_site_callback,
)


Expand Down Expand Up @@ -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):
Expand Down
51 changes: 51 additions & 0 deletions flask_stormpath/id_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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):
"""
Get user using :class:`stormpath.id_site.IdSiteCallbackResult`'s
:class:`stormpath.resources.account.Account` object. Login that
user.
"""
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 current user.
"""
logout_user()
return redirect('/')


_handle_registered = _handle_authenticated


def handle_id_site_callback(id_site_response):
"""
Handle different actions depending on
:class:`stormpath.id_site.IdSiteCallbackResult`'s status.
"""
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
}

11 changes: 11 additions & 0 deletions flask_stormpath/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ def from_login(self, login, password):

return _user

@classmethod
def from_id_site(self, account):
"""
Create a new User class given a
:class:`stormpath.resources.account.Account` object.
"""
_user = account
_user.__class__ = User

return _user

@classmethod
def from_google(self, code):
"""
Expand Down
2 changes: 2 additions & 0 deletions flask_stormpath/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
63 changes: 62 additions & 1 deletion flask_stormpath/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
current_app,
flash,
redirect,
url_for,
render_template,
request,
)
Expand All @@ -21,6 +22,7 @@
RegistrationForm,
)
from .models import User
from .id_site import handle_id_site_callback


def register():
Expand Down Expand Up @@ -396,6 +398,65 @@ def logout():

This view will log a user out of their account (destroying their session),
then redirect the user to the home page of the site.
"""
"""
logout_user()
return redirect('/')


def id_site_login():
"""
Use Stormpath SDK to generate the redirection URL for the ID Site
login page. Redirect the user to the ID Site URL.
"""
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():
"""
Use Stormpath SDK to generate the redirection URL for the ID Site
registration page. Redirect the user to the ID Site URL.
"""
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():
"""
Use Stormpath SDK to generate the redirection URL for the ID Site
forgot password page. Redirect the user to the ID Site URL.
"""
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():
"""
Use Stormpath SDK to generate the redirection URL for the ID Site
logout page. Redirect the user to the ID Site URL.
"""
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():
"""
Use Stormpath SDK to get the
:class:`stormpath.id_site.IdSiteCallbackResult` object based on
callback URL. Handle the result.
"""
ret = current_app.stormpath_manager.application.handle_id_site_callback(
request.url)
return handle_id_site_callback(ret)

16 changes: 15 additions & 1 deletion tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ def tearDown(self):
directory.delete()


class StormpathIdSiteTestCase(StormpathTestCase):
"""
StormpathTestCase with ID Site.
"""
def setUp(self):
"""Provision a new Client, Application, and Flask app with ID Site."""
self.client = bootstrap_client()
self.application = bootstrap_app(self.client)
self.app = bootstrap_flask_app(self.application, True)


def bootstrap_client():
"""
Create a new Stormpath Client from environment variables.
Expand Down Expand Up @@ -77,7 +88,7 @@ def bootstrap_app(client):
}, create_directory=True)


def bootstrap_flask_app(app):
def bootstrap_flask_app(app, use_id_site=False):
"""
Create a new, fully initialized Flask app.

Expand All @@ -92,6 +103,9 @@ def bootstrap_flask_app(app):
a.config['STORMPATH_API_KEY_SECRET'] = environ.get('STORMPATH_API_KEY_SECRET')
a.config['STORMPATH_APPLICATION'] = app.name
a.config['WTF_CSRF_ENABLED'] = False
a.config['STORMPATH_ENABLE_ID_SITE'] = use_id_site
if use_id_site:
a.config['STORMPATH_ID_SITE_CALLBACK_URL'] = '/'
StormpathManager(a)

return a
Loading