{ "info": { "author": "Alex Vaghin (alex@cloudware.it)", "author_email": "UNKNOWN", "bugtrack_url": null, "classifiers": [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Internet :: WWW/HTTP", "Topic :: Security", "Topic :: Software Development :: Libraries" ], "description": "This python module provides a simple authentication wrapper for a\r\n[Google App Engine][13] app.\r\n\r\nSupported specs:\r\n\r\n - OAuth 2.0\r\n - OAuth 1.0(a)\r\n - OpenID\r\n\r\nSupported providers out of the box:\r\n\r\n - Google (OAuth 2.0, [deprecated][14])\r\n - Google+ (OAuth 2.0)\r\n - Facebook (OAuth 2.0)\r\n - Windows Live (OAuth 2.0)\r\n - foursquare (OAuth 2.0)\r\n - Twitter (OAuth 1.0a)\r\n - LinkedIn (OAuth 1.0a, deprecated)\r\n - LinkedIn (OAuth 2.0)\r\n - OpenID, using App Engine users module API\r\n\r\nDependencies:\r\n\r\n - [python-oauth2][1]. This is actually a library implementing OAuth 1.0 specs.\r\n - [httplib2][2] (as a dependency of python-oauth2)\r\n\r\nLinks:\r\n\r\n - Demo (example app): https://simpleauth.appspot.com\r\n - Source code: https://github.com/crhym3/simpleauth\r\n - Mirror on Google Code: http://code.google.com/p/gae-simpleauth/\r\n - Discussions: https://groups.google.com/d/forum/gae-simpleauth\r\n\r\n\r\n## Install\r\n \r\n1. Clone the source repo and place `simpleauth` module into your\r\n app root or a sub dir. If you do the latter don't forget to add\r\n it to your `sys.path`.\r\n\r\n2. Get oauth2 lib (e.g. `pip install oauth2` or [clone the repo][1]) and copy it\r\n over to your app root or a sub dir.\r\n \r\n3. Get [httplib2][2] and again, copy it to your app root.\r\n\r\n## Usage\r\n\r\n1. Create a request handler by subclassing SimpleAuthHandler, e.g.\r\n\r\n ```python\r\n class AuthHandler(SomeBaseRequestHandler, SimpleAuthHandler):\r\n \"\"\"Authentication handler for all kinds of auth.\"\"\"\r\n\r\n def _on_signin(self, data, auth_info, provider, extra=None):\r\n \"\"\"Callback whenever a new or existing user is logging in.\r\n data is a user info dictionary.\r\n auth_info contains access token or oauth token and secret.\r\n extra is a dict with additional params passed to the auth init handler.\r\n\r\n See what's in it with e.g. logging.info(auth_info)\r\n \"\"\"\r\n\r\n auth_id = '%s:%s' % (provider, data['id'])\r\n\r\n # Possible flow:\r\n # \r\n # 1. check whether user exist, e.g.\r\n # User.get_by_auth_id(auth_id)\r\n #\r\n # 2. create a new user if it doesn't\r\n # User(**data).put()\r\n #\r\n # 3. sign in the user\r\n # self.session['_user_id'] = auth_id\r\n #\r\n # 4. redirect somewhere, e.g. self.redirect('/profile')\r\n #\r\n # See more on how to work the above steps here:\r\n # http://webapp-improved.appspot.com/api/webapp2_extras/auth.html\r\n # http://code.google.com/p/webapp-improved/issues/detail?id=20\r\n \r\n def logout(self):\r\n self.auth.unset_session()\r\n self.redirect('/')\r\n\r\n def _callback_uri_for(self, provider):\r\n return self.uri_for('auth_callback', provider=provider, _full=True)\r\n\r\n def _get_consumer_info_for(self, provider):\r\n \"\"\"Should return a tuple (key, secret) for auth init requests.\r\n For OAuth 2.0 you should also return a scope, e.g.\r\n ('my app/client id', 'my app/client secret', 'email,user_about_me')\r\n\r\n The scope depends solely on the provider.\r\n See example/secrets.py.template\r\n \"\"\"\r\n return secrets.AUTH_CONFIG[provider]\r\n ```\r\n\r\n Note that SimpleAuthHandler isn't a real request handler. It's up to you.\r\n For instance, SomeBaseRequestHandler could be [webapp2.RequestHandler][6].\r\n\r\n2. Add routing so that `/auth/`, `/auth//callback`\r\n and `/logout` requests go to your `AuthHandler`.\r\n \r\n For instance, in webapp2 you could do:\r\n \r\n ```python\r\n # Map URLs to handlers\r\n routes = [\r\n Route('/auth/',\r\n handler='handlers.AuthHandler:_simple_auth', name='auth_login'),\r\n Route('/auth//callback', \r\n handler='handlers.AuthHandler:_auth_callback', name='auth_callback'),\r\n Route('/logout',\r\n handler='handlers.AuthHandler:logout', name='logout')\r\n ]\r\n ```\r\n\r\n3. That's it. See a sample app in the [example dir][3].\r\n To run the example app, copy `example/secrets.py.template` into\r\n `example/secrets.py`, modify accordingly and start the app locally\r\n by executing `dev_appserver.py example/`\r\n\r\n\r\n##\u00a0OAuth scopes, keys and secrets\r\n\r\nThis section is just a bunch of links to the docs on authentication with\r\nvarious providers.\r\n\r\n### Google\r\n\r\n - Docs: https://developers.google.com/accounts/docs/OAuth2WebServer\r\n - Get client/secret: https://console.developers.google.com\r\n\r\nMultiple scopes should be space-separated, e.g. `profile email`.\r\n\r\nMultiple callback URLs on different domains are awesomely supported.\r\nIf you're running two versions of the app, say one on `localhost` and another\r\non `example.org`, simply add all of the callbacks including host, port and \r\nprotocol to `Redirect URIs` list on API Access tab.\r\n\r\n`userinfo` endpoint is [deprecated][14], use Google+ (googleplus) provider.\r\n\r\n### Facebook\r\n\r\n - Docs: https://developers.facebook.com/docs/authentication/server-side/\r\n - Get client/secret: https://developers.facebook.com/apps\r\n\r\nMultiple Scopes should be comma-separated, e.g. `user_about_me,email`.\r\n\r\nFull list of scopes:\r\nhttp://developers.facebook.com/docs/authentication/permissions/\r\n\r\nMultiple callback URLs on different domains are not supported by a single app\r\nregistration. If you need to test your app on say, `localhost` and\r\n`example.org`, you should probably register two different applications and use\r\nthe appropriate set of key/secret: one for localhost, and the other\r\nfor example.org.\r\n\r\nAlso, there's a `Sandbox Mode` checkbox on Facebook's app settings page. Make\r\nsure it's disabled for a public/live website. Otherwise, almost nobody\r\nexcept you will be able to authenticate.\r\n\r\n### LinkedIn OAuth 2.0\r\n\r\n - Docs: https://developer.linkedin.com/documents/authentication\r\n - Get client/secret: https://www.linkedin.com/secure/developer\r\n\r\nScopes are space-separated, e.g. `r_fullprofile r_emailaddress r_network`.\r\n\r\nSee Member Permissions section for more details:\r\nhttps://developer.linkedin.com/documents/authentication#granting\r\n\r\n### Windows Live\r\n\r\n - Docs: http://msdn.microsoft.com/en-us/library/live/hh243649.aspx\r\n - Get client/secret: https://account.live.com/developers/applications\r\n\r\nScopes are space-separated, e.g. `wl.signin wl.basic`.\r\n\r\nFull list of scopes:\r\nhttp://msdn.microsoft.com/en-us/library/live/hh243646.aspx\r\n\r\nMultiple callback URLs on different domains are not supported by a single app\r\nregistration. If you need to test your app on say, `localhost` and\r\n`example.org`, you should probably register two different applications and use\r\nthe appropriate set of key/secret: one for localhost, and the other\r\nfor example.org.\r\n\r\n### LinkedIn OAuth 1.0a (deprecated)\r\n\r\n - Docs: https://developer.linkedin.com/documents/authentication\r\n - Get client/secret: https://www.linkedin.com/secure/developer\r\n\r\nScopes are not supported. This is OAuth 1.0a.\r\n\r\nEven though LinkedIn will not give you any error about improper callback URI,\r\nit'll always use the value set in app's settings page. So, if you have two\r\nversions, say one on `localhost` and another on `example.org`, you'll probably\r\nwant to register two applications (e.g. `dev` and `production`) and use \r\nappropriate set of key/secret accordingly.\r\n\r\n### Twitter\r\n\r\n - Docs: https://dev.twitter.com/docs/auth/implementing-sign-twitter\r\n - Get client/secret: https://dev.twitter.com/apps\r\n\r\nScopes are not supported. This is OAuth 1.0a.\r\n\r\nWhen registering a new app, you have to specify a callback URL. Otherwise,\r\nit is considered as an `off-band` app and users will be given a PIN code\r\ninstead of being redirected back to your site.\r\n\r\nEven though Twitter will not give you any error about improper callback URI,\r\nit'll always use the value set in app's settings page. So, if you have two\r\nversions, say one on `localhost` and another on `example.org`, you'll probably\r\nwant to register two applications (e.g. `dev` and `production`) and use \r\nappropriate set of key/secret accordingly.\r\n\r\n### OpenID\r\n\r\nFor OpenID to work you'll need to set `AuthenticationType` to `FederatedLogin`\r\nin [App Engines application settings][4]. Beware of [this issue][12] if you enable FederatedLogin.\r\n\r\n\r\n## CSRF protection\r\n\r\nYou can optionally enable cross-site-request-forgery protection for OAuth 2.0:\r\n\r\n```python\r\nclass AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):\r\n\r\n # enabled CSRF state token for OAuth 2.0\r\n OAUTH2_CSRF_STATE = True\r\n\r\n # ...\r\n # rest of the stuff from step 4 of the above.\r\n```\r\n\r\nThis will use the optional OAuth 2.0 `state` param to guard against CSRFs by\r\nsetting a user session token during Authorization step and comparing it \r\nagainst the `state` parameter on callback.\r\n\r\nFor this to work your handler has to have a session dict-like object on the \r\ninstance. Here's an example using [webapp2_extras session][5]:\r\n\r\n```python\r\nimport webapp2\r\nfrom webapp2_extras import sessions\r\n\r\nclass AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):\r\n # enabled CSRF state token for OAuth 2.0\r\n OAUTH2_CSRF_STATE = True\r\n\r\n @webapp2.cached_property\r\n def session(self):\r\n \"\"\"Returns a session using the default cookie key\"\"\"\r\n return self.session_store.get_session()\r\n\r\n def dispatch(self):\r\n # Get a session store for this request.\r\n self.session_store = sessions.get_store(request=self.request)\r\n try:\r\n # Dispatch the request.\r\n webapp2.RequestHandler.dispatch(self)\r\n finally:\r\n # Save all sessions.\r\n self.session_store.save_sessions(self.response)\r\n\r\n # ...\r\n # the rest of the stuff from step 1 of Usage example.\r\n```\r\n\r\nThis simple implementation assumes it is safe to use user sessions.\r\nIf, however, user's session can be hijacked, the authentication flow could\r\nprobably be bypassed anyway and this CSRF protection becomes the least \r\nof the problems.\r\n\r\nAlternative implementation could involve `HMAC` digest. If anything serious \r\npops up (e.g. [see this SO question][7]) please submit a bug on the issue\r\ntracker.\r\n\r\n\r\n## Catching errors\r\n\r\nThere are a couple ways to catch authentication errors if you don't want your\r\napp to display a `Server Error` message when something goes wrong during\r\nan auth flow.\r\n\r\nYou can use [webapp2's built-in functionality][8] and define\r\n`handle_exception(self, exception, debug)` instance method on the handler\r\nthat processes authentication requests or on a base handler if you have one.\r\nHere's a simple example:\r\n\r\n```python\r\nclass AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):\r\n # _on_signin() and other stuff\r\n # ...\r\n\r\n def handle_exception(self, exception, debug):\r\n # Log the error\r\n logging.error(exception)\r\n \r\n # Do something based on the exception: notify users, etc.\r\n self.response.write(exception)\r\n self.response.set_status(500)\r\n```\r\n\r\nYou can also define global (app-wise) error handlers using `app.error_handlers`\r\ndict (where app is a `webapp2.WSGIApplication instance`).\r\n\r\n\r\nAnother solution is, if you're using webapp2's `dispatch` method like in the\r\nCSRF snippet above, you could do something like this:\r\n\r\n```python\r\n from simpleauth import Error as AuthError\r\n\r\n def dispatch(self):\r\n try:\r\n # Dispatch the request.\r\n webapp2.RequestHandler.dispatch(self)\r\n except AuthError as e:\r\n # Do something based on the error: notify users, etc.\r\n logging.error(e)\r\n self.redirect('/')\r\n```\r\n\r\nAlternatively, you can also use App Engine built-in functionality and define\r\nerror handlers in `app.yaml` as described in [Custom Error Responses][9].\r\n\r\nLastly, if nothing from the above works for you, override `_simple_auth()`\r\nand/or `_auth_callback()` methods. For instance:\r\n\r\n```python\r\nfrom simpleauth import SimpleAuthHandler\r\nfrom simpleauth import Error as AuthError\r\n\r\nclass AuthHandler(webapp2.RequestHandler, SimpleAuthHandler):\r\n def _simple_auth(self, provider=None):\r\n try:\r\n super(AuthHandler, self)._simple_auth(provider)\r\n except AuthError as e:\r\n # Do something based on the error: notify users, etc.\r\n logging.error(e)\r\n self.redirect('/')\r\n```\r\n\r\n## CONTRIBUTORS\r\n\r\nSubmit a pull request or better yet, use Rietveld at\r\n[codereview.appspot.com][10].\r\n\r\nThere are so many people contributed to this project (which is awesome!)\r\nbut I seemed to have lost track of some of them to put a complete and up-to-date list.\r\nThough I try keeping all [commits][11] with their authors intact, or just \r\nmention people in commit messages.\r\n\r\nIf you want to be mentioned here please do send me an email!\r\n\r\n\r\n## CHANGELOG\r\n\r\n - v0.1.4 - 2013-01-09\r\n * lxml lib requirement is now optional\r\n http://code.google.com/p/gae-simpleauth/issues/detail?id=3\r\n * Updated Windows Live OAuth 2.0 endpoints\r\n * A little more doc in this readme and code comments\r\n\r\n - v0.1.3 - 2012-09-19\r\n * CSRF protection for OAuth 2.0\r\n http://code.google.com/p/gae-simpleauth/issues/detail?id=1\r\n * Custom exceptions\r\n http://code.google.com/p/gae-simpleauth/issues/detail?id=2\r\n * Example app improvements, including:\r\n - CSRF guard\r\n - show exception messages for demo purposes\r\n - prettier output of session, profile data and auth_info dictionaries\r\n - https://github.com/crhym3/simpleauth/issues/4\r\n - https://github.com/crhym3/simpleauth/issues/5\r\n * More useful info in README\r\n\r\n\r\n[1]: https://github.com/simplegeo/python-oauth2\r\n[2]: http://code.google.com/p/httplib2/\r\n[3]: https://github.com/crhym3/simpleauth/tree/master/example\r\n[4]: https://developers.google.com/appengine/docs/adminconsole/applicationsettings\r\n[5]: http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html\r\n[6]: http://webapp-improved.appspot.com/api/webapp2.html#webapp2.RequestHandler\r\n[7]: http://security.stackexchange.com/questions/20187/oauth2-cross-site-request-forgery-and-state-parameter\r\n[8]: http://webapp-improved.appspot.com/guide/exceptions.html\r\n[9]: https://developers.google.com/appengine/docs/python/config/appconfig#Custom_Error_Responses\r\n[10]: https://codereview.appspot.com/\r\n[11]: https://github.com/crhym3/simpleauth/commits/master\r\n[12]: https://code.google.com/p/googleappengine/issues/detail?id=3258\r\n[13]: https://cloud.google.com/products/\r\n[14]: https://developers.google.com/+/api/auth-migration#timetable", "description_content_type": null, "docs_url": null, "download_url": "https://github.com/crhym3/simpleauth/archive/master.zip", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/crhym3/simpleauth", "keywords": "oauth oauth2 openid appengine google", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "simpleauth", "package_url": "https://pypi.org/project/simpleauth/", "platform": "any", "project_url": "https://pypi.org/project/simpleauth/", "project_urls": { "Download": "https://github.com/crhym3/simpleauth/archive/master.zip", "Homepage": "https://github.com/crhym3/simpleauth" }, "release_url": "https://pypi.org/project/simpleauth/0.1.5/", "requires_dist": null, "requires_python": null, "summary": "A simple auth handler for Google App Engine supporting OAuth 1.0a, 2.0 and OpenID", "version": "0.1.5" }, "last_serial": 1245951, "releases": { "0.1.1": [ { "comment_text": "", "digests": { "md5": "6fc07a48358bb8805960e2e14820b938", "sha256": "52d97831e262adddb3d0b2df3a73a0ac76e56d2b056e2c44ca34a2616d9f1787" }, "downloads": -1, "filename": "simpleauth-0.1.1.tar.gz", "has_sig": false, "md5_digest": "6fc07a48358bb8805960e2e14820b938", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 159950, "upload_time": "2012-02-12T18:58:12", "url": "https://files.pythonhosted.org/packages/c8/b6/16b3083563a2baba17b7dfb42487ebdcfbbdfa61b3cbfa43a294a7edfce1/simpleauth-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "a0bee986be2169694c15aff17542245c", "sha256": "92fe2109f178a938f065a4c9236a5b81f0766e8e38bdf4de4c1a397ec0e821fb" }, "downloads": -1, "filename": "simpleauth-0.1.2.tar.gz", "has_sig": false, "md5_digest": "a0bee986be2169694c15aff17542245c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 157934, "upload_time": "2012-02-19T16:22:22", "url": "https://files.pythonhosted.org/packages/0b/9e/c3ddc68e3055255a8ed486e87b07be28281c6ed86dfc21df46207aadabf2/simpleauth-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "d0d95379a0f51fff67a8a47aebeffd94", "sha256": "67510c5ea37e56dc2426a8186f0800f73838920b0e9f4258c177fd74e877d8a0" }, "downloads": -1, "filename": "simpleauth-0.1.3.tar.gz", "has_sig": false, "md5_digest": "d0d95379a0f51fff67a8a47aebeffd94", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 78541, "upload_time": "2012-09-19T08:05:50", "url": "https://files.pythonhosted.org/packages/7c/ab/b871118bb261d070dd25e6477a13afa813d845021d24030eafbeadf39bb4/simpleauth-0.1.3.tar.gz" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "9d8d2c821d0a7c18ae2dce7af4f2c03a", "sha256": "b337902aabd81a6009bd8b640c1f68f961c54737bdf79e01189dda93793844b4" }, "downloads": -1, "filename": "simpleauth-0.1.4.tar.gz", "has_sig": false, "md5_digest": "9d8d2c821d0a7c18ae2dce7af4f2c03a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 79808, "upload_time": "2013-01-09T21:48:54", "url": "https://files.pythonhosted.org/packages/2f/92/65cb531f56bee3ad6819434d530e9dac78a77be842cc4a195658ec3b38d6/simpleauth-0.1.4.tar.gz" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "96e6e20c3018d81934fef7a11bbf66c6", "sha256": "5b6061c624b718b928c04081e9dbabc856c2a8e35a9de0709950ffff9e114f89" }, "downloads": -1, "filename": "simpleauth-0.1.5-py2.7.egg", "has_sig": false, "md5_digest": "96e6e20c3018d81934fef7a11bbf66c6", "packagetype": "bdist_egg", "python_version": "2.7", "requires_python": null, "size": 20735, "upload_time": "2014-10-02T15:33:28", "url": "https://files.pythonhosted.org/packages/80/35/0d15590172f890fec0328162f2d4adc324cde991436c999ad39347e3bda0/simpleauth-0.1.5-py2.7.egg" }, { "comment_text": "", "digests": { "md5": "d02423c141ebeb913408630b5ada461d", "sha256": "beeefb3aa079a4893228e235556798c734c7a86df1671a96d69afbc58b14e804" }, "downloads": -1, "filename": "simpleauth-0.1.5.tar.gz", "has_sig": false, "md5_digest": "d02423c141ebeb913408630b5ada461d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16291, "upload_time": "2014-10-02T15:33:25", "url": "https://files.pythonhosted.org/packages/cd/84/9fd44175065a0ca240e6aba264563f5fd404038678e2f5fcc1928f06417f/simpleauth-0.1.5.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "96e6e20c3018d81934fef7a11bbf66c6", "sha256": "5b6061c624b718b928c04081e9dbabc856c2a8e35a9de0709950ffff9e114f89" }, "downloads": -1, "filename": "simpleauth-0.1.5-py2.7.egg", "has_sig": false, "md5_digest": "96e6e20c3018d81934fef7a11bbf66c6", "packagetype": "bdist_egg", "python_version": "2.7", "requires_python": null, "size": 20735, "upload_time": "2014-10-02T15:33:28", "url": "https://files.pythonhosted.org/packages/80/35/0d15590172f890fec0328162f2d4adc324cde991436c999ad39347e3bda0/simpleauth-0.1.5-py2.7.egg" }, { "comment_text": "", "digests": { "md5": "d02423c141ebeb913408630b5ada461d", "sha256": "beeefb3aa079a4893228e235556798c734c7a86df1671a96d69afbc58b14e804" }, "downloads": -1, "filename": "simpleauth-0.1.5.tar.gz", "has_sig": false, "md5_digest": "d02423c141ebeb913408630b5ada461d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 16291, "upload_time": "2014-10-02T15:33:25", "url": "https://files.pythonhosted.org/packages/cd/84/9fd44175065a0ca240e6aba264563f5fd404038678e2f5fcc1928f06417f/simpleauth-0.1.5.tar.gz" } ] }