Permalink
Browse files

adding provider param to _on_signin(); making example work

  • Loading branch information...
1 parent fd9eb00 commit 58946cb170676ce77a4999ee3beb42edb935f016 @x1ddos committed Feb 12, 2012
Showing with 136 additions and 33 deletions.
  1. +4 −4 README
  2. +100 −12 example/handlers.py
  3. +2 −2 example/main.py
  4. +1 −1 example/templates/layout.html
  5. +17 −4 example/templates/profile.html
  6. +12 −10 simpleauth/handler.py
View
@@ -37,7 +37,7 @@ Getting Started
class AuthHandler(SomeBaseRequestHandler, SimpleAuthHandler):
"""Authentication handler for all kinds of auth."""
- def _on_signin(self, data, auth_info):
+ def _on_signin(self, data, auth_info, provider):
"""Callback whenever a new or existing user is logging in.
data is a user info dictionary.
auth_info contains access token or oauth token and secret.
@@ -90,9 +90,9 @@ For instance, SomeBaseRequestHandler could be webapp2.RequestHandler.
# Map URLs to handlers
routes = [
- Route('/auth/<provider>', handler='handlers.AuthHandler:auth', name='auth_login'),
- Route('/auth/<provider>/callback', handler='handlers.AuthHandler:callback', name='auth_callback'),
- Route('/logout', handler='handlers.AuthHandler:logout', name='logout')
+ Route('/auth/<provider>', handler='handlers.AuthHandler:_simple_auth', name='auth_login'),
+ Route('/auth/<provider>/callback', handler='handlers.AuthHandler:_auth_callback', name='auth_callback'),
+ Route('/logout', handler='handlers.AuthHandler:logout', name='logout')
]
5. That's it. See a sample app in the example dir.
View
@@ -2,6 +2,8 @@
import logging
import secrets
+from ndb import Key
+
import webapp2
from webapp2_extras import auth, sessions, jinja2
from jinja2.runtime import TemplateNotFound
@@ -31,19 +33,20 @@ def session(self):
"""Returns a session using the default cookie key"""
return self.session_store.get_session()
- # @webapp2.cached_property
- # def auth(self):
- # return auth.get_auth()
+ @webapp2.cached_property
+ def auth(self):
+ return auth.get_auth()
@webapp2.cached_property
- def user(self):
+ def current_user(self):
"""Returns currently logged in user"""
- return auth.get_auth().get_user_by_session()
+ user_dict = self.auth.get_user_by_session()
+ return self.auth.store.user_model.get_by_id(user_dict['user_id'])
@webapp2.cached_property
def logged_in(self):
"""Returns true if a user is currently logged in, false otherwise"""
- return self.user is not None
+ return self.auth.get_user_by_session() is not None
def render(self, template_name, template_vars={}):
@@ -76,18 +79,91 @@ class ProfileHandler(BaseRequestHandler):
def get(self):
"""Handles GET /profile"""
if self.logged_in:
- self.render('profile.html', {'user': self.user, 'session': self.session})
+ self.render('profile.html', {
+ 'user': self.current_user, 'session': self.auth.get_user_by_session()
+ })
else:
self.redirect('/')
class AuthHandler(BaseRequestHandler, SimpleAuthHandler):
- """Authentication handler for all kinds of auth."""
+ """Authentication handler for OAuth 2.0, 1.0(a) and OpenID."""
- def _on_signin(self, data, auth_info):
- """Return value is ignored. Raise an exception if needed."""
- auth_id = auth_info['id']
- self.redirect('/')
+ USER_ATTRS = {
+ 'google' : {
+ 'picture': 'avatar_url',
+ 'name' : 'name',
+ 'link' : 'link'
+ },
+ 'facebook' : {
+ 'id' : lambda id: ('avatar_url', 'http://graph.facebook.com/{0}/picture?type=large'.format(id)),
+ 'name' : 'name',
+ 'link' : 'link'
+ },
+ 'windows_live': {
+ 'avatar_url': 'avatar_url',
+ 'name' : 'name',
+ 'link' : 'link'
+ },
+ 'twitter' : {
+ 'profile_image_url': 'avatar_url',
+ 'screen_name' : 'name',
+ 'link' : 'link'
+ },
+ 'linkedin' : {
+ 'picture-url' : 'avatar_url',
+ 'first-name' : 'name',
+ 'public-profile-url': 'link'
+ },
+ 'openid' : {
+ 'id' : lambda id: ('avatar_url', '/img/missing-avatar.png'),
+ 'nickname': 'name',
+ 'email' : 'link'
+ }
+ }
+
+ def _on_signin(self, data, auth_info, provider):
+ """Callback whenever a new or existing user is logging in.
+ data is a user info dictionary.
+ auth_info contains access token or oauth token and secret.
+ """
+ auth_id = '%s:%s' % (provider, data['id'])
+ logging.info('Looking for a user with id %s' % auth_id)
+
+ user = self.auth.store.user_model.get_by_auth_id(auth_id)
+ if user:
+ logging.info('Found existing user to log in')
+ # existing user. just log them in.
+ self.auth.set_session(
+ self.auth.store.user_to_dict(user)
+ )
+
+ else:
+ # check whether there's a user currently logged in
+ # then, create a new user if nobody's signed in,
+ # otherwise add this auth_id to currently logged in user.
+ if self.logged_in:
+ logging.info('Updating currently logged in user')
+
+ u = self.current_user
+ u.auth_ids.append(auth_id)
+ u.populate(**self._to_user_model_attrs(data, self.USER_ATTRS[provider]))
+ u.put()
+
+ else:
+ logging.info('Creating a brand new user')
+
+ ok, user = self.auth.store.user_model.create_user(
+ auth_id, **self._to_user_model_attrs(data, self.USER_ATTRS[provider])
+ )
+
+ if ok:
+ self.auth.set_session(
+ self.auth.store.user_to_dict(user)
+ )
+
+ # show them their profile data
+ self.redirect('/profile')
def logout(self):
self.auth.unset_session()
@@ -99,3 +175,15 @@ def _callback_uri_for(self, provider):
def _get_consumer_info_for(self, provider):
"""Returns a tuple (key, secret) for auth init requests."""
return secrets.AUTH_CONFIG[provider]
+
+ def _to_user_model_attrs(self, data, attrs_map):
+ user_attrs = {}
+ for k, v in data.iteritems():
+ if k in attrs_map:
+ key = attrs_map[k]
+ if isinstance(key, str):
+ user_attrs.setdefault(key, v)
+ else:
+ user_attrs.setdefault(*key(v))
+
+ return user_attrs
View
@@ -25,8 +25,8 @@
Route('/', handler='handlers.RootHandler'),
Route('/profile', handler='handlers.ProfileHandler', name='profile'),
- Route('/auth/<provider>', handler='handlers.AuthHandler:auth', name='auth_login'),
- Route('/auth/<provider>/callback', handler='handlers.AuthHandler:callback', name='auth_callback'),
+ Route('/auth/<provider>', handler='handlers.AuthHandler:_simple_auth', name='auth_login'),
+ Route('/auth/<provider>/callback', handler='handlers.AuthHandler:_auth_callback', name='auth_callback'),
Route('/logout', handler='handlers.AuthHandler:logout', name='logout')
]
@@ -15,7 +15,7 @@
{% block main %}{% endblock %}
</div>
<footer><nav><ul class="inline bull">
- <li>source code: <a href="">http://...</a></li>
+ <li><a href="https://github.com/crhym3/simpleauth" target="_blank">https://github.com/crhym3/simpleauth</a></li>
</ul></nav></footer>
</body>
</html>
@@ -1,8 +1,21 @@
{% extends "layout.html" %}
{% block main %}
- <h1>Hello, {{ user.name }}</h1>
- <img src="{{ user.avatar_url }}">
- <p>email: {{ ', '.join(user.emails) }}</p>
- <p><a href="{{ user.link }}" target="_blank" rel="nofollow">{{ user.link }}</a></p>
+ <div class="media">
+ <div class="img avatar">
+ <img src="{{ user.avatar_url }}">
+ </div>
+ <div class="body">
+ <h1 style="margin-top: 0; padding-top: 0">{{ user.name }}</h1>
+ <p><a href="{{ user.link }}" target="_blank" rel="nofollow">{{ user.link }}</a></p>
+ </div>
+ </div>
+
+
+ <h2>auth session</h2>
+ <pre>{{ session }}</pre>
+
+ <p style="margin-top: 2em;">
+ <a href="/logout" class="btn">logout</a>
+ </p>
{% endblock %}
View
@@ -78,7 +78,7 @@ class SimpleAuthHandler(object):
'twitter' : '_query_string_parser'
}
- def auth(self, provider=None):
+ def _simple_auth(self, provider=None):
"""Dispatcher of auth init requests, e.g.
GET /auth/PROVIDER
@@ -97,7 +97,7 @@ def auth(self, provider=None):
logging.error('Provider %s is not supported' % provider)
self._provider_not_supported(provider)
- def callback(self, provider=None):
+ def _auth_callback(self, provider=None):
"""Dispatcher of callbacks from auth providers, e.g.
/auth/PROVIDER/callback?params=...
@@ -110,7 +110,11 @@ def callback(self, provider=None):
meth = '_%s_callback' % cfg[0]
if hasattr(self, meth):
try:
- getattr(self, meth)(provider, *cfg[-1:])
+
+ user_data, auth_info = getattr(self, meth)(provider, *cfg[-1:])
+ # we're done here. the rest should be implemented by the actual app
+ self._on_signin(user_data, auth_info, provider)
+
except:
error_msg = str(sys.exc_info()[1])
logging.error(error_msg)
@@ -181,8 +185,7 @@ def _oauth2_callback(self, provider, access_token_url):
auth_info = getattr(self, self.TOKEN_RESPONSE_PARSERS[provider])(resp.content)
user_data = getattr(self, '_get_%s_user_info' % provider)(auth_info, key=consumer_key, secret=consumer_secret)
- # we're done here. the rest should be implemented by the actual app
- self._on_signin(user_data, auth_info)
+ return (user_data, auth_info)
def _oauth1_init(self, provider, auth_urls):
"""Initiates OAuth 1.0 dance"""
@@ -212,6 +215,7 @@ def _oauth1_init(self, provider, auth_urls):
def _oauth1_callback(self, provider, access_token_url):
+ """Third step of OAuth 1.0 dance."""
request_token = self.session.pop('req_token', None)
verifier = self.request.get('oauth_verifier', None)
consumer_key, consumer_secret = self._get_consumer_info_for(provider)
@@ -230,9 +234,7 @@ def _oauth1_callback(self, provider, access_token_url):
auth_info = getattr(self, self.TOKEN_RESPONSE_PARSERS[provider])(content)
user_data = getattr(self, '_get_%s_user_info' % provider)(auth_info, key=consumer_key, secret=consumer_secret)
- # we're done here. the rest should be implemented by the actual app
- self._on_signin(user_data, auth_info)
-
+ return (user_data, auth_info)
def _openid_init(self, provider='openid', identity=None):
"""Initiates OpenID dance using App Engine users module API."""
@@ -262,8 +264,8 @@ def _openid_callback(self, provider='openid', _identity=None):
'email' : user.email()
}
- # we're done here. the rest should be implemented by the actual app
- self._on_signin(uinfo, {'provider': user.federated_provider()})
+ return (uinfo, {'provider': user.federated_provider()})
+
#
# callbacks and consumer key/secrets

0 comments on commit 58946cb

Please sign in to comment.