pax_global_header00006660000000000000000000000064126013323560014513gustar00rootroot0000000000000052 comment=aa66b83d4848c09defa57384529eca83597a3692 python-social-auth-0.2.13/000077500000000000000000000000001260133235600153265ustar00rootroot00000000000000python-social-auth-0.2.13/.gitignore000066400000000000000000000006121260133235600173150ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject test.db local_settings.py sessions/ _build/ fabfile.py changelog.sh .DS_Store .\#* \#*\# python-social-auth-0.2.13/.landscape.yaml000066400000000000000000000001311260133235600202150ustar00rootroot00000000000000doc-warnings: no test-warnings: no strictness: medium max-line-length: 80 autodetect: no python-social-auth-0.2.13/.travis.yml000066400000000000000000000014351260133235600174420ustar00rootroot00000000000000language: python env: global: - REQUIREMENTS=requirements.txt - TEST_REQUIREMENTS=social/tests/requirements.txt python: - "2.6" - "2.7" - "pypy" matrix: include: - python: "3.3" env: - REQUIREMENTS=requirements-python3.txt - TEST_REQUIREMENTS=social/tests/requirements-python3.txt - python: "3.4" env: - REQUIREMENTS=requirements-python3.txt - TEST_REQUIREMENTS=social/tests/requirements-python3.txt before_install: - sudo apt-get update -qq - sudo apt-get install -y libxmlsec1-dev swig install: - "python setup.py -q install" - "travis_retry pip install -r $REQUIREMENTS" - "travis_retry pip install -r $TEST_REQUIREMENTS" script: - "nosetests --with-coverage --cover-package=social --where=social/tests" python-social-auth-0.2.13/Changelog000066400000000000000000003564321260133235600171550ustar00rootroot000000000000002015-06-24 v0.2.11 ================== * 2015-06-24 Matías Aguirre v0.2.11 * 2015-06-19 Matías Aguirre Link docs * 2015-06-19 Matías Aguirre PEP8 * 2015-06-18 Braden MacDonald Added documentation * 2015-06-17 Matías Aguirre PEP8 * 2015-05-21 Braden MacDonald Minor cleanups * 2015-05-20 Braden MacDonald Minor consistency fix * 2015-05-20 Braden MacDonald Add an integration point for extra security layers like eduPersonEntitlement * 2015-05-20 Braden MacDonald Make IdP name format slightly more flexible * 2015-05-11 Braden MacDonald Add python-saml requirement (temporary commit) * 2015-05-08 Braden MacDonald Tests for SAML backend * 2015-04-30 Braden MacDonald SAML2 backend using OneLogin's python-saml 2015-05-29 v0.2.10 ================== * 2015-05-29 Matías Aguirre v0.2.10 * 2015-05-29 Matías Aguirre Newline at end of file * 2015-05-29 Matías Aguirre Fix changetip docs errors * 2015-05-29 Matías Aguirre Coding style * 2015-05-29 Matías Aguirre PEP8 * 2015-05-29 Matías Aguirre PEP8 * 2015-05-29 Matías Aguirre Avoid storing empty values from user details * 2015-05-26 vinhub Added Azure AD docs to index.rst * 2015-05-26 Vinayak (Vin) Bhalerao Removed * 2015-05-25 sushantgawali changes in extra_data * 2015-05-18 sushantgawali added copyright / license notices * 2015-05-15 sushantgawali updated test cases * 2015-05-13 sushantgawali added get_auth_token method for ease of use * 2015-05-12 sushantgawali added test cases for AzureADOAuth2 refresh token method added * 2015-05-11 sushantgawali added generic resource setting added license text added documentation file * 2015-05-11 sushantgawali cleaned up unneeded Sharepoint related code * 2015-05-11 sushantgawali Added provider for Microsoft Azure Active Directory OAuth2 * 2015-05-20 Marek Jalovec Fixes "ImportError: No module named packages.urllib3.poolmanager" error (fixes #617) * 2015-05-20 blurrcat fix Fitbit OAuth 1 authorization URL * 2015-05-17 duoduo369 add weixin backends * 2015-05-16 Andrew Starr-Bochicchio Add a DigitalOcean backend. * 2015-05-08 Matías Aguirre Ensure that all the requirements are installed * 2015-05-08 Matías Aguirre Fix syntax (backward compatible) * 2015-05-07 Matías Aguirre Dev version flagged 2015-05-07 v0.2.9 ================= * 2015-05-07 Matías Aguirre v0.2.9 * 2015-05-07 Matías Aguirre Fix manifest definition 2015-05-07 v0.2.8 ================= * 2015-05-07 Matías Aguirre v0.2.8 * 2015-05-02 Avi אבי Alkalay אלקלעי Fixed Moves link on list of providers * 2015-05-02 Avi אבי Alkalay אלקלעי Added Moves to the list of providers * 2015-05-01 Matías Aguirre Add note about TLSv1 support in Amazon backend * 2015-05-01 Matías Aguirre Support SSL protocol override, default Amazon to TLSv1. Fixes #603 * 2015-04-27 Matías Aguirre Fix typos in docs (thanks to vsobolmaven) * 2015-04-21 Matías Aguirre Make URLs trailing slash be configurable by setting. Refs #505 * 2015-04-20 Matías Aguirre Use get_json() helper * 2015-04-20 Nick Sullivan Documentation for ChangeTip backend * 2015-04-20 Nick Sullivan fail gracefully on missing email * 2015-04-19 Matías Aguirre Allow to remove team from username in slack backend * 2015-04-19 Matías Aguirre Flag dev version 2015-04-19 v0.2.7 ================= * 2015-04-19 Matías Aguirre v0.2.7 * 2015-04-19 Matías Aguirre Don't send redirect_state to slack backend * 2015-04-17 Nick Sullivan use api domain for changetip request * 2015-04-17 Nick Sullivan ChangeTip backend * 2015-04-16 Matías Aguirre Fix clean username regex. Fixes #594 * 2015-04-16 Matías Aguirre PEP8 and switch check order * 2015-04-16 Matías Aguirre Take into account that sometimes API v2.3 returns the old querystring format. Fixes #592 * 2015-04-16 Matías Aguirre Move OAuth1 method out from the base class * 2015-04-16 zz Fix the final_username may be empty and will skip the loop. * 2015-04-16 ys.chi Alter email max length for Django app * 2015-04-15 Matías Aguirre Remove single-use var * 2015-04-15 Matías Aguirre Clean any pipeline remanents when starting the process. Refs #325 * 2015-04-15 Matías Aguirre Flag dev version * 2015-04-15 Matías Aguirre Swtich Twitter API to POST (as it's documented) * 2015-04-15 Christian Pedersen Append trailing slash in Django 2015-04-14 v0.2.6 ================= * 2015-04-14 Matías Aguirre v0.2.6 * 2015-04-14 Matías Aguirre Include tests requirements files. Fixes #590 * 2015-04-13 Matías Aguirre Fix publish task 2015-04-13 v0.2.5 ================= * 2015-04-13 Matías Aguirre v0.2.5 * 2015-04-13 Matías Aguirre Fix wheel support. Refs #588 * 2015-04-13 Matías Aguirre Add email to default list of protected userfields (popular demand) 2015-04-11 v0.2.4 ================= * 2015-04-11 Matías Aguirre v0.2.4 * 2015-04-11 Matías Aguirre Fix setting name (make it backend related). Refs #586 * 2015-04-10 Matías Aguirre Link to post about access-token based authentication * 2015-04-08 Matías Aguirre Move revoke methods to common class. Fixes #484 * 2015-04-08 Matías Aguirre Fix settings names on spotify docs. Fixes #475 * 2015-04-07 Matías Aguirre Fix links in docs * 2015-04-07 Matías Aguirre Fix Facebook test case after API version change. Refs #480 * 2015-04-07 Matías Aguirre Update Facebook to API v2.3 * 2015-04-07 Matías Aguirre Fix get_scope() override example * 2015-04-07 Matías Aguirre Raise error if token was passed but it's incomplete. Fixes #574 * 2015-04-06 Matías Aguirre Flag dev version * 2015-04-06 Matt Robenolt Build a wheel, and upload with twine * 2015-04-04 Matías Aguirre Log error messages. Fixes #507 * 2015-04-04 Matías Aguirre Pass all arguments to extra_data (save access token). * 2015-04-04 Matías Aguirre Improve http error handling on auth_complete/do_auth. Fixes #304 * 2015-04-04 Matías Aguirre Update docs about SOCIAL_AUTH_PROTECTED_USER_FIELDS. Fixes #459 * 2015-04-04 Matías Aguirre Optional trailing slash on django apps. Fixes #505 * 2015-04-04 Matías Aguirre Improve deprecation notice on behance docs * 2015-04-04 Matías Aguirre Add notice about behance broken api. Refs #530 * 2015-04-04 Matías Aguirre Remove unsupported attribute from alter field migration * 2015-04-04 Matías Aguirre Remove hard limitations on PyJWT and requests-oauthlib versions. Fixes #531 * 2015-04-04 Matías Aguirre Change title * 2015-04-04 Matías Aguirre Link backend docs * 2015-04-04 Matías Aguirre Add docs about disconnection and logging out difference. Fixes #568 * 2015-04-03 Matías Aguirre Conditional import on transaction, update docs to mention it. Fixes #572 * 2015-04-03 Matías Aguirre Define a MANIFEST.in file. Fixes #578 * 2015-04-03 Lucas Roesler Allow inactive users to login * 2015-04-02 Matías Aguirre Update django/mongoengine example (similar to default one). Refs #576 * 2015-04-02 Matías Aguirre Add backward compatibility on django app initialization. Refs #550 * 2015-04-01 M.Yasoob Ullah Khalid ☺ Update LICENSE 2015-03-31 v0.2.3 ================= * 2015-03-31 Matías Aguirre v0.2.3 * 2015-03-31 Matías Aguirre PEP8. Refs #570 * 2015-03-30 Krzysztof Hoffmann Added NaszaKlasa OAuth2 support * 2015-03-29 Buddy Lindsey, Jr. Add revoke token ability to strava * 2015-03-29 Matías Aguirre Store github login in extra data by default. Refs #567 * 2015-03-25 Jun Wang set redirect_state to false for live oauth2 * 2015-03-25 Matías Aguirre Fix backend, add quick docs. Refs #549 * 2015-03-25 Matías Aguirre Add rednose to python3 requirements too * 2015-03-23 Jerome Lefeuvre Add setup.cfg to configure flake8 and nosetests * 2015-03-23 Jerome Lefeuvre Add rednose for colored output log * 2015-03-21 Andrei Petre Add missing migration for Django app * 2015-03-19 José Padilla Specify algorithm for encoding and decoding * 2015-03-19 José Padilla Require PyJWT>=1.0.0,<2.0.0 * 2015-03-19 Matías Aguirre Remove debug print * 2015-03-19 Matías Aguirre Flush sqlalchemy session to get the object ids. Refs #390 * 2015-03-19 Johannes Start pipeline with default details arg * 2015-03-19 Jerome Lefeuvre Add `python_chameleon` to setup * 2015-03-17 Matías Aguirre Ensure to flush the db session (needed for Pyramid + sqlalchemy). Refs #390 * 2015-03-12 DanielJDufour update for django 1.9 * 2015-03-12 Matt Howland Create vend.py * 2015-03-12 Johannes Increase min request-oauthlib version to 0.3.1 * 2015-03-12 Adam Bogdał Add wunderlist backend to the list * 2015-03-11 Florian Eßer Update index.html * 2015-03-10 Adam Bogdał Add wunderlist oauth2 backend * 2015-03-07 Matías Aguirre PEP8, quotes and extra_data * 2015-03-06 Florian Eßer Add backend for EVE Online Single Sign-On (OAuth2) https://developers.eveonline.com/resource/single-sign-on * 2015-03-05 Matías Aguirre PEP8 and simplify code * 2015-03-05 Matías Aguirre PEP8 * 2015-03-05 Rafael Muñoz Cárdenas Add extra info on Google+ Sign-In doc * 2015-03-03 dobestan update Kakao OAuth2 backend : update auth process- Fixes #538 * 2015-03-03 dobestan Enable KakaoOAuth2 on example app * 2015-03-03 dobestan Disable redirect_state in kakao backend. Fixes #538 * 2015-03-02 Tom Clancy Update google.rst * 2015-03-02 Hassek modified docs * 2015-02-25 Hassek fixed refresh tokens for yahoo * 2015-02-25 Hassek added OAuth2 support to yahoo. Also, removed OAuth1 since yahoo will not be supporting it anymore * 2015-02-24 Matías Aguirre Cleanup imports and hmac creation, fix python3 compatibility * 2015-02-24 zz Fix Issue #532, get UID when use access_token ajax auth in weibo backends. * 2015-02-24 zz Fix Issue #532, get UID when use access_token ajax auth in weibo backends. * 2015-02-23 Matías Aguirre Fix zotero tests 2015-02-23 v0.2.2 ================= * 2015-02-23 Matías Aguirre v0.2.2 * 2015-02-23 Matías Aguirre PEP8/PyFlakes * 2015-02-21 Motoki Naruse login_user takes 3 parameters * 2015-02-21 Motoki Naruse Include template engine * 2015-02-21 Motoki Naruse Email column is duplicated * 2015-02-18 Sergey Kozub fix python3 handling of openid backend on sqlalchemy storage (use str instead of bytes) * 2015-02-16 Chris Lamb Don't use "import" in example method paths docs to avoid confusion * 2015-02-15 tell-k fixed bug. * 2015-02-15 tell-k add document url. * 2015-02-15 tell-k Add dribble backend. * 2015-02-14 Alejandro Baronetti Fixed issue: GET dictionary is immutable. I am not using MergeDict because it will be deprecated * 2015-02-13 Chris Martin Include username in Reddit extra_data * 2015-02-12 Eugene Agafonov [facebook-oauth2] Verifying Graph API Calls with appsecret_proof * 2015-02-11 tell-k refs #512 fixed typo * 2015-02-11 tell-k refs #512 fixed bug for py2.6 * 2015-02-11 tell-k add qiita backend * 2015-02-10 Matías Aguirre Pyflakes * 2015-02-10 Matías Aguirre PEP8 * 2015-02-07 Alejandro Baronetti Fix: REQUEST has been deprecated in Django 1.7, so we need to merge dictionaries * 2015-02-06 Clinton Blackburn Updated PyJWT Dependency * 2015-02-06 Rafael Muñoz Cárdenas Update Google documentation * 2015-02-03 Matías Aguirre Add test for nationbuilder backend * 2015-02-03 Matías Aguirre Move common code to base class * 2015-02-03 Matías Aguirre NationBuilder backend * 2015-02-03 Matías Aguirre Define methods to customize urls in OAuth2 backends * 2015-02-03 Matías Aguirre Enable debug pipeline in example app * 2015-02-03 Ian Wienand Ensure email is not None * 2015-02-02 Chris DeBlois updated mendeley oauth2 to use new api resource and also updated to grab new profile_id, name and bio * 2015-02-02 Ian Wienand Add support for Launchpad OpenId * 2015-01-30 rivf Fixed jawbone authentification * 2015-01-27 Adam Babik Added coursera backend to README * 2015-01-27 Adam Babik Docs for coursera backend * 2015-01-23 Adam Babik Added Coursera backend to django_example * 2015-01-23 Adam Babik Added backend for Coursera * 2015-01-20 ayush Added nonce unique constraint * 2015-01-19 Matías Aguirre Patch tornado arguments/cookies getting. Refs #445. Refs #346 * 2015-01-10 Chris Barna Store Spotify's refresh_token. * 2015-01-07 Nick Sullivan cleanly handle both a scope of 'identity' only and also fill in more data if we have 'read' access * 2015-01-07 Nick Sullivan properly handle data, so that it is more future proof, again. This time fix issue with team_url * 2015-01-07 Nick Sullivan update in a way that will be more future proof * 2015-01-07 Nick Sullivan when scope is reduced, the response from slack is different, handle both * 2015-01-05 Ben Davis Fixed extra_data field in django 1.7 initial migration * 2015-01-02 Jun Wang Fix YahooOAuth get primary email sorting order * 2015-01-02 Matías Aguirre PEP8 * 2015-01-02 Matías Aguirre Link docs, apply PEP8, change quotes and code style * 2015-01-02 Matías Aguirre Change expression * 2015-01-02 travoltino Update base.py * 2014-12-29 Alex Muller Correct Django SESSION_COOKIE_AGE setting * 2014-12-28 Nick Sullivan Documentation for slack backend * 2014-12-28 Nick Sullivan Slack backend * 2014-12-24 Alex Muller Update GitHub documentation * 2014-12-19 Frankie Robertson Fix #460: Call force_text on _URL settings to support reverse_lazy with default session serializer * 2014-11-27 James Potter Update django.rst * 2014-11-26 Anna Warzecha User ID is required to use any further requests * 2014-11-26 Sasha Golubev Added backend for professionali.ru * 2014-11-24 Lukas Klein Removed Orkut backend * 2014-11-24 Matías Aguirre Remove Flask-SQLAlchemy dependency from example app * 2014-11-23 Matías Aguirre Simplify flask app initialization * 2014-11-22 Matías Aguirre Allow initial definition of protected attributes * 2014-11-22 Matías Aguirre Quick khan academy docs * 2014-11-22 Matías Aguirre PEP8 * 2014-11-22 Matías Aguirre PEP8 * 2014-11-21 tschilling Allow the pipeline to change the redirect url. Moves the popping of the redirect value from the session to after the pipe line executes. * 2014-11-19 Seán Hayes Added support for Django's User.EMAIL_FIELD. * 2014-11-18 Anna Warzecha Changed test name * 2014-11-18 Anna Warzecha Fixed docs * 2014-11-18 Anna Warzecha Khan Academy oauth support now fully working * 2014-11-18 Anna Warzecha Struggling with Khan Academy again... * 2014-11-16 Anna Warzecha Fix backend name formatting * 2014-11-16 Anna Warzecha Fix readme * 2014-11-16 Anna Warzecha Basic Khan Academy support * 2014-11-15 Matías Aguirre Avoid override of custom-usernames fields with plain generated username. Refs #435 * 2014-11-15 Matías Aguirre Fix docs. Refs #436 * 2014-11-14 Matías Aguirre Remove x flag from .py file * 2014-11-11 Matías Aguirre Example on how to re-prompt a google user to get the refresh_token * 2014-11-07 Miguel Paolino Added zotero test, work in progress * 2014-11-07 Miguel Paolino Udpated README to include the Zotero backend mention * 2014-11-07 Miguel Paolino Fixed doc line * 2014-11-07 Miguel Paolino Added Zotero OAuth1 backend * 2014-11-02 Matías Aguirre Pass request to pyramid strategy. Refs #390 * 2014-11-02 Matías Aguirre Update changelog. Refs #421 * 2014-11-01 Matías Aguirre PEP8 * 2014-11-01 John Lynn Fix typo for AUTH_USER_MODEL * 2014-11-01 Matías Aguirre Set no-cache on views * 2014-11-01 Matías Aguirre Rename tokens to access_token. Refs #430 * 2014-11-01 Matías Aguirre PEP8 * 2014-11-01 Matías Aguirre PEP8 + basic docs. Refs #412 * 2014-11-01 Matías Aguirre PEP8 * 2014-10-30 Matías Aguirre Link missing doc * 2014-10-30 Matías Aguirre Fix use case snippet * 2014-10-29 Alex Parij Update base.py * 2014-10-29 Mitchel Humpherys use correct tense for `to meet' * 2014-10-26 John Lynn Fix custom user model migrations for Django 1.7 * 2014-10-23 Matías Aguirre Pick github primary email first. Fixes #413 * 2014-10-16 Christopher Grebs Fix migration issue on python 3 * 2014-10-12 SilentSokolov Fix does not match the number of arguments (for vk and ok backend) * 2014-10-08 Michal Karzyński Salesforce OAuth2 support * 2014-10-07 Matías Aguirre PEP8 and module rename * 2014-10-07 Matías Aguirre Travisci update * 2014-10-07 Matías Aguirre Enable pypy and replace sugar with unittest2. Refs #410 * 2014-10-07 Omer Katz Added Python 3.4 and PyPy to the build matrix. * 2014-10-04 micahhausler Added Django 1.7 App Config * 2014-10-02 micahhausler Switched list_display order for UserSocialAuth * 2014-10-02 micahhausler Added string method to UserSocialAuth model * 2014-10-03 Daniel Holmes Use new GoogleOAuth2 Spec * 2014-10-01 Laban Incorrect import path for db model * 2014-09-30 Matías Aguirre PEP8 * 2014-09-30 Matías Aguirre Convert docstring to comments, PEP8 * 2014-09-29 Lee Jaeyoung Apply more detailed address for kakao * 2014-09-29 Lee Jaeyoung Add Kakao to README.rst * 2014-09-28 David Zerrenner Added some legal stuff * 2014-09-27 Aarni Koskela Recreate migration with Django 1.7 final and re-PEP8. * 2014-09-26 Matías Aguirre Use getattr to get current backend from request * 2014-09-25 Matías Aguirre Doc about custom url namespace. Refs #399 * 2014-09-25 Matías Aguirre Configurable django views namespace. Refs #399 * 2014-09-25 Matías Aguirre PEP8 and more * 2014-09-24 Vera Mazhuga master add SCOPE_SEPARATOR to DisqusOAuth2 * 2014-09-24 dzerrenner added a backend for Battle.net Oauth2 auth * 2014-09-23 Matías Aguirre PEP8 * 2014-09-23 Matías Aguirre PEP8 * 2014-09-23 David Henderson Removed prefix, added example of details object. * 2014-09-23 David Henderson No good reason to skip a class * 2014-09-21 Matías Aguirre Don't update a setting value. Refs #378. Refs #377 * 2014-09-21 Tim Savage Update installing.rst * 2014-09-21 Tim Savage Update README.rst * 2014-09-18 Matías Aguirre Print arguments in the debug pipeline * 2014-09-16 Stefan Kröner Allow more Trello settings * 2014-09-12 Matías Aguirre Add debug pipeline to example app * 2014-09-12 Matías Aguirre Update snippet * 2014-09-12 David Henderson Updated to use latest api wrapper * 2014-09-11 Matías Aguirre Flag dev version 2014-09-11 v0.2.1 ================= * 2014-09-11 Matías Aguirre v0.2.1 * 2014-09-11 Matías Aguirre Take into account inconsistent instagram responses * 2014-09-11 Matías Aguirre Mension product-name google requirement * 2014-09-11 Matías Aguirre Flag dev version 2014-09-11 v0.2.0 ================= * 2014-09-11 Matías Aguirre v0.2.0 * 2014-09-11 Matías Aguirre Restore @strategy decorator with warning message * 2014-09-09 Tsung Hung updated the docs to add migrations for 1.7 while updated a constant so the warning message does not appear when running command line * 2014-09-09 Tsung Hung updated the docs to add migrations for 1.7 while updated a constant so the warning message does not appear when running command line * 2014-09-07 Amol Kher Jawbone needs params instead of data as requests * 2014-09-07 Caio Ariede Support for MineID.org * 2014-09-03 Matías Aguirre Added commets detailing pipeline functionality. Refs #361 * 2014-08-31 Matías Aguirre Enable state parameter for angel.co and spotify.com backends. Fixes #367 * 2014-08-29 Matías Aguirre PEP8 * 2014-08-29 Matías Aguirre PEP8 * 2014-08-27 Gianluca Pacchiella Fix typo * 2014-08-27 Clinton Blackburn Updated OpenId Connect Test Mixin * 2014-08-25 Matt Luongo Use a more flexible South user migration approach. * 2014-08-21 Matt Luongo Remove South from mandatory requirements. * 2014-08-21 Matt Luongo Split up the Django 1.7+ & South migrations. * 2014-08-21 Max Nanis Small grammatical edit * 2014-08-19 Martey Dodoo Fix repository links in thanks document. * 2014-08-18 Parker Phinney changed default behavior of SESSION_EXPIRATION setting * 2014-08-18 Ross Crawford-d'Heureuse added goclio * 2014-08-16 Matías Aguirre Link/img change * 2014-08-16 Matías Aguirre RTD badge * 2014-08-16 Matías Aguirre PEP8 * 2014-08-15 Matías Aguirre Support passwordless schema on mail validation pipeline * 2014-08-14 Matías Aguirre Fix backend reference. Fixes #350 * 2014-08-12 = <=> Add pushbullet backends * 2014-08-09 Matías Aguirre Fix disconnect buttons styles * 2014-08-09 Matías Aguirre PEP8 and fixed tests. Refs #348 * 2014-08-08 Clinton Blackburn Added Open ID Connect base backend * 2014-08-08 Josh Probst numeric index for format * 2014-08-07 Matías Aguirre Fix user syncdb. Refs #342 * 2014-08-07 Matías Aguirre Simplify moves backend code and add documentation. Refs #307 * 2014-08-05 Vadym Petrychenko Update vk.rst * 2014-08-02 Matías Aguirre Landscape conf * 2014-08-02 Matías Aguirre Support redirect_state in OAuth1 backends too (enable twitter by default). Refs #338 * 2014-08-02 Matías Aguirre Enable DropboxOAuth2 on example app * 2014-07-29 Chris Lamb Also populate Strava name from 'lastname' attribute: * 2014-07-29 Chris Lamb Correct reference to 'firstname' when populating forenames from Strava. * 2014-07-29 Chris Lamb Correct Stava scoping/permissions example. * 2014-07-28 Chris Martin Clean up language in social/tests/README.rst * 2014-07-27 Matías Aguirre Docs about writing custom pipeline functions * 2014-07-24 Matt Luongo Fix an import issue in the Django migrations. * 2014-07-24 Matt Luongo List test requirements. * 2014-07-24 Matt Luongo Support South and Django 1.7+ migrations. * 2014-07-23 Mike Anderson remove debugger * 2014-07-23 Mike Anderson tests for two failing cases, include all kwargs in partial pipeline session * 2014-07-22 Mike Anderson Don't overwrite clean_kwargs with kwargs * 2014-07-19 Matías Aguirre Github for teams backend. Refs #329 * 2014-07-16 Nick Sandford Fixed #327 -- Changed access token method on backend. * 2014-07-15 David Grant Slight retouch to spelling and wordage. * 2014-07-15 David Grant Minor typo. * 2014-07-08 Matías Aguirre Fix FK field descriptor for admin queries. Closes #322 * 2014-07-07 Matt Luongo Use South instead of Django 1.7 migrations. * 2014-07-07 Matías Aguirre Simple makefile for local tasks * 2014-07-07 Matías Aguirre Document django session migration script when moving from django-social-auth to python-social-auth. Refs #320 * 2014-07-07 Matías Aguirre Make user-agent setting available for all backends. Refs #317 * 2014-07-04 Harz-FEAR fix for AssertionError in pyramid * 2014-07-01 Ondrej Slinták Added Django 1.7 migrations * 2014-07-01 davidhubbard fix PR #317 * 2014-07-01 davidhubbard override request() to fix "429 Too Many Requests" * 2014-06-30 Matías Aguirre Tox runner with pyenv support * 2014-06-26 David Henderson Reinstated get_user_id override - so that we can pull from the details rather than the response * 2014-06-25 Antony Seedhouse Update django_orm.py * 2014-06-25 Antony Seedhouse Update django_orm.py * 2014-06-24 Martey Dodoo Update link to Django example in documentation. * 2014-06-22 Roman Levin Add note about access_type in docs * 2014-06-22 Avi Alkalay user first_date doesn't belong here * 2014-06-21 Avi Alkalay The Moves app backend * 2014-06-18 Matías Aguirre Initial work towards OpenIdConnect. Refs #300. Refs #284 * 2014-06-18 Matías Aguirre Useful debug pipeling function * 2014-06-18 Gabriel Le Breton text should not go into code block * 2014-06-18 Nikolaev Andrey It was impossible to change the version API Vkotnakte * 2014-06-16 Matías Aguirre Improve django example application look * 2014-06-16 Matías Aguirre Fix key access on instagram backend * 2014-06-14 Matías Aguirre Integrate flask app and flask mongoengine app * 2014-06-14 Matías Aguirre PEP8 * 2014-06-14 Matías Aguirre Fix docstring * 2014-06-14 Matías Aguirre Move common fields to base class in sqlalchemy ORMs. * 2014-06-14 Matías Aguirre Use mongoengin ORM in django me app * 2014-06-12 Matías Aguirre QQ backend * 2014-06-09 Josh Hawn Update docker backend with Docker Hub endpoints * 2014-06-08 Matías Aguirre Add missing module * 2014-06-08 Matías Aguirre Set user backend reference in django app * 2014-06-08 Matías Aguirre Update tests * 2014-06-07 Matías Aguirre Version change (no backward compatible change) * 2014-05-26 Matías Aguirre Refactor backend/strategy to avoid circular dependency * 2014-06-07 Matías Aguirre Support MergeDict and MultiDict in partial cleanup. Refs #291 2014-06-07 v0.1.26 ================== * 2014-06-07 Matías Aguirre v0.1.26 * 2014-06-07 Matías Aguirre Fix google-plus scope, support server-side flow 2014-06-07 v0.1.25 ================== * 2014-06-07 Matías Aguirre v0.1.25 * 2014-06-07 Matías Aguirre Support deprecated and new Google API. Refs #292. Refs #285 * 2014-06-07 Matías Aguirre Fix pipeline example * 2014-06-01 Matías Aguirre Document steam player data saving * 2014-05-28 Matías Aguirre Remove eclipse settings from PR merge * 2014-05-26 Matías Aguirre Document google scopes deprecation. Refs #285 * 2014-05-26 Matías Aguirre Fix title underline * 2014-05-26 Matías Aguirre Make request parameter optional. Refs #286 * 2014-05-26 Matías Aguirre PEP8 * 2014-05-24 Devin Sevilla Rdio API methods use POST * 2014-05-22 Michael Godshall Fixed Django 1.7 admin * 2014-05-20 Hector Zhao avoid updating default settings * 2014-05-17 Matías Aguirre v0.2.0-dev 2014-05-17 v0.1.24 ================== * 2014-05-17 Matías Aguirre v0.1.24 * 2014-05-17 Matías Aguirre Example for ajax auth. Refs #272, #238 * 2014-05-17 Matías Aguirre Circumvent recursive import issue in admin. Fixes #269 * 2014-05-17 Matías Aguirre Update google scopes, remove the soon to be deprecated ones. Fixes #273 * 2014-05-17 Matías Aguirre Fix title underline in docs * 2014-05-17 Ryan Choi remove mashery stuff from oauth; constrain it to beats * 2014-05-17 Ryan Choi oauth for beats * 2014-05-15 Jason Sanford Add links. * 2014-05-15 Ryan Choi remove commented code for spotify * 2014-05-15 Ryan Choi spotify oauth * 2014-05-15 Jason Sanford Python 2.6-friendly string formatting. * 2014-05-15 Jason Sanford Document MapMyFitness * 2014-05-15 Matías Aguirre Change priority for new user redirect location * 2014-05-15 Jason Sanford Test MapMyFitness backend * 2014-05-14 Jason Sanford Get started with MapMyFitness OAuth2 * 2014-05-13 Matías Aguirre MongoEngine ORM support for flask applications * 2014-05-13 swmerko from http API to https API * 2014-05-12 Matías Aguirre Remove unused import * 2014-05-10 Mark Lee Replace references to python-oauth2 with references to requests-oauthlib * 2014-05-08 Smamaxs get email on login * 2014-05-07 Marno Krahmer Change the authorization url for the xing api * 2014-05-06 Matías Aguirre PEP8 and docs about Facebook Graph 2.0 backends * 2014-05-06 Matías Aguirre Settings to override default scope/attrs and docs about them. Refs #258 * 2014-05-06 Daniel Ryan added new backend classes for Facebook that use the Open Graph 2.0 endpoints * 2014-05-01 Matías Aguirre PEP8 and logic simplification * 2014-05-01 Kyle Richelhoff Added LoginRadius backend. * 2014-04-30 momamene Add Kakao backend * 2014-04-30 Matías Aguirre Update amazon docs, drop outdate details about bug. Fixes #260 * 2014-04-23 Matías Aguirre Disable redirect_state in strava backend. Fixes #259 * 2014-04-23 Matías Aguirre Allow overrideable values for AX schema attrs and SReg attributes in OpenId. Fixes #258 * 2014-04-23 Matías Aguirre Refactor fullname, first name and last name generation. Fixes #240 * 2014-04-23 Serg Baburin Using https as required by the API. * 2014-04-19 Your Name User model fields accessors clashes issue solved * 2014-04-18 Matías Aguirre Switch VK OpenAPI to session intead of cookies. * 2014-04-14 David Blado linkedin now requires redirect uris to be verified: https://developer.linkedin.com/blog/register-your-oauth-2-redirect-urls * 2014-04-14 Matías Aguirre PEP8 * 2014-04-14 Hannes Ljungberg Add Twitch backend * 2014-04-10 Matías Aguirre Remove unused parameters from pipeline prototypes * 2014-04-08 Alexander Chernigov Handle properly refusing when entering via twitter * 2014-04-04 Matías Aguirre Remove doc about deprecated setting. Refs #241 * 2014-04-03 Matías Aguirre Option for open id providers to specify the username key in the values * 2014-04-03 Matías Aguirre Include strava backend in the index * 2014-04-02 (cdep) illabout Fix small spelling mistake. * 2014-04-02 Joe Hura Add support for Vimeo OAuth 2 as part of Vimeo API v3 * 2014-04-01 Krishan Gupta Update settings.rst * 2014-04-01 Damien Incorrect syntax given in the documention * 2014-04-01 Matías Aguirre Fix use-case snippet * 2014-04-01 Matías Aguirre Switch custom redirect state to off in mendeley OAuth2. Closes #234 * 2014-04-01 Matías Aguirre Enable Last.fm in example applications * 2014-04-01 Matías Aguirre Last.fm docs * 2014-04-01 Matías Aguirre Refactor Last.fm backend (simplify code) * 2014-03-26 root Added backend for Last.Fm. There is probably an easier way to implement this. * 2014-03-28 Matías Aguirre Make exception raise optional with setting. Add tests and docs * 2014-03-27 Matías Aguirre Stop tox on first error * 2014-03-27 Matías Aguirre Avoid passing multiple arguments to disconnect partial pipeline * 2014-03-27 Matías Aguirre Improve partial session cleaner code. Refs #231 * 2014-03-27 Piotr Czesław Kalmus login with bitbucket account, error when any verified email is set * 2014-03-27 Matías Aguirre Link docker docs in backends index * 2014-03-27 Matías Aguirre PEP8 * 2014-03-25 Fernando initial version of docker backend * 2014-03-26 Matías Aguirre Flag dev version 2014-03-26 v0.1.23 ================== * 2014-03-26 Matías Aguirre v0.1.23 * 2014-03-24 Yohan Boniface OpenStreetMap: no img element if user has no avatar * 2014-03-23 Matías Aguirre Define a custom user model * 2014-03-23 Matías Aguirre Comment about enhanced security flag in Live backend. Refs #218 * 2014-03-23 Matías Aguirre Try to use django messages app, fallback to URL. Fixes #210 * 2014-03-23 Matías Aguirre Don't assign strategy in middleware. Closes #221 * 2014-03-23 Matías Aguirre Pass the social_user to login functions. Refs #190 * 2014-03-18 Matías Aguirre Multiple scopes use case * 2014-03-18 Matías Aguirre Register by token use case * 2014-03-16 Matías Aguirre Fix strava tests and username generation. Refs #217 * 2014-03-15 Auston Bunsen final changes * 2014-03-15 Auston Bunsen updated some docs * 2014-03-15 Auston Bunsen added strava support! * 2014-03-15 Matías Aguirre Remove symlinks. Fixes #177 * 2014-03-15 Matías Aguirre Use stateless mode with Steam. Fixes #200 * 2014-03-15 Matías Aguirre Get social_user instance before login. Refs #190 * 2014-03-15 Matías Aguirre Simplify redirect cleaner method. Closes #191 * 2014-03-15 Matías Aguirre Use forms to disconnect * 2014-03-15 Matías Aguirre Mention localhost limitation on facebook. Closes #207 * 2014-03-13 Andrey Kuzmin Removes flask dependency from webpy_app * 2014-03-12 Dave Murphy Added backend for Ubuntu (One). * 2014-03-09 Matías Aguirre Make oauth_token retrieval optional. Refs #212 * 2014-03-08 Baptiste Mispelon Fixed Django < 1.4 support in context processors. * 2014-03-06 Matías Aguirre Remove bitdeli badge * 2014-03-06 Peter Schmidt Add some missing dependencies for running `social.apps.django_app.default.tests` * 2014-03-01 Matías Aguirre Link backend docs in index * 2014-03-01 Matías Aguirre Docs about flask error handling * 2014-03-01 Matías Aguirre Mark dev version * 2014-03-01 Matías Aguirre v0.1.22 * 2014-02-28 Andrey Kuzmin Fixes broken email confirmation for SQLAlchemy storage and webpy_app * 2014-02-27 Sebastian Bassi Update mendeley.py * 2014-02-27 Matías Aguirre Don't update user if it's set to None (non-authenticated pipeline continuation). Refs #198 * 2014-02-27 Matías Aguirre Set is_new flag on pipeline if user is not new. Refs #201 * 2014-02-27 Matías Aguirre Better error message * 2014-02-25 Matías Aguirre Add 'user' to default scope on coinbase backend. Closes #199 * 2014-02-24 Matías Aguirre User USERNAME_FIELD on mongoengine. Closes #197 * 2014-02-21 Matías Aguirre Dev marker * 2014-02-18 David Kingman Removed commit marker * 2014-02-13 Matías Aguirre PEP8, Python3 and example fixes * 2014-02-13 Thomas Lovett fix copy-paste typo in callback url * 2014-02-13 Thomas Lovett add clef to main README * 2014-02-14 Yan Kalchevskiy Fixed a typo. * 2014-02-13 Matías Aguirre Vimeo backend * 2014-02-13 Matías Aguirre Move badge to the top * 2014-02-13 Bitdeli Chef Add a Bitdeli badge to README * 2014-02-13 Matías Aguirre Docs about associate user by email * 2014-02-12 Matías Aguirre Style recent docs * 2014-02-13 Joe B. Lewis added information for FIELDS_STORED_IN_SESSION * 2014-02-12 Hassek removed extra_data override * 2014-02-11 Hassek updated live connection for better support * 2014-02-10 Matías Aguirre CherryPy mention in project index * 2014-02-10 Matías Aguirre CherryPy docs * 2014-02-10 Matías Aguirre Disconnection on example app * 2014-02-10 Matías Aguirre Pass user on disconnect * 2014-02-10 Matías Aguirre Get extra_data from details on openid too * 2014-02-10 Matías Aguirre Mendeley OAuth2 in example app * 2014-02-10 Matías Aguirre Fix Mendeley OAuth2 implementation, use https URLs * 2014-02-10 Matías Aguirre Switch parent class to avoid overrides * 2014-02-10 Matías Aguirre Finishe CherryPy app support and add example application * 2014-02-10 Matías Aguirre Mendeley OAuth2 docs and thanks to Sebastian Bassi (initial author) * 2014-02-10 Matías Aguirre Mendeley OAuth2 backend * 2014-02-10 Matías Aguirre Fix AuthFailed calls * 2014-02-10 Matías Aguirre Raise social-auth exception on connection error. Closes #155 * 2014-02-10 Matías Aguirre Parse token if it's an string (keep a compatible API). Refs #180 * 2014-02-09 Matías Aguirre Stick with sure 1.2.3 (higher is broken, I should drop sure) * 2014-02-09 Matías Aguirre Update sure to 1.2.5 * 2014-02-09 Matías Aguirre Fix LinkedIn OAuth2 backend, pass access token parameter in querystring. Closes #181 2014-02-05 v0.1.21 ================== * 2014-02-05 Matías Aguirre v0.1.21 * 2014-02-05 Matías Aguirre Fix iexact field lookup. Refs #179 * 2014-02-05 Matías Aguirre Restore BackendWrapper to avoid session issues (this backend is deprecated). Refs #128 * 2014-02-05 Matías Aguirre Case insensitive query on django. Closes #179 * 2014-02-03 Matías Aguirre Exclude sure broken version 1.2.4 * 2014-02-01 Michisu, Toshikazu Add version parameter to foursquare backend * 2014-01-23 Matías Aguirre Use response encoding only when available. Refs #173 * 2014-01-21 Matías Aguirre Add pixelpin to backends index * 2014-01-21 Matías Aguirre Ensure encode() before md5 call for python3. Closes #168 * 2014-01-21 lukos Added PixelPin to list of providers * 2014-01-21 Matías Aguirre PEP8, file formats and line lengths fixes * 2014-01-21 luke Add documentation for PixelPin * 2014-01-21 luke Added new PixelPin provider. * 2014-01-20 Matías Aguirre Use same DB name as other examples * 2014-01-20 Yasin Aktimur Serializer changed. * 2014-01-18 Matías Aguirre Support Weibo domain as username by setting. Closes #164 * 2014-01-18 Matías Aguirre Snippet to get people from circles on Google+ * 2014-01-17 Matías Aguirre Override get_user_id on tumblr backend. Refs #136 2014-01-17 v0.1.20 ================== * 2014-01-17 Matías Aguirre v0.1.20 * 2014-01-17 Matías Aguirre Decode bytes on Python3 otherwise it breaks session saving on Django. Refs #139 * 2014-01-16 Matías Aguirre Fix linkedin docs about attributes names. Closes #161 * 2014-01-16 Matías Aguirre Also support old keys format in linkedin backend for basic data 2014-01-16 v0.1.19 ================== * 2014-01-16 Matías Aguirre v0.1.19 * 2014-01-16 Matías Aguirre Generate packages names dynamically 2014-01-16 v0.1.18 ================== * 2014-01-16 Matías Aguirre v0.1.18 * 2014-01-15 Matías Aguirre Raise missing parameter error in facebook. Refs #153 * 2014-01-15 harshiljain AUTHORIZATION_URL changed to https * 2014-01-14 Matías Aguirre PEP8 * 2014-01-14 Javier G. Sogo stores 'access_token' for GooglePlusAuth * 2014-01-14 Javier G. Sogo for FacebookOAuth2::process_revoke_token_response call super (solves type with 'status_code') and custom processing * 2014-01-14 Javier G. Sogo moved revoking stuff to OAuthAuth class (should it be moved to BaseAuth?) * 2014-01-13 Max Tepkeev odnoklassniki backend iframe app fix * 2014-01-10 xen Cleanup docs * 2014-01-10 xen Simplify SQLAlchemy API usage * 2014-01-10 xen Update to follow current state in documentations * 2014-01-08 Roberto Robles Remove SOCIAL_AUTH prefix on redirect_uri function * 2014-01-08 Roberto Robles Fixed issue with redirect_uri with https * 2014-01-08 Matías Aguirre Link Taobao docs on backends index * 2014-01-08 Matías Aguirre Docs styling and PEP8 * 2014-01-08 Jichao Ouyang taobao docs * 2014-01-08 Jichao Ouyang get token with POST method * 2014-01-07 Matías Aguirre PEP8 and cleanups. Refs #145 * 2014-01-07 Matías Aguirre Move URLs gathering to helper * 2014-01-07 Matías Aguirre Fix dox underline * 2014-01-07 Jichao Ouyang remove unused import * 2014-01-07 Jichao Ouyang add to django example * 2014-01-07 Jichao Ouyang add support for taobao * 2014-01-06 Adam Coddington Updating readme to proclaim OAuth2 support for Dropbox. * 2014-01-06 Adam Coddington Updating Dropbox documentation to include notes regarding OAuth2 support. * 2014-01-06 Adam Coddington Adding Dropbox OAuth2 Support. * 2014-01-06 Edwin Knuth increasing length of salt field for django apps, fixes #141 * 2014-01-06 Matías Aguirre Simplify partial handling on actions * 2014-01-06 Matías Aguirre Updated readme with other dependencies. Closes #140 * 2014-01-06 Jichao Ouyang add support for taobao * 2014-01-04 Matías Aguirre Always send email validations is required * 2014-01-04 Matías Aguirre Move extra-data logic to base clase * 2014-01-02 Matías Aguirre Fix ID_KEY for Tumblr backend. Refs #136 * 2014-01-02 Matías Aguirre Use cases doc. Refs #137 * 2014-01-01 Matías Aguirre Fix docstring. Refs #136 * 2013-12-28 Matías Aguirre Line chars limit in docs. Refs #135 * 2013-12-28 Matías Aguirre PEP8. Refs #135 * 2013-12-28 Xmypblu Add support for OpenStreetMap OAuth * 2013-12-27 Matías Aguirre Update porting docs regarding session value * 2013-12-27 Matías Aguirre PEP8 * 2013-12-26 Nicolas Cortot Support for MongoEngine authentication using Custom User Model * 2013-12-25 Nick Sullivan Update reddit.py * 2013-12-17 Jay Parlar Tiny typo fix * 2013-12-16 maxtepkeev fix session expiration in vk backend * 2013-12-11 Kevin Tran Added support for named URLs and URL translation using the django built-in resolve_url before giving the url to tje HttpResponseRedirect. See also https://code.djangoproject.com/ticket/15552 * 2013-12-11 Bob Alcorn Updated pipeline example to include externalized auth; * 2013-12-09 Matías Aguirre Avoid broken email entries on yahoo API. Closes #125 * 2013-12-09 Matías Aguirre Allow unauthorized token retrieval/storage overrideable. Refs #111 * 2013-12-07 Matías Aguirre Constant type compare on HMAC signatures. Closes #122 * 2013-12-07 monkut Removed non-ascii character from author string * 2013-12-06 Hans Add test backends to the package. * 2013-12-06 Rodrigue Villetard Missing trailing slash on complete url * 2013-12-03 Matías Aguirre PEP8. Refs #116 * 2013-12-03 Stephen McDonald Add refs to getpocket.com in readme + docs * 2013-12-03 Stephen McDonald getpocket.com backend * 2013-12-02 Matías Aguirre Helper to get current backend instance. Refs #114 * 2013-11-30 Matías Aguirre Set current strategy on pyramid app * 2013-11-30 Matías Aguirre Simplify pyramid settings access * 2013-11-30 Matías Aguirre Set current strategy on webpy and flask apps * 2013-11-29 Matías Aguirre PEP8 * 2013-11-28 Matías Aguirre Link to backends docs in the modules instead of repeating the docs. Refs #107 * 2013-11-28 Matías Aguirre Yammer docs * 2013-11-28 Matías Aguirre Improves to Yahoo docs * 2013-11-28 Matías Aguirre Xing docs * 2013-11-28 Matías Aguirre Trello docs * 2013-11-28 Matías Aguirre Podio docs * 2013-11-28 Matías Aguirre Mendeley docs * 2013-11-28 Matías Aguirre Fix backends index order * 2013-11-28 Matías Aguirre LiveJournal docs * 2013-11-28 Matías Aguirre Jawbone docs * 2013-11-28 Matías Aguirre Foursquare backend docs * 2013-11-28 Matías Aguirre Fitbit docs * 2013-11-28 Matías Aguirre Fedora openid docs * 2013-11-28 Matías Aguirre Fix douban oauth1 title * 2013-11-28 Matías Aguirre Dailymotion docs * 2013-11-28 Matías Aguirre File format fix to coinbase docs * 2013-11-28 Matías Aguirre Fix backends order * 2013-11-28 Matías Aguirre BelgiumEID docs * 2013-11-28 Matías Aguirre AOL docs * 2013-11-26 Norton Wang fix uid in coinbase oauth * 2013-11-23 Norton Wang add coinbase docs, add runkeeper docs to index * 2013-11-23 Norton Wang add coinbase oauth * 2013-11-23 Norton Wang Add more examples to django_example, alphabetize, fix some grammar * 2013-11-21 Matías Aguirre Fix setting name in docs. Refs #97 * 2013-11-21 Matías Aguirre Move default pipeline definitions to constants for easy import. Refs #99 * 2013-11-21 josseph Update weibo.py * 2013-11-20 Jesse Pollak adds clef as a login provider * 2013-11-20 maxtepkeev Make vk-app backend to retrieve additional user data in respect to the *_EXTRA_DATA setting * 2013-11-19 Matías Aguirre Fix typo * 2013-11-19 Matías Aguirre Mention callback URL definition on linkedin when using oauth2. Refs #58 * 2013-11-18 Matías Aguirre Include backend name in setting if backend is defined. Refs #95 * 2013-11-18 Matías Aguirre PEP8 and simplifications. Refs #92 * 2013-11-18 Matías Aguirre Restore prvious link, fix schema in readthedocs link. Refs #93 * 2013-11-17 Sahil Gupta Updated README to point to the latest docs on Read The Docs. * 2013-11-16 Marios Google Plus backend allows for a server-side flow that can grant a refresh token that can be subsequently used to perform operations on behalf of the user, even if the user is not online. * 2013-11-14 Matías Aguirre Replace format call with string join. Closes #91 * 2013-11-14 Juan Riaza a better way * 2013-11-14 Juan Riaza fitbit uid * 2013-11-13 Matías Aguirre Fix OpenId PAPE max age check. Closes #89 * 2013-11-13 Matías Aguirre Changelog update 2013-11-13 v0.1.17 ================== * 2013-11-13 Matías Aguirre v0.1.17 * 2013-11-13 Matías Aguirre Support remember flag when calling login on flask app * 2013-11-13 Nitish Rathi Use strategy.backend.name instead of strategy.backend_name * 2013-11-13 Nitish Rathi Use strategy.backend.name instead of strategy.backend_name * 2013-11-13 Nitish Rathi Use strategy.backend.name instead of strategy.backend_name * 2013-11-13 Matías Aguirre Update ChangeLog * 2013-11-13 Matías Aguirre Define exception to signal a backend-not-found error. Refs #83 * 2013-11-11 Алексей Raise Http404 in django auth view when the backend is not found * 2013-11-10 Matías Aguirre Remove BackendWrapper reference and set current-strategy cache to access it * 2013-11-10 Matías Aguirre Set social_ prefix on request attribute to avoid conflicts with other apps. Keep social attribute if not set (backward compatibility) * 2013-11-09 Matías Aguirre Update github docs regarding callback URL. Closes #66 * 2013-11-08 yegle Mod: URL for registering Windows Live key/secret * 2013-11-08 Matías Aguirre Fix association id. Closes #78 * 2013-11-07 Matías Aguirre Mention method used * 2013-11-07 Matías Aguirre Typo fix * 2013-11-07 Matías Aguirre Update middleware docs 2013-11-07 v0.1.16 ================== * 2013-11-07 Matías Aguirre v0.1.16 * 2013-11-07 Matías Aguirre Remove unused vars * 2013-11-07 Matías Aguirre Remove or check which always default to settings.DEBUG if RAISE_EXCEPTIONS was False * 2013-11-07 Michal Čihař Include actions module in distribution * 2013-11-06 Matías Aguirre Ensure IDs to openid association removal. Closes #76 * 2013-11-05 Branden Rolston Update partial from session with newer kwargs. * 2013-11-05 Branden Rolston Use mock. * 2013-11-05 Matías Aguirre Link to tornado docs * 2013-11-05 Matías Aguirre Fix to douban access token retrieval method. Closes #72 * 2013-11-05 Matías Aguirre Custom user model in mongoengine example app. Refs #70 * 2013-11-05 Matías Aguirre PEP8 * 2013-11-05 Matías Aguirre Mention tornado on readme and docs intro * 2013-11-05 Axel Haustant Talk about tox in test documentation * 2013-11-05 Axel Haustant Upgrade HTTPretty for OSX/Requets 2.0 compatibility * 2013-11-05 Axel Haustant Added tox configuration * 2013-11-05 Axel Haustant quote message for url inclusion * 2013-11-04 Branden Rolston Return the updated dict. * 2013-11-04 Matías Aguirre v0.1.15 2013-11-04 v0.1.15 ================== * 2013-11-04 Matías Aguirre v0.1.15 * 2013-11-04 Matías Aguirre Test runkeeper backend * 2013-11-02 Matías Aguirre PEP8 and implement missing method * 2013-11-02 Martin Santos Removed prints * 2013-11-02 Matías Aguirre Fixes to Tornado application (mostly cookies handling) * 2013-11-02 Martin Santos WIP: More changes * 2013-11-02 Martin Santos WIP: strategy and example app * 2013-11-01 Martin Santos Initial tornado support * 2013-10-28 Andrey Mitroshin Function user_data returns list. This leads to exception in social/backends/oauth.py (line 340): "ValueError, dictionary update sequence element #0 has length 31; 2 is required". Taking 1st elementt of that list fixes the error. * 2013-10-23 Jason Sanford Add RunKeeper. * 2013-10-23 Matías Aguirre Fix reference * 2013-10-23 Matías Aguirre Add missing links * 2013-10-23 Matías Aguirre Support python3-openid last changes on Association class * 2013-10-23 Hannes Ljungberg Make partial_pipeline JSON serializable for django 1.6 * 2013-10-15 Matías Aguirre Add missing attribute to flask storage * 2013-10-15 Matías Aguirre Fix typo. Closes #61 * 2013-10-14 Matías Aguirre Small fixes to apfuel doc. Refs #59 * 2013-10-14 z4r <24erre@gmail.com> Appsfuel doc from dsa to psa * 2013-10-14 z4r <24erre@gmail.com> Appsfuel doc from dsa to psa * 2013-10-10 Matías Aguirre Make BackendWrapper respect backends interface. Refs #53 * 2013-10-10 Matías Aguirre Docs regarding Django 1.6 and backends enforced into AUTHENTICATION_BACKENDS. Refs #53 * 2013-10-10 Matías Aguirre Try setting with backend name and without * 2013-10-10 Matías Aguirre Remove backend_name property * 2013-10-10 Matías Aguirre Fix arguments on refresh_token() method. Refs #52 * 2013-10-10 Matías Aguirre Make backend_name a property. Refs #52 * 2013-10-10 Matías Aguirre Pass the correct name to strategy setting method * 2013-10-08 Michal Čihař Add openSUSE OpenID login * 2013-10-08 Matías Aguirre Fix url check type. Refs #49 * 2013-10-08 Matías Aguirre PEP8 and small simplification on sanitize_url check. Refs #49 * 2013-10-08 Matías Aguirre Clean every mergedict data type * 2013-10-08 Matías Aguirre Force dict type over response (convert mergedict types) * 2013-10-07 Matías Aguirre Enforce dict() on values * 2013-10-07 Daniel Barreto Check for None when `sanitize_redirect` returns in `do_complete`. * 2013-10-07 Daniel Barreto Make `sanitize_redirect` aware of possible proxies. 2013-10-07 v0.1.14 ================== * 2013-10-07 Matías Aguirre v0.1.14 * 2013-10-07 Matías Aguirre Fix encoding string between python2 and 3 * 2013-10-07 Matías Aguirre Always wrap openid session value * 2013-10-07 Matías Aguirre Encode value to avoid Python3 errors. Refs #776 * 2013-10-06 Matías Aguirre Google plus sign in docs * 2013-10-06 Matías Aguirre Fix links on google docs * 2013-10-06 Matías Aguirre Google+ Sign In backend example * 2013-10-06 Matías Aguirre Working Google+ Sign In backend * 2013-10-06 Matías Aguirre Move process_error() to upper class * 2013-10-05 Matías Aguirre Enable json serializer on example app * 2013-10-04 Markus Holtermann Fixes #45 -- AttributeError while resolving the user model in Django * 2013-10-03 Matías Aguirre Serialize only well-known types, rename function to remark the usage. Refs #36 * 2013-10-03 Matías Aguirre Use seconds to set session expiration. Refs #36 * 2013-10-02 Matías Aguirre Rename verification code parameter to avoid clashing with backends parameters * 2013-10-02 nvbn Fix work with django 1.6 * 2013-10-02 nvbn Make JSONField compatible with python 3 * 2013-10-02 Matías Aguirre Update docs regarding yahoo keys. Refs #43 * 2013-09-29 Matías Aguirre Small code changes * 2013-09-29 Matías Aguirre Remove path from urls * 2013-09-28 Matías Aguirre Only run tests on social/tests * 2013-09-28 Matías Aguirre Mention pyramid in keywords * 2013-09-28 Matías Aguirre Remove python-coveralls * 2013-09-28 Matías Aguirre Django tests * 2013-09-28 Matías Aguirre Move base classes to directories * 2013-09-27 Matías Aguirre Remove dbref=True from ReferenceField. Refs #42 * 2013-09-26 Matías Aguirre Process facebook errors on complete. Refs #40 * 2013-09-24 Matías Aguirre White list setting * 2013-09-23 Matías Aguirre Simplified django example applications * 2013-09-23 Matías Aguirre Add mongoengine to requirements * 2013-09-22 Matías Aguirre Mongoengine example * 2013-09-22 Matías Aguirre Fix url for django mongoengine support, add str_id() helper * 2013-09-22 Matías Aguirre Refactor common code on username/email backends tests * 2013-09-22 Matías Aguirre Email backend test * 2013-09-22 Matías Aguirre Remove print line * 2013-09-22 Matías Aguirre Code model on tests * 2013-09-22 Matías Aguirre build_absolute_uri test case * 2013-09-22 Matías Aguirre Small simplification on disconnect action * 2013-09-22 Matías Aguirre Username backend test case * 2013-09-22 Matías Aguirre Test suite defined on setup.py * 2013-09-22 Matías Aguirre Move tests inside the social package * 2013-09-22 Matías Aguirre Remove coveralls * 2013-09-22 Matías Aguirre First try with coveralls * 2013-09-22 Matías Aguirre More badges to README.rst * 2013-09-22 Matías Aguirre Remove python 2.5 support from setup.py * 2013-09-22 Matías Aguirre Doc clarification * 2013-09-22 Matías Aguirre Changelog update 2013-09-22 v0.1.13 ================== * 2013-09-22 Matías Aguirre v0.1.13 * 2013-09-22 Matías Aguirre Move common code to base class * 2013-09-22 Matías Aguirre Small improve to email partial pipeline on example app * 2013-09-22 Matías Aguirre Fix titles and sections on some docs * 2013-09-22 Matías Aguirre Link to pipeline section * 2013-09-22 Matías Aguirre Code model docs * 2013-09-22 Matías Aguirre Move email validation docs to pipeline.rst * 2013-09-21 Matías Aguirre Improve email validation to only validate new accounts * 2013-09-21 Matías Aguirre Email and Username backends docs * 2013-09-21 Matías Aguirre Docstring fix * 2013-09-21 Matías Aguirre Username backend * 2013-09-21 Matías Aguirre Drop password * 2013-09-21 Matías Aguirre Email validation only on new accounts * 2013-09-21 Matías Aguirre Drop password support, let that to developers in pipeline * 2013-09-21 Matías Aguirre Validate email only if needed * 2013-09-21 Matías Aguirre Small improvement on partial decorator * 2013-09-21 Matías Aguirre Enable password on user save fields * 2013-09-21 Matías Aguirre Email validation on django example app * 2013-09-21 Matías Aguirre Email validation pipeline and strategy functions * 2013-09-21 Matías Aguirre Verification code models * 2013-09-21 Matías Aguirre Django example for email auth * 2013-09-21 Matías Aguirre Initial email auth backend (no email validation yet) * 2013-09-21 Matías Aguirre Remove complex class definition on django json field. Refs #35 * 2013-09-20 Matías Aguirre Define 'POST' method for access token retrieval in Odnoklassniki backend. Refs #33 * 2013-09-19 Matías Aguirre Sanitize redirects on complete before sending it * 2013-09-18 Matías Aguirre Link to Box doc * 2013-09-18 Matías Aguirre Link Pyramid and add it to list on README * 2013-09-18 Matías Aguirre Changelog update * 2013-09-18 Matías Aguirre Changelog file * 2013-09-17 Jonathan Tsai Update README.rst * 2013-09-17 Matías Aguirre Update pipeline docs with disconnection-pipeline feature * 2013-09-17 Jonathan Tsai Update pipeline.rst * 2013-09-15 Matías Aguirre Ensure request in the pipeline * 2013-09-15 Jonathan Tsai Update README.rst * 2013-09-15 Matías Aguirre Add requirements for pyramid example app * 2013-09-15 Matías Aguirre Pyramid docs * 2013-09-15 Matías Aguirre Pyramid example app * 2013-09-15 Matías Aguirre Pyramid strategy and application * 2013-09-15 Matías Aguirre Move build_absolute_uri base code to utils * 2013-09-15 Matías Aguirre Remove unused import * 2013-09-15 Matías Aguirre Change lists to tuples * 2013-09-15 Matías Aguirre Commit session only if flagged * 2013-09-14 Matías Aguirre Return the poped value * 2013-09-14 Matías Aguirre Return the poped value * 2013-09-14 Matías Aguirre Remove prints * 2013-09-14 Matías Aguirre Partial pipeline on django example 2013-09-13 v0.1.12 ================== * 2013-09-13 Matías Aguirre v0.1.12 * 2013-09-12 Matías Aguirre Fix get_social_auth_for_user on mongoengine storage * 2013-09-12 Matías Aguirre Fix rst syntax. Fix site index linking * 2013-09-12 Matías Aguirre More settings fixes. Refs #29 * 2013-09-12 Matías Aguirre Review setting names on docs. Refs #29. Refs #28. * 2013-09-12 Matías Aguirre Review setting names on docs. Refs #29 * 2013-09-10 Matías Aguirre PEP8 and code simplification. Refs #26 * 2013-09-09 Roman Added workaround for REDIRECT_STATE and urlencoding. * 2013-09-09 Roman Fixed auth redirect URL for BaseOauth2 always redirecting wrong. OAuth2 providers expect the url to be an unquoted value. * 2013-09-09 Matías Aguirre Fix thisismyjam test * 2013-09-09 Matías Aguirre PEP8 on thisismyjam backend * 2013-09-08 Rob McQueen changing back to default KEY/SECRET naming * 2013-09-08 Sam Kuehn Remove data that should should not be stored in extra_data * 2013-09-08 Rob McQueen change title of thisismyjam docs * 2013-09-08 Rob McQueen Adding Support For ThisIsMyJam * 2013-09-08 Matías Aguirre Disconnect pipeline, move details/uid extraction to pipeline methods too. Refactor pipeline run code * 2013-09-08 Matías Aguirre New user redirect test * 2013-09-08 Sam Kuehn Add box.net to readme * 2013-09-08 Sam Kuehn Add box.net to list off supported providers * 2013-09-08 Sam Kuehn Add box.net support * 2013-09-08 Matías Aguirre Test broken disconnect * 2013-09-08 Sam Kuehn Ignore .DS_Store files * 2013-09-08 Matías Aguirre Custom user model for mongoengine backends * 2013-09-07 Matías Aguirre Thanks doc * 2013-09-07 Matías Aguirre Keep old data on refresh token if no new data was received * 2013-09-07 Matías Aguirre Fix extra data case for tuple with single value * 2013-09-07 Matías Aguirre Set request if not present on pipeline continuation, fix args passed to continue_pipeline 2013-09-03 v0.1.11 ================== * 2013-09-03 Matías Aguirre v0.1.11 * 2013-09-03 Matías Aguirre Enforce list on pipeline method * 2013-09-02 Matías Aguirre Drop regex search on steam id. Refs #23 * 2013-09-02 Matías Aguirre Steam link * 2013-09-01 Matías Aguirre Generic whitelist tests * 2013-09-01 Matías Aguirre Refactor email/domain whitelist checking, make it generic to all backends * 2013-09-01 Matías Aguirre More revoke token test on dummy backend * 2013-09-01 Matías Aguirre Simplifications to revoke token code * 2013-09-01 Matías Aguirre Revoke token test * 2013-09-01 Matías Aguirre Fixes to revoke token code * 2013-09-01 Matías Aguirre Fix test name * 2013-09-01 Matías Aguirre Rewrite conditions on user pipeline * 2013-08-31 Matías Aguirre Associate by email test * 2013-08-31 Matías Aguirre Rewrite if * 2013-08-31 Matías Aguirre Move common testing code to base class * 2013-08-31 Matías Aguirre Enable broken steam test * 2013-08-30 Matías Aguirre Comment test, needs more investigation * 2013-08-30 Matías Aguirre Test @partial pipeline decorator * 2013-08-30 Matías Aguirre Steam test on missing steam id * 2013-08-30 Matías Aguirre Fix github emails retrieval. Add tests * 2013-08-30 Matías Aguirre Change missing %s to format() call * 2013-08-30 Matías Aguirre Switch %s in favor of .format * 2013-08-30 Matías Aguirre Pass parameters by name * 2013-08-29 Matías Aguirre Fix deletion in sqlalchemy orm * 2013-08-29 Matías Aguirre Enable reddit backend in example * 2013-08-29 Matías Aguirre Add logout route, increase flask version in requirements * 2013-08-29 Matías Aguirre Remove unused parameters * 2013-08-29 Matías Aguirre Fix format string * 2013-08-29 Matías Aguirre Fetch emails if the scope allows it, support future response from API too. * 2013-08-29 Matías Aguirre Port token revoke on disconnection * 2013-08-29 Matías Aguirre Set lazy backref to user model in sqlalchemy orm * 2013-08-29 Matías Aguirre Move disconnect common code to base class 2013-08-29 v0.1.10 ================== * 2013-08-29 Matías Aguirre v0.1.10 * 2013-08-29 Matías Aguirre PEP8 * 2013-08-29 Matías Aguirre Port associate by email pipeline entry * 2013-08-29 Matías Aguirre Fix shopify and vk definitions 2013-08-29 v0.1.9 ================= * 2013-08-29 Matías Aguirre v0.1.9 * 2013-08-29 Matías Aguirre Allow to override strategy getter * 2013-08-29 Matías Aguirre Port linkedin force profile language setting from DSA * 2013-08-28 Matías Aguirre Port slug func override from DSA, define identity funcs to avoid extra ifs * 2013-08-28 Matías Aguirre Requests oauthlib still broken * 2013-08-28 Matías Aguirre Requests oauthlib still broken * 2013-08-28 Matías Aguirre Port HTTPS ensure code from DSA * 2013-08-28 Matías Aguirre Port fields length config by settings * 2013-08-28 Matías Aguirre Wording fix * 2013-07-15 Matías Aguirre Fix Steam backend steam id retrieval * 2013-07-15 Matías Aguirre Fix super call. * 2013-07-15 Matías Aguirre Django imports for version lower than 1.4 and higher * 2013-07-15 Florian Auroy Pass synchronize_session='fetch' to delete. * 2013-07-15 Florian Auroy Commit the session after removing an association. * 2013-07-14 Matías Aguirre Django admin conf for default django app * 2013-07-14 Matías Aguirre Port ExactTarget backend from DSA * 2013-07-14 Matías Aguirre Port Jawbone backend from DSA * 2013-07-14 Matías Aguirre Port Fedora backend from DSA * 2013-07-14 Matías Aguirre Port Belgium e-ID backend from DSA * 2013-07-14 Matías Aguirre Port AppsFuel backend from DSA * 2013-07-14 Matías Aguirre Port AOL backend from DSA * 2013-07-14 Matías Aguirre Dummy change * 2013-07-13 Matías Aguirre Reduce the code in openid wrapper for flask 0.10 2013-07-13 v0.1.8 ================= * 2013-07-13 Matías Aguirre v0.1.8 * 2013-07-13 Matías Aguirre Add method to determine if current user is allowed to login * 2013-06-30 Florian Auroy Fix OpenId auth with Flask 0.10 * 2013-06-20 Matías Aguirre Add instance to session before commiting it * 2013-06-20 Orchestrator81 Updated the CodersClan button to the right repo_id * 2013-06-20 Orchestrator81 Add CodersClan button to the Readme file * 2013-06-13 Martin Santos Better aproach to the default value of response * 2013-06-13 Martin Santos No mandatory param "response" in do_auth of facebook backend * 2013-06-13 Martin Santos Forgot to declare the param response at the top of the function * 2013-06-13 Martin Santos Pass expected parameter response instead expires * 2013-06-12 Matías Aguirre Pass username as named parameter 2013-06-03 v0.1.7 ================= * 2013-06-03 Matías Aguirre v0.1.7 * 2013-06-03 Matías Aguirre Fix inheritance on flask and sqlalchemy orm * 2013-06-03 Matías Aguirre Pass session into flask app init 2013-06-03 v0.1.6 ================= * 2013-06-03 Matías Aguirre v0.1.6 * 2013-06-03 Matías Aguirre Enforce db session passing on flask init 2013-06-01 v0.1.5 ================= * 2013-06-01 Matías Aguirre v0.1.5 * 2013-06-01 Matías Aguirre Simpler code to convert values to and from session * 2013-06-01 Matías Aguirre Fix is_new flag * 2013-06-01 Matías Aguirre Added @partial decorator, much simpler than adding entries to pipeline * 2013-06-01 Matías Aguirre Clean pipeline after auth process * 2013-06-01 Matías Aguirre Remove is_response() method * 2013-06-01 Matías Aguirre Simpler partial pipeline check 2013-05-31 v0.1.4 ================= * 2013-05-31 Matías Aguirre v0.1.4 * 2013-05-31 Matías Aguirre Unrestricted user fields on instance creation, defaults to username and email 2013-05-31 v0.1.3 ================= * 2013-05-31 Matías Aguirre v0.1.3 * 2013-05-30 Matías Aguirre Avoid version 0.3.2 of requests-oauthlib on python 3 (setup.py) * 2013-05-29 Matías Aguirre Avoid version 0.3.2 of requests-oauthlib on python 3 * 2013-05-29 Matías Aguirre Amazon OAuth2 backend * 2013-05-21 dongweiming Modify trello.py to pass pep8 * 2013-05-20 jgsogo added support for django custom user with no 'username' field * 2013-05-20 dongweiming Add Trello backend support * 2013-05-17 Matías Aguirre PEP8 * 2013-05-17 Matías Aguirre PEP8 * 2013-05-17 Matías Aguirre Pass decoding=None to oauthlib since the default value (utf-8) creates problems with python-requests on python3 * 2013-05-17 George Sakkis Podio backend: proper way to split the work between user_data() and get_user_details() * 2013-05-17 George Sakkis Podio backend * 2013-05-14 Matías Aguirre Rename backend import path * 2013-05-14 Matías Aguirre Avoid import error on local_settings * 2013-05-14 Matías Aguirre Add required settings to settings.py * 2013-04-23 Matías Aguirre PEP8 over vk module * 2013-04-23 Alexey Boriskin Adjust examples to the vkontakte -> vk.com rename * 2013-04-23 Alexey Boriskin Adjust documentation to the vkontakte -> vk.com rename * 2013-04-23 Alexey Boriskin Rename vkontakte to vk.com in the code * 2013-04-23 Alexey Boriskin Added test for vkontakte oauth2 backend. * 2013-04-22 Alexey Boriskin Update links and API urls: rename vkontatke.ru to vk.com because of social network official rename * 2013-04-22 Alexey Boriskin Fixed bug, which prevented VK backend from picking user data (first_name and last_name) * 2013-04-22 Alexey Boriskin Fix mixed up key and secret * 2013-04-22 Matías Aguirre Freeze httpretty dep since that package breaks python3 support quite often * 2013-04-21 Matías Aguirre Mendeley backend * 2013-04-21 Matías Aguirre Ignore invalid tokens when building setting name * 2013-04-21 Matías Aguirre Yaru OAuth2 backend * 2013-04-20 Andrey changed ACCESS_TOKEN_METHOD to POST * 2013-04-04 Matías Aguirre Support _ setting format too * 2013-04-04 Matías Aguirre Fix persona backend 2013-04-03 v0.1.2 ================= * 2013-04-03 Matías Aguirre v0.1.2 * 2013-04-03 Matías Aguirre Update tests docs * 2013-04-03 Matías Aguirre Encode string before calling b64 * 2013-04-03 Matías Aguirre Refresh token tests * 2013-04-03 Matías Aguirre Remove unused method * 2013-04-03 Matías Aguirre Reddit backend test * 2013-04-03 Matías Aguirre Dummy space align * 2013-04-03 Matías Aguirre Pipeline tests * 2013-04-03 Matías Aguirre Configurable clean step on usernames. * 2013-04-03 Matías Aguirre Rename pipeline parameter from social_user to social * 2013-04-03 Matías Aguirre Multiple accounts tests, move code from super class to subclass since it's where they belong * 2013-04-03 Matías Aguirre Rename handles from octocat to foobar on github data/tests * 2013-04-03 Matías Aguirre Rename var to avoid override of function * 2013-04-03 Matías Aguirre Encode seed() to be py3 complaint * 2013-04-03 Matías Aguirre Storage tests * 2013-04-03 Matías Aguirre Exceptions fixes, remove titled_name attribute from backends, use strategy function instead of storage * 2013-04-02 Matías Aguirre Google whitelist domains/emails tests * 2013-04-02 Matías Aguirre Fix py3 import * 2013-04-02 Matías Aguirre OpenId tests * 2013-04-02 Matías Aguirre Remove unused import * 2013-04-02 Matías Aguirre Remove unused code from google backend * 2013-04-02 Matías Aguirre Simplify steam backend code, enable it on django demo 2013-04-01 v0.1.1 ================= * 2013-04-01 Matías Aguirre v0.1.1 * 2013-04-01 Matías Aguirre Use a default dict to play with the console and django strategy * 2013-03-31 Matías Aguirre Remove exception handling * 2013-03-31 Matías Aguirre Verify tokens returned by tokes property * 2013-03-31 Matías Aguirre Discard invalid types when cleaning urls * 2013-03-31 Matías Aguirre Utils and expiration_datetime test * 2013-03-31 Matías Aguirre Simplify utc handling on expiration_datetime method * 2013-03-31 Matías Aguirre Fix exception for use with python3 * 2013-03-31 Matías Aguirre Exceptions tests * 2013-03-31 Matías Aguirre Replace __unicode__ with __str__ on exceptions * 2013-03-31 Matías Aguirre Google unique-id support test * 2013-03-31 Matías Aguirre Dummy backend test * 2013-03-31 Matías Aguirre Improve extra_data names handling, remove unused method * 2013-03-31 Matías Aguirre Move common code to base class on linkedin tests * 2013-03-31 Matías Aguirre Fix linkedin test to use json response format * 2013-03-31 Matías Aguirre Rename extra_params to params * 2013-03-31 Matías Aguirre Use linkedin JSON format instead of parsing XML * 2013-03-31 Matías Aguirre Github organization backend improves and tests * 2013-03-31 Matías Aguirre Small improve to yandex first/last name generation * 2013-03-31 Matías Aguirre Test backends info returned by social.backends.utils.user_backends_data * 2013-03-30 Matías Aguirre Tripit 100% coverage tests * 2013-03-30 Matías Aguirre Fix method type on stripe backend * 2013-03-30 Matías Aguirre Stocktwits 100% coverage tests * 2013-03-30 Matías Aguirre Improve soundcloud tests * 2013-03-30 Matías Aguirre Facebook fail on user-data test * 2013-03-30 Matías Aguirre 100% coverage of evernote backend * 2013-03-30 Matías Aguirre Simplify yammer auth_complete code * 2013-03-30 Matías Aguirre Simplify oauth1 and 2 tests code * 2013-03-30 Matías Aguirre Test running script * 2013-03-30 Matías Aguirre Simplify auth error processing * 2013-03-29 Matías Aguirre Add coverage to dependencies * 2013-03-29 Matías Aguirre Backends utils tests * 2013-03-29 Matías Aguirre Reset backends cache if force_load was set to True * 2013-03-29 Matías Aguirre Run tests with coverage * 2013-03-29 Matías Aguirre More tests * 2013-03-29 Matías Aguirre Remove else clause * 2013-03-29 Matías Aguirre Simplify user_data calls (remove try/except blocks) * 2013-03-29 Matías Aguirre Fix partial pipeline arguments to avoid messing with broken pipeline case * 2013-03-29 Matías Aguirre Simplify tokens helper in models/backends since tokens are stored in desired format already * 2013-03-29 Matías Aguirre Remove stop-pipeline exception (not used at all) * 2013-03-29 Matías Aguirre Initial cherrypy support * 2013-03-26 Matías Aguirre Fix exception raised on skyrock backend * 2013-03-26 Matías Aguirre Reddit backend docs * 2013-03-26 Matías Aguirre Protect request access in case it's None * 2013-03-26 Matías Aguirre Reddit backend * 2013-03-26 Matías Aguirre Fix refresh-token method * 2013-03-26 Matías Aguirre Move strategy loader to outside function to ease strategy creation from scripts * 2013-03-25 Matías Aguirre Github for organizations backend * 2013-03-25 Matías Aguirre Update twitter doc borrowed from DSA project * 2013-03-24 Matías Aguirre Define scope separators for linkedin oauth1 and oauth2 * 2013-03-24 Matías Aguirre Linkedin OAuth2 docs * 2013-03-24 Matías Aguirre Simplify user_data method on linkedin backends * 2013-03-24 Matías Aguirre Linkedin OAuth2 backend * 2013-03-24 Matías Aguirre Fix setting reference in linkedin docs * 2013-03-24 Matías Aguirre Fix settings references on docs * 2013-03-24 Matías Aguirre Fix linkedin docs * 2013-03-24 Matías Aguirre Fix association get method on tests * 2013-03-24 Matías Aguirre Small rename on openid base code * 2013-03-24 Matías Aguirre Remove unnecessary lines in setUp methods * 2013-03-24 Matías Aguirre Move __init__ code into setUp method * 2013-03-24 Matías Aguirre Partial pipeline tests on actions. Simplify unused code, return backend stored on session on partial pipelines * 2013-03-23 Matías Aguirre Add travis-ci build status image into README * 2013-03-23 Matías Aguirre Change install method to avoid python2/python3 dependency issues on travis-ci * 2013-03-23 Matías Aguirre Remove debug print, use print() on docstring * 2013-03-23 Matías Aguirre Add python3.3 to travis conf * 2013-03-23 Matías Aguirre Set sorted fields selectors on linkedin to avoid HTTPretty failure on tests * 2013-03-23 Matías Aguirre Travis YAML * 2013-03-23 Matías Aguirre Update tests readme * 2013-03-23 Matías Aguirre Disconnect test * 2013-03-23 Matías Aguirre Simplify actions tests, add association test * 2013-03-23 Matías Aguirre Add actions tests (login so far) * 2013-03-23 Matías Aguirre Add response class and define is_response() on test strategy * 2013-03-23 Matías Aguirre Save user username in session and verify it on tests * 2013-03-23 Matías Aguirre Define authenticate() method on test strategy * 2013-03-23 Matías Aguirre Simplify authenticate() code on flask and webpy strategies * 2013-03-23 Matías Aguirre Catch custom user attributes in case login() resets the instance * 2013-03-23 Matías Aguirre Move actions module to root module * 2013-03-22 Matías Aguirre Allow already instanced template strategies * 2013-03-22 Matías Aguirre Use setdefault() to set current template strategy on tests * 2013-03-22 Matías Aguirre Use setdefault() to set current template strategy * 2013-03-22 Matías Aguirre Move rendering process into strategy class too * 2013-03-18 Matías Aguirre Tests docs * 2013-03-18 Matías Aguirre Tests readme * 2013-03-18 Matías Aguirre Dropbox backend tests * 2013-03-18 Matías Aguirre Define parameters names on class attributes. Fix Dropbox backend * 2013-03-18 Matías Aguirre Xing backend tests * 2013-03-18 Matías Aguirre Fix xing backend * 2013-03-18 Matías Aguirre Twitter backend test * 2013-03-18 Matías Aguirre Tumblr backend test * 2013-03-18 Matías Aguirre Support POST request method on oauth1 backends tests * 2013-03-18 Matías Aguirre Add option for POST request token method * 2013-03-18 Matías Aguirre Enable tumblr backend in example app * 2013-03-18 Matías Aguirre Skyrock tests * 2013-03-18 Matías Aguirre Fix skyrock backend * 2013-03-18 Matías Aguirre Enable skyrock backend on example app * 2013-03-18 Matías Aguirre Tripit tests * 2013-03-18 Matías Aguirre Readability tests * 2013-03-18 Matías Aguirre Fix readability backend * 2013-03-18 Matías Aguirre Enable readability backend on example app * 2013-03-18 Matías Aguirre Linkedin tests * 2013-03-18 Matías Aguirre Rename test classes to reflect the backend type * 2013-03-18 Matías Aguirre Google OAuth1 test * 2013-03-18 Matías Aguirre Remove empty line * 2013-03-18 Matías Aguirre Flickr tests * 2013-03-18 Matías Aguirre Fix and simplify flickr backend * 2013-03-18 Matías Aguirre Fitbit tests * 2013-03-18 Matías Aguirre Fix fitbit backend * 2013-03-18 Matías Aguirre Evernote tests * 2013-03-18 Matías Aguirre Fix evernote backend * 2013-03-18 Matías Aguirre Fix evernote backend * 2013-03-18 Matías Aguirre Yahoo tests * 2013-03-18 Matías Aguirre Bitbucket tests * 2013-03-18 Matías Aguirre OAuth1 base tests * 2013-03-18 Matías Aguirre Fix OAuth1 unauth_token check. Support access token method on OAuth1 backends too. Fix test strategry method * 2013-03-17 Matías Aguirre Move shared code to a method * 2013-03-17 Matías Aguirre Simplify backend definition to avoid messing with sys.path on each test * 2013-03-17 Matías Aguirre Moved backends tests to backends module * 2013-03-17 Matías Aguirre Use python3 workaround utils * 2013-03-17 Matías Aguirre Removed unused imports * 2013-03-17 Matías Aguirre Stackoverflow tests * 2013-03-17 Matías Aguirre Fix stackoverflow backend * 2013-03-17 Matías Aguirre Easier way to override access token response processing on oauth2 backends * 2013-03-17 Matías Aguirre Enable stackoverflow backend on example app * 2013-03-17 Matías Aguirre Yandex tests * 2013-03-17 Matías Aguirre Fix yandex backend * 2013-03-17 Matías Aguirre Yammer tests * 2013-03-17 Matías Aguirre Fix yammer backend * 2013-03-17 Matías Aguirre Enable yammer backend in example app * 2013-03-17 Matías Aguirre Stocktwits tests * 2013-03-17 Matías Aguirre Fix stocktwits backend * 2013-03-17 Matías Aguirre Stripe tests * 2013-03-17 Matías Aguirre Fix stripe backend * 2013-03-17 Matías Aguirre Soundcloud test * 2013-03-17 Matías Aguirre Fix soundcloud backend * 2013-03-17 Matías Aguirre Small code simplification in shopify backend * 2013-03-17 Matías Aguirre Enable rdio backends in example app * 2013-03-17 Matías Aguirre Fix Rdio backends * 2013-03-17 Matías Aguirre Mixcloud tests * 2013-03-17 Matías Aguirre Fix mixcloud backend * 2013-03-17 Matías Aguirre Enable mixcloud backend into example app * 2013-03-17 Matías Aguirre Fix and simplify mail.ru backend * 2013-03-17 Matías Aguirre Live tests * 2013-03-17 Matías Aguirre Fix Live backend * 2013-03-17 Matías Aguirre Instagram tests * 2013-03-17 Matías Aguirre Fix instagram backend * 2013-03-17 Matías Aguirre Google oauth2 test * 2013-03-17 Matías Aguirre Fix google oauth2 backend * 2013-03-17 Matías Aguirre Foursquare test * 2013-03-17 Matías Aguirre Rename douban oauth2 backend. Enable douban backend in example app * 2013-03-17 Matías Aguirre Fixed foursquare backend * 2013-03-16 Matías Aguirre Disqus tests * 2013-03-16 Matías Aguirre Fix disqus backend * 2013-03-16 Matías Aguirre Dailymotion test * 2013-03-16 Matías Aguirre Fix error processing * 2013-03-16 Matías Aguirre Fix dailymotion backend * 2013-03-16 Matías Aguirre Behance tests case * 2013-03-16 Matías Aguirre Fix behance backend * 2013-03-16 Matías Aguirre Support backends that don't support state/redirect_state. Angel backend test * 2013-03-16 Matías Aguirre Allow POST method on access_token exchange. Fixes angel backend * 2013-03-16 Matías Aguirre Facebook tests * 2013-03-16 Matías Aguirre Update requests values properly * 2013-03-16 Matías Aguirre Remove unittest call from github tests * 2013-03-16 Matías Aguirre Requirements to run tests * 2013-03-16 Matías Aguirre Reshape flask example app. Use filter_by instead of filter on disconnect code * 2013-03-16 Matías Aguirre Use declarative sqlalchemy base. Drop global update on init_social. Refs #1 * 2013-03-16 Matías Aguirre Small fixes * 2013-03-16 Matías Aguirre Initial testings * 2013-03-15 Matías Aguirre Drop parse_qsl call * 2013-03-15 Matías Aguirre Remove empty line * 2013-03-15 Matías Aguirre Proper requirements for python3 and python2 * 2013-03-14 Matías Aguirre Fix token comparision to avoid None == None case * 2013-03-14 Matías Aguirre Improve refresh_token response processing * 2013-03-14 Matías Aguirre Fix use of request on stackoverflow backend * 2013-03-13 Matías Aguirre Fix google oauth1 * 2013-03-13 Matías Aguirre Fix Python2 import, define json field in a compatible way with python versions * 2013-03-13 Matías Aguirre More Python3 support * 2013-03-13 Matías Aguirre Django 1.5 and Python 3 support * 2013-03-12 Matías Aguirre Port OAuth1 backends to oauthlib and requests-oauthlib * 2013-03-12 Matías Aguirre Move views/routes code to common module * 2013-03-06 Matías Aguirre Flag new associations in the pipeline * 2013-03-05 Matías Aguirre Port from urllib/urllib2 urlopen to python-requests * 2013-03-03 Jannis Leidel Fixed South introspection path to new module structure. * 2013-02-28 Matías Aguirre Rename doc * 2013-02-28 Matías Aguirre Add some note about porting DSA settings * 2013-02-28 Matías Aguirre Mention mongoengine storage setting * 2013-02-27 Matías Aguirre Port DSA evernote expire time normalization * 2013-02-27 Matías Aguirre Link to project homepage and docs * 2013-02-27 Matías Aguirre Fix long_description on setup.py file * 2013-02-27 Matías Aguirre Remove frameworks classifier * 2013-02-27 Matías Aguirre Extra bits for versioning * 2013-02-27 Matías Aguirre Small requirements.txt for webpy example app * 2013-02-27 Matías Aguirre Comment on how to run migrations * 2013-02-27 Matías Aguirre Add default url prefix * 2013-02-27 Matías Aguirre Fix URLs paths for django app, support setting url prefix (to ease demo setup) * 2013-02-26 Matías Aguirre Add fabfile to ignore list * 2013-02-26 Matías Aguirre Mark dev version * 2013-02-26 Matías Aguirre Small markup changes * 2013-02-26 Matías Aguirre Porting from DSA docs * 2013-02-26 Matías Aguirre Update django url docs * 2013-02-26 Matías Aguirre Remove the obsolete BACKENDS attribute, simplify backends loading * 2013-02-26 Matías Aguirre Links to mailing list and irc channel * 2013-02-26 Matías Aguirre Improve disconnect process and example apps styles/disconnect triggering * 2013-02-26 Matías Aguirre Webpy example improves * 2013-02-26 Matías Aguirre Remove print statement * 2013-02-26 Matías Aguirre Fix disconnect link, must be a POST action * 2013-02-26 Matías Aguirre Flask app and example improves * 2013-02-26 Matías Aguirre Django app and example improves * 2013-02-25 Matías Aguirre Move code to avoid dependencies issues * 2013-02-25 Matías Aguirre Basic site plus implementing backends docs * 2013-02-25 Matías Aguirre Add URL attribute to open id classes to reduce methods overrides * 2013-02-24 Matías Aguirre Docs markup improves * 2013-02-24 Matías Aguirre pypi setup file * 2013-02-24 Matías Aguirre Small docs changes. Versioning * 2013-02-24 Matías Aguirre Docs * 2013-02-24 Matías Aguirre Rename some settings, improve vkontakte backend code * 2013-02-24 Matías Aguirre Four spaces indentation * 2013-02-23 Matías Aguirre Added template filters to flask app * 2013-02-23 Matías Aguirre Added context_processors for django app * 2013-02-23 Matías Aguirre Remove HTML template, leave that to developers * 2013-02-23 Matías Aguirre Ported rdio backend from DSA * 2013-02-23 Matías Aguirre Port tumblr backend from DSA * 2013-02-23 Matías Aguirre Port last douban changes from DSA * 2013-02-23 Matías Aguirre Port fields stored in session from DSA * 2013-02-23 Matías Aguirre Port slugify option from DSA * 2013-02-23 Matías Aguirre Enforce POST method on disconnect views * 2013-02-23 Matías Aguirre Ported SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL setting * 2013-02-22 Matías Aguirre Move is_active check to a function * 2013-02-22 Matías Aguirre Move is_authenticated check to a function * 2013-02-17 Matías Aguirre Add django middleware * 2013-02-17 Matías Aguirre Licence * 2013-02-17 Matías Aguirre Remove unused var * 2013-02-17 Matías Aguirre Stackoverflow backend * 2013-02-17 Matías Aguirre Readability backend * 2013-02-17 Matías Aguirre Rename to be consistent with backend name * 2013-02-17 Matías Aguirre Steam backend * 2013-02-17 Matías Aguirre Move methods * 2013-02-17 Matías Aguirre Fix removeAssociation method * 2013-02-17 Matías Aguirre Docstrings, methods movement, simple_user_exists rename to user_exists * 2013-02-17 Matías Aguirre Fix setting retrieval and remove testing webpy view * 2013-02-17 Matías Aguirre Move urls inside classes and fix a few on skyrock * 2013-01-29 Matías Aguirre Webpy example app * 2013-01-29 Matías Aguirre Webpy strategy and app * 2012-12-31 Matías Aguirre Moved get_strategy to strategies/utils.py * 2012-12-30 Matías Aguirre Cleanups * 2012-12-30 Matías Aguirre Rename modules * 2012-12-29 Matías Aguirre Add more examples links to flask example app * 2012-12-29 Matías Aguirre Fix association/nonce filtering queries * 2012-12-29 Matías Aguirre Fix absolute url building process * 2012-12-29 Matías Aguirre Enable GET/POST in routes * 2012-12-29 Matías Aguirre Set app in debug mode by default * 2012-12-29 Matías Aguirre Flask example * 2012-12-29 Matías Aguirre Flask strategy, sqlalchemy storage and flask app * 2012-12-29 Matías Aguirre Clean old settings, move code to main utils * 2012-12-28 Matías Aguirre Clean unused methods, move integrity error check to storage layer * 2012-12-27 Matías Aguirre Move store out from strategies module * 2012-12-26 Matías Aguirre Fixes and tests * 2012-12-25 Matías Aguirre Filter models by user * 2012-12-25 Matías Aguirre Rename example directory * 2012-12-25 Matías Aguirre Filter models by user * 2012-12-25 Matías Aguirre Port removeAssociation from django-social-auth * 2012-12-25 Matías Aguirre Add setting shortcut to backends * 2012-12-25 Matías Aguirre Odnoklassniki backend * 2012-12-25 Matías Aguirre Remove property decorator. PEP8 * 2012-12-25 Matías Aguirre Cookies handling * 2012-12-20 Matías Aguirre Fix key * 2012-12-16 Matías Aguirre Yandex backends * 2012-12-16 Matías Aguirre LiveJournal backend * 2012-12-16 Matías Aguirre Ported django-social-auth oauth backends * 2012-12-16 Matías Aguirre Google App Engine backend * 2012-12-16 Matías Aguirre Ported django-social-auth oauth2 backends * 2012-12-16 Matías Aguirre Facebook backend * 2012-12-15 Matías Aguirre Persona Auth * 2012-12-15 Matías Aguirre Stripe OAuth2 backend * 2012-12-15 Matías Aguirre Yahoo OpenId backend * 2012-12-15 Matías Aguirre Twitter OAuth backend * 2012-12-15 Matías Aguirre Google OpenId backend * 2012-12-15 Matías Aguirre Google oauth backend * 2012-12-14 Matías Aguirre Make openid work * 2012-12-14 Matías Aguirre Simplify Strategy and Storage loading on django app * 2012-12-14 Matías Aguirre Remove unneeded function * 2012-12-14 Matías Aguirre Apps description on __init__.py files * 2012-12-14 Matías Aguirre Restore import line * 2012-12-14 Matías Aguirre Django example requirements.txt * 2012-12-14 Matías Aguirre requirements.txt * 2012-12-14 Matías Aguirre Move dj app views/utils/urls since they can be used by mongoengine too * 2012-12-14 Matías Aguirre Initial google oauth2 backend and django example app * 2012-12-13 Matías Aguirre Initial code for this lib (not working yet) * 2012-12-12 Matías Aguirre Initial commit python-social-auth-0.2.13/LICENSE000066400000000000000000000030011260133235600163250ustar00rootroot00000000000000Copyright (c) 2012-2015, Matías Aguirre All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-social-auth-0.2.13/MANIFEST.in000066400000000000000000000005551260133235600170710ustar00rootroot00000000000000global-include *.py include *.txt Changelog LICENSE README.rst recursive-include docs *.rst recursive-include social/tests *.txt graft examples recursive-exclude .tox * recursive-exclude social *.pyc recursive-exclude examples *.pyc recursive-exclude examples *.db recursive-exclude examples local_settings.py recursive-exclude examples/webpy_example/sessions * python-social-auth-0.2.13/Makefile000066400000000000000000000010321260133235600167620ustar00rootroot00000000000000docs: sphinx-build docs/ docs/_build/ site: docs rsync -avkz site/ tarf:sites/psa/ build: python setup.py sdist python setup.py bdist_wheel --python-tag py2 BUILD_VERSION=3 python setup.py bdist_wheel --python-tag py3 publish: python setup.py sdist upload python setup.py bdist_wheel --python-tag py2 upload BUILD_VERSION=3 python setup.py bdist_wheel --python-tag py3 upload clean: find . -name '*.py[co]' -delete find . -name '__pycache__' -delete rm -rf python_social_auth.egg-info dist build .PHONY: site docs publish python-social-auth-0.2.13/README.rst000066400000000000000000000234261260133235600170240ustar00rootroot00000000000000Python Social Auth ================== Python Social Auth is an easy-to-setup social authentication/registration mechanism with support for several frameworks and auth providers. Crafted using base code from django-social-auth, it implements a common interface to define new authentication providers from third parties, and to bring support for more frameworks and ORMs. .. image:: https://travis-ci.org/omab/python-social-auth.png?branch=master :target: https://travis-ci.org/omab/python-social-auth .. image:: https://badge.fury.io/py/python-social-auth.png :target: http://badge.fury.io/py/python-social-auth .. image:: https://pypip.in/d/python-social-auth/badge.png :target: https://crate.io/packages/python-social-auth?version=latest .. image:: https://readthedocs.org/projects/python-social-auth/badge/?version=latest :target: https://readthedocs.org/projects/python-social-auth/?badge=latest :alt: Documentation Status .. contents:: Table of Contents Features ======== This application provides user registration and login using social sites credentials. Here are some features, which is probably not a full list yet. Supported frameworks -------------------- Multiple frameworks are supported: * Django_ * Flask_ * Pyramid_ * Webpy_ * Tornado_ More frameworks can be added easily (and should be even easier in the future once the code matures). Auth providers -------------- Several services are supported by simply defining backends (new ones can be easily added or current ones extended): * Amazon_ OAuth2 http://login.amazon.com/website * Angel_ OAuth2 * AOL_ OpenId http://www.aol.com/ * Appsfuel_ OAuth2 * Behance_ OAuth2 * BelgiumEIDOpenId_ OpenId https://www.e-contract.be/ * Bitbucket_ OAuth1 * Box_ OAuth2 * Clef_ OAuth2 * Coursera_ OAuth2 * Dailymotion_ OAuth2 * DigitalOcean_ OAuth2 https://developers.digitalocean.com/documentation/oauth/ * Disqus_ OAuth2 * Douban_ OAuth1 and OAuth2 * Dropbox_ OAuth1 and OAuth2 * Evernote_ OAuth1 * Exacttarget OAuth2 * Facebook_ OAuth2 and OAuth2 for Applications * Fedora_ OpenId http://fedoraproject.org/wiki/OpenID * Fitbit_ OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Jawbone_ OAuth2 https://jawbone.com/up/developer/authentication * Kakao_ OAuth2 https://developer.kakao.com * `Khan Academy`_ OAuth1 * Launchpad_ OpenId * Linkedin_ OAuth1 * Live_ OAuth2 * Livejournal_ OpenId * LoginRadius_ OAuth2 and Application Auth * Mailru_ OAuth2 * MapMyFitness_ OAuth2 * Mendeley_ OAuth1 http://mendeley.com * Mixcloud_ OAuth2 * `Moves app`_ OAuth2 https://dev.moves-app.com/docs/authentication * `Mozilla Persona`_ * NaszaKlasa_ OAuth2 * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * OpenStreetMap_ OAuth1 http://wiki.openstreetmap.org/wiki/OAuth * OpenSuse_ OpenId http://en.opensuse.org/openSUSE:Connect * PixelPin_ OAuth2 * Pocket_ OAuth2 * Podio_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Reddit_ OAuth2 https://github.com/reddit/reddit/wiki/OAuth2 * Shopify_ OAuth2 * Skyrock_ OAuth1 * Soundcloud_ OAuth2 * Stackoverflow_ OAuth2 * Steam_ OpenId * Stocktwits_ OAuth2 * Strava_ OAuth2 * Stripe_ OAuth2 * Taobao_ OAuth2 http://open.taobao.com/doc/detail.htm?id=118 * ThisIsMyJam_ OAuth1 https://www.thisismyjam.com/developers/authentication * Trello_ OAuth1 https://trello.com/docs/gettingstarted/oauth.html * Tripit_ OAuth1 * Tumblr_ OAuth1 * Twilio_ Auth * Twitter_ OAuth1 * Uber_ OAuth2 * VK.com_ OpenAPI, OAuth2 and OAuth2 for Applications * Weibo_ OAuth2 * Withings_ OAuth1 * Wunderlist_ OAuth2 * Xing_ OAuth1 * Yahoo_ OpenId and OAuth2 * Yammer_ OAuth2 * Yandex_ OAuth1, OAuth2 and OpenId * Zotero_ OAuth1 User data --------- Basic user data population, to allow custom field values from provider's response. Social accounts association --------------------------- Multiple social accounts can be associated to a single user. Authentication processing ------------------------- Extensible pipeline to handle authentication/association mechanism in ways that suits your project. Dependencies ============ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on requests-oauthlib_ - Several backends demand application registration on their corresponding sites and other dependencies like sqlalchemy_ on Flask and Webpy. - Other dependencies: * six_ * requests_ Documents ========= Project homepage is available at http://psa.matiasaguirre.net/ and documents at http://psa.matiasaguirre.net or http://python-social-auth.readthedocs.org/. Installation ============ From pypi_:: $ pip install python-social-auth Or:: $ easy_install python-social-auth Or clone from github_:: $ git clone git://github.com/omab/python-social-auth.git And add social to ``PYTHONPATH``:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/python-social-auth/ Or:: $ cd python-social-auth $ sudo python setup.py install Upgrading --------- Django with South ~~~~~~~~~~~~~~~~~ Upgrading from 0.1 to 0.2 is likely to cause problems trying to apply a migration when the tables already exist. In this case a fake migration needs to be applied:: $ python manage.py migrate --fake default Support --------------------- If you're having problems with using the project, use the support forum at CodersClan. .. image:: http://www.codersclan.net/graphics/getSupport_github4.png :target: http://codersclan.net/forum/index.php?repo_id=8 Copyrights and License ====================== ``python-social-auth`` is protected by BSD license. Check the LICENSE_ for details. The base work was derived from django-social-auth_ work and copyrighted too, check `django-social-auth LICENSE`_ for details: .. _LICENSE: https://github.com/omab/python-social-auth/blob/master/LICENSE .. _django-social-auth: https://github.com/omab/django-social-auth .. _django-social-auth LICENSE: https://github.com/omab/django-social-auth/blob/master/LICENSE .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _myOpenID: https://www.myopenid.com/ .. _Angel: https://angel.co .. _Appsfuel: http://docs.appsfuel.com .. _Behance: https://www.behance.net .. _Bitbucket: https://bitbucket.org .. _Box: https://www.box.com .. _Clef: https://getclef.com/ .. _Coursera: https://www.coursera.org/ .. _Dailymotion: https://dailymotion.com .. _DigitalOcean: https://www.digitalocean.com/ .. _Disqus: https://disqus.com .. _Douban: http://www.douban.com .. _Dropbox: https://dropbox.com .. _Evernote: https://www.evernote.com .. _Facebook: https://www.facebook.com .. _Fitbit: https://fitbit.com .. _Flickr: http://www.flickr.com .. _Foursquare: https://foursquare.com .. _Google App Engine: https://developers.google.com/appengine/ .. _Github: https://github.com .. _Google: http://google.com .. _Instagram: https://instagram.com .. _LaunchPad: https://help.launchpad.net/YourAccount/OpenID .. _Linkedin: https://www.linkedin.com .. _Live: https://live.com .. _Livejournal: http://livejournal.com .. _Khan Academy: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication .. _Mailru: https://mail.ru .. _MapMyFitness: http://www.mapmyfitness.com/ .. _Mixcloud: https://www.mixcloud.com .. _Moves app: https://dev.moves-app.com/docs/ .. _Mozilla Persona: http://www.mozilla.org/persona/ .. _NaszaKlasa: https://developers.nk.pl/ .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Pocket: http://getpocket.com .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _Skyrock: https://skyrock.com .. _Soundcloud: https://soundcloud.com .. _Stocktwits: https://stocktwits.com .. _Strava: http://strava.com .. _Stripe: https://stripe.com .. _Taobao: http://open.taobao.com/doc/detail.htm?id=118 .. _Tripit: https://www.tripit.com .. _Twilio: https://www.twilio.com .. _Twitter: http://twitter.com .. _Uber: http://uber.com .. _VK.com: http://vk.com .. _Weibo: https://weibo.com .. _Wunderlist: https://wunderlist.com .. _Xing: https://www.xing.com .. _Yahoo: http://yahoo.com .. _Yammer: https://www.yammer.com .. _Yandex: https://yandex.ru .. _Readability: http://www.readability.com/ .. _Stackoverflow: http://stackoverflow.com/ .. _Steam: http://steamcommunity.com/ .. _Rdio: https://www.rdio.com .. _Tumblr: http://www.tumblr.com/ .. _Amazon: http://login.amazon.com/website .. _AOL: http://www.aol.com/ .. _BelgiumEIDOpenId: https://www.e-contract.be/ .. _Fedora: http://fedoraproject.org/wiki/OpenID .. _Jawbone: https://jawbone.com/up/developer/authentication .. _Mendeley: http://mendeley.com .. _Reddit: https://github.com/reddit/reddit/wiki/OAuth2 .. _OpenSuse: http://en.opensuse.org/openSUSE:Connect .. _ThisIsMyJam: https://www.thisismyjam.com/developers/authentication .. _Trello: https://trello.com/docs/gettingstarted/oauth.html .. _Django: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _Flask: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _Webpy: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _Tornado: http://www.tornadoweb.org/ .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/ .. _sqlalchemy: http://www.sqlalchemy.org/ .. _pypi: http://pypi.python.org/pypi/python-social-auth/ .. _OpenStreetMap: http://www.openstreetmap.org .. _six: http://pythonhosted.org/six/ .. _requests: http://docs.python-requests.org/en/latest/ .. _PixelPin: http://pixelpin.co.uk .. _Zotero: http://www.zotero.org/ python-social-auth-0.2.13/docs/000077500000000000000000000000001260133235600162565ustar00rootroot00000000000000python-social-auth-0.2.13/docs/Makefile000066400000000000000000000110261260133235600177160ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DjangoSocialAuth.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DjangoSocialAuth.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/DjangoSocialAuth" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DjangoSocialAuth" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." python-social-auth-0.2.13/docs/backends/000077500000000000000000000000001260133235600200305ustar00rootroot00000000000000python-social-auth-0.2.13/docs/backends/amazon.rst000066400000000000000000000016271260133235600220550ustar00rootroot00000000000000Amazon ====== Amazon implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `Amazon App Console`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_AMAZON_KEY = '...' SOCIAL_AUTH_AMAZON_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.amazon.AmazonOAuth2', ... ) Further documentation at `Website Developer Guide`_ and `Getting Started for Web`_. **Note:** This backend supports TLSv1 protocol since SSL will be deprecated from May 25, 2015 .. _Amazon App Console: http://login.amazon.com/manageApps .. _Website Developer Guide: https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf .. _Getting Started for Web: http://login.amazon.com/website python-social-auth-0.2.13/docs/backends/angel.rst000066400000000000000000000010401260133235600216430ustar00rootroot00000000000000Angel List ========== Angel uses OAuth v2 for Authentication. - Register a new application at the `Angel List API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ANGEL_KEY = '' SOCIAL_AUTH_ANGEL_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_ANGEL_AUTH_EXTRA_ARGUMENTS = {'scope': 'email messages'} **Note:** Angel List does not currently support returning ``state`` parameter used to validate the auth process. .. _Angel List API: https://angel.co/api/oauth/faq python-social-auth-0.2.13/docs/backends/aol.rst000066400000000000000000000003361260133235600213370ustar00rootroot00000000000000AOL === AOL OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.aol.AOLOpenId', ... ) python-social-auth-0.2.13/docs/backends/appsfuel.rst000066400000000000000000000022361260133235600224040ustar00rootroot00000000000000Appsfuel ======== Appsfuel uses OAuth v2 for Authentication check the `official docs`_ too. - Sign up at the `Appsfuel Developer Program`_ - Create and verify a new app - On the dashboard click on **Show API keys** - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_APPSFUEL_KEY = '' SOCIAL_AUTH_APPSFUEL_SECRET = '' Appsfuel gives you the chance to integrate with **Live** or **Sandbox** env. Appsfuel Live ------------- - Add 'social.backends.contrib.appsfuel.AppsfuelBackend' into your ``AUTHENTICATION_BACKENDS``. - Then you can start using ``{% url social:begin 'appsfuel' %}`` in your templates Appsfuel Sandbox ---------------- - Add ``'social.backends.appsfuel.AppsfuelOAuth2Sandbox'`` into your ``AUTHENTICATION_BACKENDS``. - Then you can start using ``{% url social:begin 'appsfuel-sandbox' %}`` in your templates - Define the settings:: SOCIAL_AUTH_APPSFUEL_SANDBOX_KEY = '' SOCIAL_AUTH_APPSFUEL_SANDBOX_SECRET = '' .. _official docs: http://docs.appsfuel.com/api_reference#api_integration .. _Appsfuel Developer Program: https://developer.appsfuel.com python-social-auth-0.2.13/docs/backends/azuread.rst000066400000000000000000000013561260133235600222220ustar00rootroot00000000000000Microsoft Azure Active Directory ================================ To enable OAuth2 support: - Fill in ``Client ID`` and ``Client Secret`` settings. These values can be obtained easily as described in `Azure AD Application Registration`_ doc:: SOCIAL_AUTH_AZUREAD_OAUTH2_KEY = '' SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_AZUREAD_OAUTH2_RESOURCE = '' This is the resource you would like to access after authentication succeeds. Some of the possible values are: ``https://graph.windows.net`` or ``https://-my.sharepoint.com``. .. _Azure AD Application Registration: https://msdn.microsoft.com/en-us/library/azure/dn132599.aspx python-social-auth-0.2.13/docs/backends/battlenet.rst000066400000000000000000000016271260133235600225520ustar00rootroot00000000000000Battle.net ========== Blizzard implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `Battlenet Developer Portal`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_BATTLENET_OAUTH2_KEY = '...' SOCIAL_AUTH_BATTLENET_OAUTH2_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.battlenet.BattleNetOAuth2', ... ) Note: The API returns an accountId which will be used as identifier for the user. If you want to allow the user to choose a username from his own characters, some further steps are required, see the use cases part of the documentation. Further documentation at `Developer Guide`_. .. _Battlenet Developer Portal: https://dev.battle.net/ .. _Developer Guide: https://dev.battle.net/docs/read/oauth python-social-auth-0.2.13/docs/backends/beats.rst000066400000000000000000000010161260133235600216560ustar00rootroot00000000000000Beats ===== Beats supports OAuth 2. - Register a new application at `Beats Music API`_, and follow the instructions below. OAuth2 ------ Add the Beats OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.beats.BeatsOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_BEATS_OAUTH2_KEY = '' SOCIAL_AUTH_BEATS_OAUTH2_SECRET = '' .. _Beats Music API: https://developer.beatsmusic.com/docs python-social-auth-0.2.13/docs/backends/behance.rst000066400000000000000000000020541260133235600221500ustar00rootroot00000000000000Behance ======= DEPRECATED NOTICE ----------------- **NOTE:** IT SEEMS THAT BEHANCE HAS DROPPED THEIR OAUTH2 SUPPORT WITHOUT MUCH NOTICE BESIDE A `BLOG POST`_ ON SEPTEMBER 2014 MENTIONING THAT IT WILL BE INTRODUCED "SOON". THIS BACKEND IS IN DEPRECATED STATE FOR NOW. Behance uses OAuth2 for its auth mechanism. - Register a new application at `Behance App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_BEHANCE_KEY = '' SOCIAL_AUTH_BEHANCE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_BEHANCE_SCOPE = [...] Check available permissions at `Possible Scopes`_. Also check the rest of their doc at `Behance Developer Documentation`_. .. _Behance App Registration: http://www.behance.net/dev/register .. _Possible Scopes: http://www.behance.net/dev/authentication#scopes .. _Behance Developer Documentation: http://www.behance.net/dev .. _BLOG POST: http://blog.behance.net/dev/introducing-the-behance-api python-social-auth-0.2.13/docs/backends/belgium_eid.rst000066400000000000000000000004041260133235600230250ustar00rootroot00000000000000Belgium EID =========== Belgium EID OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.belgiumeid.BelgiumEIDOpenId', ... ) python-social-auth-0.2.13/docs/backends/bitbucket.rst000066400000000000000000000036451260133235600225460ustar00rootroot00000000000000Bitbucket ========= Bitbucket supports both OAuth2 and OAuth1 logins. 1. Register a new OAuth Consumer by following the instructions in the Bitbucket documentation: `OAuth on Bitbucket`_ Note: For OAuth2, your consumer MUST have the "account" scope otherwise the user profile information (username, name, etc.) won't be accessible. 2. Configure the appropriate settings for OAuth2 or OAuth1 (see below). OAuth2 ------ - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_BITBUCKET_OAUTH2_KEY = '' SOCIAL_AUTH_BITBUCKET_OAUTH2_SECRET = '' - If you would like to restrict access to only users with verified e-mail addresses, set ``SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY = True`` By default the setting is set to ``False`` since it's possible for a project to gather this information by other methods. OAuth1 ------ - OAuth1 works similarly to OAuth2, but you must fill in the following settings instead:: SOCIAL_AUTH_BITBUCKET_KEY = '' SOCIAL_AUTH_BITBUCKET_SECRET = '' - If you would like to restrict access to only users with verified e-mail addresses, set ``SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY = True``. By default the setting is set to ``False`` since it's possible for a project to gather this information by other methods. User ID ------- Bitbucket recommends the use of UUID_ as the user identifier instead of ``username`` since they can change and impose a security risk. For that reason ``UUID`` is used by default, but for backward compatibility reasons, it's possible to get the old behavior again by defining this setting:: SOCIAL_AUTH_BITBUCKET_USERNAME_AS_ID = True .. _UUID: https://confluence.atlassian.com/display/BITBUCKET/Use+the+Bitbucket+REST+APIs .. _OAuth on Bitbucket: https://confluence.atlassian.com/display/BITBUCKET/OAuth+on+Bitbucket python-social-auth-0.2.13/docs/backends/box.rst000066400000000000000000000012361260133235600213540ustar00rootroot00000000000000Box.net ======= Box works similar to Facebook (OAuth2). - Register an application at `Manage Box Applications`_ - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_BOX_KEY = '' SOCIAL_AUTH_BOX_SECRET = '' - By default the token is not permanent, it will last an hour. To refresh the access token just do:: from social.apps.django_app.utils import load_strategy strategy = load_strategy(backend='box') user = User.objects.get(pk=foo) social = user.social_auth.filter(provider='box')[0] social.refresh_token(strategy=strategy) .. _Manage Box Applications: https://app.box.com/developers/services python-social-auth-0.2.13/docs/backends/changetip.rst000066400000000000000000000011321260133235600225210ustar00rootroot00000000000000ChangeTip ========= ChangeTip - Register a new application at ChangeTip_, set the callback URL to ``http://example.com/complete/changetip/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_CHANGETIP_KEY = '' SOCIAL_AUTH_CHANGETIP_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_CHANGETIP_SCOPE = [...] See auth scopes at `ChangeTip OAuth docs`_. .. _ChangeTip: https://www.changetip.com/api .. _ChangeTip OAuth docs: https://www.changetip.com/api/auth/#!#scopes python-social-auth-0.2.13/docs/backends/clef.rst000066400000000000000000000006161260133235600214760ustar00rootroot00000000000000Clef ====== Clef works similar to Facebook (OAuth). - Register a new application at `Clef Developers`_, set the callback URL to ``http://example.com/complete/clef/`` replacing ``example.com`` with your domain. - Fill ``App Id`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_CLEF_KEY = '' SOCIAL_AUTH_CLEF_SECRET = '' .. _Clef Developers: https://getclef.com/developerpython-social-auth-0.2.13/docs/backends/coinbase.rst000066400000000000000000000010241260133235600223420ustar00rootroot00000000000000Coinbase ======== Coinbase uses OAuth2. - Register an application at Coinbase_ - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_COINBASE_KEY = '' SOCIAL_AUTH_COINBASE_SECRET = '' - Set the ``redirect_url`` on coinbase. Make sure to include the trailing slash, eg. ``http://hostname/complete/coinbase/`` - Specify scopes with:: SOCIAL_AUTH_COINBASE_SCOPE = [...] By default the scope is set to ``balance``. .. _Coinbase: https://coinbase.com/oauth/applications python-social-auth-0.2.13/docs/backends/coursera.rst000066400000000000000000000015051260133235600224060ustar00rootroot00000000000000Coursera ============ Coursera uses a variant of OAuth2 authentication. The details of the API can be found at `OAuth2-based APIs - Coursera Technology`_. Take the following steps in order to use the backend: 1. Create an account at `Coursera`_. 2. Open `Developer Console`_, create an organisation and application. 3. Set **Client ID** as a ``SOCIAL_AUTH_COURSERA_KEY`` and **Secret Key** as a ``SOCIAL_AUTH_COURSERA_SECRET`` in your local settings. 4. Add the backend to ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.coursera.CourseraOAuth2', ... ) .. _OAuth2-based APIs - Coursera Technology: https://tech.coursera.org/app-platform/oauth2/ .. _Coursera: https://accounts.coursera.org/console .. _Developer Console: https://accounts.coursera.org/console python-social-auth-0.2.13/docs/backends/dailymotion.rst000066400000000000000000000013621260133235600231140ustar00rootroot00000000000000DailyMotion =========== DailyMotion uses OAuth2. In order to enable the backend follow: - Register an application at `DailyMotion Developer Portal`_ - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_DAILYMOTION_KEY = '' SOCIAL_AUTH_DAILYMOTION_SECRET = '' - Set the ``Callback URL`` to ``http:///complete/dailymotion/`` - Specify scopes with:: SOCIAL_AUTH_DAILYMOTION_SCOPE = [...] Available scopes are listed in the `Requesting Extended Permissions`_ section. .. _DailyMotion Developer Portal: http://www.dailymotion.com/profile/developer/new .. _Requesting Extended Permissions: http://www.dailymotion.com/doc/api/authentication.html#requesting-extended-permissions python-social-auth-0.2.13/docs/backends/digitalocean.rst000066400000000000000000000017141260133235600232100ustar00rootroot00000000000000DigitalOcean ============ DigitalOcean uses OAuth2 for its auth process. See the full `DigitalOcean developer's documentation`_ for more information. - Register a new application in the `Apps & API page`_ in the DigitalOcean control panel, setting the callback URL to ``http://example.com/complete/digitalocean/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings:: SOCIAL_AUTH_DIGITALOCEAN_KEY = '' SOCIAL_AUTH_DIGITALOCEAN_SECRET = '' - By default, only ``read`` permissions are granted. In order to create, destroy, and take other actions on the user's resources, you must request ``read write`` permissions like so:: SOCIAL_AUTH_DIGITALOCEAN_AUTH_EXTRA_ARGUMENTS = {'scope': 'read write'} .. _DigitalOcean developer's documentation: https://developers.digitalocean.com/documentation/ .. _Apps & API page: https://cloud.digitalocean.com/settings/applications python-social-auth-0.2.13/docs/backends/disqus.rst000066400000000000000000000010241260133235600220670ustar00rootroot00000000000000Disqus ====== Disqus uses OAuth v2 for Authentication. - Register a new application at the `Disqus API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_DISQUS_KEY = '' SOCIAL_AUTH_DISQUS_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_DISQUS_AUTH_EXTRA_ARGUMENTS = {'scope': 'likes comments relationships'} Check `Disqus Auth API`_ for details. .. _Disqus Auth API: http://disqus.com/api/docs/auth/ .. _Disqus API: http://disqus.com/api/applications/ python-social-auth-0.2.13/docs/backends/docker.rst000066400000000000000000000011241260133235600220270ustar00rootroot00000000000000Docker ====== Docker.io OAuth2 ---------------- Docker.io now supports OAuth2 for their API. In order to set it up: - Register a new application by following the instructions in their website: `Register Your Application`_ - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOCKER_KEY = '' SOCIAL_AUTH_DOCKER_SECRET = '' - Add ``'social.backends.docker.DockerOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _Register Your Application: http://docs.docker.io/en/latest/reference/api/docker_io_oauth_api/#register-your-application python-social-auth-0.2.13/docs/backends/douban.rst000066400000000000000000000026521260133235600220370ustar00rootroot00000000000000Douban ====== Douban supports OAuth 1 and 2. Douban OAuth1 ------------- Douban OAuth 1 works similar to Twitter OAuth. Douban offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Douban OAuth these two keys are needed. Further documentation at `Douban Services & API`_: - Register a new application at `Douban API Key`_, make sure to mark the **web application** checkbox. - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOUBAN_KEY = '' SOCIAL_AUTH_DOUBAN_SECRET = '' - Add ``'social.backends.douban.DoubanOAuth'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. Douban OAuth2 ------------- Recently Douban launched their OAuth2 support and the new developer site, you can find documentation at `Douban Developers`_. To setup OAuth2 follow: - Register a new application at `Create A Douban App`_, make sure to mark the **web application** checkbox. - Fill **Consumer Key** and **Consumer Secret** values in settings:: SOCIAL_AUTH_DOUBAN_OAUTH2_KEY = '' SOCIAL_AUTH_DOUBAN_OAUTH2_SECRET = '' - Add ``'social.backends.douban.DoubanOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _Douban Services & API: http://www.douban.com/service/ .. _Douban API Key: http://www.douban.com/service/apikey/apply .. _Douban Developers: http://developers.douban.com/ .. _Create A Douban App : http://developers.douban.com/apikey/apply python-social-auth-0.2.13/docs/backends/dribbble.rst000066400000000000000000000011431260133235600223260ustar00rootroot00000000000000Dribbble ======== Dribbble - Register a new application at Dribbble_, set the callback URL to ``http://example.com/complete/dribbble/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_DRIBBBLE_KEY = '' SOCIAL_AUTH_DRIBBBLE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_DRIBBBLE_SCOPE = [...] See auth scopes at `Dribbble Developer docs`_. .. _Dribbble: https://dribbble.com/account/applications/new .. _Dribbble Developer docs: http://developer.dribbble.com/v1/oauth/ python-social-auth-0.2.13/docs/backends/dropbox.rst000066400000000000000000000016661260133235600222500ustar00rootroot00000000000000Dropbox ======= Dropbox supports both OAuth 1 and 2. - Register a new application at `Dropbox Developers`_, and follow the instructions below for the version of OAuth for which you are adding support. OAuth1 ------ Add the Dropbox OAuth backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.dropbox.DropboxOAuth', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_DROPBOX_KEY = '' SOCIAL_AUTH_DROPBOX_SECRET = '' OAuth2 ------ Add the Dropbox OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.dropbox.DropboxOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_DROPBOX_OAUTH2_KEY = '' SOCIAL_AUTH_DROPBOX_OAUTH2_SECRET = '' .. _Dropbox Developers: https://www.dropbox.com/developers/apps python-social-auth-0.2.13/docs/backends/email.rst000066400000000000000000000043361260133235600216570ustar00rootroot00000000000000Email Auth ========== python-social-auth_ comes with an EmailAuth_ backend which comes handy when your site uses requires the plain old email and password authentication mechanism. Actually that's a lie since the backend doesn't handle password at all, that's up to the developer to validate the password in and the proper place to do it is the pipeline, right after the user instance was retrieved or created. The reason to leave password handling to the developer is because too many things are really tied to the project, like the field where the password is stored, salt handling, password hashing algorithm and validation. So just add the pipeline functions that will do that following the needs of your project. Backend settings ---------------- ``SOCIAL_AUTH_EMAIL_FORM_URL = '/login-form/'`` Used to redirect the user to the login/signup form, it must have at least one field named ``email``. Form submit should go to ``/complete/email``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. ``SOCIAL_AUTH_EMAIL_FORM_HTML = 'login_form.html'`` The template will be used to render the login/signup form to the user, it must have at least one field named ``email``. Form submit should go to ``/complete/email``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. Email validation ---------------- Check *Email validation* pipeline in the `pipeline docs`_. Password handling ----------------- Here's an example of password handling to add to the pipeline:: def user_password(strategy, user, is_new=False, *args, **kwargs): if strategy.backend.name != 'email': return password = strategy.request_data()['password'] if is_new: user.set_password(password) user.save() elif not user.validate_password(password): # return {'user': None, 'social': None} raise AuthException(strategy.backend) .. _python-social-auth: https://github.com/omab/python-social-auth .. _EmailAuth: https://github.com/omab/python-social-auth/blob/master/social/backends/email.py#L5 .. _pipeline docs: ../pipeline.html#email-validation python-social-auth-0.2.13/docs/backends/eveonline.rst000066400000000000000000000013501260133235600225450ustar00rootroot00000000000000EVE Online Single Sign-On (SSO) =============================== The EVE Single Sign-On (SSO) works similar to GitHub (OAuth2). - Register a new application at `EVE Developers`_, set the callback URL to ``http://example.com/complete/eveonline/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Secret Key`` values from EVE Developers in the settings:: SOCIAL_AUTH_EVEONLINE_KEY = '' SOCIAL_AUTH_EVEONLINE_SECRET = '' - If you want to use EVE Character names as user names, use this setting:: SOCIAL_AUTH_CLEAN_USERNAMES = False - If you want to access EVE Online's CREST API, use:: SOCIAL_AUTH_EVEONLINE_SCOPE = ['publicData'] .. _EVE Developers: https://developers.eveonline.com/ python-social-auth-0.2.13/docs/backends/evernote.rst000066400000000000000000000012211260133235600224050ustar00rootroot00000000000000Evernote OAuth ============== Evernote OAuth 1.0 for its authentication workflow. - Register a new application at `Evernote API Key form`_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_EVERNOTE_KEY = '' SOCIAL_AUTH_EVERNOTE_SECRET = '' Sandbox ------- Evernote supports a sandbox mode for testing, there's a custom backend for it which name is ``evernote-sandbox`` instead of ``evernote``. Same settings apply but use these instead:: SOCIAL_AUTH_EVERNOTE_SANDBOX_KEY = '' SOCIAL_AUTH_EVERNOTE_SANDBOX_SECRET = '' .. _Evernote API Key form: http://dev.evernote.com/support/api_key.php python-social-auth-0.2.13/docs/backends/facebook.rst000066400000000000000000000065561260133235600223470ustar00rootroot00000000000000Facebook ======== OAuth2 ------ Facebook uses OAuth2 for its auth process. Further documentation at `Facebook development resources`_: - Register a new application at `Facebook App Creation`_, don't use ``localhost`` as ``App Domains`` and ``Site URL`` since Facebook won't allow them. Use a placeholder like ``myapp.com`` and define that domain in your ``/etc/hosts`` or similar file. - fill ``App Id`` and ``App Secret`` values in values:: SOCIAL_AUTH_FACEBOOK_KEY = '' SOCIAL_AUTH_FACEBOOK_SECRET = '' - Define ``SOCIAL_AUTH_FACEBOOK_SCOPE`` to get extra permissions from facebook. Email is not sent by deafault, to get it, you must request the ``email`` permission:: SOCIAL_AUTH_FACEBOOK_SCOPE = ['email'] - Define ``SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS`` to pass extra parameters to https://graph.facebook.com/me when gathering the user profile data (you need to explicitly ask for fields like ``email`` using ``fields`` key):: SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'locale': 'ru_RU', 'fields': 'id, name, email, age_range' } If you define a redirect URL in Facebook setup page, be sure to not define http://127.0.0.1:8000 or http://localhost:8000 because it won't work when testing. Instead I define http://myapp.com and setup a mapping on ``/etc/hosts``. Canvas Application ------------------ If you need to perform authentication from Facebook Canvas application: - Create your canvas application at http://developers.facebook.com/apps - In Facebook application settings specify your canvas URL ``mysite.com/fb`` (current default) - Setup your Python Social Auth settings and your application namespace:: SOCIAL_AUTH_FACEBOOK_APP_KEY = '' SOCIAL_AUTH_FACEBOOK_APP_SECRET = '' SOCIAL_AUTH_FACEBOOK_APP_NAMESPACE = '' - Launch your testing server on port 80 (use sudo or nginx or apache) for browser to be able to load it when Facebook calls canvas URL - Open your Facebook page via http://apps.facebook.com/app_namespace or better via http://www.facebook.com/pages/user-name/user-id?sk=app_app-id - After that you will see this page in a right way and will able to connect to application and login automatically after connection - Provide a template to be rendered, it must have this JavaScript snippet (or similar) in it:: More info on the topic at `Facebook Canvas Application Authentication`_. Graph 2.0 --------- If looking for `Graph 2.0`_ support, use the backends ``Facebook2OAuth2`` (OAuth2) and/or ``Facebook2AppOAuth2`` (Canvas application). .. _Facebook development resources: http://developers.facebook.com/docs/authentication/ .. _Facebook App Creation: http://developers.facebook.com/setup/ .. _Facebook Canvas Application Authentication: http://www.ikrvss.ru/2011/09/22/django-social-auth-and-facebook-canvas-applications/ .. _Graph 2.0: https://developers.facebook.com/blog/post/2014/04/30/the-new-facebook-login/ python-social-auth-0.2.13/docs/backends/fedora.rst000066400000000000000000000003551260133235600220250ustar00rootroot00000000000000Fedora ====== Fedora OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.fedora.FedoraOpenId', ... ) python-social-auth-0.2.13/docs/backends/fitbit.rst000066400000000000000000000007221260133235600220440ustar00rootroot00000000000000Fitbit ====== Fitbit offers OAuth1 as their auth mechanism. In order to enable it, follow: - Register a new application at `Fitbit dev portal`_, be sure to select ``Browser`` as the application type. Set the ``Callback URL`` to ``http:////complete/fitbit/``. - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_FITBIT_KEY = '' SOCIAL_AUTH_FITBIT_SECRET = '' .. _Fitbit dev portal: https://dev.fitbit.com/apps/new python-social-auth-0.2.13/docs/backends/flickr.rst000066400000000000000000000010311260133235600220270ustar00rootroot00000000000000Flickr ====== Flickr uses OAuth v1.0 for authentication. - Register a new application at the `Flickr App Garden`_, and - fill ``Key`` and ``Secret`` values in the settings:: SOCIAL_AUTH_FLICKR_KEY = '' SOCIAL_AUTH_FLICKR_SECRET = '' - Flickr might show a messages saying "Oops! Flickr doesn't recognise the permission set.", if encountered with this error, just define this setting:: SOCIAL_AUTH_FLICKR_AUTH_EXTRA_ARGUMENTS = {'perms': 'read'} .. _Flickr App Garden: http://www.flickr.com/services/apps/create/ python-social-auth-0.2.13/docs/backends/foursquare.rst000066400000000000000000000007161260133235600227620ustar00rootroot00000000000000Foursquare ========== Foursquare uses OAuth2. In order to enable the backend follow: - Register an application at `Foursquare Developers Portal`_, set the ``Redirect URI`` to ``http:///complete/foursquare/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_FOURSQUARE_KEY = '' SOCIAL_AUTH_FOURSQUARE_SECRET = '' .. _Foursquare Developers Portal: https://foursquare.com/developers/register python-social-auth-0.2.13/docs/backends/github.rst000066400000000000000000000031621260133235600220460ustar00rootroot00000000000000GitHub ====== GitHub works similar to Facebook (OAuth). - Register a new application at `GitHub Developers`_, set the callback URL to ``http://example.com/complete/github/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings:: SOCIAL_AUTH_GITHUB_KEY = '' SOCIAL_AUTH_GITHUB_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_GITHUB_SCOPE = [...] GitHub for Organizations ------------------------ When defining authentication for organizations, use the ``GithubOrganizationOAuth2`` backend instead. The settings are the same as the non-organization backend, but the names must be:: SOCIAL_AUTH_GITHUB_ORG_* Be sure to define the organization name using the setting:: SOCIAL_AUTH_GITHUB_ORG_NAME = '' This name will be used to check that the user really belongs to the given organization and discard it if they're not part of it. GitHub for Teams ---------------- Similar to ``GitHub for Organizations``, there's a GitHub for Teams backend, use the backend ``GithubTeamOAuth2``. The settings are the same as the basic backend, but the names must be:: SOCIAL_AUTH_GITHUB_TEAM_* Be sure to define the ``Team ID`` using the setting:: SOCIAL_AUTH_GITHUB_TEAM_ID = '' This ``id`` will be used to check that the user really belongs to the given team and discard it if they're not part of it. Github for Enterprises ---------------------- Check the docs :ref:`github-enterprise` if planning to use Github Enterprises. .. _GitHub Developers: https://github.com/settings/applications/new python-social-auth-0.2.13/docs/backends/github_enterprise.rst000066400000000000000000000034501260133235600243060ustar00rootroot00000000000000.. _github-enterprise: GitHub Enterprise ================= GitHub Enterprise works similar to regular Github, which is in turn based on Facebook (OAuth). - Register a new application on your instance of `GitHub Enterprise Developers`_, set the callback URL to ``http://example.com/complete/github/`` replacing ``example.com`` with your domain. - Fill the ``Client ID`` and ``Client Secret`` values from GitHub in the settings:: SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY = '' SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_GITHUB_ENTERPRISE_SCOPE = [...] GitHub Enterprise for Organizations ----------------------------------- When defining authentication for organizations, use the ``GithubEnterpriseOrganizationOAuth2`` backend instead. The settings are the same as the non-organization backend, but the names must be:: SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_* Be sure to define the organization name using the setting:: SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME = '' This name will be used to check that the user really belongs to the given organization and discard it if they're not part of it. GitHub Enterprise for Teams --------------------------- Similar to ``GitHub Enterprise for Organizations``, there's a GitHub for Teams backend, use the backend ``GithubEnterpriseTeamOAuth2``. The settings are the same as the basic backend, but the names must be:: SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_* Be sure to define the ``Team ID`` using the setting:: SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID = '' This ``id`` will be used to check that the user really belongs to the given team and discard it if they're not part of it. .. _GitHub Enterprise Developers: https:///settings/applications/new python-social-auth-0.2.13/docs/backends/google.rst000066400000000000000000000213731260133235600220440ustar00rootroot00000000000000Google ====== This section describes how to setup the different services provided by Google. Google OAuth ------------ .. attention:: **Google OAuth deprecation** Important: OAuth 1.0 was officially deprecated on April 20, 2012, and will be shut down on April 20, 2015. We encourage you to migrate to any of the other protocols. Google provides ``Consumer Key`` and ``Consumer Secret`` keys to registered applications, but also allows unregistered application to use their authorization system with, but beware that this method will display a security banner to the user telling that the application is not trusted. Check `Google OAuth`_ and make your choice. - fill ``Consumer Key`` and ``Consumer Secret`` values:: SOCIAL_AUTH_GOOGLE_OAUTH_KEY = '' SOCIAL_AUTH_GOOGLE_OAUTH_SECRET = '' anonymous values will be used if not configured as described in their `OAuth reference`_ - setup any needed extra scope in:: SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [...] Google OAuth2 ------------- Recently Google launched OAuth2 support following the definition at `OAuth2 draft`. It works in a similar way to plain OAuth mechanism, but developers **must** register an application and apply for a set of keys. Check `Google OAuth2`_ document for details. When creating the application in the Google Console be sure to fill the ``PRODUCT NAME`` at ``API & auth -> Consent screen`` form. To enable OAuth2 support: - fill ``Client ID`` and ``Client Secret`` settings, these values can be obtained easily as described on `OAuth2 Registering`_ doc:: SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '' - setup any needed extra scope:: SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [...] Check which applications can be included in their `Google Data Protocol Directory`_ Google+ Sign-In --------------- `Google+ Sign In`_ works a lot like OAuth2, but most of the initial work is done by their Javascript which thens calls a defined handler to complete the auth process. * To enable the backend create an application using the `Google console`_ and following the steps from the `official guide`_. Make sure to enable the Google+ API in the console. * Fill in the key settings looking inside the Google console the subsection ``Credentials`` inside ``API & auth``:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.google.GooglePlusAuth', ) SOCIAL_AUTH_GOOGLE_PLUS_KEY = '...' SOCIAL_AUTH_GOOGLE_PLUS_SECRET = '...' ``SOCIAL_AUTH_GOOGLE_PLUS_KEY`` corresponds to the variable ``CLIENT ID``. ``SOCIAL_AUTH_GOOGLE_PLUS_SECRET`` corresponds to the variable ``CLIENT SECRET``. * Add the sign-in button to your template, you can use the SDK button or add your own and attacht he click handler to it (check `Google+ Identity Sign-In`_ documentation about it)::
Google+ Sign In
* Add the Javascript snippet in the same template as above:: * Logging out Logging-out can be tricky when using the the platform SDK because it can trigger an automatic sign-in when listening to the user status change. With the method show above, that won't happen, but if the UI depends more in the SDK values than the backend, then things can get out of sync easilly. To prevent this, the user should be logged-out from Google+ platform too. This can be accomplished by doing:: Google OpenId ------------- Google OpenId works straightforward, not settings are needed. Domains or emails whitelists can be applied too, check the whitelists_ settings for details. Orkut ----- As of September 30, 2014, Orkut has been `shut down`_. User identification ------------------- Optional support for static and unique Google Profile ID identifiers instead of using the e-mail address for account association can be enabled with:: SOCIAL_AUTH_GOOGLE_OAUTH_USE_UNIQUE_USER_ID = True or:: SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID = True depending on the backends in use. Refresh Tokens -------------- To get an OAuth2 refresh token along with the access token, you must pass an extra argument: ``access_type=offline``. To do this with Google+ sign-in:: SOCIAL_AUTH_GOOGLE_PLUS_AUTH_EXTRA_ARGUMENTS = { 'access_type': 'offline' } Scopes deprecation ------------------ Google is deprecating the full-url scopes from `Sept 1, 2014`_ in favor of ``Google+ API`` and the recently introduced shorter scopes names. But ``python-social-auth`` already introduced the scopes change at e3525187_ which was released at ``v0.1.24``. But, to enable the new scopes the application requires ``Google+ API`` to be enabled in the `Google console`_ dashboard, the change is quick and quite simple, but if any developer desires to keep using the old scopes, it's possible with the following settings:: # Google OAuth2 (google-oauth2) SOCIAL_AUTH_GOOGLE_OAUTH2_IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] # Google+ SignIn (google-plus) SOCIAL_AUTH_GOOGLE_PLUS_IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH_GOOGLE_PLUS_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] To ease the change, the old API and scopes is still supported by the application, the new values are the default option but if having troubles supporting them you can default to the old values by defining this setting:: SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API = True SOCIAL_AUTH_GOOGLE_PLUS_USE_DEPRECATED_API = True .. _Google support: http://www.google.com/support/a/bin/answer.py?hl=en&answer=162105 .. _Google OpenID: http://code.google.com/apis/accounts/docs/OpenID.html .. _Google OAuth: http://code.google.com/apis/accounts/docs/OAuth.html .. _Google OAuth2: http://code.google.com/apis/accounts/docs/OAuth2.html .. _OAuth2 Registering: http://code.google.com/apis/accounts/docs/OAuth2.html#Registering .. _OAuth2 draft: http://tools.ietf.org/html/draft-ietf-oauth-v2-10 .. _OAuth reference: http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth .. _shut down: https://support.google.com/orkut/?csw=1#Authenticating .. _Google Data Protocol Directory: http://code.google.com/apis/gdata/docs/directory.html .. _whitelists: ../configuration/settings.html#whitelists .. _Google+ Sign In: https://developers.google.com/+/web/signin/ .. _Google console: https://code.google.com/apis/console .. _official guide: https://developers.google.com/+/web/signin/#step_1_create_a_client_id_and_client_secret .. _Sept 1, 2014: https://developers.google.com/+/api/auth-migration#timetable .. _e3525187: https://github.com/omab/python-social-auth/commit/e35251878a88954cecf8e575eca27c63164b9f67 .. _Google+ Identity Sign-In: https://developers.google.com/identity/sign-in/web/sign-in python-social-auth-0.2.13/docs/backends/implementation.rst000066400000000000000000000267111260133235600236160ustar00rootroot00000000000000Adding a new backend ==================== Add new backends is quite easy, usually adding just a ``class`` with a couple settings and methods overrides to retrieve user data from services API. Follow the details below. Common attributes ----------------- First, lets check the common attributes for all backend types. ``name = ''`` Any backend needs a name, usually the popular name of the service is used, like ``facebook``, ``twitter``, etc. It must be unique, otherwise another backend can take precedence if it's listed before in ``AUTHENTICATION_BACKENDS`` setting. ``ID_KEY = None`` Defines the attribute in the service response that identifies the user as unique in the service, the value is later stored in the ``uid`` attribute in the ``UserSocialAuth`` instance. ``REQUIRES_EMAIL_VALIDATION = False`` Flags the backend to enforce email validation during the pipeline (if the corresponding pipeline ``social.pipeline.mail.mail_validation`` was enabled). ``EXTRA_DATA = None`` During the auth process some basic user data is returned by the provider or retrieved by ``user_data()`` method which usually is used to call some API on the provider to retrieve it. This data will be stored under ``UserSocialAuth.extra_data`` attribute, but to make it accessible under some common names on different providers, this attribute defines a list of tuples in the form ``(name, alias)`` where ``name`` is the key in the user data (which should be a ``dict`` instance) and ``alias`` is the name to store it on ``extra_data``. OAuth ----- OAuth1 and OAuth2 provide share some common definitions based on the shared behavior during the auth process, like a successful API response from ``AUTHORIZATION_URL`` usually returns some basic user data like a user Id. Shared attributes ***************** ``name`` This defines the backend name and identifies it during the auth process. The name is used in the URLs ``/login/`` and ``/complete/``. ``ID_KEY = 'id'`` Default key name where user identification field is defined, it's used on auth process when some basic user data is returned. This Id is stored in ``UserSocialAuth.uid`` field, this together the ``UserSocialAuth.provider`` field is used to unique identify a user association. ``SCOPE_PARAMETER_NAME = 'scope'`` Scope argument is used to tell the provider the API endpoints you want to call later, it's a permissions request granted over the ``access_token`` later retrieved. Default value is ``scope`` since that's usually the name used in the URL parameter, but can be overridden if needed. ``DEFAULT_SCOPE = None`` Some providers give nothing about the user but some basic data in required like the user Id or an email address. Default scope attribute is used to specify a default value for ``scope`` argument to request those extra used bits. ``SCOPE_SEPARATOR = ' '`` The ``scope`` argument is usually a list of permissions to request, the list is joined used a separator, usually just a blank space, but differ from provider to provider, override the default value with this attribute if it differs. OAuth2 ****** OAuth2 backends are fairly simple to implement; just a few settings, a method override and it's mostly ready to go. The key points on this backends are: ``AUTHORIZATION_URL`` This is the entry point for the authorization mechanism, users must be redirected to this URL, used on ``auth_url`` method which builds the redirect address with ``AUTHORIZATION_URL`` plus some arguments (``client_id``, ``redirect_uri``, ``response_type``, and ``state``). ``ACCESS_TOKEN_URL`` Must point to the API endpoint that provides an ``access_token`` needed to authenticate in users behalf on future API calls. ``REFRESH_TOKEN_URL`` Some providers give the option to renew the ``access_token`` since they are usually limited in time, once that time runs out, the token is invalidated and cannot be used any more. This attribute should point to that API endpoint. ``RESPONSE_TYPE`` The response type expected on the auth process, default value is ``code`` as dictated by OAuth2 definition. Override it if default value doesn't fit the provider implementation. ``STATE_PARAMETER`` OAuth2 defines that an ``state`` parameter can be passed in order to validate the process, it's kind of a CSRF check to avoid man in the middle attacks. Some don't recognise it or don't return it which will make the auth process invalid. Set this attribute to ``False`` in that case. ``REDIRECT_STATE`` For those providers that don't recognise the ``state`` parameter, the app can add a ``redirect_state`` argument to the ``redirect_uri`` to mimic it. Set this value to ``False`` if the provider likes to verify the ``redirect_uri`` value and this parameter invalidates that check. Example code:: from social.backends.oauth import BaseOAuth2 class GithubOAuth2(BaseOAuth2): """Github OAuth authentication backend""" name = 'github' AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize' ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Github account""" return {'username': response.get('login'), 'email': response.get('email') or '', 'first_name': response.get('name')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://api.github.com/user?' + urlencode({ 'access_token': access_token }) try: return json.load(self.urlopen(url)) except ValueError: return None OAuth1 ****** OAuth1 process is a bit more trickier, `Twitter Docs`_ explains it quite well. Beside the ``AUTHORIZATION_URL`` and ``ACCESS_TOKEN_URL`` attributes, a third one is needed used when starting the process. ``REQUEST_TOKEN_URL = ''`` During the auth process an unauthorized token is needed to start the process, later this token is exchanged for an ``access_token``. This setting points to the API endpoint where that unauthorized token can be retrieved. Example code:: from xml.dom import minidom from social.backends.oauth import ConsumerBasedOAuth class TripItOAuth(ConsumerBasedOAuth): """TripIt OAuth authentication backend""" name = 'tripit' AUTHORIZATION_URL = 'https://www.tripit.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.tripit.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.tripit.com/oauth/access_token' EXTRA_DATA = [('screen_name', 'screen_name')] def get_user_details(self, response): """Return user details from TripIt account""" try: first_name, last_name = response['name'].split(' ', 1) except ValueError: first_name = response['name'] last_name = '' return {'username': response['screen_name'], 'email': response['email'], 'fullname': response['name'], 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" url = 'https://api.tripit.com/v1/get/profile' request = self.oauth_request(access_token, url) content = self.fetch_response(request) try: dom = minidom.parseString(content) except ValueError: return None return { 'id': dom.getElementsByTagName('Profile')[0].getAttribute('ref'), 'name': dom.getElementsByTagName( 'public_display_name')[0].childNodes[0].data, 'screen_name': dom.getElementsByTagName( 'screen_name')[0].childNodes[0].data, 'email': dom.getElementsByTagName( 'is_primary')[0].parentNode.getElementsByTagName( 'address')[0].childNodes[0].data, } OpenId ------ OpenId is fair simpler that OAuth since it's used for authentication rather than authorization (regardless it's used for authorization too). A single attribute is usually needed, the authentication URL endpoint. ``URL = ''`` OpenId endpoint where to redirect the user. Sometimes the URL is user dependant, like in myOpenId_ where the URL is ``https://.myopenid.com``. For those cases where the user must input it's handle (or full URL). The backend must override the ``openid_url()`` method to retrieve it and return a full URL to where the user will be redirected. Example code:: from social.backends.open_id import OpenIdAuth from social.exceptions import AuthMissingParameter class LiveJournalOpenId(OpenIdAuth): """LiveJournal OpenID authentication backend""" name = 'livejournal' def get_user_details(self, response): """Generate username from identity url""" values = super(LiveJournalOpenId, self).get_user_details(response) values['username'] = values.get('username') or \ urlparse.urlsplit(response.identity_url)\ .netloc.split('.', 1)[0] return values def openid_url(self): """Returns LiveJournal authentication URL""" if not self.data.get('openid_lj_user'): raise AuthMissingParameter(self, 'openid_lj_user') return 'http://%s.livejournal.com' % self.data['openid_lj_user'] Auth APIs --------- For others authentication types, a ``BaseAuth`` class is defined to help. Those custom auth methods must override the ``auth_url()`` and ``auth_complete()`` methods. Example code:: from google.appengine.api import users from social.backends.base import BaseAuth from social.exceptions import AuthException class GoogleAppEngineAuth(BaseAuth): """GoogleAppengine authentication backend""" name = 'google-appengine' def get_user_id(self, details, response): """Return current user id.""" user = users.get_current_user() if user: return user.user_id() def get_user_details(self, response): """Return user basic information (id and email only).""" user = users.get_current_user() return {'username': user.user_id(), 'email': user.email(), 'fullname': '', 'first_name': '', 'last_name': ''} def auth_url(self): """Build and return complete URL.""" return users.create_login_url(self.redirect_uri) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance.""" if not users.get_current_user(): raise AuthException('Authentication error') kwargs.update({'response': '', 'backend': self}) return self.strategy.authenticate(*args, **kwargs) .. _Twitter Docs: https://dev.twitter.com/docs/auth/implementing-sign-twitter .. _myOpenId: https://www.myopenid.com/ python-social-auth-0.2.13/docs/backends/index.rst000066400000000000000000000034521260133235600216750ustar00rootroot00000000000000Backends ======== Here's a list and detailed instruction on how to setup the support for each backend. Adding new backend support -------------------------- Add new backends is quite easy, usually adding just a ``class`` with a couple methods overrides to retrieve user data from services API. Follow the details in the *Implementation* docs. .. toctree:: :maxdepth: 2 implementation Supported backends ------------------ Here's the list of currently supported backends. Non-social backends ******************* .. toctree:: :maxdepth: 2 email username Base OAuth and OpenId classes ***************************** .. toctree:: :maxdepth: 2 oauth openid saml Social backends *************** .. toctree:: :maxdepth: 2 amazon angel aol appsfuel azuread battlenet beats behance belgium_eid bitbucket box changetip clef coinbase coursera dailymotion digitalocean disqus docker douban dribbble dropbox eveonline evernote facebook fedora fitbit flickr foursquare github github_enterprise google instagram jawbone kakao khanacademy lastfm launchpad linkedin livejournal live loginradius mailru mapmyfitness meetup mendeley mineid mixcloud moves naszaklasa nationbuilder odnoklassnikiru openstreetmap orbi persona pixelpin pocket podio qiita qq rdio readability reddit runkeeper salesforce shopify skyrock slack soundcloud spotify suse stackoverflow steam stocktwits strava stripe taobao thisismyjam trello tripit tumblr twilio twitch twitter uber vend vimeo vk weibo withings wunderlist xing yahoo yammer zotero python-social-auth-0.2.13/docs/backends/instagram.rst000066400000000000000000000007061260133235600225520ustar00rootroot00000000000000Instagram ========= Instagram uses OAuth v2 for Authentication. - Register a new application at the `Instagram API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_INSTAGRAM_KEY = '' SOCIAL_AUTH_INSTAGRAM_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_INSTAGRAM_AUTH_EXTRA_ARGUMENTS = {'scope': 'likes comments relationships'} .. _Instagram API: http://instagr.am/developer/ python-social-auth-0.2.13/docs/backends/jawbone.rst000066400000000000000000000012631260133235600222110ustar00rootroot00000000000000Jawbone ======= Jawbone uses OAuth2. In order to enable the backend follow: - Register an application at `Jawbone Developer Portal`_, set the ``OAuth redirect URIs`` to ``http:///complete/jawbone/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_JAWBONE_KEY = '' SOCIAL_AUTH_JAWBONE_SECRET = '' - Specify scopes with:: SOCIAL_AUTH_JAWBONE_SCOPE = [...] Available scopes are listed in the `Jawbone Authentication Reference`_, "socpes" section. .. _Jawbone Developer Portal: https://jawbone.com/up/developer/account/ .. _Jawbone Authentication Reference: https://jawbone.com/up/developer/authentication python-social-auth-0.2.13/docs/backends/kakao.rst000066400000000000000000000006211260133235600216470ustar00rootroot00000000000000Kakao ====== Kakao uses OAuth v2 for Authentication. - Register a new applicationat the `Kakao API`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_KAKAO_KEY = '' SOCIAL_AUTH_KAKAO_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_KAKAO_SCOPE = [...] .. _Kakao API: https://developers.kakao.com/docs/restapi python-social-auth-0.2.13/docs/backends/khanacademy.rst000066400000000000000000000013741260133235600230340ustar00rootroot00000000000000Khan Academy ============ Khan Academy uses a variant of OAuth1 authentication flow. Check the API details at `Khan Academy API Authentication`_. Follow this steps in order to use the backend: - Register a new application at `Khan Academy API Apps`_, - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_KHANACADEMY_OAUTH1_KEY = '' SOCIAL_AUTH_KHANACADEMY_OAUTH1_SECRET = '' - Add the backend to ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.khanacademy.KhanAcademyOAuth1', ... ) .. _Khan Academy API Authentication: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication .. _Khan Academy API Apps: http://www.khanacademy.org/api-apps/register python-social-auth-0.2.13/docs/backends/lastfm.rst000066400000000000000000000010361260133235600220500ustar00rootroot00000000000000Last.fm ======= Last.fm uses a similar authentication process than OAuth2 but it's not. In order to enable the support for it just: - Register an application at `Get an API Account`_, set the Last.fm callback to something sensible like http://your.site/complete/lastfm - Fill in the **API Key** and **API Secret** values in your settings:: SOCIAL_AUTH_LASTFM_KEY = '' SOCIAL_AUTH_LASTFM_SECRET = '' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting. .. _Get an API Account: http://www.last.fm/api/account/create python-social-auth-0.2.13/docs/backends/launchpad.rst000066400000000000000000000004371260133235600225250ustar00rootroot00000000000000Launchpad ========= `Ubuntu Launchpad `_ OpenId doesn't require major settings beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.launchpad.LaunchpadOpenId', ... ) python-social-auth-0.2.13/docs/backends/linkedin.rst000066400000000000000000000053721260133235600223660ustar00rootroot00000000000000LinkedIn ======== LinkedIn supports OAuth1 and OAuth2. Migration between each type is fair simple since the same Key / Secret pair is used for both authentication types. LinkedIn OAuth setup is similar to any other OAuth service. The auth flow is explained on `LinkedIn Developers`_ docs. First you will need to register an app att `LinkedIn Developer Network`_. OAuth1 ------ - Fill the application key and secret in your settings:: SOCIAL_AUTH_LINKEDIN_KEY = '' SOCIAL_AUTH_LINKEDIN_SECRET = '' - Application scopes can be specified by:: SOCIAL_AUTH_LINKEDIN_SCOPE = [...] Check the available options at `LinkedIn Scopes`_. If you want to request a user's email address, you'll need specify that your application needs access to the email address use the ``r_emailaddress`` scope. - To request extra fields using `LinkedIn fields selectors`_ just define this setting:: SOCIAL_AUTH_LINKEDIN_FIELD_SELECTORS = [...] with the needed fields selectors, also define ``SOCIAL_AUTH_LINKEDIN_EXTRA_DATA`` properly as described in `OAuth `_, that way the values will be stored in ``UserSocialAuth.extra_data`` field. By default ``id``, ``first-name`` and ``last-name`` are requested and stored. For example, to request a user's email, headline, and industry from the Linkedin API and store the information in ``UserSocialAuth.extra_data``, you would add these settings:: # Add email to requested authorizations. SOCIAL_AUTH_LINKEDIN_SCOPE = ['r_basicprofile', 'r_emailaddress', ...] # Add the fields so they will be requested from linkedin. SOCIAL_AUTH_LINKEDIN_FIELD_SELECTORS = ['email-address', 'headline', 'industry'] # Arrange to add the fields to UserSocialAuth.extra_data SOCIAL_AUTH_LINKEDIN_EXTRA_DATA = [('id', 'id'), ('firstName', 'first_name'), ('lastName', 'last_name'), ('emailAddress', 'email_address'), ('headline', 'headline'), ('industry', 'industry')] OAuth2 ------ OAuth2 works exacly the same than OAuth1, but the settings must be named as:: SOCIAL_AUTH_LINKEDIN_OAUTH2_* Looks like LinkedIn is forcing the definition of the callback URL in the application when OAuth2 is used. Be sure to set the proper values, otherwise a ``(400) Client Error: Bad Request`` might be returned by their service. .. _LinkedIn fields selectors: http://developer.linkedin.com/docs/DOC-1014 .. _LinkedIn Scopes: https://developer.linkedin.com/documents/authentication#granting .. _LinkedIn Developer Network: https://www.linkedin.com/secure/developer .. _LinkedIn Developers: http://developer.linkedin.com/documents/authentication python-social-auth-0.2.13/docs/backends/live.rst000066400000000000000000000014421260133235600215220ustar00rootroot00000000000000MSN Live Connect ================ Live uses OAuth2 for its connect workflow, notice that it isn't OAuth WRAP. - Register a new application at `Live Connect Developer Center`_, set your site domain as redirect domain, - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_LIVE_KEY = '' SOCIAL_AUTH_LIVE_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_LIVE_SCOPE = [...] Defaults are ``wl.basic`` and ``wl.emails``. Latter one is necessary to retrieve user email. - Ensure to have a valid ``Redirect URL`` (``http://your-domain/complete/live``) defined in the application if ``Enhanced security redirection`` is enabled. .. _Live Connect Developer Center: https://account.live.com/developers/applications/create python-social-auth-0.2.13/docs/backends/livejournal.rst000066400000000000000000000010401260133235600231070ustar00rootroot00000000000000LiveJournal =========== LiveJournal provides OpenId, it doesn't require any major settings in order to work, beside being defined on ``AUTHENTICATION_BACKENDS```:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.aol.AOLOpenId', ... ) LiveJournal OpenId is provided by URLs in the form ``http://.livejournal.com``, this application retrieves the ``username`` from the data in the current request by checking a parameter named ``openid_lj_user`` which can be sent by ``POST`` or ``GET``. python-social-auth-0.2.13/docs/backends/loginradius.rst000066400000000000000000000035711260133235600231100ustar00rootroot00000000000000LoginRadius =========== LoginRadius uses OAuth2 for Authentication with other providers with an HTML widget used to trigger the auth process. - Register a new application at the `LoginRadius Website`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_LOGINRADIUS_KEY = '' SOCIAL_AUTH_LOGINRADIUS_SECRET = '' - Since the auth process is triggered by LoginRadius JS script, you need to sever such content to the user, all you need to do that is a template with the following content::
Put that content in a template named ``loginradius.html`` (accessible to your framework), or define a name with ``SOCIAL_AUTH_LOGINRADIUS_TEMPLATE`` setting, like:: SOCIAL_AUTH_LOGINRADIUS_LOCAL_HTML = 'loginradius.html' The template context will have the current backend instance under the ``backend`` name, also the application key (``LOGINRADIUS_KEY``) and the redirect URL (``LOGINRADIUS_REDIRECT_URL``). - Further documentation can be found at `LoginRadius API Documentation`_ and `LoginRadius Datapoints`_ .. _LoginRadius Website: https://loginradius.com/ .. _LoginRadius API Documentation: http://api.loginradius.com/help/ .. _LoginRadius Datapoints: http://www.loginradius.com/datapoints/ python-social-auth-0.2.13/docs/backends/mailru.rst000066400000000000000000000002521260133235600220520ustar00rootroot00000000000000Mail.ru OAuth ============= Mail.ru uses OAuth2 workflow, to use it fill in settings:: SOCIAL_AUTH_MAILRU_OAUTH2_KEY = '' SOCIAL_AUTH_MAILRU_OAUTH2_SECRET = '' python-social-auth-0.2.13/docs/backends/mapmyfitness.rst000066400000000000000000000005041260133235600233000ustar00rootroot00000000000000MapMyFitness ============ MapMyFitness uses OAuth v2 for authentication. - Register a new application at the `MapMyFitness API`_, and - fill ``key`` and ``secret`` values in the settings:: SOCIAL_AUTH_MAPMYFITNESS_KEY = '' SOCIAL_AUTH_MAPMYFITNESS_SECRET = '' .. _MapMyFitness API: https://www.mapmyapi.com python-social-auth-0.2.13/docs/backends/meetup.rst000066400000000000000000000006121260133235600220600ustar00rootroot00000000000000Meetup ====== Meetup.com uses OAuth2 for its auth mechanism. - Register a new OAuth Consumer at `Meetup Consumer Registration`_, set your consumer name, redirect uri. - Fill ``key`` and ``secret`` values in the settings:: SOCIAL_AUTH_MEETUP_KEY = '' SOCIAL_AUTH_MEETUP_SECRET = '' .. _Meetup Consumer Registration: https://secure.meetup.com/meetup_api/oauth_consumers/create python-social-auth-0.2.13/docs/backends/mendeley.rst000066400000000000000000000021001260133235600223550ustar00rootroot00000000000000Mendeley ======== Mendeley supports OAuth1 and OAuth2, they are in the process of deprecating OAuth1 API (which should be fully deprecated on April 2014, check their announcement_). OAuth1 ------ In order to support OAuth1 (not recomended, use OAuth2 instead): - Register a new application at `Mendeley Application Registration`_ - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_MENDELEY_KEY = '' SOCIAL_AUTH_MENDELEY_SECRET = '' OAuth2 ------ In order to support OAuth2: - Register a new application at `Mendeley Application Registration`_, or migrate your OAuth1 application, check their `migration steps here`_. - Fill **Application ID** and **Application Secret** values:: SOCIAL_AUTH_MENDELEY_OAUTH2_KEY = '' SOCIAL_AUTH_MENDELEY_OAUTH2_SECRET = '' .. _Mendeley Application Registration: http://dev.mendeley.com/applications/register/ .. _announcement: https://sites.google.com/site/mendeleyapi/home/authentication .. _migration steps here: https://groups.google.com/forum/#!topic/mendeley-open-api-developers/KmUQW9I0ST0 python-social-auth-0.2.13/docs/backends/mineid.rst000066400000000000000000000012371260133235600220320ustar00rootroot00000000000000MineID ====== MineID works similar to Facebook (OAuth). - Register a new application at `MineID.org`_, set the callback URL to ``http://example.com/complete/mineid/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_MINEID_KEY = '' SOCIAL_AUTH_MINEID_SECRET = '' Self-hosted MineID ------------------ Since MineID is an Open Source software and can be self-hosted, you can change settings to point to your instance:: SOCIAL_AUTH_MINEID_HOST = 'www.your-mineid-instance.com' SOCIAL_AUTH_MINEID_SCHEME = 'https' # or 'http' .. _MineID.org: https://www.mineid.org/ python-social-auth-0.2.13/docs/backends/mixcloud.rst000066400000000000000000000020141260133235600224030ustar00rootroot00000000000000Mixcloud OAuth2 =============== The `Mixcloud API`_ offers support for authorization. To this backend support: - Register a new application at `Mixcloud Developers`_ - Add Mixcloud backend to ``AUTHENTICATION_BACKENDS`` in settings:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.mixcloud.MixcloudOAuth2', ) - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_MIXCLOUD_KEY = '' SOCIAL_AUTH_MIXCLOUD_SECRET = '' - Similar to the other OAuth backends you can define:: SOCIAL_AUTH_MIXCLOUD_EXTRA_DATA = [('username', 'username'), ('name', 'name'), ('pictures', 'pictures'), ('url', 'url')] as a list of tuples ``(response name, alias)`` to store user profile data on the ``UserSocialAuth.extra_data``. .. _Mixcloud API: http://www.mixcloud.com/developers/documentation .. _Mixcloud Developers: http://www.mixcloud.com/developers python-social-auth-0.2.13/docs/backends/moves.rst000066400000000000000000000016101260133235600217110ustar00rootroot00000000000000Moves ===== Moves_ provides an OAuth2 authentication flow. In order to enable it: - Register an application at `Manage Your Apps`_, remember to fill the ``Redirect URI`` once the application was created. - Fill **Client ID** and **Client secret** in the settings:: SOCIAL_AUTH_MOVES_KEY = '' SOCIAL_AUTH_MOVES_SECRET = '' - Define the mandatory scope for your application:: SOCIAL_AUTH_MOVES_SCOPE = ['activity', 'location'] The scope parameter is required by Moves_ but the backend doesn't set a default one to minimize the application permissions request, so it's mandatory for the developer to define this setting. - Add the backend to the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.moves.MovesOAuth2', ... ) .. _Moves: http://moves-app.com/ .. _Manage Your Apps: https://dev.moves-app.com/apps python-social-auth-0.2.13/docs/backends/naszaklasa.rst000066400000000000000000000013521260133235600227130ustar00rootroot00000000000000NationBuilder ============= `NaszaKlasa supports OAuth2`_ as their authentication mechanism. Follow these steps in order to use it: - Register a new application at your `NK Developers`_ (define the `Callback URL` to ``http://example.com/complete/nk/`` where ``example.com`` is your domain). - Fill the ``Client ID`` and ``Client Secret`` values from the newly created application:: SOCIAL_AUTH_NK_KEY = '' SOCIAL_AUTH_NK_SECRET = '' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.nk.NKOAuth2', ... ) .. _NaszaKlasa supports OAuth2: https://developers.nk.pl .. _NK Developers: https://developers.nk.pl/developers/oauth2client/formpython-social-auth-0.2.13/docs/backends/nationbuilder.rst000066400000000000000000000016341260133235600234250ustar00rootroot00000000000000NationBuilder ============= `NationBuilder supports OAuth2`_ as their authentication mechanism. Follow these steps in order to use it: - Register a new application at your `Nation Admin panel`_ (define the `Callback URL` to ``http://example.com/complete/nationbuilder/`` where ``example.com`` is your domain). - Fill the ``Client ID`` and ``Client Secret`` values from the newly created application:: SOCIAL_AUTH_NATIONBUILDER_KEY = '' SOCIAL_AUTH_NATIONBUILDER_SECRET = '' - Also define your NationBuilder slug:: SOCIAL_AUTH_NATIONBUILDER_SLUG = 'your-nationbuilder-slug' - Enable the backend in ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.nationbuilder.NationBuilderOAuth2' ... ) .. _Nation Admin panel: https://psa.nationbuilder.com/admin/apps .. _NationBuilder supports OAuth2: http://nationbuilder.com/api_quickstart python-social-auth-0.2.13/docs/backends/oauth.rst000066400000000000000000000021271260133235600217040ustar00rootroot00000000000000OAuth ===== OAuth_ communication demands a set of keys exchange to validate the client authenticity prior to user approbation. Twitter, and Facebook facilitates these keys by application registration, Google works the same, but provides the option for unregistered applications. Check next sections for details. OAuth_ backends also can store extra data in ``UserSocialAuth.extra_data`` field by defining a set of values names to retrieve from service response. Settings is per backend and its name is dynamically checked using uppercase backend name as prefix:: SOCIAL_AUTH__EXTRA_DATA Example:: SOCIAL_AUTH_FACEBOOK_EXTRA_DATA = [(..., ...)] Settings must be a list of tuples mapping value name in response and value alias used to store. A third value (boolean) is supported, its purpose is to signal if the value should be discarded if it evaluates to ``False``, this is to avoid replacing old (needed) values when they don't form part of current response. If not present, then this check is avoided and the value will replace any data. .. _OAuth: http://oauth.net/ python-social-auth-0.2.13/docs/backends/odnoklassnikiru.rst000066400000000000000000000033701260133235600240040ustar00rootroot00000000000000Odnoklassniki.ru ================ There are two options with Odnoklassniki: either you use OAuth2 workflow to authenticate odnoklassniki users at external site, or you authenticate users within your IFrame application. OAuth2 ------ If you use OAuth2 workflow, you need to: - register a new application with `OAuth registration form`_ - fill out some settings:: SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_KEY = '' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_SECRET = '' SOCIAL_AUTH_ODNOKLASSNIKI_OAUTH2_PUBLIC_NAME = '' - add ``'social.backends.odnoklassniki.OdnoklassnikiOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. IFrame applications ------------------- If you want to authenticate users in your IFrame application, - read `Rules for application developers`_ - fill out `Developers registration form`_ - get your personal sandbox - fill out some settings:: SOCIAL_AUTH_ODNOKLASSNIKI_APP_KEY = '' SOCIAL_AUTH_ODNOKLASSNIKI_APP_SECRET = '' SOCIAL_AUTH_ODNOKLASSNIKI_APP_PUBLIC_NAME = '' - add ``'social.backends.odnoklassniki.OdnoklassnikiApp'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS`` - sign a public offer and do some bureaucracy You may also use:: SOCIAL_AUTH_ODNOKLASSNIKI_APP_EXTRA_USER_DATA_LIST Defaults to empty tuple, for the list of available fields see `Documentation on user.getInfo`_ .. _OAuth registration form: http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=13992188 .. _Rules for application developers: http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki.ru+Third+Party+Platform .. _Developers registration form: http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=5668937 .. _Documentation on user.getInfo: http://dev.odnoklassniki.ru/wiki/display/ok/REST+API+-+users.getInfo python-social-auth-0.2.13/docs/backends/openid.rst000066400000000000000000000033731260133235600220460ustar00rootroot00000000000000OpenId ====== OpenId_ support is simpler to implement than OAuth_. Google and Yahoo providers are supported by default, others are supported by POST method providing endpoint URL. OpenId_ backends can store extra data in ``UserSocialAuth.extra_data`` field by defining a set of values names to retrieve from any of the used schemas, AttributeExchange and SimpleRegistration. As their keywords differ we need two settings. Settings is per backend, so we have two possible values for each one. Name is dynamically checked using uppercase backend name as prefix:: SOCIAL_AUTH__SREG_EXTRA_DATA SOCIAL_AUTH__AX_EXTRA_DATA Example:: SOCIAL_AUTH_GOOGLE_SREG_EXTRA_DATA = [(..., ...)] SOCIAL_AUTH_GOOGLE_AX_EXTRA_DATA = [(..., ...)] Settings must be a list of tuples mapping value name in response and value alias used to store. A third value (boolean) is supported to, it's purpose is to signal if the value should be discarded if it evaluates to ``False``, this is to avoid replacing old (needed) values when they don't form part of current response. If not present, then this check is avoided and the value will replace any data. Username -------- The OpenId_ backend will check for a ``username`` key in the values returned by the server, but default to ``first-name`` + ``last-name`` if that key is missing. It's possible to indicate the username key in the values If the username is under a different key with a setting, but backends should have defined a default value. For example:: SOCIAL_AUTH_FEDORA_USERNAME_KEY = 'nickname' This setting indicates that the username should be populated by the ``nickname`` value in the Fedora OpenId_ provider. .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ python-social-auth-0.2.13/docs/backends/openstreetmap.rst000066400000000000000000000012761260133235600234560ustar00rootroot00000000000000OpenStreetMap ============= OpenStreetMap supports OAuth 1.0 and 1.0a but 1.0a should be used for the new applications, as 1.0 is for support of legacy clients only. Access tokens currently do not expire automatically. More documentation at `OpenStreetMap Wiki`_: - Login to your account - Register your application as OAuth consumer on your `OpenStreetMap user settings page`_, and - Set ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_OPENSTREETMAP_KEY = '' SOCIAL_AUTH_OPENSTREETMAP_SECRET = '' .. _OpenStreetMap Wiki: http://wiki.openstreetmap.org/wiki/OAuth .. _OpenStreetMap user settings page: http://www.openstreetmap.org/user/username/oauth_clients/new python-social-auth-0.2.13/docs/backends/orbi.rst000066400000000000000000000005531260133235600215200ustar00rootroot00000000000000Orbi ==== Orbi OAuth v2 for Authentication. - Register a new applicationat the `Orbi API`_, and - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_ORBI_KEY = '' SOCIAL_AUTH_ORBI_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_KAKAO_SCOPE = ['all'] .. _Orbi API: http://orbi.kr python-social-auth-0.2.13/docs/backends/persona.rst000066400000000000000000000030741260133235600222350ustar00rootroot00000000000000Mozilla Persona =============== Support for `Mozilla Persona`_ is possible by posting the ``assertion`` code to ``/complete/persona/`` URL. The setup doesn't need any setting, just the usual `Mozilla Persona`_ javascript include in your document and the needed mechanism to trigger the POST to `python-social-auth`_::
Mozilla Persona
.. _python-social-auth: https://github.com/omab/python-social-auth .. _Mozilla Persona: http://www.mozilla.org/persona/ python-social-auth-0.2.13/docs/backends/pixelpin.rst000066400000000000000000000022351260133235600224140ustar00rootroot00000000000000PixelPin ======== PixelPin itself supports OAuth 1 and 2 but this provider only supports OAuth2. PixelPin OAuth2 --------------- Developer documentation for PixelPin can be found at https://login.pixelpin.co.uk/Developer/Index.aspx To setup OAuth2 do the following: - Register a new developer account at `PixelPin Developers`_. You require a PixelPin account to create developer accounts. Sign up at `PixelPin Account Page`_ For the value of redirect uri, use whatever path you need to return to on your web application. The example code provided with the plugin uses ``http:///complete/pixelpin-oauth2/``. Once verified by email, record the values of client id and secret for the next step. - Fill **Consumer Key** and **Consumer Secret** values in your settings.py file:: SOCIAL_AUTH_PIXELPIN_OAUTH2_KEY = '' SOCIAL_AUTH_PIXELPIN_OAUTH2_SECRET = '' - Add ``'social.backends.pixelpin.PixelPinOAuth2'`` into your ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``. .. _PixelPin homepage: http://pixelpin.co.uk/ .. _PixelPin Account Page: https://login.pixelpin.co.uk/ .. _PixelPin Developers: https://login.pixelpin.co.uk/Developers/Index.aspx python-social-auth-0.2.13/docs/backends/pocket.rst000066400000000000000000000004261260133235600220510ustar00rootroot00000000000000Pocket ====== Pocket uses a weird variant of OAuth v2 that only defines a consumer key. - Register a new application at the `Pocket API`_, and - fill ``consumer key`` value in the settings:: SOCIAL_AUTH_POCKET_KEY = '' .. _Pocket API: http://getpocket.com/developer/ python-social-auth-0.2.13/docs/backends/podio.rst000066400000000000000000000005011260133235600216700ustar00rootroot00000000000000Podio ===== Podio offers OAuth2 as their auth mechanism. In order to enable it, follow: - Register a new application at `Podio API Keys`_ - Fill **Client Id** and **Client Secret** values:: SOCIAL_AUTH_PODIO_KEY = '' SOCIAL_AUTH_PODIO_SECRET = '' .. _Podio API Keys: https://developers.podio.com/api-key python-social-auth-0.2.13/docs/backends/qiita.rst000066400000000000000000000010711260133235600216700ustar00rootroot00000000000000Qiita ===== Qiita - Register a new application at Qiita_, set the callback URL to ``http://example.com/complete/qiita/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_QIITA_KEY = '' SOCIAL_AUTH_QIITA_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_QIITA_SCOPE = [...] See auth scopes at `Qiita Scopes docs`_. .. _Qiita: https://qiita.com/settings/applications .. _Qiita Scopes docs: https://qiita.com/api/v2/docs#スコープ python-social-auth-0.2.13/docs/backends/qq.rst000066400000000000000000000012551260133235600212060ustar00rootroot00000000000000QQ == QQ implemented OAuth2 protocol for their authentication mechanism. To enable ``python-social-auth`` support follow this steps: 1. Go to `QQ`_ and create an application. 2. Fill App Id and Secret in your project settings:: SOCIAL_AUTH_QQ_KEY = '...' SOCIAL_AUTH_QQ_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.qq.QQOauth2', ... ) The values for ``nickname``, ``figureurl_qq_1`` and ``gender`` will be stored in the ``extra_data`` field. The ``nickname`` will be used as the account username. ``figureurl_qq_1`` can be used as the profile image. .. _QQ: http://connect.qq.com/ python-social-auth-0.2.13/docs/backends/rdio.rst000066400000000000000000000016271260133235600215250ustar00rootroot00000000000000Rdio ==== Rdio provides OAuth 1 and 2 support for their authentication process. OAuth 1.0a ---------- To setup Rdio OAuth 1.0a, add the following to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.rdio.RdioOAuth1', ... ) SOCIAL_AUTH_RDIO_OAUTH1_KEY = '' SOCIAL_AUTH_RDIO_OAUTH1_SECRET = '' OAuth 2.0 --------- To setup Rdio OAuth 2.0, add the following to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.rdio.RdioOAuth2', ... ) SOCIAL_AUTH_RDIO_OAUTH2_KEY = os.environ['RDIO_OAUTH2_KEY'] SOCIAL_AUTH_RDIO_OAUTH2_SECRET = os.environ['RDIO_OAUTH2_SECRET'] SOCIAL_AUTH_RDIO_OAUTH2_SCOPE = [] Extra Fields ------------ The following extra fields are automatically requested: - rdio_id - rdio_icon_url - rdio_profile_url - rdio_username - rdio_stream_region python-social-auth-0.2.13/docs/backends/readability.rst000066400000000000000000000010661260133235600230560ustar00rootroot00000000000000Readability =========== Readability works similarly to Twitter, in that you'll need a ``Consumer Key`` and ``Consumer Secret``. These can be obtained in the ``Connections`` section of your ``Account`` page. - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_READABILITY_KEY = '' SOCIAL_AUTH_READABILITY_SECRET = '' That's it! By default you'll get back:: username first_name last_name with EXTRA_DATA, you can get:: date_joined kindle_email_address avatar_url email_into_address python-social-auth-0.2.13/docs/backends/reddit.rst000066400000000000000000000022551260133235600220410ustar00rootroot00000000000000Reddit ====== Reddit implements `OAuth2 authentication workflow`_. To enable it, just follow: - Register an application at `Reddit Preferences Apps`_ - Fill the **Consumer Key** and **Consumer Secret** values in your settings:: SOCIAL_AUTH_REDDIT_KEY = '' SOCIAL_AUTH_REDDIT_SECRET = '' - By default the token is not permanent, it will last an hour. To get a refresh token just define:: SOCIAL_AUTH_REDDIT_AUTH_EXTRA_ARGUMENTS = {'duration': 'permanent'} This will store the ``refresh_token`` in ``UserSocialAuth.extra_data`` attribute, to refresh the access token just do:: from social.apps.django_app.utils import load_strategy strategy = load_strategy(backend='reddit') user = User.objects.get(pk=foo) social = user.social_auth.filter(provider='reddit')[0] social.refresh_token(strategy=strategy, redirect_uri='http://localhost:8000/complete/reddit/') Reddit requires ``redirect_uri`` when refreshing the token and it must be the same value used during the auth process. .. _Reddit Preferences Apps: https://ssl.reddit.com/prefs/apps/ .. _OAuth2 authentication workflow: https://github.com/reddit/reddit/wiki/OAuth2 python-social-auth-0.2.13/docs/backends/runkeeper.rst000066400000000000000000000005161260133235600225640ustar00rootroot00000000000000RunKeeper ========= RunKeeper uses OAuth v2 for authentication. - Register a new application at the `RunKeeper API`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_RUNKEEPER_KEY = '' SOCIAL_AUTH_RUNKEEPER_SECRET = '' .. _RunKeeper API: http://developer.runkeeper.com/healthgraph python-social-auth-0.2.13/docs/backends/salesforce.rst000066400000000000000000000025561260133235600227200ustar00rootroot00000000000000Salesforce ========== Salesforce uses OAuth v2 for Authentication, check the `official docs`_. - Create an app following the steps in the `Defining Connected Apps`_ docs. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SALESFORCE_OAUTH2_KEY = '' SOCIAL_AUTH_SALESFORCE_OAUTH2_SECRET = '' - Add the backend to the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.salesforce.SalesforceOAuth2', ... ) - Then you can start using ``{% url social:begin 'salesforce-oauth2' %}`` in your templates If using the sandbox mode: - Fill these settings instead:: SOCIAL_AUTH_SALESFORCE_OAUTH2_SANDBOX_KEY = '' SOCIAL_AUTH_SALESFORCE_OAUTH2_SANDBOX_SECRET = '' - And this backend:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.salesforce.SalesforceOAuth2Sandbox', ... ) - Then you can start using ``{% url social:begin 'salesforce-oauth2-sandbox' %}`` in your templates .. _official docs: https://www.salesforce.com/us/developer/docs/api_rest/Content/intro_understanding_web_server_oauth_flow.htm .. _Defining Connected Apps: https://www.salesforce.com/us/developer/docs/api_rest/Content/intro_defining_remote_access_applications.htm python-social-auth-0.2.13/docs/backends/saml.rst000066400000000000000000000162251260133235600215240ustar00rootroot00000000000000SAML ==== The SAML backend allows users to authenticate with any provider that supports the SAML 2.0 protocol (commonly used for corporate or academic single sign on). The SAML backend for python-social-auth allows your web app to act as a SAML Service Provider. You can configure one or more SAML Identity Providers that users can use for authentication. For example, if your users are students, you could enable Harvard and MIT as identity providers, so that students of either of those two universities can use their campus login to access your app. Required Dependency ------------------- You must install python-saml_ 2.1.3 or higher in order to use this backend. Required Configuration ---------------------- At a minimum, you must add the following to your project's settings: - ``SOCIAL_AUTH_SAML_SP_ENTITY_ID``: The SAML Entity ID for your app. This should be a URL that includes a domain name you own. It doesn't matter what the URL points to. Example: ``http://saml.yoursite.com`` - ``SOCIAL_AUTH_SAML_SP_PUBLIC_CERT``: The X.509 certificate string for the key pair that your app will use. You can generate a new self-signed key pair with:: openssl req -new -x509 -days 3652 -nodes -out saml.crt -keyout saml.key The contents of ``saml.crt`` should then be used as the value of this setting (you can omit the first and last lines, which aren't required). - ``SOCIAL_AUTH_SAML_SP_PRIVATE_KEY``: The private key to be used by your app. If you used the example openssl command given above, set this to the contents of ``saml.key`` (again, you can omit the first and last lines). - ``SOCIAL_AUTH_SAML_ORG_INFO``: A dictionary that contains information about your app. You must specify values for English at a minimum. Each language's entry should specify a ``name`` (not shown to the user), a ``displayname`` (shown to the user), and a URL. See the following example:: { "en-US": { "name": "example", "displayname": "Example Inc.", "url": "http://example.com", } } - ``SOCIAL_AUTH_SAML_TECHNICAL_CONTACT``: A dictionary with two values, ``givenName`` and ``emailAddress``, describing the name and email of a technical contact responsible for your app. Example:: {"givenName": "Tech Gal", "emailAddress": "technical@example.com"} - ``SOCIAL_AUTH_SAML_TECHNICAL_CONTACT``: A dictionary with two values, ``givenName`` and ``emailAddress``, describing the name and email of a support contact for your app. Example:: SOCIAL_AUTH_SAML_SUPPORT_CONTACT = { "givenName": "Support Guy", "emailAddress": "support@example.com", } - ``SOCIAL_AUTH_SAML_ENABLED_IDPS``: The most important setting. List the Entity ID, SSO URL, and x.509 public key certificate for each provider that your app wants to support. The SSO URL must support the ``HTTP-Redirect`` binding. You can get these values from the provider's XML metadata. Here's an example, for TestShib_ (the values come from TestShib's metadata_):: { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADA ... 8Bbnl+ev0peYzxFyF5sQA==", } } Basic Usage ----------- - Set all of the required configuration variables described above. - Generate the SAML XML metadata for your app. The best way to do this is to create a new view/page/URL in your app that will call the backend's ``generate_metadata_xml()`` method. Here's an example of how to do this in Django:: def saml_metadata_view(request): complete_url = reverse('social:complete', args=("saml", )) saml_backend = load_backend( load_strategy(request), "saml", redirect_uri=complete_url, ) metadata, errors = saml_backend.generate_metadata_xml() if not errors: return HttpResponse(content=metadata, content_type='text/xml') - Download the metadata for your app that was generated by the above method, and send it to each Identity Provider (IdP) that you wish to use. Each IdP must install and configure your metadata on their system before it will work. - Now everything is set! To allow users to login with any given IdP, you need to give them a link to the python-social-auth "begin"/"auth" URL and include an ``idp`` query parameter that specifies the name of the IdP to use. This is needed since the backend supports multiple IdPs. The names of the IdPs are the keys used in the ``SOCIAL_AUTH_SAML_ENABLED_IDPS`` setting. Django example:: # In view: context['testshib_url'] = u"{base}?{params}".format( base=reverse('social:begin', kwargs={'backend': 'saml'}), params=urllib.urlencode({'next': '/home', 'idp': 'testshib'}) ) # In template: TestShib Login # Result: TestShib Login - Testing with the TestShib_ provider is recommended, as it is known to work well. Advanced Settings ----------------- - ``SOCIAL_AUTH_SAML_SP_EXTRA``: This can be set to a dict, and any key/value pairs specified here will be passed to the underlying ``python-saml`` library configuration's ``sp`` setting. Refer to the ``python-saml`` documentation for details. - ``SOCIAL_AUTH_SAML_SECURITY_CONFIG``: This can be set to a dict, and any key/value pairs specified here will be passed to the underlying ``python-saml`` library configuration's ``security`` setting. Two useful keys that you can set are ``metadataCacheDuration`` and ``metadataValidUntil``, which control the expiry time of your XML metadata. By default, a cache duration of 10 days will be used, which means that IdPs are allowed to cache your metadata for up to 10 days, but no longer. ``metadataCacheDuration`` must be specified as an ISO 8601 duration string (e.g. `P1D` for one day). Advanced Usage -------------- You can subclass the ``SAMLAuth`` backend to provide custom functionality. In particular, there are two methods that are designed for subclasses to override: - ``get_idp(self, idp_name)``: Given the name of an IdP, return an instance of ``SAMLIdentityProvider`` with the details of the IdP. Override this method if you wish to use some other method for configuring the available identity providers, such as fetching them at runtime from another server, or using a list of providers from a Shibboleth federation. - ``_check_entitlements(self, idp, attributes)``: This method gets called during the login process and is where you can decide to accept or reject a user based on the user's SAML attributes. For example, you can restrict access to your application to only accept users who belong to a certain department. After inspecting the passed attributes parameter, do nothing to allow the user to login, or raise ``social.exceptions.AuthForbidden`` to reject the user. .. _python-saml: https://github.com/onelogin/python-saml .. _TestShib: https://www.testshib.org/ .. _metadata: https://www.testshib.org/metadata/testshib-providers.xml python-social-auth-0.2.13/docs/backends/shopify.rst000066400000000000000000000017351260133235600222510ustar00rootroot00000000000000Shopify ======= Shopify uses OAuth 2 for authentication. To use this backend, you must install the package ``shopify`` from the `Github project`_. Currently supports v2+ - Register a new application at `Shopify Partners`_, and - Set the Auth Type to OAuth2 in the application settings - Set the Application URL to http://[your domain]/login/shopify/ - fill ``API Key`` and ``Shared Secret`` values in your django settings:: SOCIAL_AUTH_SHOPIFY_KEY = '' SOCIAL_AUTH_SHOPIFY_SECRET = '' - fill the scope permissions that you require into the settings `Shopify API`_:: SOCIAL_AUTH_SHOPIFY_SCOPE = ['write_script_tags', 'read_orders', 'write_customers', 'read_products'] .. _Shopify Partners: http://www.shopify.com/partners .. _Shopify API: http://api.shopify.com/authentication.html#scopes .. _Github project: https://github.com/Shopify/shopify_python_api python-social-auth-0.2.13/docs/backends/skyrock.rst000066400000000000000000000012211260133235600222430ustar00rootroot00000000000000Skyrock ======= OAuth based Skyrock Connect. Skyrock offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Skyrock these two keys are needed. Further documentation at `Skyrock developer resources`_: - Register a new application at `Skyrock App Creation`_, - Your callback domain should match your application URL in your application configuration. - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_SKYROCK_KEY = '' SOCIAL_AUTH_SKYROCK_SECRET = '' .. _Skyrock developer resources: http://www.skyrock.com/developer/ .. _Skyrock App Creation: https://wwwskyrock.com/developer/application python-social-auth-0.2.13/docs/backends/slack.rst000066400000000000000000000010501260133235600216530ustar00rootroot00000000000000Slack ===== Slack - Register a new application at Slack_, set the callback URL to ``http://example.com/complete/slack/`` replacing ``example.com`` with your domain. - Fill ``Client ID`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SLACK_KEY = '' SOCIAL_AUTH_SLACK_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_SLACK_SCOPE = [...] See auth scopes at `Slack OAuth docs`_. .. _Slack: https://api.slack.com/applications .. _Slack OAuth docs: https://api.slack.com/docs/oauth python-social-auth-0.2.13/docs/backends/soundcloud.rst000066400000000000000000000015101260133235600227360ustar00rootroot00000000000000SoundCloud ========== SoundCloud uses OAuth2 for its auth mechanism. - Register a new application at `SoundCloud App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_SOUNDCLOUD_KEY = '' SOCIAL_AUTH_SOUNDCLOUD_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_SOUNDCLOUD_SCOPE = [...] Possible scope values are `*` or `non-expiring` according to their `/connect documentation`_. Check the rest of their doc at `SoundCloud Developer Documentation`_. .. _SoundCloud App Registration: http://soundcloud.com/you/apps/new .. _SoundCloud Developer Documentation: http://developers.soundcloud.com/docs .. _/connect documentation: http://developers.soundcloud.com/docs/api/reference#connect python-social-auth-0.2.13/docs/backends/spotify.rst000066400000000000000000000010301260133235600222510ustar00rootroot00000000000000Spotify ======= Spotify supports OAuth 2. - Register a new application at `Spotify Web API`_, and follow the instructions below. OAuth2 ------ Add the Spotify OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.spotify.SpotifyOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_SPOTIFY_KEY = '' SOCIAL_AUTH_SPOTIFY_SECRET = '' .. _Spotify Web API: https://developer.spotify.com/spotify-web-api python-social-auth-0.2.13/docs/backends/stackoverflow.rst000066400000000000000000000010231260133235600234470ustar00rootroot00000000000000Stackoverflow ============= Stackoverflow uses OAuth 2.0 - "Register For An App Key" at the `Stack Exchange API`_ site. Set your OAuth domain and application website settings. - Add the ``Client Id``, ``Client Secret`` and ``API Key`` values in settings:: SOCIAL_AUTH_STACKOVERFLOW_KEY = '' SOCIAL_AUTH_STACKOVERFLOW_SECRET = '' SOCIAL_AUTH_STACKOVERFLOW_API_KEY = '' - You can ask for extra permissions with:: SOCIAL_AUTH_STACKOVERFLOW_SCOPE = [...] .. _Stack Exchange API: https://api.stackexchange.com/ python-social-auth-0.2.13/docs/backends/steam.rst000066400000000000000000000006661260133235600217030ustar00rootroot00000000000000Steam OpenId ============ Steam OpenId works quite straightforward, but to retrieve some user data (known as ``player`` on Steam API) a Steam API Key is needed. Configurable settings: - Supply a Steam API Key from `Steam Dev`_:: SOCIAL_AUTH_STEAM_API_KEY = key - To save ``player`` data provided by Steam into ``extra_data``:: SOCIAL_AUTH_STEAM_EXTRA_DATA = ['player'] .. _Steam Dev: http://steamcommunity.com/dev/apikey python-social-auth-0.2.13/docs/backends/stocktwits.rst000066400000000000000000000007701260133235600230040ustar00rootroot00000000000000StockTwits ========== StockTwits uses OAuth 2 for authentication. - Register a new application at https://stocktwits.com/developers/apps - Set the Website URL to http://[your domain]/ - fill ``Consumer Key`` and ``Consumer Secret`` values in your django settings:: SOCIAL_AUTH_STOCKTWITS_KEY = '' SOCIAL_AUTH_STOCKTWITS_SECRET = '' .. _StockTwits authentication docs: http://stocktwits.com/developers/docs/authentication .. _StockTwits API: http://stocktwits.com/developers/docs/api python-social-auth-0.2.13/docs/backends/strava.rst000066400000000000000000000006401260133235600220620ustar00rootroot00000000000000Strava ========= Strava uses OAuth v2 for Authentication. - Register a new application at the `Strava API`_, and - fill ``Client ID`` and ``Client Secret`` from strava.com values in the settings:: SOCIAL_AUTH_STRAVA_KEY = '' SOCIAL_AUTH_STRAVA_SECRET = '' - extra scopes can be defined by using:: SOCIAL_AUTH_STRAVA_SCOPE = ['view_private'] .. _Strava API: https://www.strava.com/settings/api python-social-auth-0.2.13/docs/backends/stripe.rst000066400000000000000000000017071260133235600220750ustar00rootroot00000000000000Stripe ====== Stripe uses OAuth2 for its authorization service. To setup Stripe backend: - Register a new application at `Stripe App Creation`_, and - Grab the ``client_id`` value in ``Applications`` tab and fill the ``App Id`` setting:: SOCIAL_AUTH_STRIPE_KEY = 'ca_...' - Grab the ``Test Secret Key`` in the ``API Keys`` tab and fille the ``App Secret`` setting:: SOCIAL_AUTH_STRIPE_SECRET = '...' - Define ``SOCIAL_AUTH_STRIPE_SCOPE`` with the desired scope (options are ``read_only`` and ``read_write``):: SOCIAL_AUTH_STRIPE_SCOPE = ['read_only'] - Add the needed backend to ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.stripe.StripeOAuth2', ... ) More info on Stripe OAuth2 at `Integrating OAuth`_. .. _Stripe App Creation: https://manage.stripe.com/#account/applications/settings .. _Integrating OAuth: https://stripe.com/docs/connect/oauth python-social-auth-0.2.13/docs/backends/suse.rst000066400000000000000000000005401260133235600215400ustar00rootroot00000000000000SUSE ==== This section describes how to setup the different services provided by SUSE and openSUSE. openSUSE OpenId --------------- openSUSE OpenId works straightforward, not settings are needed. Domains or emails whitelists can be applied too, check the whitelists_ settings for details. .. _whitelists: ../configuration/settings.html#whitelists python-social-auth-0.2.13/docs/backends/taobao.rst000066400000000000000000000005401260133235600220260ustar00rootroot00000000000000Taobao OAuth ============ Taobao OAuth 2.0 workflow. - Register a new application at Open `Open Taobao`_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_TAOBAO_KEY = '' SOCIAL_AUTH_TAOBAO_SECRET = '' By default ``token`` is stored in ``extra_data`` field. .. _Open Taobao: http://open.taobao.com python-social-auth-0.2.13/docs/backends/thisismyjam.rst000066400000000000000000000010421260133235600231200ustar00rootroot00000000000000ThisIsMyJam =========== ThisIsMyJam uses OAuth1 for its auth mechanism. - Register a new application at `ThisIsMyJam App Registration`_, set your application name, website and redirect URI. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_THISISMYJAM_KEY = '' SOCIAL_AUTH_THISISMYJAM_SECRET = '' Check the rest of their doc at `ThisIsMyJam API Docs`_. .. _ThisIsMyJam App Registration: https://www.thisismyjam.com/developers .. _ThisIsMyJam API Docs: https://www.thisismyjam.com/developers/docs python-social-auth-0.2.13/docs/backends/trello.rst000066400000000000000000000013031260133235600220600ustar00rootroot00000000000000Trello ====== Trello provides OAuth1 support for their authentication process. In order to enable it, follow: - Generate an Application Key pair at `Trello Developers API Keys`_ - Fill **Consumer Key** and **Consumer Secret** settings:: SOCIAL_AUTH_TRELLO_KEY = '...' SOCIAL_AUTH_TRELLO_SECRET = '...' There are also two optional settings: - your app name, otherwise the authorization page will say "Let An unknown application use your account?":: SOCIAL_AUTH_TRELLO_APP_NAME = 'My App' - the expiration period, social auth defaults to 'never', but you can change it:: SOCIAL_AUTH_TRELLO_EXPIRATION = '30days' .. _Trello Developers API Keys: https://trello.com/1/appKey/generate python-social-auth-0.2.13/docs/backends/tripit.rst000066400000000000000000000007561260133235600221050ustar00rootroot00000000000000TripIt ====== TripIt offers per application keys named ``API Key`` and ``API Secret``. To enable TripIt these two keys are needed. Further documentation at `TripIt Developer Center`_: - Register a new application at `TripIt App Registration`_, - fill **API Key** and **API Secret** values:: SOCIAL_AUTH_TRIPIT_KEY = '' SOCIAL_AUTH_TRIPIT_SECRET = '' .. _TripIt Developer Center: https://www.tripit.com/developer .. _TripIt App Registration: https://www.tripit.com/developer/create python-social-auth-0.2.13/docs/backends/tumblr.rst000066400000000000000000000006141260133235600220700ustar00rootroot00000000000000Tumblr ====== Tumblr uses OAuth 1.0a for authentication. - Register a new application at http://www.tumblr.com/oauth/apps - Set the ``Default callback URL`` to http://[your domain]/ - fill ``OAuth Consumer Key`` and ``Secret Key`` values in your Django settings:: SOCIAL_AUTH_TUMBLR_KEY = '' SOCIAL_AUTH_TUMBLR_SECRET = '' .. _Tumblr API: http://www.tumblr.com/docs/en/api/v2 python-social-auth-0.2.13/docs/backends/twilio.rst000066400000000000000000000010141260133235600220650ustar00rootroot00000000000000Twilio ====== - Register a new application at `Twilio Connect Api`_ - Fill ``SOCIAL_AUTH_TWILIO_KEY`` and ``SOCIAL_AUTH_TWILIO_SECRET`` values in the settings:: SOCIAL_AUTH_TWILIO_KEY = '' SOCIAL_AUTH_TWILIO_SECRET = '' - Add desired authentication backends to Django's ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS`` setting:: 'social.backends.twilio.TwilioAuth', - Usage example:: Enter using Twilio .. _Twilio Connect API: https://www.twilio.com/user/account/connect/apps python-social-auth-0.2.13/docs/backends/twitch.rst000066400000000000000000000010551260133235600220650ustar00rootroot00000000000000Twitch ====== Twitch works similar to Facebook (OAuth). - Register a new application in the `connections tab`_ of your Twitch settings page, set the callback URL to ``http://example.com/complete/twitch/`` replacing ``example.com`` with your domain. - Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_TWITCH_KEY = '' SOCIAL_AUTH_TWITCH_SECRET = '' - Also it's possible to define extra permissions with:: SOCIAL_AUTH_TWITCH_SCOPE = [...] .. _connections tab: http://www.twitch.tv/settings/connections python-social-auth-0.2.13/docs/backends/twitter.rst000066400000000000000000000021141260133235600222620ustar00rootroot00000000000000Twitter ======= Twitter offers per application keys named ``Consumer Key`` and ``Consumer Secret``. To enable Twitter these two keys are needed. Further documentation at `Twitter development resources`_: - Register a new application at `Twitter App Creation`_, - Check the **Allow this application to be used to Sign in with Twitter** checkbox. If you don't check this box, Twitter will force your user to login every time. - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_TWITTER_KEY = '' SOCIAL_AUTH_TWITTER_SECRET = '' - You need to specify an URL callback or the application will be marked as Client type instead of the Browser. Almost any dummy value will work if you plan some test. Twitter usually fails with a 401 error when trying to call the request-token URL, this is usually caused by server datetime errors (check miscellaneous section). Installing ``ntp`` and syncing the server date with some pool does the trick. .. _Twitter development resources: http://dev.twitter.com/pages/auth .. _Twitter App Creation: http://twitter.com/apps/new python-social-auth-0.2.13/docs/backends/uber.rst000066400000000000000000000011571260133235600215230ustar00rootroot00000000000000Uber ========= Uber uses OAuth v2 for Authentication. - Register a new application at the `Uber API`_, and follow the instructions below OAuth2 ========= 1. Add the Uber OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.uber.UberOAuth2', ... ) 2. Fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_UBER_KEY = '' SOCIAL_AUTH_UBER_SECRET = '' 3. Scope should be defined by using:: SOCIAL_AUTH_UBER_SCOPE = ['profile', 'request'] .. _Uber API: https://developer.uber.com/dashboard python-social-auth-0.2.13/docs/backends/username.rst000066400000000000000000000041611260133235600224030ustar00rootroot00000000000000Username Auth ============= python-social-auth_ comes with an UsernameAuth_ backend which comes handy when your site uses requires the plain old username and password authentication mechanism. Actually that's a lie since the backend doesn't handle password at all, that's up to the developer to validate the password in and the proper place to do it is the pipeline, right after the user instance was retrieved or created. The reason to leave password handling to the developer is because too many things are really tied to the project, like the field where the password is stored, salt handling, password hashing algorithm and validation. So just add the pipeline functions that will do that following the needs of your project. Backend settings ---------------- ``SOCIAL_AUTH_USERNAME_FORM_URL = '/login-form/'`` Used to redirect the user to the login/signup form, it must have at least one field named ``username``. Form submit should go to ``/complete/username``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. ``SOCIAL_AUTH_USERNAME_FORM_HTML = 'login_form.html'`` The template will be used to render the login/signup form to the user, it must have at least one field named ``username``. Form submit should go to ``/complete/username``, or if it goes to your view, then your view should complete the process calling ``social.actions.do_complete``. Password handling ----------------- Here's an example of password handling to add to the pipeline:: def user_password(strategy, user, is_new=False, *args, **kwargs): if strategy.backend.name != 'username': return password = strategy.request_data()['password'] if is_new: user.set_password(password) user.save() elif not user.validate_password(password): # return {'user': None, 'social': None} raise AuthException(strategy.backend) .. _python-social-auth: https://github.com/omab/python-social-auth .. _UsernameAuth: https://github.com/omab/python-social-auth/blob/master/social/backends/username.py#L5 python-social-auth-0.2.13/docs/backends/vend.rst000066400000000000000000000011041260133235600215120ustar00rootroot00000000000000Vend ==== Vend supports OAuth 2. - Register a new application at `Vend Developers Portal`_ - Add the Vend OAuth2 backend to your settings page:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.vend.VendOAuth2', ... ) - Fill ``App Key`` and ``App Secret`` values in the settings:: SOCIAL_AUTH_VEND_OAUTH2_KEY = '' SOCIAL_AUTH_VEND_OAUTH2_SECRET = '' More details on their docs_. .. _Vend Developers Portal: https://developers.vendhq.com/developer/applications .. _docs: https://developers.vendhq.com/documentation python-social-auth-0.2.13/docs/backends/vimeo.rst000066400000000000000000000013211260133235600216760ustar00rootroot00000000000000Vimeo ===== Vimeo uses OAuth1 to grant access to their API. In order to get the backend running follow: - Register an application at `Vimeo Developer Portal`_ filling the required settings. Ensure to fill ``App Callback URL`` field with ``http:///complete/vimeo/`` - Fill in the **Client Id** and **Client Secret** values in your settings:: SOCIAL_AUTH_VIMEO_KEY = '' SOCIAL_AUTH_VIMEO_SECRET = '' - Specify scopes with:: SOCIAL_AUTH_VIMEO_SCOPE = [...] - Add the backend to ``AUTHENTICATION_BACKENDS``:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.vimeo.VimeoOAuth1', ... ) .. _Vimeo Developer Portal: https://developer.vimeo.com/apps/new python-social-auth-0.2.13/docs/backends/vk.rst000066400000000000000000000103661260133235600212100ustar00rootroot00000000000000VK.com (former Vkontakte) ========================= VK.com (former Vkontakte) auth service support. OAuth2 ------ VK.com uses OAuth2 for Authentication. - Register a new application at the `VK.com API`_, - fill ``Application Id`` and ``Application Secret`` values in the settings:: SOCIAL_AUTH_VK_OAUTH2_KEY = '' SOCIAL_AUTH_VK_OAUTH2_SECRET = '' - Add ``'social.backends.vk.VKOAuth2'`` into your ``AUTHENTICATION_BACKENDS``. - Then you can start using ``/login/vk-oauth2`` in your link href. - Also it's possible to define extra permissions with:: SOCIAL_AUTH_VK_OAUTH2_SCOPE = [...] See the `VK.com list of permissions`_. OAuth2 Application ------------------ To support OAuth2 authentication for VK.com applications: - Create your IFrame application at VK.com. - In application settings specify your IFrame URL ``mysite.com/vk`` (current default). - Fill ``Application ID`` and ``Application Secret`` settings:: SOCIAL_AUTH_VK_APP_KEY = '' SOCIAL_AUTH_VK_APP_SECRET = '' - Fill ``user_mode``:: SOCIAL_AUTH_VK_APP_USER_MODE = 2 Possible values: - ``0``: there will be no check whether a user connected to your application or not - ``1``: ``python-social-auth`` will check ``is_app_user`` parameter VK.com sends when user opens application page one time - ``2``: (safest) ``python-social-auth`` will check status of user interactively (useful when you have interactive authentication via AJAX) - Add a snippet similar to this into your login template:: Click to authenticate To test, launch the server using ``sudo ./manage.py mysite.com:80`` for browser to be able to load it when VK.com calls IFrame URL. Open your VK.com application page via http://vk.com/app. Now you are able to connect to application and login automatically after connection when visiting application page. For more details see `authentication for VK.com applications`_ OpenAPI ------- You can also use VK.com's own OpenAPI to log in, but you need to provide HTML template with JavaScript code to authenticate, check below for an example. - Get an OpenAPI App Id and add it to the settings:: SOCIAL_AUTH_VK_OPENAPI_ID = '' This app id will be passed to the template as ``VK_APP_ID``. Snippet example:: Click to authorize .. _VK.com OAuth: http://vk.com/dev/authentication .. _VK.com list of permissions: http://vk.com/dev/permissions .. _VK.com API: http://vk.com/dev/methods .. _authentication for VK.com applications: http://www.ikrvss.ru/2011/11/08/django-social-auh-and-vkontakte-application/ python-social-auth-0.2.13/docs/backends/weibo.rst000066400000000000000000000013041260133235600216650ustar00rootroot00000000000000Weibo OAuth =========== Weibo OAuth 2.0 workflow. - Register a new application at Weibo_. - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_WEIBO_KEY = '' SOCIAL_AUTH_WEIBO_SECRET = '' By default ``account id``, ``profile_image_url`` and ``gender`` are stored in extra_data field. The user name is used by default to build the user instance ``username``, sometimes this contains non-ASCII characters which might not be desirable for the website. To avoid this issue it's possible to use the Weibo ``domain`` which will be inside the ASCII range by defining this setting:: SOCIAL_AUTH_WEIBO_DOMAIN_AS_USERNAME = True .. _Weibo: http://open.weibo.com python-social-auth-0.2.13/docs/backends/withings.rst000066400000000000000000000005251260133235600224200ustar00rootroot00000000000000Withings ======== Withings uses OAuth v1 for Authentication. - Register a new application at the `Withings API`_, and - fill ``Client ID`` and ``Client Secret`` from withings.com values in the settings:: SOCIAL_AUTH_WITHINGS_KEY = '' SOCIAL_AUTH_WITHINGS_SECRET = '' .. _Withings API: https://oauth.withings.com/partner/add python-social-auth-0.2.13/docs/backends/wunderlist.rst000066400000000000000000000005561260133235600227700ustar00rootroot00000000000000Wunderlist ========== Wunderlist uses OAuth v2 for Authentication. - Register a new application at `Wunderlist Developer Portal`_, and - fill ``Client Id`` and ``Client Secret`` values in the settings:: SOCIAL_AUTH_WUNDERLIST_KEY = '' SOCIAL_AUTH_WUNDERLIST_SECRET = '' .. _Wunderlist Developer Portal: https://developer.wunderlist.com/applications python-social-auth-0.2.13/docs/backends/xing.rst000066400000000000000000000005201260133235600215240ustar00rootroot00000000000000XING ==== XING uses OAuth1 for their auth mechanism, in order to enable the backend follow: - Register a new application at `XING Apps Dashboard`_, - Fill **Consumer Key** and **Consumer Secret** values:: SOCIAL_AUTH_XING_KEY = '' SOCIAL_AUTH_XING_SECRET = '' .. _XING Apps Dashboard: https://dev.xing.com/applications python-social-auth-0.2.13/docs/backends/yahoo.rst000066400000000000000000000014221260133235600217000ustar00rootroot00000000000000Yahoo ===== Yahoo supports OpenId and OAuth2 for their auth flow. Yahoo OpenId ------------ OpenId doesn't require any particular configuration beside enabling the backend in the ``AUTHENTICATION_BACKENDS`` setting:: AUTHENTICATION_BACKENDS = ( ... 'social.backends.yahoo.YahooOpenId', ... ) Yahoo OAuth2 ------------ OAuth 2.0 workflow, useful if you are planning to use Yahoo's API. - Register a new application at `Yahoo Developer Center`_, set your app domain and configure scopes (they can't be overriden by application). - Fill ``Consumer Key`` and ``Consumer Secret`` values in the settings:: SOCIAL_AUTH_YAHOO_OAUTH2_KEY = '' SOCIAL_AUTH_YAHOO_OAUTH2_SECRET = '' .. _Yahoo Developer Center: https://developer.yahoo.com/ python-social-auth-0.2.13/docs/backends/yammer.rst000066400000000000000000000011511260133235600220520ustar00rootroot00000000000000Yammer ====== Yammer users OAuth2 for their auth mechanism, this application supports Yammer OAuth2 in production and staging modes. Production Mode --------------- In order to enable the backend, follow: - Register an application at `Client Applications`_ - Fill **Client Key** and **Client Secret** settings:: SOCIAL_AUTH_YAMMER_KEY = '...' SOCIAL_AUTH_YAMMER_SECRET = '...' Staging Mode ------------ Staging mode is configured the same as ``Production Mode``, but settings are prefixed with:: SOCIAL_AUTH_YAMMER_STAGING_* .. _Client Applications: https://www.yammer.com/client_applications python-social-auth-0.2.13/docs/backends/zotero.rst000066400000000000000000000012371260133235600221070ustar00rootroot00000000000000Zotero ====== Zotero implements OAuth1 as their authentication mechanism for their Web API v3. 1. Go to the `Zotero app registration page`_ to register your application. 2. Fill the **Client ID** and **Client Secret** in your project settings:: SOCIAL_AUTH_ZOTERO_KEY = '...' SOCIAL_AUTH_ZOTERO_SECRET = '...' 3. Enable the backend:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( ... 'social.backends.zotero.ZoteroOAuth', ... ) Further documentation at `Zotero Web API v3 page`_. .. _Zotero app registration page: https://www.zotero.org/oauth/apps .. _Zotero Web API v3 page: https://www.zotero.org/support/dev/web_api/v3/start python-social-auth-0.2.13/docs/conf.py000066400000000000000000000013331260133235600175550ustar00rootroot00000000000000# -*- coding: utf-8 -*- extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode'] templates_path = ['_templates'] source_suffix = '.rst' master_doc = 'index' project = u'Python Social Auth' copyright = u'2012, Matías Aguirre' exclude_patterns = ['_build'] pygments_style = 'sphinx' html_theme = 'nature' html_static_path = [] htmlhelp_basename = 'PythonSocialAuthdoc' latex_documents = [ ('index', 'PythonSocialAuth.tex', u'Python Social Auth Documentation', u'Matías Aguirre', 'manual'), ] man_pages = [ ('index', 'pythonsocialauth', u'Python Social Auth Documentation', [u'Matías Aguirre'], 1) ] intersphinx_mapping = {'http://docs.python.org/': None} python-social-auth-0.2.13/docs/configuration/000077500000000000000000000000001260133235600211255ustar00rootroot00000000000000python-social-auth-0.2.13/docs/configuration/cherrypy.rst000066400000000000000000000046071260133235600235330ustar00rootroot00000000000000CherryPy Framework ================== CherryPy framework is supported, it works but I'm sure there's room for improvements. The implementation uses SQLAlchemy as ORM and expects some values accessible on ``cherrypy.request`` for it to work. At the moment the configuration is expected on ``cherrypy.config`` but ideally it should be an application configuration instead. Expected values are: ``cherrypy.request.user`` Current logged in user, load it in your application on a ``before_handler`` handler. ``cherrypy.request.db`` Current database session, again, load it in your application on a ``before_handler``. Dependencies ------------ The `CherryPy built-in application` depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Enabling the application ------------------------ The application is defined on ``social.apps.cherrypy_app.views.CherryPyPSAViews``, register it in the preferred way for your project. Check the rest of the docs for the other settings like enabling authentication backends and backends keys. Models Setup ------------ The models are located in ``social.apps.cherrypy_app.models``. A reference to your ``User`` model is required to be defined in the project settings, it should be an import path, for example:: cherrypy.config.update({ 'SOCIAL_AUTH_USER_MODEL': 'models.User' }) Login mechanism --------------- By default the application sets the session value ``user_id``, this is a simple solution and it should be improved, if you want to provider your own login mechanism you can do it by defining the ``SOCIAL_AUTH_LOGIN_METHOD`` setting, it should be an import path to a callable, like this:: SOCIAL_AUTH_USER_MODEL = 'app.login_user' And an example of this function:: def login_user(strategy, user): strategy.session_set('user_id', user.id) Then, ensure to load the user in your application at ``cherrypy.request.user``, for example:: def load_user(): user_id = cherrypy.session.get('user_id') if user_id: cherrypy.request.user = cherrypy.request.db.query(User).get(user_id) else: cherrypy.request.user = None cherrypy.tools.authenticate = cherrypy.Tool('before_handler', load_user) .. _CherryPy built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/cherrypy_app .. _sqlalchemy: http://www.sqlalchemy.org/ python-social-auth-0.2.13/docs/configuration/django.rst000066400000000000000000000154251260133235600231300ustar00rootroot00000000000000Django Framework ================ Django framework has a little more support since this application was derived from `django-social-auth`_. Here are some details on configuring this application on Django. Register the application ------------------------ The `Django built-in app`_ comes with two ORMs, one for default Django ORM and another for MongoEngine_ ORM. Add the application to ``INSTALLED_APPS`` setting, for default ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.default', ... ) And for MongoEngine_ ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.me', ... ) Also ensure to define the MongoEngine_ storage setting:: SOCIAL_AUTH_STORAGE = 'social.apps.django_app.me.models.DjangoStorage' Database -------- (For Django 1.7 and higher) sync database to create needed models:: ./manage.py makemigrations If you're still using South, you'll need override SOUTH_MIGRATION_MODULES_:: SOUTH_MIGRATION_MODULES = { 'default': 'social.apps.django_app.default.south_migrations' } Note that Django's app labels take the last part of the import, so in this case ``social.apps.django_app.default`` becomes ``default`` here. Sync database to create needed models:: ./manage.py syncdb Authentication backends ----------------------- Add desired authentication backends to Django's AUTHENTICATION_BACKENDS_ setting:: AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', ... 'django.contrib.auth.backends.ModelBackend', ) Take into account that backends **must** be defined in AUTHENTICATION_BACKENDS_ or Django won't pick them when trying to authenticate the user. Don't miss ``django.contrib.auth.backends.ModelBackend`` if using ``django.contrib.auth`` application or users won't be able to login by username / password method. URLs entries ------------ Add URLs entries:: urlpatterns = patterns('', ... url('', include('social.apps.django_app.urls', namespace='social')) ... ) In case you need a custom namespace, this setting is also needed:: SOCIAL_AUTH_URL_NAMESPACE = 'social' Template Context Processors --------------------------- There's a context processor that will add backends and associations data to template context:: TEMPLATE_CONTEXT_PROCESSORS = ( ... 'social.apps.django_app.context_processors.backends', 'social.apps.django_app.context_processors.login_redirect', ... ) ``backends`` context processor will load a ``backends`` key in the context with three entries on it: ``associated`` It's a list of ``UserSocialAuth`` instances related with the currently logged in user. Will be empty if there's no current user. ``not_associated`` A list of available backend names not associated with the current user yet. If there's no user logged in, it will be a list of all available backends. ``backends`` A list of all available backend names. ORMs ---- As detailed above the built-in Django application supports default ORM and MongoEngine_ ORM. When using MongoEngine_ make sure you've followed the instructions for `MongoEngine Django integration`_, as you're now utilizing that user model. The `MongoEngine_` backend was developed and tested with version 0.6.10 of `MongoEngine_`. Alternate storage models implementations currently follow a tight pattern of models that behave near or identical to Django ORM models. It is currently not decoupled from this pattern by any abstraction layer. If you would like to implement your own alternate, please see the ``social.apps.django_app.default.models`` and ``social.apps.django_app.me.models`` modules for guidance. Exceptions Middleware --------------------- A base middleware is provided that handles ``SocialAuthBaseException`` by providing a message to the user via the Django messages framework, and then responding with a redirect to a URL defined in one of the middleware methods. The middleware is at ``social.apps.django_app.middleware.SocialAuthExceptionMiddleware``. Any method can be overrided but for simplifications these two are the recommended:: get_message(request, exception) get_redirect_uri(request, exception) By default, the message is the exception message and the URL for the redirect is the location specified by the ``LOGIN_ERROR_URL`` setting. If a valid backend was detected by ``strategy()`` decorator, it will be available at ``request.strategy.backend`` and ``process_exception()`` will use it to build a backend-dependent redirect URL but fallback to default if not defined. Exception processing is disabled if any of this settings is defined with a ``True`` value:: _SOCIAL_AUTH_RAISE_EXCEPTIONS = True SOCIAL_AUTH_RAISE_EXCEPTIONS = True RAISE_EXCEPTIONS = True DEBUG = True The redirect destination will get two ``GET`` parameters: ``message = ''`` Message from the exception raised, in some cases it's the message returned by the provider during the auth process. ``backend = ''`` Backend name that was used, if it was a valid backend. Django Admin ------------ The default application (not the MongoEngine_ one) contains an ``admin.py`` module that will be auto-discovered by the usual mechanism. But, by the nature of the application which depends on the existence of a user model, it's easy to fall in a recursive import ordering making the application fail to load. This happens because the admin module will build a set of fields to populate the ``search_fields`` property to search for related users in the administration UI, but this requires the user model to be retrieved which might not be defined at that time. To avoid this issue define the following setting to circumvent the import error:: SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['field1', 'field2'] For example:: SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['username', 'first_name', 'email'] The fields listed **must** be user models fields. .. _MongoEngine: http://mongoengine.org .. _MongoEngine Django integration: http://mongoengine-odm.readthedocs.org/en/latest/django.html .. _django-social-auth: https://github.com/omab/django-social-auth .. _Django built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _AUTHENTICATION_BACKENDS: http://docs.djangoproject.com/en/dev/ref/settings/?from=olddocs#authentication-backends .. _django@dc43fbc: https://github.com/django/django/commit/dc43fbc2f21c12e34e309d0e8a121020391aa03a .. _SOUTH_MIGRATION_MODULES: http://south.readthedocs.org/en/latest/settings.html#south-migration-modules python-social-auth-0.2.13/docs/configuration/flask.rst000066400000000000000000000113731260133235600227640ustar00rootroot00000000000000Flask Framework =============== Flask reusable applications are tricky (or I'm not capable enough). Here are details on how to enable this application on Flask. Dependencies ------------ The `Flask built-in app` depends on sqlalchemy_, there's initial support for MongoEngine_ ORM too (check below for more details). Enabling the application ------------------------ The applications define a `Flask Blueprint`_, which needs to be registered once the Flask app is configured by:: from social.apps.flask_app.routes import social_auth app.register_blueprint(social_auth) For MongoEngine_ you need this setting:: SOCIAL_AUTH_STORAGE = 'social.apps.flask_app.me.models.FlaskStorage' Models Setup ------------ At the moment the models for python-social-auth_ are defined inside a function because they need the reference to the current db instance and the User model used on your project (check *User model reference* below). Once the Flask app and the database are defined, call ``init_social`` to register the models:: from social.apps.flask_app.default.models import init_social init_social(app, db) For MongoEngine_:: from social.apps.flask_app.me.models import init_social init_social(app, db) So far I wasn't able to find another way to define the models on another way rather than making it as a side-effect of calling this function since the database is not available and ``current_app`` cannot be used on init time, just run time. User model reference -------------------- The application keeps a reference to the User model used by your project, define it by using this setting:: SOCIAL_AUTH_USER_MODEL = 'foobar.models.User' The value must be the import path to the User model. Global user ----------- The application expects the current logged in user accesible at ``g.user``, define a handler like this to ensure that:: @app.before_request def global_user(): g.user = get_current_logged_in_user Flask-Login ----------- The application works quite well with Flask-Login_, ensure to have some similar handlers to these:: @login_manager.user_loader def load_user(userid): try: return User.query.get(int(userid)) except (TypeError, ValueError): pass @app.before_request def global_user(): g.user = login.current_user # Make current user available on templates @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} Remembering sessions -------------------- The users session can be remembered when specified on login. The common implementation for this feature is to pass a parameter from the login form (``remember_me``, ``keep``, etc), to flag the action. Flask-Login_ will mark the session as persistent if told so. python-social-auth_ will check for a given name (``keep``) by default, but since providers won't pass parameters back to the application, the value must be persisted in the session before the authentication process happens. So, the following setting is required for this to work:: SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['keep'] It's possible to override the default name with this setting:: SOCIAL_AUTH_REMEMBER_SESSION_NAME = 'remember_me' Don't use the value ``remember`` since that will clash with Flask-Login_ which pops the value from the session. Then just pass the parameter ``keep=1`` as a GET or POST parameter. Exceptions handling ------------------- The Django application has a middleware (that fits in the framework architecture) to facilitate the different exceptions_ handling raised by python-social-auth_. The same can be accomplished (even on a simpler way) in Flask by defining an errorhandler_. For example the next code will redirect any social-auth exception to a ``/socialerror`` URL:: from social.exceptions import SocialAuthBaseException @app.errorhandler(500) def error_handler(error): if isinstance(error, SocialAuthBaseException): return redirect('/socialerror') Be sure to set your debug and test flags to ``False`` when testing this on your development environment, otherwise the exception will be raised and error handlers won't be called. .. _Flask Blueprint: http://flask.pocoo.org/docs/blueprints/ .. _Flask-Login: https://github.com/maxcountryman/flask-login .. _python-social-auth: https://github.com/omab/python-social-auth .. _Flask built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _sqlalchemy: http://www.sqlalchemy.org/ .. _exceptions: https://github.com/omab/python-social-auth/blob/master/social/exceptions.py .. _errorhandler: http://flask.pocoo.org/docs/api/#flask.Flask.errorhandler .. _MongoEngine: http://mongoengine.org python-social-auth-0.2.13/docs/configuration/index.rst000066400000000000000000000010101260133235600227560ustar00rootroot00000000000000Configuration ============= All the apps share the settings names, some settings for Django framework are special (like ``AUTHENTICATION_BACKENDS``). Below there's a main settings document detailing the configuration and they purpose, plus sections detailed for each framework and they particularities. Support for more frameworks will be added in the future, pull-requests are very welcome. Contents: .. toctree:: :maxdepth: 2 settings django flask pyramid cherrypy webpy porting_from_dsa python-social-auth-0.2.13/docs/configuration/porting_from_dsa.rst000066400000000000000000000120351260133235600252140ustar00rootroot00000000000000Porting from django-social-auth =============================== Being a derivative work from django-social-auth_, porting from it to python-social-auth_ should be an easy task. Porting to others libraries usually is a pain, I'm trying to make this as easy as possible. Installed apps -------------- On django-social-auth_ there was a single application to add into ``INSTALLED_APPS`` plus a setting to define which ORM to be used (default or MongoEngine). Now the apps are split and there's not need for that extra setting. When using the default ORM:: INSTALLED_APPS = ( ... 'social.apps.django_app.default', ... ) And when using MongoEngine:: INSTALLED_APPS = ( ... 'social.apps.django_app.me', ... ) The models table names were defined to be compatible with those used on django-social-auth_, so data is not needed to be migrated. URLs ---- The URLs are namespaced, you can chose your namespace, the `example app`_ uses the ``social`` namespace. Replace the old include with:: urlpatterns = patterns('', ... url('', include('social.apps.django_app.urls', namespace='social')) ... ) On templates use a namespaced URL:: {% url 'social:begin' "google-oauth2" %} Account disconnection URL would be:: {% url 'social:disconnect_individual' provider, id %} Porting settings ---------------- All python-social-auth_ settings are prefixed with ``SOCIAL_AUTH_``, except for some exception on Django framework, ``AUTHENTICATION_BACKENDS`` remains the same for obvious reasons. All backends settings have the backend name into it, all uppercase and with dashes replaced with underscores, take for instance Google OAuth2 backend is named ``google-oauth2``, any setting name related to that backend should start with ``SOCIAL_AUTH_GOOGLE_OAUTH2_``. Keys and secrets are some mandatory settings needed for OAuth providers, to keep consistency the names follow the same naming convention ``*_KEY`` for the application key, and ``*_SECRET`` for the secret. OAuth1 backends use to have ``CONSUMER`` in the setting name, not anymore. Following with the Google OAuth2 example:: SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = '...' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = '...' Remember that the name of the backend is needed in the settings, and names differ a little from backend to backend, like `Facebook OAuth2 backend`_ name is ``facebook``. So the settings should be:: SOCIAL_AUTH_FACEBOOK_KEY = '...' SOCIAL_AUTH_FACEBOOK_SECRET = '...' Authentication backends ----------------------- Import path for authentication backends changed a little, there's no more ``contrib`` module, there's no need for it. Some backends changed the names to have some consistency, check the backends, it should be easy to track the names changes. Examples of the new import paths:: AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.facebook.FacebookOAuth2', ) Session ------- Django stores the last authentication backend used in the user session as an import path, this can cause import troubles when porting since the old import paths aren't valid anymore. Some solutions to this problem are: 1. Clean the session and force the users to login again in your site 2. Run a migration script that will update the authentication backend session value for each session in your database. This implies figuring out the new import path for each backend you have configured, which is the value used in ``AUTHENTICATION_BACKENDS`` setting. `@tomgruner`_ created a Gist here_ that updates the value just for Facebook backend. A ``template`` for this script would look like this:: from django.contrib.sessions.models import Session BACKENDS = { 'social_auth.backends.facebook.FacebookBackend': 'social.backends.facebook.FacebookOAuth2' } for sess in Session.objects.iterator(): session_dict = sess.get_decoded() if '_auth_user_backend' in session_dict.keys(): # Change old backend import path from new backend import path if session_dict['_auth_user_backend'].startswith('social_auth'): session_dict['_auth_user_backend'] = BACKENDS[session_dict['_auth_user_backend']] new_sess = Session.objects.save(sess.session_key, session_dict, sess.expire_date) print 'New session saved {}'.format(new_sess.pk) .. _django-social-auth: https://github.com/omab/django-social-auth .. _python-social-auth: https://github.com/omab/python-social-auth .. _example app: https://github.com/omab/python-social-auth/blob/master/examples/django_example/example/urls.py#L17 .. _Facebook OAuth2 backend: https://github.com/omab/python-social-auth/blob/master/social/backends/facebook.py#L29 .. _@tomgruner: https://github.com/tomgruner .. _here: https://gist.github.com/tomgruner/5ce8bb1f4c55d17b5b25 python-social-auth-0.2.13/docs/configuration/pyramid.rst000066400000000000000000000110151260133235600233220ustar00rootroot00000000000000Pyramid Framework ================= Pyramid_ reusable applications are tricky (or I'm not capable enough). Here are details on how to enable this application on Pyramid. Dependencies ------------ The `Pyramid built-in app`_ depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Enabling the application ------------------------ The application can be scanned by ``Configurator.scan()``, also it defines an ``includeme()`` in the ``__init__.py`` file which will add the needed routes to your application configuration. To scan it just add:: config.include('social.apps.pyramid_app') config.scan('social.apps.pyramid_app') Models Setup ------------ At the moment the models for python-social-auth_ are defined inside a function because they need the reference to the current DB instance and the User model used on your project (check *User model reference* below). Once the Pyramid application configuration and database are defined, call ``init_social`` to register the models:: from social.apps.pyramid_app.models import init_social init_social(config, Base, DBSession) So far I wasn't able to find another way to define the models on another way rather than making it as a side-effect of calling this function since the database is not available and ``current_app`` cannot be used on initialization time, just run time. User model reference -------------------- The application keeps a reference to the User model used by your project, define it by using this setting:: SOCIAL_AUTH_USER_MODEL = 'foobar.models.User' The value must be the import path to the User model. Global user ----------- The application expects the current logged in user accessible at ``request.user``, the example application ensures that with this hander:: def get_user(request): user_id = request.session.get('user_id') if user_id: user = DBSession.query(User)\ .filter(User.id == user_id)\ .first() else: user = None return user The handler is added to the configuration doing:: config.add_request_method('example.auth.get_user', 'user', reify=True) This is just a simple example, probably your project does it in a better way. User login ---------- Since the application doesn't make any assumption on how you are going to login the users, you need to specify it. In order to do that, define these settings:: SOCIAL_AUTH_LOGIN_FUNCTION = 'example.auth.login_user' SOCIAL_AUTH_LOGGEDIN_FUNCTION = 'example.auth.login_required' The first one must accept the strategy used and the user instance that was created or retrieved from the database, there you can set the user id in the session or cookies or whatever place used later to retrieve the id again and load the user from the database (check the snippet above in *Global User*). The second one is used to ensure that there's a user logged in when calling the disconnect view. It must accept a ``User`` instance and return ``True`` or ``Flase``. Check the auth.py_ in the example application for details on how it's done there. Social auth in templates context -------------------------------- To access the social instances related to a user in the template context, you can do so by accessing the ``social_auth`` attribute in the user instance::
  • ${social.provider}
  • Also you can add the backends (associated and not associated to a user) by enabling this context function in your project:: from pyramid.events import subscriber, BeforeRender from social.apps.pyramid_app.utils import backends @subscriber(BeforeRender) def add_social(event): request = event['request'] event.update(backends(request, request.user)) That will load a dict with entries:: { 'associated': [...], 'not_associated': [...], 'backends': [...] } The ``associated`` key will have all the associated ``UserSocialAuth`` instances related to the given user. ``not_associated`` will have the backends names not associated and backends will have all the enabled backends names. .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _python-social-auth: https://github.com/omab/python-social-auth .. _Pyramid built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/pyramid_app .. _sqlalchemy: http://www.sqlalchemy.org/ .. _auth.py: https://github.com/omab/python-social-auth/blob/master/examples/pyramid_example/example/auth.py python-social-auth-0.2.13/docs/configuration/settings.rst000066400000000000000000000301361260133235600235220ustar00rootroot00000000000000Configuration ============= Application setup ----------------- Once the application is installed (check Installation_) define the following settings to enable the application behavior. Also check the sections dedicated to each framework for detailed instructions. Settings name ------------- Almost all settings are prefixed with ``SOCIAL_AUTH_``, there are some exceptions for Django framework like ``AUTHENTICATION_BACKENDS``. All settings can be defined per-backend by adding the backend name to the setting name like ``SOCIAL_AUTH_TWITTER_LOGIN_URL``. Settings discovery is done by reducing the name starting with backend setting, then app setting and finally global setting, for example:: SOCIAL_AUTH_TWITTER_LOGIN_URL SOCIAL_AUTH_LOGIN_URL LOGIN_URL The backend name is generated from the ``name`` attribute from the backend class by uppercasing it and replacing ``-`` with ``_``. Keys and secrets ---------------- - Setup needed OAuth keys (see OAuth_ section for details):: SOCIAL_AUTH_TWITTER_KEY = 'foobar' SOCIAL_AUTH_TWITTER_SECRET = 'bazqux' OpenId backends don't require keys usually, but some need some API Key to call any API on the provider. Check Backends_ sections for details. Authentication backends ----------------------- Register the backends you plan to use, on Django framework use the usual ``AUTHENTICATION_BACKENDS`` settings, for others, define ``SOCIAL_AUTH_AUTHENTICATION_BACKENDS``:: SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', ... ) URLs options ------------ These URLs are used on different steps of the auth process, some for successful results and others for error situations. ``SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/logged-in/'`` Used to redirect the user once the auth process ended successfully. The value of ``?next=/foo`` is used if it was present ``SOCIAL_AUTH_LOGIN_ERROR_URL = '/login-error/'`` URL where the user will be redirected in case of an error ``SOCIAL_AUTH_LOGIN_URL = '/login-url/'`` Is used as a fallback for ``LOGIN_ERROR_URL`` ``SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/new-users-redirect-url/'`` Used to redirect new registered users, will be used in place of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` if defined. Note that ``?next=/foo`` is appended if present, if you want new users to go to next, you'll need to do it yourself. ``SOCIAL_AUTH_NEW_ASSOCIATION_REDIRECT_URL = '/new-association-redirect-url/'`` Like ``SOCIAL_AUTH_NEW_USER_REDIRECT_URL`` but for new associated accounts (user is already logged in). Used in place of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` ``SOCIAL_AUTH_DISCONNECT_REDIRECT_URL = '/account-disconnected-redirect-url/'`` The user will be redirected to this URL when a social account is disconnected ``SOCIAL_AUTH_INACTIVE_USER_URL = '/inactive-user/'`` Inactive users can be redirected to this URL when trying to authenticate. Successful URLs will default to ``SOCIAL_AUTH_LOGIN_URL`` while error URLs will fallback to ``SOCIAL_AUTH_LOGIN_ERROR_URL``. User model ---------- ``UserSocialAuth`` instances keep a reference to the ``User`` model of your project, since this is not known, the ``User`` model must be configured by a setting:: SOCIAL_AUTH_USER_MODEL = 'foo.bar.User' ``User`` model must have a ``username`` and ``email`` field, these are required. Also an ``is_authenticated`` and ``is_active`` boolean flags are recommended, these can be methods if necessary (must return ``True`` or ``False``). If the model lacks them a ``True`` value is assumed. Tweaking some fields length --------------------------- Some databases impose limitations to indexes columns (like MySQL InnoDB), these limitations won't play nice on some ``UserSocialAuth`` fields. To avoid such error define some of the following settings. ``SOCIAL_AUTH_UID_LENGTH = `` Used to define the max length of the field `uid`. A value of 223 should work when using MySQL InnoDB which impose a 767 bytes limit (assuming UTF-8 encoding). ``SOCIAL_AUTH_NONCE_SERVER_URL_LENGTH = `` ``Nonce`` model has a unique constraint over ``('server_url', 'timestamp', 'salt')``, salt has a max length of 40, so ``server_url`` length must be tweaked using this setting. ``SOCIAL_AUTH_ASSOCIATION_SERVER_URL_LENGTH = `` or ``SOCIAL_AUTH_ASSOCIATION_HANDLE_LENGTH = `` ``Association`` model has a unique constraint over ``('server_url', 'handle')``, both fields lengths can be tweaked by these settings. Username generation ------------------- Some providers return an username, others just an Id or email or first and last names. The application tries to build a meaningful username when possible but defaults to generating one if needed. An UUID is appended to usernames in case of collisions. Here are some settings to control usernames generation. ``SOCIAL_AUTH_UUID_LENGTH = 16`` This controls the length of the UUID appended to usernames. ``SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = True`` If you want to use the full email address as the ``username``, define this setting. ``SOCIAL_AUTH_SLUGIFY_USERNAMES = False`` For those that prefer slugged usernames, the ``get_username`` pipeline can apply a slug transformation (code borrowed from Django project) by defining this setting to ``True``. The feature is disabled by default to to not force this option to all projects. ``SOCIAL_AUTH_CLEAN_USERNAMES = True`` By default the regex ``r'[^\w.@+-_]+'`` is applied over usernames to clean them from usual undesired characters like spaces. Set this setting to ``False`` to disable this behavior. Extra arguments on auth processes --------------------------------- Some providers accept particular GET parameters that produce different results during the auth process, usually used to show different dialog types (mobile version, etc). You can send extra parameters on auth process by defining settings per backend, example to request Facebook to show Mobile authorization page, define:: FACEBOOK_AUTH_EXTRA_ARGUMENTS = {'display': 'touch'} For other providers, just define settings in the form:: _AUTH_EXTRA_ARGUMENTS = {...} Also, you can send extra parameters on request token process by defining settings in the same way explained above but with this other suffix:: _REQUEST_TOKEN_EXTRA_ARGUMENTS = {...} Basic information is requested to the different providers in order to create a coherent user instance (with first and last name, email and full name), this could be too intrusive for some sites that want to ask users the minimum data possible. It's possible to override the default values requested by defining any of the following settings, for Open Id providers:: SOCIAL_AUTH__IGNORE_DEFAULT_AX_ATTRS = True SOCIAL_AUTH__AX_SCHEMA_ATTRS = [ (schema, alias) ] For OAuth backends:: SOCIAL_AUTH__IGNORE_DEFAULT_SCOPE = True SOCIAL_AUTH__SCOPE = [ ... ] Processing redirects and urlopen -------------------------------- The application issues several redirects and API calls, this following settings allow some tweaks to the behavior of these. ``SOCIAL_AUTH_SANITIZE_REDIRECTS = False`` The auth process finishes with a redirect, by default it's done to the value of ``SOCIAL_AUTH_LOGIN_REDIRECT_URL`` but can be overridden with ``next`` GET argument. If this settings is ``True``, this application will very the domain of the final URL and only redirect to it if it's on the same domain. ``SOCIAL_AUTH_REDIRECT_IS_HTTPS = False`` On projects behind a reverse proxy that uses HTTPS, the redirect URIs can became with the wrong schema (``http://`` instead of ``https://``) when the request lacks some headers, and might cause errors with the auth process, to force HTTPS in the final URIs set this setting to ``True`` ``SOCIAL_AUTH_URLOPEN_TIMEOUT = 30`` Any ``urllib2.urlopen`` call will be performed with the default timeout value, to change it without affecting the global socket timeout define this setting (the value specifies timeout seconds). ``urllib2.urlopen`` uses ``socket.getdefaulttimeout()`` value by default, so setting ``socket.setdefaulttimeout(...)`` will affect ``urlopen`` when this setting is not defined, otherwise this setting takes precedence. Also this might affect other places in Django. ``timeout`` argument was introduced in python 2.6 according to `urllib2 documentation`_ Whitelists ---------- Registration can be limited to a set of users identified by their email address or domain name. To white-list just set any of these settings: ``SOCIAL_AUTH__WHITELISTED_DOMAINS = ['foo.com', 'bar.com']`` Supply a list of domain names to be white-listed. Any user with an email address on any of the allowed domains will login successfully, otherwise ``AuthForbidden`` is raised. ``SOCIAL_AUTH__WHITELISTED_EMAILS = ['me@foo.com', 'you@bar.com']`` Supply a list of email addresses to be white-listed. Any user with an email address in this list will login successfully, otherwise ``AuthForbidden`` is raised. Miscellaneous settings ---------------------- ``SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['email',]`` During the pipeline process a ``dict`` named ``details`` will be populated with the needed values to create the user instance, but it's also used to update the user instance. Any value in it will be checked as an attribute in the user instance (first by doing ``hasattr(user, name)``). Usually there are attributes that cannot be updated (like ``username``, ``id``, ``email``, etc.), those fields need to be *protect*. Set any field name that requires *protection* in this setting, and it won't be updated. ``SOCIAL_AUTH_SESSION_EXPIRATION = False`` By default, user session expiration time will be set by your web framework (in Django, for example, it is set with `SESSION_COOKIE_AGE`_). Some providers return the time that the access token will live, which is stored in ``UserSocialAuth.extra_data`` under the key ``expires``. Changing this setting to True will override your web framework's session length setting and set user session lengths to match the ``expires`` value from the auth provider. ``SOCIAL_AUTH_OPENID_PAPE_MAX_AUTH_AGE = `` Enable `OpenID PAPE`_ extension support by defining this setting. ``SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['foo',]`` If you want to store extra parameters from POST or GET in session, like it was made for ``next`` parameter, define this setting with the parameter names. In this case ``foo`` field's value will be stored when user follows this link ``...``. ``SOCIAL_AUTH_PASSWORDLESS = False`` When this setting is ``True`` and ``social.pipeline.mail.send_validation`` is enabled, it allows the implementation of a `passwordless authentication mechanism`_. Example of this implementation can be found at psa-passwordless_. Account disconnection --------------------- Disconnect is an side-effect operation and should be done by POST method only, some CSRF protection is encouraged (and enforced on Django app). Ensure that any call to `/disconnect//` or `/disconnect///` is done using POST. .. _urllib2 documentation: http://docs.python.org/library/urllib2.html#urllib2.urlopen .. _OpenID PAPE: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html .. _Installation: ../installing.html .. _Backends: ../backends/index.html .. _OAuth: http://oauth.net/ .. _passwordless authentication mechanism: https://medium.com/@ninjudd/passwords-are-obsolete-9ed56d483eb .. _psa-passwordless: https://github.com/omab/psa-passwordless .. _SESSION_COOKIE_AGE: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-SESSION_COOKIE_AGE python-social-auth-0.2.13/docs/configuration/webpy.rst000066400000000000000000000036321260133235600230110ustar00rootroot00000000000000Webpy Framework =============== Webpy_ framework is easy to setup, once that python-social-auth_ is installed or accessible in the ``PYTHONPATH``, just add the needed configurations to make it run. Dependencies ------------ The `Webpy built-in app` depends on sqlalchemy_, there's no support for others ORMs yet but pull-requests are welcome. Configuration ------------- Add the needed settings into ``web.config`` store. Settings are prefixed with ``SOCIAL_AUTH_`` but there's a helper for it:: from social.utils import setting_name web.config[setting_name('USER_MODEL')] = 'models.User' web.config[setting_name('LOGIN_REDIRECT_URL')] = '/done/' web.config[setting_name('AUTHENTICATION_BACKENDS')] = ( 'social.backends.google.GoogleOAuth2', ... ) Add all the settings needed for the app (check Configuration_ section for details). URLs ---- Add the social application into URLs:: from social.apps.webpy_app import app as social_app urls = ( ... '', social_app.app_social ... ) Session ------- python-social-auth_ depends on sessions storage to keep some essential values, usually redirects and ``state`` parameters used to validate authentication process on OAuth providers. The `Webpy built-in app` expects the session reference to be available under ``web.web_session`` so ensure it's available there. User model ---------- Like the other apps, the User model must be defined on settings since a reference to it is kept on ``UserSocialAuth`` instance. Define like this:: web.config[setting_name('USER_MODEL')] = 'models.User' Where the value is the import path to the User model used on your project. .. _python-social-auth: https://github.com/omab/python-social-auth .. _Webpy: http://webpy.org/ .. _Webpy built-in app: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _sqlalchemy: http://www.sqlalchemy.org/ python-social-auth-0.2.13/docs/copyright.rst000066400000000000000000000007611260133235600210240ustar00rootroot00000000000000Copyrights and Licence ====================== ``python-social-auth`` is protected by BSD licence. Check the LICENCE_ for details. The base work was derived from django-social-auth_ work and copyrighted too, check `django-social-auth LICENCE`_ for details: .. _LICENCE: https://github.com/omab/python-social-auth/blob/master/LICENSE .. _django-social-auth: https://github.com/omab/django-social-auth .. _django-social-auth LICENCE: https://github.com/omab/django-social-auth/blob/master/LICENSE python-social-auth-0.2.13/docs/developer_intro.rst000066400000000000000000000153551260133235600222210ustar00rootroot00000000000000Beginners Guide =============== This is an attempt to bring together a number of concepts in python-social-auth (psa) so that you will understand how it fits into your system. This definitely has a Django flavor to it (because that's how I learned it). Understanding PSA URLs ----------------------- If you have not seen namespaced URLs before, you are about to be introduced. When you add the PSA entry to your urls.py, it looks like this:: url(r'', include('social.apps.django_app.urls', namespace='social')) that "namespace" part on the end is what keeps the names in the PSA-world from colliding with the names in your app, or other 3rd-party apps. So your login link will look like this:: Login (See how "social" in the URL mapping matches the value of "namespace" in the urls.py entry?) Understanding Backends ---------------------- PSA implements a lot of backends. Find the entry in the docs for your backend, and if it's there, follow the steps to enable it, which come down to 1) Set up SOCIAL_AUTH_{backend} variables in settings.py. (The settings vary, based on the backends) 2) Adding your backend to AUTHENTICATION_BACKENDS in settings.py. If you need to implement a different backend (for instance, let's say you want to use Intuit's OpenID), you can subclass the nearest one and override the "name" attribute:: from social.backends.open_id import OpenIDAuth class IntuitOpenID(OpenIDAuth): name = 'intuit' And then add your new backend to AUTHENTICATION_BACKENDS in settings.py. A couple notes about the pipeline: The standard pipeline does not log the user in until after the pipeline has completed. So if you get a value in the user key of the accumulative dictionary, that implies that the user was logged in when the process started. Understanding the Pipeline -------------------------- Reversing a URL like ``{% url 'social:begin' 'github' %}`` will give you a url like:: http://example.com/login/github And clicking on that link will cause the "pipeline" to be started. The pipeline is a list of functions that build up data about the user as we go through the steps of the authentication process. (If you really want to understand the pipeline, look at the source in ``social/backends/base.py``, and see the ``run_pipeline()`` function in ``BaseAuth``.) The design contract for each function in the pipeline is: 1) The pipeline starts with a four-item dictionary (the accumulative dictionary) which is updated with the results of each function in the pipeline. The initial four values are: ``strategy`` contains a strategy object ``backend`` contains the backend being used during this pipeline run ``request`` contains a dictionary of the request keys. Note to Django users -- this is not an HttpRequest object, it is actually the results of ``request.REQUEST``. ``details`` which is an empty dict. 2) If the function returns a dictionary or something False-ish, add the contents of the dictionary to an accumulative dictionary (called ``out`` in ``run_pipeline``), and call the next step in the pipeline with the accumulative dictionary. 3) If something else is returned (for example, a subclass of ``HttpResponse``), then return that to the browser. 4) If the pipeline completes, *THEN* the user is authenticated (logged in). So if you are finding an authenticated user object while the pipeline is running, that means that the user was logged in when the pipeline started. There is one pipeline for your site as a whole -- if you have backend-specific logic, you have to make your pipeline steps smart enough to skip the step if it is not relevant. This is as simple as:: def my_custom_step(strategy, backend, request, details, *args, **kwargs): if backend_name != 'my_custom_backend': return # otherwise, do the special steps for your custom backend Interrupting the Pipeline (and communicating with views) --------------------------------------------------------- Let's say you want to add a custom step in the pipeline -- you want the user to establish a password so that they can come directly to your site in the future. We can do that with the @partial decorator, which tells the pipeline to keep track of where it is so that it can be restarted. The first thing we need to do is set up a way for our views to communicate with the pipeline. That is done by adding a value to the settings file to tell us which values should be passed back and forth between the Django session and the pipeline:: SOCIAL_AUTH_FIELDS_STORED_IN_SESSION = ['local_password',] In our pipeline code, we would have:: from django.shortcuts import redirect from django.contrib.auth.models import User from social.pipeline.partial import partial # partial says "we may interrupt, but we will come back here again" @partial def collect_password(strategy, backend, request, details, *args, **kwargs): # request['local_password'] is set by the pipeline infrastructure # because it exists in FIELDS_STORED_IN_SESSION if not request.get('local_password', None): # if we return something besides a dict or None, then that is # returned to the user -- in this case we will redirect to a # view that can be used to get a password return redirect("myapp.views.collect_password") # grab the user object from the database (remember that they may # not be logged in yet) and set their password. (Assumes that the # email address was captured in an earlier step.) user = User.objects.get(email=kwargs['email']) user.set_password(request['local_password']) user.save() # continue the pipeline return In our view code, we would have something like:: class PasswordForm(forms.Form): secret_word = forms.CharField(max_length=10) def get_user_password(request): if request.method == 'POST': form = PasswordForm(request.POST) if form.is_valid(): # because of FIELDS_STORED_IN_SESSION, this will get copied # to the request dictionary when the pipeline is resumed request.session['local_password'] = form.cleaned_data['secret_word'] # once we have the password stashed in the session, we can # tell the pipeline to resume by using the "complete" endpoint return redirect(reverse('social:complete', args=("backend_name,"))) else: form = PasswordForm() return render(request, "password_form.html") Note that the ``social:complete`` will re-enter the pipeline with the same function that interrupted it (in this case, collect_password). python-social-auth-0.2.13/docs/exceptions.rst000066400000000000000000000030671260133235600211770ustar00rootroot00000000000000Exceptions ========== This set of exceptions were introduced to describe the situations a bit more than just the ``ValueError`` usually raised. ``SocialAuthBaseException`` Base class for all social auth exceptions. ``AuthException`` Base exception class for authentication process errors. ``AuthFailed`` Authentication failed for some reason. ``AuthCanceled`` Authentication was canceled by the user. ``AuthUnknownError`` An unknown error stoped the authentication process. ``AuthTokenError`` Unauthorized or access token error, it was invalid, impossible to authenticate or user removed permissions to it. ``AuthMissingParameter`` A needed parameter to continue the process was missing, usually raised by the services that need some POST data like myOpenID. ``AuthAlreadyAssociated`` A different user has already associated the social account that the current user is trying to associate. ``WrongBackend`` Raised when the backend given in the URLs is invalid (not enabled or registered). ``NotAllowedToDisconnect`` Raised on disconnect action when it's not safe for the user to disconnect the social account, probably because the user lacks a password or another social account. ``AuthStateMissing`` The state parameter is missing from the server response. ``AuthStateForbidden`` The state parameter returned by the server is not the one sent. ``AuthTokenRevoked`` Raised when the user revoked the access_token in the provider. These are a subclass of ``ValueError`` to keep backward compatibility. python-social-auth-0.2.13/docs/index.rst000066400000000000000000000021621260133235600201200ustar00rootroot00000000000000Welcome to Python Social Auth's documentation! ============================================== Python Social Auth aims to be an easy to setup social authentication and authorization mechanism for Python projects supporting protocols like OAuth (1 and 2), OpenId and others. The initial codebase is derived from django-social-auth_ with the idea of generalizing the process to suite the different frameworks around, providing the needed tools to bring support to new frameworks. django-social-auth_ itself was a product of modified code from django-twitter-oauth_ and django-openid-auth_ projects. Contents: .. toctree:: :maxdepth: 2 intro installing configuration/index pipeline strategies storage exceptions backends/index developer_intro logging_out tests use_cases thanks copyright Indices and Tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _django-social-auth: http://github.com/omab/django-social-auth .. _django-twitter-oauth: https://github.com/henriklied/django-twitter-oauth .. _django-openid-auth: https://launchpad.net/django-openid-auth python-social-auth-0.2.13/docs/installing.rst000066400000000000000000000024731260133235600211620ustar00rootroot00000000000000Installation ============ Dependencies ------------ Dependencies that **must** be met to use the application: - OpenId_ support depends on python-openid_ - OAuth_ support depends on requests-oauthlib_ - Several backends demands application registration on their corresponding sites and other dependencies like sqlalchemy_ on Flask and Webpy. Get a copy ---------- From pypi_:: $ pip install python-social-auth Or:: $ easy_install python-social-auth Or clone from github_:: $ git clone git://github.com/omab/python-social-auth.git And add social to ``PYTHONPATH``:: $ export PYTHONPATH=$PYTHONPATH:$(pwd)/python-social-auth/ Or:: $ cd python-social-auth $ sudo python setup.py install .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _pypi: http://pypi.python.org/pypi/python-social-auth/ .. _github: https://github.com/omab/python-social-auth .. _python-openid: http://pypi.python.org/pypi/python-openid/ .. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/ .. _sqlalchemy: http://www.sqlalchemy.org/ Upgrading --------- Django with South ~~~~~~~~~~~~~~~~~ Upgrading from 0.1 to 0.2 is likely to cause problems trying to apply a migration when the tables already exist. In this case a fake migration needs to be applied: $ python manage.py migrate --fake default python-social-auth-0.2.13/docs/intro.rst000066400000000000000000000115731260133235600201520ustar00rootroot00000000000000Introduction ============ Python Social Auth aims to be an easy to setup social authentication and authorization mechanism for Python projects supporting protocols like OAuth_ (1 and 2), OpenId_ and others. Features -------- This application provides user registration and login using social sites credentials, here are some features, probably not a full list yet. Supported frameworks ******************** Multiple frameworks support: * Django_ * Flask_ * Pyramid_ * Webpy_ * Tornado_ More frameworks can be added easily (and should be even easier in the future once the code matures). Auth providers ************** Several supported service by simple backends definition (easy to add new ones or extend current one): * Angel_ OAuth2 * Beats_ OAuth2 * Behance_ OAuth2 * Bitbucket_ OAuth1 * Box_ OAuth2 * Dailymotion_ OAuth2 * Disqus_ OAuth2 * Douban_ OAuth1 and OAuth2 * Dropbox_ OAuth1 * Evernote_ OAuth1 * Facebook_ OAuth2 and OAuth2 for Applications * Fitbit_ OAuth1 * Flickr_ OAuth1 * Foursquare_ OAuth2 * `Google App Engine`_ Auth * Github_ OAuth2 * Google_ OAuth1, OAuth2 and OpenId * Instagram_ OAuth2 * Kakao_ OAuth2 * Linkedin_ OAuth1 * Live_ OAuth2 * Livejournal_ OpenId * Mailru_ OAuth2 * MineID_ OAuth2 * Mixcloud_ OAuth2 * `Mozilla Persona`_ * NaszaKlasa_ OAuth2 * Odnoklassniki_ OAuth2 and Application Auth * OpenId_ * Podio_ OAuth2 * Rdio_ OAuth1 and OAuth2 * Readability_ OAuth1 * Shopify_ OAuth2 * Skyrock_ OAuth1 * Soundcloud_ OAuth2 * Spotify_ OAuth2 * ThisIsMyJam_ OAuth1 * Stackoverflow_ OAuth2 * Steam_ OpenId * Stocktwits_ OAuth2 * Stripe_ OAuth2 * Tripit_ OAuth1 * Tumblr_ OAuth1 * Twilio_ Auth * Twitch_ OAuth2 * Twitter_ OAuth1 * Vimeo_ OAuth1 * VK.com_ OpenAPI, OAuth2 and OAuth2 for Applications * Weibo_ OAuth2 * Wunderlist_ OAuth2 * Xing_ OAuth1 * Yahoo_ OpenId and OAuth1 * Yammer_ OAuth2 * Yandex_ OAuth1, OAuth2 and OpenId User data ********* Basic user data population, to allow custom fields values from providers response. Social accounts association *************************** Multiple social accounts can be associated to a single user. Authentication and disconnection processing ******************************************* Extensible pipeline to handle authentication, association and disconnection mechanism in ways that suits your project. Check `Authentication Pipeline`_ section. .. _OpenId: http://openid.net/ .. _OAuth: http://oauth.net/ .. _myOpenID: https://www.myopenid.com/ .. _Angel: https://angel.co .. _Beats: https://www.beats.com .. _Behance: https://www.behance.net .. _Bitbucket: https://bitbucket.org .. _Box: https://www.box.com .. _Dailymotion: https://dailymotion.com .. _Disqus: https://disqus.com .. _Douban: http://www.douban.com .. _Dropbox: https://dropbox.com .. _Evernote: https://www.evernote.com .. _Facebook: https://www.facebook.com .. _Fitbit: https://fitbit.com .. _Flickr: http://www.flickr.com .. _Foursquare: https://foursquare.com .. _Google App Engine: https://developers.google.com/appengine/ .. _Github: https://github.com .. _Google: http://google.com .. _Instagram: https://instagram.com .. _Kakao: https://kakao.com .. _Linkedin: https://www.linkedin.com .. _Live: https://www.live.com .. _Livejournal: http://livejournal.com .. _Mailru: https://mail.ru .. _MineID: https://www.mineid.org .. _Mixcloud: https://www.mixcloud.com .. _Mozilla Persona: http://www.mozilla.org/persona/ .. _NaszaKlasa: https://developers.nk.pl/ .. _Odnoklassniki: http://www.odnoklassniki.ru .. _Podio: https://podio.com .. _Shopify: http://shopify.com .. _Skyrock: https://skyrock.com .. _Soundcloud: https://soundcloud.com .. _Spotify: https://www.spotify.com .. _ThisIsMyJam: https://thisismyjam.com .. _Stocktwits: https://stocktwits.com .. _Stripe: https://stripe.com .. _Tripit: https://www.tripit.com .. _Twilio: https://www.twilio.com .. _Twitch: http://www.twitch.tv/ .. _Twitter: http://twitter.com .. _VK.com: http://vk.com .. _Weibo: http://weibo.com .. _Wunderlist: http://wunderlist.com .. _Xing: https://www.xing.com .. _Yahoo: http://yahoo.com .. _Yammer: https://www.yammer.com .. _Yandex: https://yandex.ru .. _Readability: http://www.readability.com/ .. _Stackoverflow: http://stackoverflow.com/ .. _Steam: http://steamcommunity.com/ .. _Rdio: https://www.rdio.com .. _Vimeo: https://vimeo.com/ .. _Tumblr: http://www.tumblr.com/ .. _Django: https://github.com/omab/python-social-auth/tree/master/social/apps/django_app .. _Flask: https://github.com/omab/python-social-auth/tree/master/social/apps/flask_app .. _Pyramid: http://www.pylonsproject.org/projects/pyramid/about .. _Webpy: https://github.com/omab/python-social-auth/tree/master/social/apps/webpy_app .. _Tornado: http://www.tornadoweb.org/ .. _Authentication Pipeline: pipeline.html python-social-auth-0.2.13/docs/logging_out.rst000066400000000000000000000026011260133235600213240ustar00rootroot00000000000000Disconnect and Logging Out ========================== It's a common misconception that ``disconnect`` action is the same as logging the user out, but is far from it. ``Disconnect`` is the way that your users have to say to you "forget about my account", that implies removing the ``UserSocialAuth`` instance that was created, this also implies that the user won't be able to login back into your site with the social account, instead the action will be a signup, a new user instance will be created, not related to the previous one. Logging out is just a way to say "forget my current session", and usually implies removing cookies, invalidating a session hash, etc. The many frameworks have their own ways to logout an account (Django has ``django.contrib.auth.logout``), ``flask-login`` has it's own way too with `logout_user()`_. Since disconnecting a social account means that the user won't be able to log back in with that social provider into the same user, python-social-auth will check that the user account is in a valid state for disconnection (it has at least one more social account associated, or a password, etc). This behavior can be overridden by changing the `Disconnection Pipeline`_. .. _logout_user(): https://github.com/maxcountryman/flask-login/blob/a96de342eae560deec008a02179f593c3799b3ba/flask_login.py#L718-L739 .. _Disconnection Pipeline: pipeline.html#disconnection-pipeline python-social-auth-0.2.13/docs/pipeline.rst000066400000000000000000000332031260133235600206160ustar00rootroot00000000000000Pipeline ======== python-social-auth_ uses an extendible pipeline mechanism where developers can introduce their functions during the authentication, association and disconnection flows. The functions will receive a variable set of arguments related to the current process, common arguments are the current ``strategy``, ``user`` (if any) and ``request``. It's recommended that all the function also define an ``**kwargs`` in the parameters to avoid errors for unexpected arguments. Each pipeline entry can return a ``dict`` or ``None``, any other type of return value is treated as a response instance and returned directly to the client, check *Partial Piepeline* below for details. If a ``dict`` is returned, the value in the set will be merged into the ``kwargs`` argument for the next pipeline entry, ``None`` is taken as if ``{}`` was returned. Authentication Pipeline ----------------------- The final process of the authentication workflow is handled by an operations pipeline where custom functions can be added or default items can be removed to provide a custom behavior. The default pipeline is a mechanism that creates user instances and gathers basic data from providers. The default pipeline is composed by:: ( # Get the information we can about the user and return it in a simple # format to create the user instance later. On some cases the details are # already part of the auth response from the provider, but sometimes this # could hit a provider API. 'social.pipeline.social_auth.social_details', # Get the social uid from whichever service we're authing thru. The uid is # the unique identifier of the given user in the provider. 'social.pipeline.social_auth.social_uid', # Verifies that the current auth process is valid within the current # project, this is were emails and domains whitelists are applied (if # defined). 'social.pipeline.social_auth.auth_allowed', # Checks if the current social-account is already associated in the site. 'social.pipeline.social_auth.social_user', # Make up a username for this person, appends a random string at the end if # there's any collision. 'social.pipeline.user.get_username', # Send a validation email to the user to verify its email address. # Disabled by default. # 'social.pipeline.mail.mail_validation', # Associates the current social details with another user account with # a similar email address. Disabled by default. # 'social.pipeline.social_auth.associate_by_email', # Create a user account if we haven't found one yet. 'social.pipeline.user.create_user', # Create the record that associated the social account with this user. 'social.pipeline.social_auth.associate_user', # Populate the extra_data field in the social record with the values # specified by settings (and the default ones like access_token, etc). 'social.pipeline.social_auth.load_extra_data', # Update the user record with any changed info from the auth service. 'social.pipeline.user.user_details' ) It's possible to override it by defining the setting ``SOCIAL_AUTH_PIPELINE``, for example a pipeline that won't create users, just accept already registered ones would look like this:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) Note that this assumes the user is already authenticated, and thus the ``user`` key in the dict is populated. In cases where the authentication is purely external, a pipeline method must be provided that populates the ``user`` key. Example:: SOCIAL_AUTH_PIPELINE = ( 'myapp.pipeline.load_user', 'social.pipeline.social_auth.social_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) Each pipeline function will receive the following parameters: * Current strategy (which gives access to current store, backend and request) * User ID given by authentication provider * User details given by authentication provider * ``is_new`` flag (initialized as ``False``) * Any arguments passed to ``auth_complete`` backend method, default views pass these arguments: - current logged in user (if it's logged in, otherwise ``None``) - current request Disconnection Pipeline ---------------------- Like the authentication pipeline, it's possible to define a disconnection pipeline if needed. For example, this can be useful on sites where a user that disconnects all the related social account is required to fill a password to ensure the authentication process in the future. This can be accomplished by overriding the default disconnection pipeline and setup a function that checks if the user has a password, in case it doesn't a redirect to a fill-your-password form can be returned and later continue the disconnection process, take into account that disconnection ensures the POST method by default, a simple method to ensure this, is to make your form POST to ``/disconnect/`` and set the needed password in your pipeline function. Check *Partial Pipeline* below. In order to override the disconnection pipeline, just define the setting:: SOCIAL_AUTH_DISCONNECT_PIPELINE = ( # Verifies that the social association can be disconnected from the current # user (ensure that the user login mechanism is not compromised by this # disconnection). 'social.pipeline.disconnect.allowed_to_disconnect', # Collects the social associations to disconnect. 'social.pipeline.disconnect.get_entries', # Revoke any access_token when possible. 'social.pipeline.disconnect.revoke_tokens', # Removes the social associations. 'social.pipeline.disconnect.disconnect' ) Partial Pipeline ---------------- It's possible to cut the pipeline process to return to the user asking for more data and resume the process later. To accomplish this decorate the function that will cut the process with the ``@partial`` decorator located at ``social/pipeline/partial.py``. The old ``social.pipeline.partial.save_status_to_session`` is now deprecated. When it's time to resume the process just redirect the user to ``/complete//`` or ``/disconnect//`` view. The pipeline will resume in the same function that cut the process. ``@partial`` and ``save_status_to_session`` stores needed data into user session under the key ``partial_pipeline``. To get the backend in order to redirect to any social view, just do:: backend = session['partial_pipeline']['backend'] Check the `example applications`_ to check a basic usage. Email validation ---------------- There's a pipeline to validate email addresses, but it relies a lot on your project. The pipeline is at ``social.pipeline.mail.mail_validation`` and it's a partial pipeline, it will return a redirect to an URL that you can use to tell the users that an email validation was sent to them. If you want to mention the email address you can get it from the session under the key ``email_validation_address``. In order to send the validation python-social-auth_ needs a function that will take care of it, this function is defined by the developer with the setting ``SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION``. It should be an import path. This function should take three arguments ``strategy``, ``backend`` and ``code``. ``code`` is a model instance used to validate the email address, it contains three fields: ``code = '...'`` Holds an ``uuid.uuid4()`` value and it's the code used to identify the validation process. ``email = '...'`` Email address trying to be validate. ``verified = True / False`` Flag marking if the email was verified or not. You should use the code in this instance the build the link for email validation which should go to ``/complete/email?verification_code=``, if using Django you can do it with:: from django.core.urlresolvers import reverse url = strategy.build_absolute_uri( reverse('social:complete', args=(strategy.backend_name,)) ) + '?verification_code=' + code.code On Flask:: from flask import url_for url = url_for('social.complete', backend=strategy.backend_name, _external=True) + '?verification_code=' + code This pipeline can be used globally with any backend if this setting is defined:: SOCIAL_AUTH_FORCE_EMAIL_VALIDATION = True Or individually by defining the setting per backend basis like ``SOCIAL_AUTH_TWITTER_FORCE_EMAIL_VALIDATION = True``. Extending the Pipeline ====================== The main purpose of the pipeline (either creation or deletion pipelines), is to allow extensibility for developers, you can jump in the middle of it, do changes to the data, create other models instances, ask users for data, or even halt the whole process. Extending the pipeline implies: 1. Writing a function 2. Locate it in a accessible path (accessible in the way that it can be imported) 3. Override the default pipeline definition with one that includes your function. Writing the function is quite simple. Depending on the place you locate it will determine the arguments it will receive, for example, adding your function after ``social.pipeline.user.create_user`` ensures that you get the user instance (created or already existent) instead of a ``None`` value. The pipeline functions will get quite a lot of arguments, ranging from the backend in use, different model instances, server requests and provider responses. To enumerate a few: ``strategy`` The current strategy instance. ``backend`` The current backend instance. ``uid`` User ID in the provider, this ``uid`` should identify the user in the current provider. ``response = {} or object()`` The server user-details response, it depends on the protocol in use (and sometimes the provider implementation of such protocol), but usually it's just a ``dict`` with the user profile details in such provider. Lots of information related to the user is provided here, sometimes the ``scope`` will increase the amount of information in this response on OAuth providers. ``details = {}`` Basic user details generated by the backend, used to create/update the user model details (this ``dict`` will contain values like ``username``, ``email``, ``first_name``, ``last_name`` and ``fullname``). ``user = None`` The user instance (or ``None`` if it wasn't created or retrieved from the database yet). ``social = None`` This is the associated ``UserSocialAuth`` instance for the given user (or ``None`` if it wasn't created or retrieved from the DB yet). Usually when writing your custom pipeline function, you just want to get some values from the ``response`` parameter. But you can do even more, like call other APIs endpoints to retrieve even more details about the user, store them on some other place, etc. Here's an example of a simple pipeline function that will create a ``Profile`` class related to the current user, this profile will store some simple details returned by the provider (``Facebook`` in this example). The usual Facebook ``response`` looks like this:: { 'username': 'foobar', 'access_token': 'CAAD...', 'first_name': 'Foo', 'last_name': 'Bar', 'verified': True, 'name': 'Foo Bar', 'locale': 'en_US', 'gender': 'male', 'expires': '5183999', 'email': 'foo@bar.com', 'updated_time': '2014-01-14T15:58:35+0000', 'link': 'https://www.facebook.com/foobar', 'timezone': -3, 'id': '100000126636010' } Let's say we are interested in storing the user profile link, the gender and the timezone in our ``Profile`` model:: def save_profile(backend, user, response, *args, **kwargs): if backend.name == 'facebook': profile = user.get_profile() if profile is None: profile = Profile(user_id=user.id) profile.gender = response.get('gender') profile.link = response.get('link') profile.timezone = response.get('timezone') profile.save() Now all that's needed is to tell ``python-social-auth`` to use this function in the pipeline, since it needs the user instance, it needs to be put after ``create_user`` function:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'path.to.save_profile', # <--- set the path to the function 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) If the return value of the function is a ``dict``, the values will be merged into the next pipeline function parameters, so, for instance, if you want the ``profile`` instance to be available to the next function, all that it needs to do is return ``{'profile': profile}``. .. _python-social-auth: https://github.com/omab/python-social-auth .. _example applications: https://github.com/omab/python-social-auth/tree/master/examples python-social-auth-0.2.13/docs/storage.rst000066400000000000000000000157641260133235600204710ustar00rootroot00000000000000Storage ======= Different frameworks support different ORMs, Storage solves the different interfaces moving the common API to mixins classes. These mixins are used on apps when defining the different models used by ``python-social-auth``. Social User ----------- This model associates a social account data with a user in the system, it contains the provider name and user ID (``uid``) which should identify the social account in the remote provider, plus some extra data (``extra_data``) which is JSON encoded field with extra information from the provider (usually avatars and similar). When implementing this model, it must inherits from UserMixin_ and extend the needed methods: * Username:: @classmethod def get_username(cls, user): """Return the username for given user""" raise NotImplementedError('Implement in subclass') @classmethod def username_max_length(cls): """Return the max length for username""" raise NotImplementedError('Implement in subclass') * User model:: @classmethod def user_model(cls): """Return the user model""" raise NotImplementedError('Implement in subclass') @classmethod def changed(cls, user): """The given user instance is ready to be saved""" raise NotImplementedError('Implement in subclass') @classmethod def user_exists(cls, username): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ raise NotImplementedError('Implement in subclass') @classmethod def create_user(cls, username, email=None): """Create a user with given username and (optional) email""" raise NotImplementedError('Implement in subclass') @classmethod def get_user(cls, pk): """Return user instance for given id""" raise NotImplementedError('Implement in subclass') * Social user:: @classmethod def get_social_auth(cls, provider, uid): """Return UserSocialAuth for given provider and uid""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth_for_user(cls, user): """Return all the UserSocialAuth instances for given user""" raise NotImplementedError('Implement in subclass') @classmethod def create_social_auth(cls, user, uid, provider): """Create a UserSocialAuth instance for given user""" raise NotImplementedError('Implement in subclass') * Social disconnection:: @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): """Return if it's safe to disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def disconnect(cls, name, user, association_id=None): """Disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') Nonce ----- This is a helper class for OpenId mechanism, it stores a one-use number, shouldn't be used by the project since it's for internal use only. When implementing this model, it must inherits from NonceMixin_, and override the needed method:: @classmethod def use(cls, server_url, timestamp, salt): """Create a Nonce instance""" raise NotImplementedError('Implement in subclass') Association ----------- Another OpenId helper class, it stores basic data to keep the OpenId association. Like Nonce_ this is for internal use only. When implementing this model, it must inherits from AssociationMixin_, and override the needed methods:: @classmethod def store(cls, server_url, association): """Create an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def get(cls, *args, **kwargs): """Get an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def remove(cls, ids_to_delete): """Remove an Association instance""" raise NotImplementedError('Implement in subclass') Validation code --------------- This class is used to keep track of email validations codes following the usual email validation mechanism of sending an email to the user with a unique code. This model is used by the partial pipeline ``social.pipeline.mail.mail_validation``. Check the docs at *Email validation* in `pipeline docs`_. When implementing the model for your framework only one method needs to be overridden:: @classmethod def get_code(cls, code): """Return the Code instance with the given code value""" raise NotImplementedError('Implement in subclass') Storage interface ----------------- There's a helper class used by strategies to hide the real models names under a common API, an instance of this class is used by strategies to access the storage modules. When implementing this class it must inherits from BaseStorage_, add the needed models references and implement the needed method:: class StorageImplementation(BaseStorage): user = UserModel nonce = NonceModel association = AssociationModel code = CodeModel @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" raise NotImplementedError('Implement in subclass') SQLAlchemy and Django mixins ---------------------------- Currently there are partial implementations of mixins for `SQLAlchemy ORM`_ and `Django ORM`_ with common code used later on current implemented applications. **When using `SQLAlchemy ORM`_ and ``ZopeTransactionExtension``, it's recommended to use the transaction_ application to handle them.** Models Examples --------------- Check for current implementations for `Django App`_, `Flask App`_, `Pyramid App`_, and `Webpy App`_ for examples of implementations. .. _UserMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L15 .. _NonceMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L149 .. _AssociationMixin: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L161 .. _BaseStorage: https://github.com/omab/python-social-auth/blob/master/social/storage/base.py#L201 .. _SQLAlchemy ORM: https://github.com/omab/python-social-auth/blob/master/social/storage/sqlalchemy_orm.py .. _Django ORM: https://github.com/omab/python-social-auth/blob/master/social/storage/django_orm.py .. _Django App: https://github.com/omab/python-social-auth/blob/master/social/apps/django_app/default/models.py .. _Flask App: https://github.com/omab/python-social-auth/blob/master/social/apps/flask_app/models.py .. _Pyramid App: https://github.com/omab/python-social-auth/blob/master/social/apps/pyramid_app/models.py .. _Webpy App: https://github.com/omab/python-social-auth/blob/master/social/apps/webpy_app/models.py .. _pipeline docs: pipeline.html#email-validation .. _transaction: https://pypi.python.org/pypi/transaction python-social-auth-0.2.13/docs/strategies.rst000066400000000000000000000041711260133235600211650ustar00rootroot00000000000000Strategies ========== Different strategies are defined to encapsulate the different frameworks capabilities under a common API to reuse as much code as possible. Description ----------- An strategy responsibility is to provide access to: * Request data and host information and URI building * Session access * Project settings * Response types (HTML and redirects) * HTML rendering Different frameworks implement these features on different ways, thus the need for these interfaces. Implementing a new Strategy --------------------------- The following methods must be defined on strategies sub-classes. Request:: def request_data(self): """Return current request data (POST or GET)""" raise NotImplementedError('Implement in subclass') def request_host(self): """Return current host value""" raise NotImplementedError('Implement in subclass') def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" raise NotImplementedError('Implement in subclass') Session:: def session_get(self, name): """Return session value for given key""" raise NotImplementedError('Implement in subclass') def session_set(self, name, value): """Set session value for given key""" raise NotImplementedError('Implement in subclass') def session_pop(self, name): """Pop session value for given key""" raise NotImplementedError('Implement in subclass') Settings:: def get_setting(self, name): """Return value for given setting name""" raise NotImplementedError('Implement in subclass') Responses:: def html(self, content): """Return HTTP response with given content""" raise NotImplementedError('Implement in subclass') def redirect(self, url): """Return a response redirect to the given URL""" raise NotImplementedError('Implement in subclass') def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.13/docs/tests.rst000066400000000000000000000027671260133235600201660ustar00rootroot00000000000000Testing python-social-auth ========================== Testing the application is fair simple, just met the dependencies and run the testing suite. The testing suite uses HTTPretty_ to mock server responses, it's not a live test against the providers API, to do it that way, a browser and a tool like Selenium are needed, that's slow, prone to errors on some cases, and some of the application examples must be running to perform the testing. Plus real Key and Secret pairs, in the end it's a mess to test functionality which is the real point. By mocking the server responses, we can test the backends functionality (and other areas too) easily and quick. Installing dependencies ----------------------- Go to the tests_ directory and install the dependencies listed in the requirements.txt_. Then run with ``nosetests`` command, or with the ``run_tests.sh`` script. Tox --- You can use tox_ to test compatibility against all supported Python versions: .. code-block:: bash $ pip install tox # if not present $ tox Pending ------- At the moment only OAuth1, OAuth2 and OpenId backends are being tested, and just login and partial pipeline features are covered by the test. There's still a lot to work on, like: * Frameworks support .. _HTTPretty: https://github.com/gabrielfalcao/HTTPretty .. _tests: https://github.com/omab/python-social-auth/tree/master/tests .. _requirements.txt: https://github.com/omab/python-social-auth/blob/master/tests/requirements.txt .. _tox: http://tox.readthedocs.org/ python-social-auth-0.2.13/docs/thanks.rst000066400000000000000000000137301260133235600203040ustar00rootroot00000000000000Thanks ====== python-social-auth_ is the result of almost 3 years of development done on django-social-auth_ which is the result of my initial work and the thousands lines of code contributed by so many developers that took time to work on improvements, report bugs and hunt them down to propose a fix. So, here is a big list of users that helped to build this library (if somebody is missed let me know and I'll update the list): * kjoconnor_ * krvss_ * estebistec_ * mrmch_ * uruz_ * maraujop_ * bacher09_ * dokterbob_ * hassek_ * andrusha_ * vicalloy_ * caioariede_ * danielgtaylor_ * stephenmcd_ * gugu_ * yrik_ * dhendo_ * yekibud_ * tmackenzie_ * LuanP_ * jezdez_ * serdardalgic_ * Jolmberg_ * ChrisCooper_ * marselester_ * eshellman_ * micrypt_ * revolunet_ * dasevilla_ * seansay_ * hepochen_ * gibuloto_ * crodjer_ * sidmitra_ * ryr_ * inve1_ * mback2k_ * hannesstruss_ * NorthIsUp_ * tonyxiao_ * dhepper_ * Troytft_ * gardaud_ * oinopion_ * gameguy43_ * vinigracindo_ * syabro_ * bashmish_ * ggreer_ * avillavi_ * r4vi_ * roderyc_ * daonb_ * slon7_ * JasonGiedymin_ * tymofij_ * Cassus_ * martey_ * t0m_ * johnthedebs_ * jammons_ * stefanw_ * maxgrosse_ * mattucf_ * tadeo_ * haxoza_ * bradbeattie_ * henward0_ * bernardokyotoku_ * czpython_ * glasscube42_ * assiotis_ * dbaxa_ * JasonSanford_ * originell_ * cihann_ * niftynei_ * mikesun_ * 1st_ * betonimig_ * ozexpert_ * stephenLee_ * julianvargasalvarez_ * youngrok_ * garrypolley_ * amirouche_ * fmoga_ * pydanny_ * pygeek_ * dgouldin_ * kotslon_ * kirkchris_ * barracel_ * sayar_ * kulbir_ * Morgul_ * spstpl_ * bluszcz_ * vbsteven_ * sbassi_ .. _python-social-auth: https://github.com/omab/python-social-auth .. _django-social-auth: https://github.com/omab/django-social-auth .. _kjoconnor: https://github.com/kjoconnor .. _krvss: https://github.com/krvss .. _estebistec: https://github.com/estebistec .. _mrmch: https://github.com/mrmch .. _uruz: https://github.com/uruz .. _maraujop: https://github.com/maraujop .. _bacher09: https://github.com/bacher09 .. _dokterbob: https://github.com/dokterbob .. _hassek: https://github.com/hassek .. _andrusha: https://github.com/andrusha .. _vicalloy: https://github.com/vicalloy .. _caioariede: https://github.com/caioariede .. _danielgtaylor: https://github.com/danielgtaylor .. _stephenmcd: https://github.com/stephenmcd .. _gugu: https://github.com/gugu .. _yrik: https://github.com/yrik .. _dhendo: https://github.com/dhendo .. _yekibud: https://github.com/yekibud .. _tmackenzie: https://github.com/tmackenzie .. _LuanP: https://github.com/LuanP .. _jezdez: https://github.com/jezdez .. _serdardalgic: https://github.com/serdardalgic .. _Jolmberg: https://github.com/Jolmberg .. _ChrisCooper: https://github.com/ChrisCooper .. _marselester: https://github.com/marselester .. _eshellman: https://github.com/eshellman .. _micrypt: https://github.com/micrypt .. _revolunet: https://github.com/revolunet .. _dasevilla: https://github.com/dasevilla .. _seansay: https://github.com/seansay .. _hepochen: https://github.com/hepochen .. _gibuloto: https://github.com/gibuloto .. _crodjer: https://github.com/crodjer .. _sidmitra: https://github.com/sidmitra .. _ryr: https://github.com/ryr .. _inve1: https://github.com/inve1 .. _mback2k: https://github.com/mback2k .. _hannesstruss: https://github.com/hannesstruss .. _NorthIsUp: https://github.com/NorthIsUp .. _tonyxiao: https://github.com/tonyxiao .. _dhepper: https://github.com/dhepper .. _Troytft: https://github.com/Troytft .. _gardaud: https://github.com/gardaud .. _oinopion: https://github.com/oinopion .. _gameguy43: https://github.com/gameguy43 .. _vinigracindo: https://github.com/vinigracindo .. _syabro: https://github.com/syabro .. _bashmish: https://github.com/bashmish .. _ggreer: https://github.com/ggreer .. _avillavi: https://github.com/avillavi .. _r4vi: https://github.com/r4vi .. _roderyc: https://github.com/roderyc .. _daonb: https://github.com/daonb .. _slon7: https://github.com/slon7 .. _JasonGiedymin: https://github.com/JasonGiedymin .. _tymofij: https://github.com/tymofij .. _Cassus: https://github.com/Cassus .. _martey: https://github.com/martey .. _t0m: https://github.com/t0m .. _johnthedebs: https://github.com/johnthedebs .. _jammons: https://github.com/jammons .. _stefanw: https://github.com/stefanw .. _maxgrosse: https://github.com/maxgrosse .. _mattucf: https://github.com/mattucf .. _tadeo: https://github.com/tadeo .. _haxoza: https://github.com/haxoza .. _bradbeattie: https://github.com/bradbeattie .. _henward0: https://github.com/henward0 .. _bernardokyotoku: https://github.com/bernardokyotoku .. _czpython: https://github.com/czpython .. _glasscube42: https://github.com/glasscube42 .. _assiotis: https://github.com/assiotis .. _dbaxa: https://github.com/dbaxa .. _JasonSanford: https://github.com/JasonSanford .. _originell: https://github.com/originell .. _cihann: https://github.com/cihann .. _niftynei: https://github.com/niftynei .. _mikesun: https://github.com/mikesun .. _1st: https://github.com/1st .. _betonimig: https://github.com/betonimig .. _ozexpert: https://github.com/ozexpert .. _stephenLee: https://github.com/stephenLee .. _julianvargasalvarez: https://github.com/julianvargasalvarez .. _youngrok: https://github.com/youngrok .. _garrypolley: https://github.com/garrypolley .. _amirouche: https://github.com/amirouche .. _fmoga: https://github.com/fmoga .. _pydanny: https://github.com/pydanny .. _pygeek: https://github.com/pygeek .. _dgouldin: https://github.com/dgouldin .. _kotslon: https://github.com/kotslon .. _kirkchris: https://github.com/kirkchris .. _barracel: https://github.com/barracel .. _sayar: https://github.com/sayar .. _kulbir: https://github.com/kulbir .. _Morgul: https://github.com/Morgul .. _spstpl: https://github.com/spstpl .. _bluszcz: https://github.com/bluszcz .. _vbsteven: https://github.com/vbsteven .. _sbassi: https://github.com/sbassi python-social-auth-0.2.13/docs/use_cases.rst000066400000000000000000000306011260133235600207620ustar00rootroot00000000000000Use Cases ========= Some miscellaneous options and use cases for python-social-auth_. Return the user to the original page ------------------------------------ There's a common scenario where it's desired to return the user back to the original page from where it was requested to login. For that purpose, the usual ``next`` query-string argument is used, the value of this parameter will be stored in the session and later used to redirect the user when login was successful. In order to use it just define it with your link, for instance, when using Django:: Login with Facebook Pass custom GET/POST parameters and retrieve them on authentication ------------------------------------------------------------------- In some cases, you might need to send data over the URL, and retrieve it while processing the after-effect. For example, for conditionally executing code in custom pipelines. In such cases, add it to ``FIELDS_STORED_IN_SESSION``. In your settings:: FIELDS_STORED_IN_SESSION = ['key'] In template:: Login with Facebook In your custom pipeline, retrieve it using:: strategy.session_get('key') Retrieve Google+ Friends ------------------------ Google provides a `People API endpoint`_ to retrieve the people in your circles on Google+. In order to access that API first we need to define the needed scope:: SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/plus.login' ] Once we have the ``access token`` we can call the API like this:: import requests user = User.objects.get(...) social = user.social_auth.get(provider='google-oauth2') response = requests.get( 'https://www.googleapis.com/plus/v1/people/me/people/visible', params={'access_token': social.extra_data['access_token']} ) friends = response.json()['items'] Associate users by email ------------------------ Sometimes it's desirable that social accounts are automatically associated if the email already matches a user account. For example, if a user signed up with his Facebook account, then logged out and next time tries to use Google OAuth2 to login, it could be nice (if both social sites have the same email address configured) that the user gets into his initial account created by Facebook backend. This scenario is possible by enabling the ``associate_by_email`` pipeline function, like this:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', # <--- enable this one 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) This feature is disabled by default because it's not 100% secure to automate this process with all the backends. Not all the providers will validate your email account and others users could take advantage of that. Take for instance User A registered in your site with the email ``foo@bar.com``. Then a malicious user registers into another provider that doesn't validate his email with that same account. Finally this user will turn to your site (which supports that provider) and sign up to it, since the email is the same, the malicious user will take control over the User A account. Signup by OAuth access_token ---------------------------- It's a common scenario that mobile applications will use an SDK to signup a user within the app, but that signup won't be reflected by python-social-auth_ unless the corresponding database entries are created. In order to do so, it's possible to create a view / route that creates those entries by a given ``access_token``. Take the following code for instance (the code follows Django conventions, but versions for others frameworks can be implemented easily):: from django.contrib.auth import login from social.apps.django_app.utils import psa # Define an URL entry to point to this view, call it passing the # access_token parameter like ?access_token=. The URL entry must # contain the backend, like this: # # url(r'^register-by-token/(?P[^/]+)/$', # 'register_by_access_token') @psa('social:complete') def register_by_access_token(request, backend): # This view expects an access_token GET parameter, if it's needed, # request.backend and request.strategy will be loaded with the current # backend and strategy. token = request.GET.get('access_token') user = request.backend.do_auth(request.GET.get('access_token')) if user: login(request, user) return 'OK' else: return 'ERROR' The snippet above is quite simple, it doesn't return JSON and usually this call will be done by AJAX. It doesn't return the user information, but that's something that can be extended and filled to suit the project where it's going to be used. This topic is well addressed in `A Rest API using Django and authentication with OAuth2 AND third parties!`_ wrote by `Félix Descôteaux`_. Multiple scopes per provider ---------------------------- At the moment python-social-auth_ doesn't provide a method to define multiple scopes for single backend, this is usually desired since it's recommended to ask the user for the minimum scope possible and increase the access when it's really needed. It's possible to add a new backend extending the original one to accomplish that behavior, there are two ways to do it. 1. Overriding ``get_scope()`` method:: from social.backends.facebook import FacebookOAuth2 class CustomFacebookOAuth2(FacebookOauth2): def get_scope(self): scope = super(CustomFacebookOAuth2, self).get_scope() if self.data.get('extrascope'): scope = scope + [('foo', 'bar')] return scope This method is quite simple, it overrides the method that returns the scope value in a backend (``get_scope()``) and adds extra values tot he list if it was indicated by a parameter in the ``GET`` or ``POST`` data (``self.data``). Put this new backend in some place in your project and replace the original ``FacebookOAuth2`` in ``AUTHENTICATION_BACKENDS`` with this new version. When overriding this method, take into account that the default output the base class for ``get_scope()`` is the raw value from the settings (whatever they are defined), doing this will actually update the value in your settings for all the users:: scope = super(CustomFacebookOAuth2, self).get_scope() scope += ['foo', 'bar'] Instead do it like this:: scope = super(CustomFacebookOAuth2, self).get_scope() scope = scope + ['foo', 'bar'] 2. It's possible to do the same by defining a second backend which extends from the original but overrides the name, this will imply new URLs and also new settings for the new backend (since the name is used to build the settings names), it also implies a new application in the provider since not all providers give you the option of defining multiple redirect URLs. To do it just add a backend like:: from social.backends.facebook import FacebookOAuth2 class CustomFacebookOAuth2(FacebookOauth2): name = 'facebook-custom' Put this new backend in some place in your project keeping the original ``FacebookOAuth2`` in ``AUTHENTICATION_BACKENDS``. Now a new set of URLs will be functional:: /login/facebook-custom /complete/facebook-custom /disconnect/facebook-custom And also a new set of settings:: SOCIAL_AUTH_FACEBOOK_CUSTOM_KEY = '...' SOCIAL_AUTH_FACEBOOK_CUSTOM_SECRET = '...' SOCIAL_AUTH_FACEBOOK_CUSTOM_SCOPE = [...] When the extra permissions are needed, just redirect the user to ``/login/facebook-custom`` and then get the social auth entry for this new backend with ``user.social_auth.get(provider='facebook-custom')`` and use the ``access_token`` in it. Enable a user to choose a username from his World of Warcraft characters ------------------------------------------------------------------------ If you want to register new users on your site via battle.net, you can enable these users to choose a username from their own World-of-Warcraft characters. To do this, use the ``battlenet-oauth2`` backend along with a small form to choose the username. The form is rendered via a partial pipeline item like this:: @partial def pick_character_name(backend, details, response, is_new=False, *args, **kwargs): if backend.name == 'battlenet-oauth2' and is_new: data = backend.strategy.request_data() if data.get('character_name') is None: # New user and didn't pick a character name yet, so we render # and send a form to pick one. The form must do a POST/GET # request to the same URL (/complete/battlenet-oauth2/). In this # example we expect the user option under the key: # character_name # you have to filter the result list according to your needs. # In this example, only guild members are allowed to sign up. char_list = [ c['name'] for c in backend.get_characters(response.get('access_token')) if 'guild' in c and c['guild'] == '' ] return render_to_response('pick_character_form.html', {'charlist': char_list, }) else: # The user selected a character name return {'username': data.get('character_name')} Don't forget to add the partial to the pipeline:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'path.to.pick_character_name', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) It needs to be somewhere before create_user because the partial will change the username according to the users choice. Re-prompt Google OAuth2 users to refresh the ``refresh_token`` -------------------------------------------------------------- A ``refresh_token`` also expire, a ``refresh_token`` can be lost, but they can also be refreshed (or re-fetched) if you ask to Google the right way. In order to do so, set this setting:: SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = { 'access_type': 'offline', 'approval_prompt': 'auto' } Then link the users to ``/login/google-oauth2?approval_prompt=force``. If you want to refresh the ``refresh_token`` only on those users that don't have it, do it with a pipeline function:: def redirect_if_no_refresh_token(backend, response, social, *args, **kwargs): if backend.name == 'google-oauth2' and social and \ response.get('refresh_token') is None and \ social.extra_data.get('refresh_token') is None: return redirect('/login/google-oauth2?approval_prompt=force') Set this pipeline after ``social_user``:: SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'path.to.redirect_if_no_refresh_token', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', ) .. _python-social-auth: https://github.com/omab/python-social-auth .. _People API endpoint: https://developers.google.com/+/api/latest/people/list .. _Félix Descôteaux: https://twitter.com/FelixDescoteaux .. _A Rest API using Django and authentication with OAuth2 AND third parties!: http://httplambda.com/a-rest-api-with-django-and-oauthw-authentication/ python-social-auth-0.2.13/examples/000077500000000000000000000000001260133235600171445ustar00rootroot00000000000000python-social-auth-0.2.13/examples/cherrypy_example/000077500000000000000000000000001260133235600225245ustar00rootroot00000000000000python-social-auth-0.2.13/examples/cherrypy_example/__init__.py000066400000000000000000000041241260133235600246360ustar00rootroot00000000000000import sys sys.path.append('../..') import cherrypy from jinja2 import Environment, FileSystemLoader from social.apps.cherrypy_app.utils import backends from social.apps.cherrypy_app.views import CherryPyPSAViews from db.saplugin import SAEnginePlugin from db.satool import SATool from db.user import User SAEnginePlugin(cherrypy.engine, 'sqlite:///test.db').subscribe() class PSAExample(CherryPyPSAViews): @cherrypy.expose def index(self): return self.render_to('home.html') @cherrypy.expose def done(self): user = getattr(cherrypy.request, 'user', None) if user is None: raise cherrypy.HTTPRedirect('/') return self.render_to('done.html', user=user, backends=backends(user)) @cherrypy.expose def logout(self): raise cherrypy.HTTPRedirect('/') def render_to(self, tpl, **ctx): return cherrypy.tools.jinja2env.get_template(tpl).render(**ctx) def load_user(): user_id = cherrypy.session.get('user_id') if user_id: cherrypy.request.user = cherrypy.request.db.query(User).get(user_id) else: cherrypy.request.user = None def session_commit(): cherrypy.session.save() try: from local_settings import SOCIAL_SETTINGS except ImportError: print 'Define a local_settings.py using local_settings.py.template as base' SOCIAL_SETTINGS = {} if __name__ == '__main__': cherrypy.config.update({ 'server.socket_port': 8000, 'tools.sessions.on': True, 'tools.sessions.storage_type': 'ram', 'tools.db.on': True, 'tools.authenticate.on': True, 'SOCIAL_AUTH_USER_MODEL': 'db.user.User', 'SOCIAL_AUTH_LOGIN_URL': '/', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/done', }) cherrypy.config.update(SOCIAL_SETTINGS) cherrypy.tools.jinja2env = Environment( loader=FileSystemLoader('templates') ) cherrypy.tools.db = SATool() cherrypy.tools.authenticate = cherrypy.Tool('before_handler', load_user) cherrypy.tools.session = cherrypy.Tool('on_end_resource', session_commit) cherrypy.quickstart(PSAExample()) python-social-auth-0.2.13/examples/cherrypy_example/db/000077500000000000000000000000001260133235600231115ustar00rootroot00000000000000python-social-auth-0.2.13/examples/cherrypy_example/db/__init__.py000066400000000000000000000001241260133235600252170ustar00rootroot00000000000000from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() python-social-auth-0.2.13/examples/cherrypy_example/db/saplugin.py000066400000000000000000000023521260133235600253070ustar00rootroot00000000000000# -*- coding: utf-8 -*- from cherrypy.process import plugins from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker class SAEnginePlugin(plugins.SimplePlugin): def __init__(self, bus, connection_string=None): self.sa_engine = None self.connection_string = connection_string self.session = scoped_session(sessionmaker(autoflush=True, autocommit=False)) super(SAEnginePlugin, self).__init__(bus) def start(self): self.sa_engine = create_engine(self.connection_string, echo=False) self.bus.subscribe('bind-session', self.bind) self.bus.subscribe('commit-session', self.commit) def stop(self): self.bus.unsubscribe('bind-session', self.bind) self.bus.unsubscribe('commit-session', self.commit) if self.sa_engine: self.sa_engine.dispose() self.sa_engine = None def bind(self): self.session.configure(bind=self.sa_engine) return self.session def commit(self): try: self.session.commit() except: self.session.rollback() raise finally: self.session.remove() python-social-auth-0.2.13/examples/cherrypy_example/db/satool.py000066400000000000000000000014001260133235600247570ustar00rootroot00000000000000# -*- coding: utf-8 -*- import cherrypy class SATool(cherrypy.Tool): def __init__(self): super(SATool, self).__init__('before_handler', self.bind_session, priority=20) def _setup(self): super(SATool, self)._setup() cherrypy.request.hooks.attach('on_end_resource', self.commit_transaction, priority=80) def bind_session(self): session = cherrypy.engine.publish('bind-session').pop() cherrypy.request.db = session def commit_transaction(self): if not hasattr(cherrypy.request, 'db'): return cherrypy.request.db = None cherrypy.engine.publish('commit-session') python-social-auth-0.2.13/examples/cherrypy_example/db/user.py000066400000000000000000000006311260133235600244410ustar00rootroot00000000000000from sqlalchemy import Column, Integer, String, Boolean from db import Base class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active python-social-auth-0.2.13/examples/cherrypy_example/local_settings.py.template000066400000000000000000000040131260133235600277200ustar00rootroot00000000000000SOCIAL_SETTINGS = { 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ), 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': '', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': '' } python-social-auth-0.2.13/examples/cherrypy_example/requirements.txt000066400000000000000000000000111260133235600260000ustar00rootroot00000000000000cherrypy python-social-auth-0.2.13/examples/cherrypy_example/syncbd.py000066400000000000000000000006541260133235600243650ustar00rootroot00000000000000import sys sys.path.append('../..') from sqlalchemy import create_engine import cherrypy cherrypy.config.update({ 'SOCIAL_AUTH_USER_MODEL': 'db.user.User', }) from social.apps.cherrypy_app.models import SocialBase from db import Base from db.user import User if __name__ == '__main__': engine = create_engine('sqlite:///test.db') Base.metadata.create_all(engine) SocialBase.metadata.create_all(engine) python-social-auth-0.2.13/examples/cherrypy_example/templates/000077500000000000000000000000001260133235600245225ustar00rootroot00000000000000python-social-auth-0.2.13/examples/cherrypy_example/templates/base.html000066400000000000000000000010261260133235600263210ustar00rootroot00000000000000 Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.13/examples/cherrypy_example/templates/done.html000066400000000000000000000007701260133235600263410ustar00rootroot00000000000000{% extends "base.html" %} {% block content %}

    You are logged in as {{ user.username }}!

    Associated:

      {% for assoc in backends.associated %}
    • {{ assoc.provider }}
    • {% endfor %}

    Associate:

      {% for name in backends.not_associated %}
    • {{ name }}
    • {% endfor %}
    {% endblock %} python-social-auth-0.2.13/examples/cherrypy_example/templates/home.html000066400000000000000000000063011260133235600263400ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    ThisIsMyJam OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    MineID OAuth2
    Persona
    {% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.13/examples/django_example/000077500000000000000000000000001260133235600221215ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/000077500000000000000000000000001260133235600235545ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/__init__.py000066400000000000000000000000001260133235600256530ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/app/000077500000000000000000000000001260133235600243345ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/app/__init__.py000066400000000000000000000000001260133235600264330ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/app/decorators.py000066400000000000000000000007211260133235600270530ustar00rootroot00000000000000from functools import wraps from django.template import RequestContext from django.shortcuts import render_to_response def render_to(tpl): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): out = func(request, *args, **kwargs) if isinstance(out, dict): out = render_to_response(tpl, out, RequestContext(request)) return out return wrapper return decorator python-social-auth-0.2.13/examples/django_example/example/app/mail.py000066400000000000000000000007561260133235600256400ustar00rootroot00000000000000from django.conf import settings from django.core.mail import send_mail from django.core.urlresolvers import reverse def send_validation(strategy, backend, code): url = '{0}?verification_code={1}'.format( reverse('social:complete', args=(backend.name,)), code.code ) url = strategy.request.build_absolute_uri(url) send_mail('Validate your account', 'Validate your account {0}'.format(url), settings.EMAIL_FROM, [code.email], fail_silently=False) python-social-auth-0.2.13/examples/django_example/example/app/models.py000066400000000000000000000002341260133235600261700ustar00rootroot00000000000000# Define a custom User class to work with django-social-auth from django.contrib.auth.models import AbstractUser class CustomUser(AbstractUser): pass python-social-auth-0.2.13/examples/django_example/example/app/pipeline.py000066400000000000000000000007041260133235600265140ustar00rootroot00000000000000from django.shortcuts import redirect from social.pipeline.partial import partial @partial def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): if kwargs.get('ajax') or user and user.email: return elif is_new and not details.get('email'): email = strategy.request_data().get('email') if email: details['email'] = email else: return redirect('require_email') python-social-auth-0.2.13/examples/django_example/example/app/templatetags/000077500000000000000000000000001260133235600270265ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/app/templatetags/__init__.py000066400000000000000000000000001260133235600311250ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/app/templatetags/backend_utils.py000066400000000000000000000040761260133235600322160ustar00rootroot00000000000000import re from django import template from social.backends.oauth import OAuthAuth register = template.Library() name_re = re.compile(r'([^O])Auth') @register.filter def backend_name(backend): name = backend.__class__.__name__ name = name.replace('OAuth', ' OAuth') name = name.replace('OpenId', ' OpenId') name = name.replace('Sandbox', '') name = name_re.sub(r'\1 Auth', name) return name @register.filter def backend_class(backend): return backend.name.replace('-', ' ') @register.filter def icon_name(name): return { 'stackoverflow': 'stack-overflow', 'google-oauth': 'google', 'google-oauth2': 'google', 'google-openidconnect': 'google', 'yahoo-oauth': 'yahoo', 'facebook-app': 'facebook', 'email': 'envelope', 'vimeo': 'vimeo-square', 'linkedin-oauth2': 'linkedin', 'vk-oauth2': 'vk', 'live': 'windows', 'username': 'user', }.get(name, name) @register.filter def social_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name not in ['username', 'email']] backends.sort(key=lambda b: b[0]) return [backends[n:n + 10] for n in range(0, len(backends), 10)] @register.filter def legacy_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name in ['username', 'email']] backends.sort(key=lambda b: b[0]) return backends @register.filter def oauth_backends(backends): backends = [(name, backend) for name, backend in backends.items() if issubclass(backend, OAuthAuth)] backends.sort(key=lambda b: b[0]) return backends @register.simple_tag(takes_context=True) def associated(context, backend): user = context.get('user') context['association'] = None if user and user.is_authenticated(): try: context['association'] = user.social_auth.filter( provider=backend.name )[0] except IndexError: pass return '' python-social-auth-0.2.13/examples/django_example/example/app/views.py000066400000000000000000000042221260133235600260430ustar00rootroot00000000000000import json from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect from django.contrib.auth.decorators import login_required from django.contrib.auth import logout as auth_logout, login from social.backends.oauth import BaseOAuth1, BaseOAuth2 from social.backends.google import GooglePlusAuth from social.backends.utils import load_backends from social.apps.django_app.utils import psa from example.app.decorators import render_to def logout(request): """Logs out user""" auth_logout(request) return redirect('/') def context(**extra): return dict({ 'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None), 'plus_scope': ' '.join(GooglePlusAuth.DEFAULT_SCOPE), 'available_backends': load_backends(settings.AUTHENTICATION_BACKENDS) }, **extra) @render_to('home.html') def home(request): """Home view, displays login mechanism""" if request.user.is_authenticated(): return redirect('done') return context() @login_required @render_to('home.html') def done(request): """Login complete view, displays user data""" return context() @render_to('home.html') def validation_sent(request): return context( validation_sent=True, email=request.session.get('email_validation_address') ) @render_to('home.html') def require_email(request): backend = request.session['partial_pipeline']['backend'] return context(email_required=True, backend=backend) @psa('social:complete') def ajax_auth(request, backend): if isinstance(request.backend, BaseOAuth1): token = { 'oauth_token': request.REQUEST.get('access_token'), 'oauth_token_secret': request.REQUEST.get('access_token_secret'), } elif isinstance(request.backend, BaseOAuth2): token = request.REQUEST.get('access_token') else: raise HttpResponseBadRequest('Wrong backend type') user = request.backend.do_auth(token, ajax=True) login(request, user) data = {'id': user.id, 'username': user.username} return HttpResponse(json.dumps(data), mimetype='application/json') python-social-auth-0.2.13/examples/django_example/example/settings.py000066400000000000000000000203751260133235600257750ustar00rootroot00000000000000import sys from os.path import abspath, dirname, join sys.path.insert(0, '../..') DEBUG = True TEMPLATE_DEBUG = DEBUG ROOT_PATH = abspath(dirname(__file__)) ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'test.db' } } TIME_ZONE = 'America/Montevideo' LANGUAGE_CODE = 'en-us' SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True MEDIA_ROOT = '' MEDIA_URL = '' STATIC_ROOT = '' STATIC_URL = '/static/' STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) SECRET_KEY = '#$5btppqih8=%ae^#&7en#kyi!vh%he9rg=ed#hm6fnw9^=umc' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'example.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'example.wsgi.application' TEMPLATE_DIRS = ( join(ROOT_PATH, 'templates'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'social.apps.django_app.default', 'example.app', ) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'social.apps.django_app.context_processors.backends', ) AUTHENTICATION_BACKENDS = ( 'social.backends.amazon.AmazonOAuth2', 'social.backends.angel.AngelOAuth2', 'social.backends.aol.AOLOpenId', 'social.backends.appsfuel.AppsfuelOAuth2', 'social.backends.beats.BeatsOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.belgiumeid.BelgiumEIDOpenId', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.clef.ClefOAuth2', 'social.backends.coinbase.CoinbaseOAuth2', 'social.backends.coursera.CourseraOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.douban.DoubanOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.dropbox.DropboxOAuth2', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.fedora.FedoraOpenId', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.github.GithubOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOpenId', 'social.backends.google.GooglePlusAuth', 'social.backends.google.GoogleOpenIdConnect', 'social.backends.instagram.InstagramOAuth2', 'social.backends.jawbone.JawboneOAuth2', 'social.backends.kakao.KakaoOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.linkedin.LinkedinOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.mailru.MailruOAuth2', 'social.backends.mendeley.MendeleyOAuth', 'social.backends.mendeley.MendeleyOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.mixcloud.MixcloudOAuth2', 'social.backends.nationbuilder.NationBuilderOAuth2', 'social.backends.odnoklassniki.OdnoklassnikiOAuth2', 'social.backends.open_id.OpenIdAuth', 'social.backends.openstreetmap.OpenStreetMapOAuth', 'social.backends.persona.PersonaAuth', 'social.backends.podio.PodioOAuth2', 'social.backends.rdio.RdioOAuth1', 'social.backends.rdio.RdioOAuth2', 'social.backends.readability.ReadabilityOAuth', 'social.backends.reddit.RedditOAuth2', 'social.backends.runkeeper.RunKeeperOAuth2', 'social.backends.skyrock.SkyrockOAuth', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.spotify.SpotifyOAuth2', 'social.backends.stackoverflow.StackoverflowOAuth2', 'social.backends.steam.SteamOpenId', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.stripe.StripeOAuth2', 'social.backends.suse.OpenSUSEOpenId', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.trello.TrelloOAuth', 'social.backends.tripit.TripItOAuth', 'social.backends.tumblr.TumblrOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.vk.VKOAuth2', 'social.backends.weibo.WeiboOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yahoo.YahooOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.yammer.YammerOAuth2', 'social.backends.yandex.YandexOAuth2', 'social.backends.vimeo.VimeoOAuth1', 'social.backends.lastfm.LastFmAuth', 'social.backends.moves.MovesOAuth2', 'social.backends.vend.VendOAuth2', 'social.backends.email.EmailAuth', 'social.backends.username.UsernameAuth', 'django.contrib.auth.backends.ModelBackend', ) AUTH_USER_MODEL = 'app.CustomUser' LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/done/' URL_PATH = '' SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.default.models.DjangoStorage' SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/userinfo.profile' ] # SOCIAL_AUTH_EMAIL_FORM_URL = '/signup-email' SOCIAL_AUTH_EMAIL_FORM_HTML = 'email_signup.html' SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'example.app.mail.send_validation' SOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email-sent/' # SOCIAL_AUTH_USERNAME_FORM_URL = '/signup-username' SOCIAL_AUTH_USERNAME_FORM_HTML = 'username_signup.html' SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'example.app.pipeline.require_email', 'social.pipeline.mail.mail_validation', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.debug.debug', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', 'social.pipeline.debug.debug' ) TEST_RUNNER = 'django.test.runner.DiscoverRunner' # SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['first_name', 'last_name', 'email', # 'username'] try: from example.local_settings import * except ImportError: pass python-social-auth-0.2.13/examples/django_example/example/templates/000077500000000000000000000000001260133235600255525ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_example/example/templates/home.html000066400000000000000000000453211260133235600273750ustar00rootroot00000000000000{% load backend_utils %} Python Social Auth

    Python Social Auth

    {% if user.is_authenticated %}
    You are logged in as {{ user.username }}!
    {% endif %}
    {% for name, backend in available_backends|legacy_backends %} {% associated backend %} {% if association %}
    {% csrf_token %} Disconnect {{ backend|backend_name }}
    {% else %} {{ backend|backend_name }} {% endif %} {% endfor %} Ajax
    {% if backend %} {% endif %} {% if plus_id %} {% endif %} python-social-auth-0.2.13/examples/django_example/example/urls.py000066400000000000000000000012771260133235600251220ustar00rootroot00000000000000from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^$', 'example.app.views.home'), url(r'^admin/', include(admin.site.urls)), url(r'^email-sent/', 'example.app.views.validation_sent'), url(r'^login/$', 'example.app.views.home'), url(r'^logout/$', 'example.app.views.logout'), url(r'^done/$', 'example.app.views.done', name='done'), url(r'^ajax-auth/(?P[^/]+)/$', 'example.app.views.ajax_auth', name='ajax-auth'), url(r'^email/$', 'example.app.views.require_email', name='require_email'), url(r'', include('social.apps.django_app.urls', namespace='social')) ) python-social-auth-0.2.13/examples/django_example/example/wsgi.py000066400000000000000000000021531260133235600251000ustar00rootroot00000000000000""" WSGI config for dj project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) python-social-auth-0.2.13/examples/django_example/manage.py000077500000000000000000000003701260133235600237260ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) python-social-auth-0.2.13/examples/django_example/requirements.txt000066400000000000000000000000371260133235600254050ustar00rootroot00000000000000django>=1.4 python-social-auth python-social-auth-0.2.13/examples/django_me_example/000077500000000000000000000000001260133235600226025ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/000077500000000000000000000000001260133235600242355ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/__init__.py000066400000000000000000000000001260133235600263340ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/000077500000000000000000000000001260133235600250155ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/__init__.py000066400000000000000000000000001260133235600271140ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/decorators.py000066400000000000000000000007211260133235600275340ustar00rootroot00000000000000from functools import wraps from django.template import RequestContext from django.shortcuts import render_to_response def render_to(tpl): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): out = func(request, *args, **kwargs) if isinstance(out, dict): out = render_to_response(tpl, out, RequestContext(request)) return out return wrapper return decorator python-social-auth-0.2.13/examples/django_me_example/example/app/mail.py000066400000000000000000000007561260133235600263210ustar00rootroot00000000000000from django.conf import settings from django.core.mail import send_mail from django.core.urlresolvers import reverse def send_validation(strategy, backend, code): url = '{0}?verification_code={1}'.format( reverse('social:complete', args=(backend.name,)), code.code ) url = strategy.request.build_absolute_uri(url) send_mail('Validate your account', 'Validate your account {0}'.format(url), settings.EMAIL_FROM, [code.email], fail_silently=False) python-social-auth-0.2.13/examples/django_me_example/example/app/models.py000066400000000000000000000000001260133235600266400ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/pipeline.py000066400000000000000000000007041260133235600271750ustar00rootroot00000000000000from django.shortcuts import redirect from social.pipeline.partial import partial @partial def require_email(strategy, details, user=None, is_new=False, *args, **kwargs): if kwargs.get('ajax') or user and user.email: return elif is_new and not details.get('email'): email = strategy.request_data().get('email') if email: details['email'] = email else: return redirect('require_email') python-social-auth-0.2.13/examples/django_me_example/example/app/templatetags/000077500000000000000000000000001260133235600275075ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/templatetags/__init__.py000066400000000000000000000000001260133235600316060ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/app/templatetags/backend_utils.py000066400000000000000000000042131260133235600326700ustar00rootroot00000000000000import re from django import template from social.backends.oauth import OAuthAuth from social.apps.django_app.me.models import UserSocialAuth register = template.Library() name_re = re.compile(r'([^O])Auth') @register.filter def backend_name(backend): name = backend.__class__.__name__ name = name.replace('OAuth', ' OAuth') name = name.replace('OpenId', ' OpenId') name = name.replace('Sandbox', '') name = name_re.sub(r'\1 Auth', name) return name @register.filter def backend_class(backend): return backend.name.replace('-', ' ') @register.filter def icon_name(name): return { 'stackoverflow': 'stack-overflow', 'google-oauth': 'google', 'google-oauth2': 'google', 'google-openidconnect': 'google', 'yahoo-oauth': 'yahoo', 'facebook-app': 'facebook', 'email': 'envelope', 'vimeo': 'vimeo-square', 'linkedin-oauth2': 'linkedin', 'vk-oauth2': 'vk', 'live': 'windows', 'username': 'user', }.get(name, name) @register.filter def social_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name not in ['username', 'email']] backends.sort(key=lambda b: b[0]) return [backends[n:n + 10] for n in range(0, len(backends), 10)] @register.filter def legacy_backends(backends): backends = [(name, backend) for name, backend in backends.items() if name in ['username', 'email']] backends.sort(key=lambda b: b[0]) return backends @register.filter def oauth_backends(backends): backends = [(name, backend) for name, backend in backends.items() if issubclass(backend, OAuthAuth)] backends.sort(key=lambda b: b[0]) return backends @register.simple_tag(takes_context=True) def associated(context, backend): user = context.get('user') context['association'] = None if user and user.is_authenticated(): try: context['association'] = UserSocialAuth.objects.filter( user=user, provider=backend.name )[0] except IndexError: pass return '' python-social-auth-0.2.13/examples/django_me_example/example/app/views.py000066400000000000000000000042221260133235600265240ustar00rootroot00000000000000import json from django.conf import settings from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect from django.contrib.auth.decorators import login_required from django.contrib.auth import logout as auth_logout, login from social.backends.oauth import BaseOAuth1, BaseOAuth2 from social.backends.google import GooglePlusAuth from social.backends.utils import load_backends from social.apps.django_app.utils import psa from example.app.decorators import render_to def logout(request): """Logs out user""" auth_logout(request) return redirect('/') def context(**extra): return dict({ 'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None), 'plus_scope': ' '.join(GooglePlusAuth.DEFAULT_SCOPE), 'available_backends': load_backends(settings.AUTHENTICATION_BACKENDS) }, **extra) @render_to('home.html') def home(request): """Home view, displays login mechanism""" if request.user.is_authenticated(): return redirect('done') return context() @login_required @render_to('home.html') def done(request): """Login complete view, displays user data""" return context() @render_to('home.html') def validation_sent(request): return context( validation_sent=True, email=request.session.get('email_validation_address') ) @render_to('home.html') def require_email(request): backend = request.session['partial_pipeline']['backend'] return context(email_required=True, backend=backend) @psa('social:complete') def ajax_auth(request, backend): if isinstance(request.backend, BaseOAuth1): token = { 'oauth_token': request.REQUEST.get('access_token'), 'oauth_token_secret': request.REQUEST.get('access_token_secret'), } elif isinstance(request.backend, BaseOAuth2): token = request.REQUEST.get('access_token') else: raise HttpResponseBadRequest('Wrong backend type') user = request.backend.do_auth(token, ajax=True) login(request, user) data = {'id': user.id, 'username': user.username} return HttpResponse(json.dumps(data), mimetype='application/json') python-social-auth-0.2.13/examples/django_me_example/example/settings.py000066400000000000000000000162711260133235600264560ustar00rootroot00000000000000import sys from os.path import abspath, dirname, join import mongoengine sys.path.insert(0, '../..') DEBUG = True TEMPLATE_DEBUG = DEBUG ROOT_PATH = abspath(dirname(__file__)) ADMINS = ( # ('Your Name', 'your_email@example.com'), ) MANAGERS = ADMINS DATABASES = { 'default': { 'ENGINE': 'django.db.backends.dummy', } } TIME_ZONE = 'America/Montevideo' LANGUAGE_CODE = 'en-us' SITE_ID = 1 USE_I18N = True USE_L10N = True USE_TZ = True MEDIA_ROOT = '' MEDIA_URL = '' STATIC_ROOT = '' STATIC_URL = '/static/' STATICFILES_DIRS = ( # Put strings here, like "/home/html/static" or "C:/www/django/static". # Always use forward slashes, even on Windows. # Don't forget to use absolute paths, not relative paths. ) STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', # 'django.contrib.staticfiles.finders.DefaultStorageFinder', ) SECRET_KEY = '#$5btppqih8=%ae^#&7en#kyi!vh%he9rg=ed#hm6fnw9^=umc' TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', # 'django.template.loaders.eggs.Loader', ) MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', # Uncomment the next line for simple clickjacking protection: # 'django.middleware.clickjacking.XFrameOptionsMiddleware', ) ROOT_URLCONF = 'example.urls' # Python dotted path to the WSGI application used by Django's runserver. WSGI_APPLICATION = 'example.wsgi.application' TEMPLATE_DIRS = ( join(ROOT_PATH, 'templates'), ) INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.admin', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', 'mongoengine.django.mongo_auth', 'social.apps.django_app.me', 'example.app', ) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'require_debug_false': { '()': 'django.utils.log.RequireDebugFalse' } }, 'handlers': { 'mail_admins': { 'level': 'ERROR', 'filters': ['require_debug_false'], 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django.request': { 'handlers': ['mail_admins'], 'level': 'ERROR', 'propagate': True, }, } } TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'social.apps.django_app.context_processors.backends', ) AUTH_USER_MODEL = 'mongo_auth.MongoUser' MONGOENGINE_USER_DOCUMENT = 'mongoengine.django.auth.User' SESSION_ENGINE = 'mongoengine.django.sessions' SESSION_SERIALIZER = 'mongoengine.django.sessions.BSONSerializer' mongoengine.connect('psa', host='mongodb://localhost/psa') # MONGOENGINE_USER_DOCUMENT = 'example.app.models.User' # SOCIAL_AUTH_USER_MODEL = 'example.app.models.User' AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.linkedin.LinkedinOAuth2', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.douban.DoubanOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.mixcloud.MixcloudOAuth2', 'social.backends.rdio.RdioOAuth1', 'social.backends.rdio.RdioOAuth2', 'social.backends.yammer.YammerOAuth2', 'social.backends.stackoverflow.StackoverflowOAuth2', 'social.backends.readability.ReadabilityOAuth', 'social.backends.skyrock.SkyrockOAuth', 'social.backends.tumblr.TumblrOAuth', 'social.backends.reddit.RedditOAuth2', 'social.backends.steam.SteamOpenId', 'social.backends.podio.PodioOAuth2', 'social.backends.amazon.AmazonOAuth2', 'social.backends.email.EmailAuth', 'social.backends.username.UsernameAuth', 'social.backends.wunderlist.WunderlistOAuth2', 'mongoengine.django.auth.MongoEngineBackend', 'django.contrib.auth.backends.ModelBackend', ) LOGIN_URL = '/login/' LOGIN_REDIRECT_URL = '/done/' URL_PATH = '' SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.me.models.DjangoStorage' SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = [ 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/userinfo.profile' ] # SOCIAL_AUTH_EMAIL_FORM_URL = '/signup-email' SOCIAL_AUTH_EMAIL_FORM_HTML = 'email_signup.html' SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'example.app.mail.send_validation' SOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email-sent/' # SOCIAL_AUTH_USERNAME_FORM_URL = '/signup-username' SOCIAL_AUTH_USERNAME_FORM_HTML = 'username_signup.html' SOCIAL_AUTH_PIPELINE = ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'example.app.pipeline.require_email', 'social.pipeline.mail.mail_validation', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details' ) TEST_RUNNER = 'django.test.runner.DiscoverRunner' try: from example.local_settings import * except ImportError: pass python-social-auth-0.2.13/examples/django_me_example/example/templates/000077500000000000000000000000001260133235600262335ustar00rootroot00000000000000python-social-auth-0.2.13/examples/django_me_example/example/templates/home.html000066400000000000000000000434111260133235600300540ustar00rootroot00000000000000{% load backend_utils %} Python Social Auth

    Python Social Auth

    {% if user.is_authenticated %}
    You are logged in as {{ user.username }}!
    {% endif %}
    {% for name, backend in available_backends|legacy_backends %} {% associated backend %} {% if association %}
    {% csrf_token %} Disconnect {{ backend|backend_name }}
    {% else %} {{ backend|backend_name }} {% endif %} {% endfor %} Ajax
    {% if backend %} {% endif %} {% if plus_id %}
    {% csrf_token %}
    {% endif %} python-social-auth-0.2.13/examples/django_me_example/example/urls.py000066400000000000000000000012771260133235600256030ustar00rootroot00000000000000from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^$', 'example.app.views.home'), url(r'^admin/', include(admin.site.urls)), url(r'^email-sent/', 'example.app.views.validation_sent'), url(r'^login/$', 'example.app.views.home'), url(r'^logout/$', 'example.app.views.logout'), url(r'^done/$', 'example.app.views.done', name='done'), url(r'^ajax-auth/(?P[^/]+)/$', 'example.app.views.ajax_auth', name='ajax-auth'), url(r'^email/$', 'example.app.views.require_email', name='require_email'), url(r'', include('social.apps.django_app.urls', namespace='social')) ) python-social-auth-0.2.13/examples/django_me_example/example/wsgi.py000066400000000000000000000021531260133235600255610ustar00rootroot00000000000000""" WSGI config for dj project. This module contains the WSGI application used by Django's development server and any production WSGI deployments. It should expose a module-level variable named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover this application via the ``WSGI_APPLICATION`` setting. Usually you will have the standard Django WSGI application here, but it also might make sense to replace the whole Django WSGI application with a custom one that later delegates to the Django one. For example, you could introduce WSGI middleware here, or combine a Django application with an application of another framework. """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION # setting points here. from django.core.wsgi import get_wsgi_application application = get_wsgi_application() # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) python-social-auth-0.2.13/examples/django_me_example/manage.py000077500000000000000000000003701260133235600244070ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == '__main__': os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) python-social-auth-0.2.13/examples/django_me_example/requirements.txt000066400000000000000000000000671260133235600260710ustar00rootroot00000000000000django>=1.4,<1.8 mongoengine>=0.8.6 python-social-auth python-social-auth-0.2.13/examples/flask_example/000077500000000000000000000000001260133235600217575ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_example/__init__.py000066400000000000000000000030371260133235600240730ustar00rootroot00000000000000import sys from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from flask import Flask, g from flask.ext import login sys.path.append('../..') from social.apps.flask_app.routes import social_auth from social.apps.flask_app.template_filters import backends from social.apps.flask_app.default.models import init_social # App app = Flask(__name__) app.config.from_object('flask_example.settings') try: app.config.from_object('flask_example.local_settings') except ImportError: pass # DB engine = create_engine(app.config['SQLALCHEMY_DATABASE_URI']) Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) db_session = scoped_session(Session) app.register_blueprint(social_auth) init_social(app, db_session) login_manager = login.LoginManager() login_manager.login_view = 'main' login_manager.login_message = '' login_manager.init_app(app) from flask_example import models from flask_example import routes @login_manager.user_loader def load_user(userid): try: return models.user.User.query.get(int(userid)) except (TypeError, ValueError): pass @app.before_request def global_user(): g.user = login.current_user @app.teardown_appcontext def commit_on_success(error=None): if error is None: db_session.commit() else: db_session.rollback() db_session.remove() @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} app.context_processor(backends) python-social-auth-0.2.13/examples/flask_example/manage.py000077500000000000000000000011261260133235600235640ustar00rootroot00000000000000#!/usr/bin/env python import sys from flask.ext.script import Server, Manager, Shell sys.path.append('..') from flask_example import app, db_session, engine manager = Manager(app) manager.add_command('runserver', Server()) manager.add_command('shell', Shell(make_context=lambda: { 'app': app, 'db_session': db_session })) @manager.command def syncdb(): from flask_example.models import user from social.apps.flask_app.default import models user.Base.metadata.create_all(engine) models.PSABase.metadata.create_all(engine) if __name__ == '__main__': manager.run() python-social-auth-0.2.13/examples/flask_example/models/000077500000000000000000000000001260133235600232425ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_example/models/__init__.py000066400000000000000000000001271260133235600253530ustar00rootroot00000000000000from flask_example.models import user from social.apps.flask_app.default import models python-social-auth-0.2.13/examples/flask_example/models/user.py000066400000000000000000000011311260133235600245660ustar00rootroot00000000000000from sqlalchemy import Column, String, Integer, Boolean from sqlalchemy.ext.declarative import declarative_base from flask.ext.login import UserMixin from flask_example import db_session Base = declarative_base() Base.query = db_session.query_property() class User(Base, UserMixin): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active python-social-auth-0.2.13/examples/flask_example/requirements.txt000066400000000000000000000000701260133235600252400ustar00rootroot00000000000000Flask Flask-Login Flask-Script Werkzeug pysqlite Jinja2 python-social-auth-0.2.13/examples/flask_example/routes/000077500000000000000000000000001260133235600233005ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_example/routes/__init__.py000066400000000000000000000001171260133235600254100ustar00rootroot00000000000000from flask_example.routes import main from social.apps.flask_app import routes python-social-auth-0.2.13/examples/flask_example/routes/main.py000066400000000000000000000006131260133235600245760ustar00rootroot00000000000000from flask import render_template, redirect from flask.ext.login import login_required, logout_user from flask_example import app @app.route('/') def main(): return render_template('home.html') @login_required @app.route('/done/') def done(): return render_template('done.html') @app.route('/logout') def logout(): """Logout view""" logout_user() return redirect('/') python-social-auth-0.2.13/examples/flask_example/settings.py000066400000000000000000000043301260133235600241710ustar00rootroot00000000000000from os.path import dirname, abspath SECRET_KEY = 'random-secret-key' SESSION_COOKIE_NAME = 'psa_session' DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:////%s/test.db' % dirname(abspath(__file__)) DEBUG_TB_INTERCEPT_REDIRECTS = False SESSION_PROTECTION = 'strong' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'flask_example.models.user.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) python-social-auth-0.2.13/examples/flask_example/templates/000077500000000000000000000000001260133235600237555ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_example/templates/base.html000066400000000000000000000010261260133235600255540ustar00rootroot00000000000000 Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.13/examples/flask_example/templates/done.html000066400000000000000000000010731260133235600255710ustar00rootroot00000000000000{% extends "base.html" %} {% block content %}

    You are logged in as {{ user.username }}!

    Associated:

    {% for assoc in backends.associated %}
    {{ assoc.provider }}
    {% endfor %}

    Associate:

      {% for name in backends.not_associated %}
    • {{ name }}
    • {% endfor %}
    {% endblock %} python-social-auth-0.2.13/examples/flask_example/templates/home.html000066400000000000000000000107251260133235600256000ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    ThisIsMyJam OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Clef OAuth2
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    MineID OAuth2
    Persona
    {% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.13/examples/flask_me_example/000077500000000000000000000000001260133235600224405ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_me_example/__init__.py000066400000000000000000000022721260133235600245540ustar00rootroot00000000000000import sys from flask import Flask, g from flask.ext import login from flask.ext.mongoengine import MongoEngine sys.path.append('../..') from social.apps.flask_app.routes import social_auth from social.apps.flask_app.me.models import init_social from social.apps.flask_app.template_filters import backends # App app = Flask(__name__) app.config.from_object('flask_me_example.settings') app.debug = True try: app.config.from_object('flask_me_example.local_settings') except ImportError: pass # DB db = MongoEngine(app) app.register_blueprint(social_auth) init_social(app, db) login_manager = login.LoginManager() login_manager.login_view = 'main' login_manager.login_message = '' login_manager.init_app(app) from flask_me_example import models from flask_me_example import routes @login_manager.user_loader def load_user(userid): try: return models.user.User.objects.get(id=userid) except (TypeError, ValueError): pass @app.before_request def global_user(): g.user = login.current_user @app.context_processor def inject_user(): try: return {'user': g.user} except AttributeError: return {'user': None} app.context_processor(backends) python-social-auth-0.2.13/examples/flask_me_example/manage.py000077500000000000000000000005411260133235600242450ustar00rootroot00000000000000#!/usr/bin/env python import sys from flask.ext.script import Server, Manager, Shell sys.path.append('..') from flask_me_example import app, db manager = Manager(app) manager.add_command('runserver', Server()) manager.add_command('shell', Shell(make_context=lambda: { 'app': app, 'db': db })) if __name__ == '__main__': manager.run() python-social-auth-0.2.13/examples/flask_me_example/models/000077500000000000000000000000001260133235600237235ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_me_example/models/__init__.py000066400000000000000000000001251260133235600260320ustar00rootroot00000000000000from flask_me_example.models import user from social.apps.flask_app.me import models python-social-auth-0.2.13/examples/flask_me_example/models/user.py000066400000000000000000000006531260133235600252570ustar00rootroot00000000000000from mongoengine import StringField, EmailField, BooleanField from flask.ext.login import UserMixin from flask_me_example import db class User(db.Document, UserMixin): username = StringField(max_length=200) password = StringField(max_length=200, default='') name = StringField(max_length=100) email = EmailField() active = BooleanField(default=True) def is_active(self): return self.active python-social-auth-0.2.13/examples/flask_me_example/requirements.txt000066400000000000000000000001251260133235600257220ustar00rootroot00000000000000Flask Flask-Login Flask-Script Werkzeug Jinja2 mongoengine==0.8.4 python-social-auth python-social-auth-0.2.13/examples/flask_me_example/routes/000077500000000000000000000000001260133235600237615ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_me_example/routes/__init__.py000066400000000000000000000001221260133235600260650ustar00rootroot00000000000000from flask_me_example.routes import main from social.apps.flask_app import routes python-social-auth-0.2.13/examples/flask_me_example/routes/main.py000066400000000000000000000006161260133235600252620ustar00rootroot00000000000000from flask import render_template, redirect from flask.ext.login import login_required, logout_user from flask_me_example import app @app.route('/') def main(): return render_template('home.html') @app.route('/done/') @login_required def done(): return render_template('done.html') @app.route('/logout') def logout(): """Logout view""" logout_user() return redirect('/') python-social-auth-0.2.13/examples/flask_me_example/settings.py000066400000000000000000000043531260133235600246570ustar00rootroot00000000000000from flask_me_example import app app.debug = True SECRET_KEY = 'random-secret-key' SESSION_COOKIE_NAME = 'psa_session' DEBUG = False MONGODB_SETTINGS = {'DB': 'psa_db'} DEBUG_TB_INTERCEPT_REDIRECTS = False SESSION_PROTECTION = 'strong' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'flask_me_example.models.user.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.lastfm.LastFmAuth', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) python-social-auth-0.2.13/examples/flask_me_example/templates/000077500000000000000000000000001260133235600244365ustar00rootroot00000000000000python-social-auth-0.2.13/examples/flask_me_example/templates/base.html000066400000000000000000000010261260133235600262350ustar00rootroot00000000000000 Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.13/examples/flask_me_example/templates/done.html000066400000000000000000000010731260133235600262520ustar00rootroot00000000000000{% extends "base.html" %} {% block content %}

    You are logged in as {{ user.username }}!

    Associated:

    {% for assoc in backends.associated %}
    {{ assoc.provider }}
    {% endfor %}

    Associate:

      {% for name in backends.not_associated %}
    • {{ name }}
    • {% endfor %}
    {% endblock %} python-social-auth-0.2.13/examples/flask_me_example/templates/home.html000066400000000000000000000107161260133235600262610ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    LastFm
    ThisIsMyJam OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Clef OAuth2
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    Persona
    {% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.13/examples/pyramid_example/000077500000000000000000000000001260133235600223245ustar00rootroot00000000000000python-social-auth-0.2.13/examples/pyramid_example/CHANGES.txt000066400000000000000000000000341260133235600241320ustar00rootroot000000000000000.0 --- - Initial version python-social-auth-0.2.13/examples/pyramid_example/MANIFEST.in000066400000000000000000000002021260133235600240540ustar00rootroot00000000000000include *.txt *.ini *.cfg *.rst recursive-include example *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml python-social-auth-0.2.13/examples/pyramid_example/README.txt000066400000000000000000000003431260133235600240220ustar00rootroot00000000000000example README ============== Getting Started --------------- - cd - $VENV/bin/python setup.py develop - $VENV/bin/initialize_example_db development.ini - $VENV/bin/pserve development.ini python-social-auth-0.2.13/examples/pyramid_example/development.ini000066400000000000000000000025501260133235600253510ustar00rootroot00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:example pyramid.reload_templates = true pyramid.debug_authorization = false pyramid.debug_notfound = true pyramid.debug_routematch = true pyramid.default_locale_name = en pyramid.includes = pyramid_debugtoolbar pyramid_tm sqlalchemy.url = sqlite:///%(here)s/test.db # By default, the toolbar only appears for clients from IP addresses # '127.0.0.1' and '::1'. # debugtoolbar.hosts = 127.0.0.1 ::1 ### # wsgi server configuration ### [server:main] use = egg:waitress#main host = 0.0.0.0 port = 8000 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, example, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = INFO handlers = console [logger_example] level = DEBUG handlers = qualname = example [logger_sqlalchemy] level = INFO handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s python-social-auth-0.2.13/examples/pyramid_example/example/000077500000000000000000000000001260133235600237575ustar00rootroot00000000000000python-social-auth-0.2.13/examples/pyramid_example/example/__init__.py000066400000000000000000000023401260133235600260670ustar00rootroot00000000000000import sys sys.path.append('../..') from pyramid.config import Configurator from pyramid.session import UnencryptedCookieSessionFactoryConfig from sqlalchemy import engine_from_config from social.apps.pyramid_app.models import init_social from .models import DBSession, Base def main(global_config, **settings): """This function returns a Pyramid WSGI application.""" engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.bind = engine session_factory = UnencryptedCookieSessionFactoryConfig('thisisasecret') config = Configurator(settings=settings, session_factory=session_factory, autocommit=True) config.include('pyramid_chameleon') config.add_static_view('static', 'static', cache_max_age=3600) config.add_request_method('example.auth.get_user', 'user', reify=True) config.add_route('home', '/') config.add_route('done', '/done') config.include('example.settings') config.include('example.local_settings') config.include('social.apps.pyramid_app') init_social(config, Base, DBSession) config.scan() config.scan('social.apps.pyramid_app') return config.make_wsgi_app() python-social-auth-0.2.13/examples/pyramid_example/example/auth.py000066400000000000000000000013311260133235600252700ustar00rootroot00000000000000from pyramid.events import subscriber, BeforeRender from social.apps.pyramid_app.utils import backends from example.models import DBSession, User def login_user(backend, user, user_social_auth): backend.strategy.session_set('user_id', user.id) def login_required(request): return getattr(request, 'user', None) is not None def get_user(request): user_id = request.session.get('user_id') if user_id: user = DBSession.query(User)\ .filter(User.id == user_id)\ .first() else: user = None return user @subscriber(BeforeRender) def add_social(event): request = event['request'] event['social'] = backends(request, request.user) python-social-auth-0.2.13/examples/pyramid_example/example/local_settings.py.template000066400000000000000000000047161260133235600311650ustar00rootroot00000000000000SOCIAL_AUTH_KEYS = { 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY': '', 'SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET': '', 'SOCIAL_AUTH_TWITTER_KEY': '', 'SOCIAL_AUTH_TWITTER_SECRET': '', 'SOCIAL_AUTH_STRIPE_KEY': '', 'SOCIAL_AUTH_STRIPE_SECRET': '', 'SOCIAL_AUTH_STRIPE_SCOPE': [], 'SOCIAL_AUTH_FACEBOOK_KEY': '', 'SOCIAL_AUTH_FACEBOOK_SECRET': '', 'SOCIAL_AUTH_FACEBOOK_APP_KEY': '', 'SOCIAL_AUTH_FACEBOOK_APP_SECRET': '', 'SOCIAL_AUTH_FACEBOOK_APP_NAMESPACE': '', 'SOCIAL_AUTH_YAHOO_OAUTH_KEY': '', 'SOCIAL_AUTH_YAHOO_OAUTH_SECRET': '', 'SOCIAL_AUTH_ANGEL_KEY': '', 'SOCIAL_AUTH_ANGEL_SECRET': '', 'SOCIAL_AUTH_BEHANCE_KEY': '', 'SOCIAL_AUTH_BEHANCE_SECRET': '', 'SOCIAL_AUTH_BEHANCE_SCOPE': [], 'SOCIAL_AUTH_BITBUCKET_KEY': '', 'SOCIAL_AUTH_BITBUCKET_SECRET': '', 'SOCIAL_AUTH_LINKEDIN_KEY': '', 'SOCIAL_AUTH_LINKEDIN_SECRET': '', 'SOCIAL_AUTH_LINKEDIN_SCOPE': [], 'SOCIAL_AUTH_GITHUB_KEY': '', 'SOCIAL_AUTH_GITHUB_SECRET': '', 'SOCIAL_AUTH_FOURSQUARE_KEY': '', 'SOCIAL_AUTH_FOURSQUARE_SECRET': '', 'SOCIAL_AUTH_INSTAGRAM_KEY': '', 'SOCIAL_AUTH_INSTAGRAM_SECRET': '', 'SOCIAL_AUTH_LIVE_KEY': '', 'SOCIAL_AUTH_LIVE_SECRET': '', 'SOCIAL_AUTH_VKONTAKTE_OAUTH2_KEY': '', 'SOCIAL_AUTH_VKONTAKTE_OAUTH2_SECRET': '', 'SOCIAL_AUTH_DAILYMOTION_KEY': '', 'SOCIAL_AUTH_DAILYMOTION_SECRET': '', 'SOCIAL_AUTH_DISQUS_KEY': '', 'SOCIAL_AUTH_DISQUS_SECRET': '', 'SOCIAL_AUTH_DROPBOX_KEY': '', 'SOCIAL_AUTH_DROPBOX_SECRET': '', 'SOCIAL_AUTH_EVERNOTE_SANDBOX_KEY': '', 'SOCIAL_AUTH_EVERNOTE_SANDBOX_SECRET': '', 'SOCIAL_AUTH_FITBIT_KEY': '', 'SOCIAL_AUTH_FITBIT_SECRET': '', 'SOCIAL_AUTH_FLICKR_KEY': '', 'SOCIAL_AUTH_FLICKR_SECRET': '', 'SOCIAL_AUTH_SOUNDCLOUD_KEY': '', 'SOCIAL_AUTH_SOUNDCLOUD_SECRET': '', 'SOCIAL_AUTH_STOCKTWITS_KEY': '', 'SOCIAL_AUTH_STOCKTWITS_SECRET': '', 'SOCIAL_AUTH_TRIPIT_KEY': '', 'SOCIAL_AUTH_TRIPIT_SECRET': '', 'SOCIAL_AUTH_TWILIO_KEY': '', 'SOCIAL_AUTH_TWILIO_SECRET': '', 'SOCIAL_AUTH_XING_KEY': '', 'SOCIAL_AUTH_XING_SECRET': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_KEY': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_SECRET': '', 'SOCIAL_AUTH_YANDEX_OAUTH2_API_URL': '', 'SOCIAL_AUTH_REDDIT_KEY': '', 'SOCIAL_AUTH_REDDIT_SECRET': '', 'SOCIAL_AUTH_REDDIT_AUTH_EXTRA_ARGUMENTS': {}, } def includeme(config): config.registry.settings.update(SOCIAL_AUTH_KEYS) python-social-auth-0.2.13/examples/pyramid_example/example/models.py000066400000000000000000000011401260133235600256100ustar00rootroot00000000000000from sqlalchemy import Column, Integer, String, Boolean from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker from zope.sqlalchemy import ZopeTransactionExtension DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) email = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) active = Column(Boolean, default=True) python-social-auth-0.2.13/examples/pyramid_example/example/scripts/000077500000000000000000000000001260133235600254465ustar00rootroot00000000000000python-social-auth-0.2.13/examples/pyramid_example/example/scripts/__init__.py000066400000000000000000000000121260133235600275500ustar00rootroot00000000000000# package python-social-auth-0.2.13/examples/pyramid_example/example/scripts/initializedb.py000066400000000000000000000017101260133235600304660ustar00rootroot00000000000000import os import sys sys.path.append('../..') from sqlalchemy import engine_from_config from pyramid.paster import get_appsettings, setup_logging from pyramid.scripts.common import parse_vars from social.apps.pyramid_app.models import init_social from example.models import DBSession, Base from example.settings import SOCIAL_AUTH_SETTINGS def usage(argv): cmd = os.path.basename(argv[0]) print('usage: %s [var=value]\n' '(example: "%s development.ini")' % (cmd, cmd)) sys.exit(1) def main(argv=sys.argv): if len(argv) < 2: usage(argv) config_uri = argv[1] options = parse_vars(argv[2:]) setup_logging(config_uri) settings = get_appsettings(config_uri, options=options) init_social(SOCIAL_AUTH_SETTINGS, Base, DBSession) engine = engine_from_config(settings, 'sqlalchemy.') DBSession.configure(bind=engine) Base.metadata.create_all(engine) if __name__ == '__main__': main() python-social-auth-0.2.13/examples/pyramid_example/example/settings.py000066400000000000000000000045571260133235600262040ustar00rootroot00000000000000SOCIAL_AUTH_SETTINGS = { 'SOCIAL_AUTH_LOGIN_URL': '/', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': '/done', 'SOCIAL_AUTH_USER_MODEL': 'example.models.User', 'SOCIAL_AUTH_LOGIN_FUNCTION': 'example.auth.login_user', 'SOCIAL_AUTH_LOGGEDIN_FUNCTION': 'example.auth.login_required', 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.twitter.TwitterOAuth', 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.twilio.TwilioAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) } def includeme(config): config.registry.settings.update(SOCIAL_AUTH_SETTINGS) python-social-auth-0.2.13/examples/pyramid_example/example/templates/000077500000000000000000000000001260133235600257555ustar00rootroot00000000000000python-social-auth-0.2.13/examples/pyramid_example/example/templates/done.pt000066400000000000000000000015151260133235600272510ustar00rootroot00000000000000 Social Auth Pyramid Example

    You are logged in as ${request.user.username}!

    Associated:

    Associate:

    python-social-auth-0.2.13/examples/pyramid_example/example/templates/home.pt000066400000000000000000000124331260133235600272550ustar00rootroot00000000000000 Social Auth Pyramid Example Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    ThisIsMyJam OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Clef OAuth2
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    Reddit OAuth2
    Persona
    python-social-auth-0.2.13/examples/pyramid_example/example/tests.py000066400000000000000000000027331260133235600255000ustar00rootroot00000000000000import unittest import transaction from pyramid import testing from .models import DBSession class TestMyViewSuccessCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) Base.metadata.create_all(engine) with transaction.manager: model = MyModel(name='one', value=55) DBSession.add(model) def tearDown(self): DBSession.remove() testing.tearDown() def test_passing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info['one'].name, 'one') self.assertEqual(info['project'], 'example') class TestMyViewFailureCondition(unittest.TestCase): def setUp(self): self.config = testing.setUp() from sqlalchemy import create_engine engine = create_engine('sqlite://') from .models import ( Base, MyModel, ) DBSession.configure(bind=engine) def tearDown(self): DBSession.remove() testing.tearDown() def test_failing_view(self): from .views import my_view request = testing.DummyRequest() info = my_view(request) self.assertEqual(info.status_int, 500) python-social-auth-0.2.13/examples/pyramid_example/example/views.py000066400000000000000000000003471260133235600254720ustar00rootroot00000000000000from pyramid.view import view_config @view_config(route_name='home', renderer='templates/home.pt') def home(request): return {} @view_config(route_name='done', renderer='templates/done.pt') def done(request): return {} python-social-auth-0.2.13/examples/pyramid_example/production.ini000066400000000000000000000022501260133235600252120ustar00rootroot00000000000000### # app configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html ### [app:main] use = egg:example pyramid.reload_templates = false pyramid.debug_authorization = false pyramid.debug_notfound = false pyramid.debug_routematch = false pyramid.default_locale_name = en pyramid.includes = pyramid_tm sqlalchemy.url = sqlite:///%(here)s/test.db [server:main] use = egg:waitress#main host = 0.0.0.0 port = 6543 ### # logging configuration # http://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html ### [loggers] keys = root, example, sqlalchemy [handlers] keys = console [formatters] keys = generic [logger_root] level = WARN handlers = console [logger_example] level = WARN handlers = qualname = example [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine # "level = INFO" logs SQL queries. # "level = DEBUG" logs SQL queries and results. # "level = WARN" logs neither. (Recommended for production systems.) [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s python-social-auth-0.2.13/examples/pyramid_example/requirements.txt000066400000000000000000000000331260133235600256040ustar00rootroot00000000000000python-social-auth pyramid python-social-auth-0.2.13/examples/pyramid_example/setup.cfg000066400000000000000000000007511260133235600241500ustar00rootroot00000000000000[nosetests] match=^test nocapture=1 cover-package=example with-coverage=1 cover-erase=1 [compile_catalog] directory = example/locale domain = example statistics = true [extract_messages] add_comments = TRANSLATORS: output_file = example/locale/example.pot width = 80 [init_catalog] domain = example input_file = example/locale/example.pot output_dir = example/locale [update_catalog] domain = example input_file = example/locale/example.pot output_dir = example/locale previous = true python-social-auth-0.2.13/examples/pyramid_example/setup.py000066400000000000000000000022561260133235600240430ustar00rootroot00000000000000import os from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(here, 'README.txt')) as f: README = f.read() with open(os.path.join(here, 'CHANGES.txt')) as f: CHANGES = f.read() requires = [ 'pyramid', 'SQLAlchemy', 'transaction', 'pyramid_tm', 'pyramid_debugtoolbar', 'zope.sqlalchemy', 'waitress', 'pyramid_chameleon', ] setup(name='example', version='0.0', description='example', long_description=README + '\n\n' + CHANGES, classifiers=[ "Programming Language :: Python", "Framework :: Pyramid", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", ], author='', author_email='', url='', keywords='web wsgi bfg pylons pyramid', packages=find_packages(), include_package_data=True, zip_safe=False, test_suite='example', install_requires=requires, entry_points="""\ [paste.app_factory] main = example:main [console_scripts] initialize_example_db = example.scripts.initializedb:main """, ) python-social-auth-0.2.13/examples/tornado_example/000077500000000000000000000000001260133235600223255ustar00rootroot00000000000000python-social-auth-0.2.13/examples/tornado_example/__init__.py000066400000000000000000000000001260133235600244240ustar00rootroot00000000000000python-social-auth-0.2.13/examples/tornado_example/app.py000066400000000000000000000035671260133235600234720ustar00rootroot00000000000000import sys sys.path.append('../..') from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import scoped_session, sessionmaker import tornado.httpserver import tornado.ioloop import tornado.options import tornado.web from social.apps.tornado_app.models import init_social from social.apps.tornado_app.routes import SOCIAL_AUTH_ROUTES import settings engine = create_engine('sqlite:///test.db', echo=False) session = scoped_session(sessionmaker(bind=engine)) Base = declarative_base() class MainHandler(tornado.web.RequestHandler): def get(self): self.render('templates/home.html') class DoneHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): from models import User user_id = self.get_secure_cookie('user_id') user = session.query(User).get(int(user_id)) self.render('templates/done.html', user=user) class LogoutHandler(tornado.web.RequestHandler): def get(self): self.request.redirect('/') tornado.options.parse_command_line() tornado_settings = dict((k, getattr(settings, k)) for k in dir(settings) if not k.startswith('__')) application = tornado.web.Application(SOCIAL_AUTH_ROUTES + [ (r'/', MainHandler), (r'/done/', DoneHandler), (r'/logout/', LogoutHandler), ], cookie_secret='adb528da-20bb-4386-8eaf-09f041b569e0', **tornado_settings) def main(): init_social(Base, session, tornado_settings) http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8000) tornado.ioloop.IOLoop.instance().start() def syncdb(): from models import user_syncdb init_social(Base, session, tornado_settings) Base.metadata.create_all(engine) user_syncdb() if __name__ == '__main__': if len(sys.argv) > 1 and sys.argv[1] == 'syncdb': syncdb() else: main() python-social-auth-0.2.13/examples/tornado_example/models.py000066400000000000000000000007321260133235600241640ustar00rootroot00000000000000from sqlalchemy import Column, Integer, String from app import Base, engine class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(30), nullable=False) first_name = Column(String(30), nullable=True) last_name = Column(String(30), nullable=True) email = Column(String(75), nullable=False) password = Column(String(128), nullable=True) def user_syncdb(): Base.metadata.create_all(engine) python-social-auth-0.2.13/examples/tornado_example/settings.py000066400000000000000000000040071260133235600245400ustar00rootroot00000000000000SQLALCHEMY_DATABASE_URI = 'sqlite:///test.db' SOCIAL_AUTH_LOGIN_URL = '/' SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/done/' SOCIAL_AUTH_USER_MODEL = 'models.User' SOCIAL_AUTH_AUTHENTICATION_BACKENDS = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.reddit.RedditOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) from local_settings import * python-social-auth-0.2.13/examples/tornado_example/templates/000077500000000000000000000000001260133235600243235ustar00rootroot00000000000000python-social-auth-0.2.13/examples/tornado_example/templates/base.html000066400000000000000000000010141260133235600261170ustar00rootroot00000000000000 Social {% block content %}{% end %} {% block scripts %}{% end %} python-social-auth-0.2.13/examples/tornado_example/templates/done.html000066400000000000000000000001521260133235600261340ustar00rootroot00000000000000{% extends "base.html" %} {% block content %}

    You are logged in as {{ user.username }}!

    {% end %} python-social-auth-0.2.13/examples/tornado_example/templates/home.html000066400000000000000000000063411260133235600261450ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    ThisIsMyJam OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Clef OAuth2
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    MineID OAuth2
    Persona
    {% end %} {% block scripts %} {% end %} python-social-auth-0.2.13/examples/webpy_example/000077500000000000000000000000001260133235600220055ustar00rootroot00000000000000python-social-auth-0.2.13/examples/webpy_example/__init__.py000066400000000000000000000000001260133235600241040ustar00rootroot00000000000000python-social-auth-0.2.13/examples/webpy_example/app.py000066400000000000000000000064621260133235600231470ustar00rootroot00000000000000import sys sys.path.append('../..') import web from web.contrib.template import render_jinja from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from social.utils import setting_name from social.apps.webpy_app.utils import psa, backends from social.apps.webpy_app import app as social_app import local_settings web.config.debug = False web.config[setting_name('USER_MODEL')] = 'models.User' web.config[setting_name('AUTHENTICATION_BACKENDS')] = ( 'social.backends.open_id.OpenIdAuth', 'social.backends.google.GoogleOpenId', 'social.backends.google.GoogleOAuth2', 'social.backends.google.GoogleOAuth', 'social.backends.twitter.TwitterOAuth', 'social.backends.yahoo.YahooOpenId', 'social.backends.stripe.StripeOAuth2', 'social.backends.persona.PersonaAuth', 'social.backends.facebook.FacebookOAuth2', 'social.backends.facebook.FacebookAppOAuth2', 'social.backends.yahoo.YahooOAuth', 'social.backends.angel.AngelOAuth2', 'social.backends.behance.BehanceOAuth2', 'social.backends.bitbucket.BitbucketOAuth', 'social.backends.box.BoxOAuth2', 'social.backends.linkedin.LinkedinOAuth', 'social.backends.github.GithubOAuth2', 'social.backends.foursquare.FoursquareOAuth2', 'social.backends.instagram.InstagramOAuth2', 'social.backends.live.LiveOAuth2', 'social.backends.vk.VKOAuth2', 'social.backends.dailymotion.DailymotionOAuth2', 'social.backends.disqus.DisqusOAuth2', 'social.backends.dropbox.DropboxOAuth', 'social.backends.eveonline.EVEOnlineOAuth2', 'social.backends.evernote.EvernoteSandboxOAuth', 'social.backends.fitbit.FitbitOAuth', 'social.backends.flickr.FlickrOAuth', 'social.backends.livejournal.LiveJournalOpenId', 'social.backends.soundcloud.SoundcloudOAuth2', 'social.backends.thisismyjam.ThisIsMyJamOAuth1', 'social.backends.stocktwits.StocktwitsOAuth2', 'social.backends.tripit.TripItOAuth', 'social.backends.clef.ClefOAuth2', 'social.backends.twilio.TwilioAuth', 'social.backends.xing.XingOAuth', 'social.backends.yandex.YandexOAuth2', 'social.backends.podio.PodioOAuth2', 'social.backends.mineid.MineIDOAuth2', 'social.backends.wunderlist.WunderlistOAuth2', ) web.config[setting_name('LOGIN_REDIRECT_URL')] = '/done/' urls = ( '^/$', 'main', '^/done/$', 'done', '', social_app.app_social ) render = render_jinja('templates/') class main(object): def GET(self): return render.home() class done(social_app.BaseViewClass): def GET(self): user = self.get_current_user() return render.done(user=user, backends=backends(user)) engine = create_engine('sqlite:///test.db', echo=True) def load_sqla(handler): web.ctx.orm = scoped_session(sessionmaker(bind=engine)) try: return handler() except web.HTTPError: web.ctx.orm.commit() raise except: web.ctx.orm.rollback() raise finally: web.ctx.orm.commit() # web.ctx.orm.expunge_all() Session = sessionmaker(bind=engine) Session.configure(bind=engine) app = web.application(urls, locals()) app.add_processor(load_sqla) session = web.session.Session(app, web.session.DiskStore('sessions')) web.db_session = Session() web.web_session = session if __name__ == "__main__": app.run() python-social-auth-0.2.13/examples/webpy_example/migrate.py000066400000000000000000000003201260133235600240020ustar00rootroot00000000000000from app import engine from models import Base from social.apps.webpy_app.models import SocialBase if __name__ == '__main__': Base.metadata.create_all(engine) SocialBase.metadata.create_all(engine) python-social-auth-0.2.13/examples/webpy_example/models.py000066400000000000000000000010151260133235600236370ustar00rootroot00000000000000from sqlalchemy import Column, Integer, String, Boolean from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(200)) password = Column(String(200), default='') name = Column(String(100)) email = Column(String(200)) active = Column(Boolean, default=True) def is_active(self): return self.active def is_authenticated(self): return True python-social-auth-0.2.13/examples/webpy_example/requirements.txt000066400000000000000000000000541260133235600252700ustar00rootroot00000000000000Jinja2==2.6 web.py==0.37 python-social-auth python-social-auth-0.2.13/examples/webpy_example/templates/000077500000000000000000000000001260133235600240035ustar00rootroot00000000000000python-social-auth-0.2.13/examples/webpy_example/templates/base.html000066400000000000000000000010261260133235600256020ustar00rootroot00000000000000 Social {% block content %}{% endblock %} {% block scripts %}{% endblock %} python-social-auth-0.2.13/examples/webpy_example/templates/done.html000066400000000000000000000007711260133235600256230ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Logged in as {{ user.username }}!

    Associated:

    {% for assoc in backends["associated"] %}
    {{ assoc.provider }}
    {% endfor %}

    Associate:

      {% for name in backends["not_associated"] %}
    • {{ name }}
    • {% endfor %}
    {% endblock %} python-social-auth-0.2.13/examples/webpy_example/templates/home.html000066400000000000000000000064221260133235600256250ustar00rootroot00000000000000{% extends "base.html" %} {% block content %} Google OAuth2
    Google OAuth
    Google OpenId
    Twitter OAuth
    Yahoo OpenId
    Yahoo OAuth
    Stripe OAuth2
    Facebook OAuth2
    Facebook App
    Angel OAuth2
    Behance OAuth2
    Bitbucket OAuth
    Box OAuth2
    LinkedIn OAuth
    Github OAuth2
    Foursquare OAuth2
    Instagram OAuth2
    Live OAuth2
    VK.com OAuth2
    Dailymotion OAuth2
    Disqus OAuth2
    Dropbox OAuth
    Evernote OAuth (sandbox mode)
    Fitbit OAuth
    Flickr OAuth
    Soundcloud OAuth2
    ThisIsMyJamm OAuth1
    Stocktwits OAuth2
    Tripit OAuth
    Clef OAuth2
    Twilio
    Xing OAuth
    Yandex OAuth2
    Podio OAuth2
    MineID OAuth2
    Persona
    {% endblock %} {% block scripts %} {% endblock %} python-social-auth-0.2.13/requirements-python3.txt000066400000000000000000000001461260133235600222150ustar00rootroot00000000000000python3-openid>=3.0.1 requests>=1.1.0 oauthlib>=0.3.8 requests-oauthlib>0.3.2 six>=1.2.0 PyJWT>=1.0.0 python-social-auth-0.2.13/requirements.txt000066400000000000000000000001441260133235600206110ustar00rootroot00000000000000python-openid>=2.2 requests>=2.5.1 oauthlib>=0.3.8 requests-oauthlib>=0.3.1 six>=1.2.0 PyJWT>=1.0.0 python-social-auth-0.2.13/run_tox.sh000077500000000000000000000000671260133235600173660ustar00rootroot00000000000000#!/bin/bash which pyenv && eval "$(pyenv init -)" tox python-social-auth-0.2.13/setup.cfg000066400000000000000000000003311260133235600171440ustar00rootroot00000000000000[flake8] max-line-length = 119 # Ignore some well known paths exclude = .venv,.tox,dist,doc,build,*.egg,db/env.py,db/versions/*.py [nosetests] verbosity=2 with-coverage=1 cover-erase=1 cover-package=social rednose=1 python-social-auth-0.2.13/setup.py000066400000000000000000000052761260133235600170520ustar00rootroot00000000000000# -*- coding: utf-8 -*- """Setup file for easy installation""" import sys import os from os.path import join, dirname, split from setuptools import setup PY3 = os.environ.get('BUILD_VERSION') == '3' or sys.version_info[0] == 3 version = __import__('social').__version__ LONG_DESCRIPTION = """ Python Social Auth is an easy to setup social authentication/registration mechanism with support for several frameworks and auth providers. Crafted using base code from django-social-auth, implements a common interface to define new authentication providers from third parties. And to bring support for more frameworks and ORMs. """ def long_description(): """Return long description from README.rst if it's present because it doesn't get installed.""" try: return open(join(dirname(__file__), 'README.rst')).read() except IOError: return LONG_DESCRIPTION def path_tokens(path): if not path: return [] head, tail = split(path) return path_tokens(head) + [tail] def get_packages(): exclude_pacakages = ('__pycache__',) packages = [] for path_info in os.walk('social'): tokens = path_tokens(path_info[0]) if tokens[-1] not in exclude_pacakages: packages.append('.'.join(tokens)) return packages requirements_file, tests_requirements_file = { False: ('requirements.txt', 'social/tests/requirements.txt'), True: ('requirements-python3.txt', 'social/tests/requirements-python3.txt') }[PY3] with open(requirements_file, 'r') as f: requirements = f.readlines() with open(tests_requirements_file, 'r') as f: tests_requirements = [line for line in f.readlines() if '@' not in line] setup( name='python-social-auth', version=version, author='Matias Aguirre', author_email='matiasaguirre@gmail.com', description='Python social authentication made simple.', license='BSD', keywords='django, flask, pyramid, webpy, openid, oauth, social auth', url='https://github.com/omab/python-social-auth', packages=get_packages(), long_description=long_description(), install_requires=requirements, classifiers=[ 'Development Status :: 4 - Beta', 'Topic :: Internet', 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Environment :: Web Environment', 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3' ], package_data={ 'social/tests': ['social/tests/*.txt'] }, include_package_data=True, tests_require=tests_requirements, test_suite='social.tests', zip_safe=False ) python-social-auth-0.2.13/site/000077500000000000000000000000001260133235600162725ustar00rootroot00000000000000python-social-auth-0.2.13/site/css/000077500000000000000000000000001260133235600170625ustar00rootroot00000000000000python-social-auth-0.2.13/site/css/bootstrap-responsive.min.css000066400000000000000000000407211260133235600245720ustar00rootroot00000000000000/*! * Bootstrap Responsive v2.3.0 * * Copyright 2012 Twitter, Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world @twitter by @mdo and @fat. */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} python-social-auth-0.2.13/site/css/bootstrap.min.css000066400000000000000000003167231260133235600224070ustar00rootroot00000000000000/*! * Bootstrap v2.3.0 * * Copyright 2012 Twitter, Inc * Licensed under the Apache License v2.0 * http://www.apache.org/licenses/LICENSE-2.0 * * Designed and built with all the love in the world @twitter by @mdo and @fat. */.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover,a:focus{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover,a.muted:focus{color:#808080}.text-warning{color:#c09853}a.text-warning:hover,a.text-warning:focus{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover,a.text-error:focus{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover,a.text-info:focus{color:#2d6987}.text-success{color:#468847}a.text-success:hover,a.text-success:focus{color:#356635}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;padding-right:5px;padding-left:5px;*zoom:1}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;font-size:0;white-space:nowrap;vertical-align:middle}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:focus>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:focus>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{width:16px;background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #ccc;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover,.btn:focus{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover,.navbar-link:focus{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover,a.thumbnail:focus{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover,.carousel-control:focus{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,0.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} python-social-auth-0.2.13/site/css/site.css000066400000000000000000000000661260133235600205420ustar00rootroot00000000000000body { padding-top: 60px; padding-bottom: 40px; } python-social-auth-0.2.13/site/docs000077700000000000000000000000001260133235600215432../docs/_build/ustar00rootroot00000000000000python-social-auth-0.2.13/site/img/000077500000000000000000000000001260133235600170465ustar00rootroot00000000000000python-social-auth-0.2.13/site/img/glyphicons-halflings-white.png000066400000000000000000000211111260133235600250120ustar00rootroot00000000000000PNG  IHDRӳ{PLTEmmmⰰᒒttt󻻻bbbeeeggg𶶶xxx󛛛Ƽ몪֢UUU鿿rOtRNS#_ /oS?C kDOS_6>4!~a @1_'onҋM3BQjp&%!l"Xqr; A[<`am}43/0IPCM!6(*gK&YQGDP,`{VP-x)h7e1]W$1bzSܕcO]U;Zi'y"؆K 64Y*.v@c.};tN%DI !ZЏ5LH26 ɯ" -bE,,)ʏ B>mn6pmRO wm@V#?'CȑZ#qb|$:)/E%nRqChn%i̓}lm ?idd",`H"r.z~(bQU&)5X#EMR<*p[[%.Ọk7lIoJF lV!̡ăuH`&,zRk$|$lXbjߪdU?Σ$HW$U'HE3*խU\}( zhVk}guRk$%|T|ck獳"D_W+.Q)@ƽHbslTDR2Xm#a 3lYzj㒚#! 4J8(cvt]aT D ΅Q?^-_^$:\V $N|=(vZ'q6Z׆B5V!y3K㱿bv4xR]al!IoP@tVyL٪mlڿIUb|[*lke'*WddDӝ}\W_WߝrN?vޫ۲X%0uoui*JVƦb%}i5IYlNE-wςf_W3mI-mQ)S kTC7m<"܌bT|'$ҘR&>O p6tSN\ׯLm\r@3uT b7t.5.q3r0=8TiJ\6uF R32^'ŪxI F8O{%8kJMSȴdBEdWCYO:/ON/I_=xFE! =i:o~ y?''[͓[͓[͓[͓[ͭ.U>$PƦc%]\c:| ,eSZ,oXrX!R@Zv 0>?* <|N60;{ad2v+D^t[q!۞V}fۨϏYeॗ)Vyl|" fUq@Ǽ4Y-Y-!6aB:o%JIUQ|UKO`=\ :0x Pau@!KPdxhw1>$j΍vZdxSUA&[URd7øzk/rU^w:I.VǮc>q.!zSr&2)Wg R -iQ 8Pa\ОU%iݡU_=p Lu(N?0?Æ:]άtB%U|NsorNf ,P !v" Y6hL_@@bscqgv4||0lϟ$S9bʱj#~?o}}7sAPm:IV=n !{{hEࢪ8suoLT$;VscqD3 ༂3.DBB4&V' T `D6Ϸqyj8V*X%@s\jrN$|=5Ά 'mUiKi%CI:ssaƅ`*`=l)>u՘MeuSI_OL_}o&jzp{lu:O)s%Q@$<]f xO%PCbhr2PKpf5Në3^o]eJiB464^tuٲU֌:G4'22YpuG'/Py4?.SBP_>I 1t3ΓBɭɭɭɭVVVVVs]!67(g y@ 4>Q VF}^Xׇڼje26 L%YGh lC})< !EEPZWZV+@†R 5{@ouɐ4&H6ey V݀VťcqZޒrJyByFzFN$Hb*+jՏqэ ګkݿUXle1d0d^-B%} {Y%r*j5Ak5u",:~ҸY~ hSA~6 fulՇf{ȵQtATHZkƭ/_Sn u']b]|m`BāJ,O$du]Zs FL:aǙT4o~by?wpj滥A(x]†f~an֧/^dڲcՇ,!1i&xi_VK@ip̓9Vi%a; L?0J*Ū5U'x^6V[^ {eU|:0=0d۫o*Jq%[YN.sQLud[29I:WnmXlڃ6!lNlVէKUjV\J%UߊBLcKfb>a=b~R]aG%[js@/9MطݘU>yɲX@} Ftg^vO\Ӹwvpz3K5i!$P>ā'VƛL2r@UMKZ6tw맟¦bm1h||]}~0MjA(JJP68C&yr׉e}j_cJ?I0k>šW |Bޝ."TEXd 8!cw*E(J)![W"j_ТeX_XB;oO0~?:PC (.[!Wq%*leY)E<^KZT60.#A\5;Rmtkd/8)5~^0 #Ckgey)ͶԺ6ĥ<(?&uAVm0^h.txR*a':,H|ō l5z;8+e#b'#|}2w(|KcJ l6 w^Տoi3H R ̔9,YgPְ:N [5SR![)]i}`mN4Хv`|;f(FltL8÷Z#AO%Y)NU5YedJE3dZذݣHT1 ;8MjnʏӤqp 1h^<<>yt{?|'j)}YUU{@V/J1F+7䀉[OWO[ yUY!?BD%DWj>-Ai6xz)U R7 d@g\so)a4zf[W+> P> |qLG8vȣlj2Zt+VA6gT *ʆUz(m)CD `He/.:zN9pgo &NC׃އ>Wհ_Hj)Xe6F7pm-`'c.AZ=^e8F;{Rtn(z!S7o Iew3]bܗ85|iϠRJkʱZRO+8U&:]ZieR(JMޗ7Z@5a^\GzsρU*rMezT^:ɬͦX=>$ bi>U&XQoybbGk8 Ҙn).Սo ^MmdZi$soo*{4eLbLٳ""mx:`:mk[geTެ)'0*TB{!I ''''[͓[͓[͓[͓[]Zj Q.e '/yvQ71(Z&X?(_Z){tڀmZWϏ)-C jqn,̋"IvUL!h꛿skAcrN佚фVE40yX~4zʸV㳰%,)fqtpu~  *^0:ܲ33JO(ZB?K^ v]unlWi0p6[착C_5X#[wX3b廫R{NKAe Se|wxso>P\儔ԕ6;nVmfI$V͓J-J%֌0UwYЎSnum藮xz˗VƫIvnW_qLZ"_Xz 8]Ap?C543zw({7e*Ȳ`۰!AQ:KUnz]1yVGaCm0PY ٚUx6TT&hV9V ӬzÑ 1[XzZ9erqJND/gX*9oN6D` {I%Mz9—TQ7f\"j_3~xB'ܷY]*KЌ%"5"qxq~ƕ=jS>jV&~]2xzF1X_yD<#NRB}K/iy !V^˿eJ}/FkA7 S+.(ecJ:zWZ몖wQ~ä́p6,e5,+,tv%O^OO}ן -O7>ekC6wa_C |9*WA)UJg8=:mjUvqysܒLglC6+[FSWg9wV31A ND<$5e(s[ ۨbaF.]KIENDB`python-social-auth-0.2.13/site/img/glyphicons-halflings.png000066400000000000000000000307771260133235600237160ustar00rootroot00000000000000PNG  IHDRtEXtSoftwareAdobe ImageReadyqe<1IDATx}ml\EW^ɺD$|nw';vю8m0kQSnSV;1KGsԩ>UoTU1cƖYuּca&#C,pؚ>kں ULW -sn3Vq~NocI~L{- H8%_M£wB6EW,ĢpY2+(Y@&A/3kXhߍ-aA<>P'\J;(}#Qz:4%m?nfntK*l9J+DIYu1YZ^(]YYEf@ОlXz]Ut u &5-PW}@t|#LY=s܂,w#+R+?Ƌax X0"ea)tG*ԡwVwV^rf%xB(qּ4>WG#lWU<ЁXJVѶlR$kDVrI7:X%X1NEzw;y9z9O%~~uɗ*=Ixcy}Y(ou ±N$^j e\iX񝜬];Y-rѲ&>!zlYaVHVN԰9=]=mRMdOUC JUiT}rWW'ڹu)ʢF"YU#P׾&ܑЅROwyzm$Os? +^FTIEq%&~ >M}]ԖwA? [Nteexn(措BdMTpʥnqqS?bWXmW6x*{V_!VjΧsVL^j XkQjU6sk̩n~[qǸ-` O:G7l"ksRe2vQ=QƼJUX`gQy~ ďKȰE]#P:td\T/u;س:Jc-%'e q ?j"/yh48Zi1|JUu>_N;hxwNU JQU7\j̮bT:B?6oJ1Ί%I UY-Ii4{=rǤ7@)HKJ+f4X8Cd?'j1 N< 39EWo VTGzg# %D0#ܠ3[tiآ( U,]125|Ṋfw7w u+Š]Db]K xbW ՛7|ВX㕛{UcGXk¬|(h)IUa)lp 3luPU]D)/7~4Wt5J}V X0z VM;>Gԙ^|gF:jaZ^)74C#jwr,еSlGu;1vm><)}ZQՖ&mZ:1UMB~ a:/᜗:KWWOҠ&Y2f7cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘g*3fF5LbN2#Tf=C`!ZGUe꣇e2V<1mkS4iϗ*.{N8Xaj~ڀnAx,%fE:|YDVj ¢lg6(:k~MM5?4 ]WO>诋WZiG|QGJeK[YcյpmjE\f/ǎ8&OQ3 .3tt2'-V8pXSrY#J!Q ",ub@FK:u^iy[]<.Cw+W\)b kr-.MtڀMqʄ۰#$^X$"V`T4m~w%Pp1|+&UxY8*r8:k7QЃҀT$Ўƙ S>~Sjs:5q.w&_Z.X=:ވbw` _kd{'0:ds#qi!224nq\9-KUTsSUuVo@;Uz>^=Np>oPO @I@'Gj5o*U>^*ew>ͫʧ᫠Q5 ̈́<$#5Jٻj6e)_ d]2B:^(*:8JYS鬆Kݗ ]U4_rj{5ׇaǑ/yV?GtGb@xPU7O3|鍪 IQ5QGw *(;wf0*PUU<YƔvbt5{2!,}Ҧ:)j2OkΪ' ֊0I.q\(%ojQĖՇa<ԍexAgt'[d;׸`rcdjPFU$UeJI6T&Z}z(z vfuz {}ۿߝݞlxUZ謊.Y岟b%nw@ǩS9|źs%>_o#9\EU~/ځt(r[QZuOo;!MrU]0TcpDő?.cPuF;L_Sb}R/J_+h2$ai UǩS9>Є}76rzu~国4oĨ 1J ^̘~iC޸55G׹]gwsn zTuO=?/zƲc>Οb#7ֻcgkޛTUj*-T=]uu}>ݨNЭ [ ]:%/_ Sz]6D.mD7Uƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1c>J4hPP+A;'G_XKmL5I.},wFFum$S-E-;Õ C3I-`BRx1ғTJݕ;hΊ8 DYJo;Yš5MKɰM;%Pd9KhnD[zgVh,'C p!^M(WK2X>UQ%^p8 ˽^#Ζ؄+.@gCz%ɔ-Pr KX n>=ՔѨeSvRLz5%9UQS \WիK'hp)ô Jrh M0F (f_R5///G+x 1"eS 5 :Tf=+7Qɧ\TEs༬rYs8&k#pSՊ5MTbD܊[Ng5Q\s5PB@[8ɨV1&4Wsy[Ǿ wU2V77jމd^~YfC_h;a.&M i UWpzs`>/"'OI۲y:BzdTq£=йb:"m/-/PWDQǴ͐57m`H%AV!Hԛ׿@"Qzދ|ߒT-*OU^Ҧ6!Cwk|h&Hd5LEYy'ƣ7%*{=)Z%ٝP *G]/8Lw$?8M)\į/#7Ufd7'6\h1 vIfEIr=1w\WKVZHKgZ͡$mx % `j}TuTQJZ*H>*QxkLFTyU-)ôbiA|q`F'+ 4^Qy xH)#t^?@]^`ARSqjgB:rK۷l<2-4YKhgQLxVwP~M Φ0l 3ƅaŊITȀhwJmxIMչ|U7xˆS~2ߕ?kW1kC3];YnSґAeXYz8,'x< k7Kx]$x$vgT#w;o@ z_Vmn|HֵhZg-^TAn- )@4[*9xKƋj>!,Vt:eqn8%ohS(2\Q^aigF3vTUDVlQꅧWc%Ueq4ҝº/U $_Q!>t| ,țG<tC[xTXmf|Q%d#jUՆ|; H[bά#,Ws7NT1~m&ǻ{' \㟾 bBKJo8%!$Qj:/RX)$Sy޳ 䍧RDUg_D軦J\jN֖SU;~?Ohssdƣ}6(T <_4b5 ^N N%8QejF7toMyө`)g[/|?өJuGL坕/=CTܠhdifHcǞG4,`D՞{'xG_p/5@m +$jVH3a"*ũ,,HJҵȸT^Qyo&IÉJUVwWLeM~3tA6rwɤ6տ \0HL%LX5c@HHÃZ|NV+7WM{cig*ȸU7iÉбzd * ?gtX8̝OX:]2ɍ]p^++>AVڛE{ DB.&/56ArxY#ܕy)cKQtȪ~! ;C}ʃtf{6$NVsj wupZ)zŁ|-wg+nMVj/d+U~ͯi:_ix whqr>駃-x뼬)ݷyR=! ì:J/lIkV@n74758Z KJ(Uxz1w)^\ԣzȪ󲦨c2f؍v+6f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘2N oC\F1ִ UZJV̚\4Mgq1z{&YT ,HX~D u\g}x>+YdN̮ol ZX+F[/j+S~2/jV8Jr^ԉ]J}J*ۏ<2԰&JݣjOM@ѯ#0O[SXB^ uze\]dd./xXE f'vO_H${%;kt7ށmő|d{aފ^ǛڎE5ʋBr]W=_SAf(0 oU5q ,_\luz˪uz㻲o=Yi~| 0+=VJت /ލzM\zCL[U:|k*^8"\Wٚ\ .XTjX5 SkFu\1 q'mģ/QUؕ*AɽDNZ׮?_[# ˍ4:^j|5LG ||øBW{6[uQF.1$qF9IHg)\5>C#uXZ$#*<ߐsRv1Tj>Jm>*#( [Fhsש5*jQʼ&&&P犛L[Q1* ;X}Iΰ[Q?qQZ Hݙ֞VEsBCZ9JTK tup˷ /O,.kUdsOHMg4=-)+ؿh2Nw/r|WQn=GIU;'j,vfdzpe$V GTYsBZO1pj:r"nTUSCgr veAۘ˜FC+Ֆ#[JTe'v9-3 Dmӻuuz?0 o hxuY &_54=f07kלU0]D:jdw/+PGUVS<\2uatc^zYRąmC+7#,|:iNw*|^sm|X>Ъ^1\#͹ &%{,2U>ݎ.c05z# ogNO+Q쓭 ,˗-%K\[S_`y+b_94"U+Ύap}I[M,B.NtwHj漬E L߀ 0DX(kڵ NoU{gquz RwkէRx'uZ[3'zyyד%sƕ3jYF\s=m1&VAɼ?k\+]6yモ1gtOIW7al|1 >$]e 7؝WIe?ަL#>| ҭ] pM5MUdI61ԠeǼYGhOn3խR:^k_'Yuuq#p# J2xl>OjcY馃!ڡ+sZ/ D}2AY mpc#<'xSKx`*W[,e|6BH)㶤kjpDU(2qzx9*tqa/, Z[ 0>Ө֜xN)fă@qըFU՝w(a;ˋ>|Tc|w2eiT]*!_\WG{ ]^݅Z5t|6oYHaO@= my^akE.uz]#٥hWv(:,6A߉JFa\ wWex>vetuMYA>).,;ɦCbwjE)W Fӫ@s4e6^Q9oI}4x<.B?B߫#$Hx.x9,a!RTpgd5xBe.L7@* AsduttSVUaRU|I xG߃$T񭟬#_IFMŒ_X@foQIDII?|%$r {ENĸwޕqq?Dؽ}}o/`ӣCTi /ywO rD 9YUD] Ή@s]+'UaL} hrU'7:sU|k)H@hNq#ϵ8y˭Xű#w 1!흉R'7fuד0p!WÖW+Nmp\-ioD$g٠˅%%ÐmV]̱rw*Z}y+L Nouj}xt)lStuqxmNyKUOnDbhf}k>6ufT%{ <񐮸mjFcmUïc;w8@dGFUA& =nq5]iP}z:k⼶-ʓ Κl*'UzaxWFdZzTNRs+# wzgi:MBqtM l#^'Gߣ*^t{=rERnQ$adJl02%Tڊ^<~g?Of*U^?:N+o[PUs|QR']V-L)H K䐞 mYn\4}YVD hR;g-'3aסM Dh}1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌ3f̘1cƌk*Ț4`L$b U4\dt'>HȄ|.+Y+/Gy2OCWv3v,'kia W O6߯E=Hv $LlxI躍/}^]x\3 ɮ5 QT&G9Ay^i}O[5ޱwq4,s JJI.myE^%'VB~dׯ}*j* ~uTk\fKЬ*Y]_v'I˨鑩6Xo'j&uɧngT]oڌ9\*wVHӖ| >:5EF'J ɝ`!A e~_;5ױϊ镋m_&OVi<}"靍hW9X6KPƣ G"ƭ?/O^hCHLciPj)}QQզ#tMg9 xGw~d;_J+RỲ<;e 5/Qs/5N[!a+NPb+ѺI}-t_qU=MKʞY5no*vvbʊ{]| ~ Z{-끇^FVviϵ3Ya=6ndS;-ʹ^;uꪪ^ |=_w+"i&4l#wir|W3U$"J~O@]~tRJVMHw:̦@?>O?vdrtS*$&~1>Z}^nL(]f*&*QaIꝄ|3*O?r?*4Gyz[k/tkQϖWCCKk/x5|S*`ϹγQEwy o KYqTb$-/PtsZNKQ*>ݢU@Џ"JQ;¹& Lx;+T /+O赟> (T?ķD^N*'p$IW֐W~ =J|_UTe7ְP`;CYjk=sU[mߙ-;};2|wo1p0~>0m @Jrǟcٷ4͜?q\UUIV?2L/+Шꄾ< ܇^T ?tj\JrҀB*=km X,n}aՒIadp׷ll{\6v8RꅟҲf1F|Տ;e=\D ,D:ψrxQT◎*|{nS 9~=}ӕG~%j:Dj<ឫ:jO% $T8!jvm|'OЗ¹➱z\vsIv`Ȕʨj-^$-^G Q{m`T#c֞㸝|n.ߪN$O JUVʼt,jg-mסּNV z:(Ι*|1Ux=Yk*t MNNDUhK ؞X(刄Rv!#B_cxRŹoE5Dg>?fXQQ˔|@"աMveC>mO$H#]Y I=)_`k* :a>!X!W^wҒl'<;vwgIt_?Jh`#E:fdx=6Wu<Ӌd2di˂c#h¬c4?<HFYoVpN;ݷJ\ >` (t3{>⦊;;qFx4YcS$w.da*k|Q,+xs^K߫P^nO֮L5mIwl?-.ʲJ8 F B.-:2Ȕ!/A#b_m%I($|PZ[1G{^#o>3mw?'cx[^:Wk/`'=~֥W(gQbfv7UzM3+؍K:4|GCtA+Kʨ{@Ɩ [05E|yn4MIENDB`python-social-auth-0.2.13/site/index.html000066400000000000000000000114561260133235600202760ustar00rootroot00000000000000 Python Social Auth

    Python Social Auth

    Python Social Auth is an easy to setup social authentication/registration mechanism with support for several frameworks and auth providers.

    Crafted using base code from django-social-auth, implements a common interface to define new authentication providers from third parties. And to bring support for more frameworks and ORMs.

    Learn more »

    Frameworks

    The lib supports a few frameworks at the moment with Django, Flask, Pyramid, Webpy, CherryPy and Tornado and more to come. The frameworks API should ease the implementation to increase the number of frameworks supported.

    View details »

    Authentication Providers

    Ported from django-social-auth, the application brings plenty of authentication providers, many from popular services like Google, Facebook, Twitter and Github. The backends API have some implementation details on how to implement your own backends.

    View details »

    ORMs

    There are multiple ORM python libraries around, some frameworks has their own built-in version too. python-social-auth tries to support the different interfaces available, at the moment SQLAlchemy, Django ORM and Mongoengine are supported, but with the Storage API it should be easy to add more support.

    View details »

    Development and Contact

    The code is available on Github, report any issue if you find any. Pull requests are always welcome. There's a mailing list and IRC channel #python-social-auth on Freenode network.

    View details »


    © Matías Aguirre 2012

    python-social-auth-0.2.13/site/js/000077500000000000000000000000001260133235600167065ustar00rootroot00000000000000python-social-auth-0.2.13/site/js/bootstrap.min.js000066400000000000000000000674021260133235600220540ustar00rootroot00000000000000/*! * Bootstrap.js by @fat & @mdo * Copyright 2012 Twitter, Inc. * http://www.apache.org/licenses/LICENSE-2.0.txt */ !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||s.toggleClass("open"),n.focus(),!1},keydown:function(n){var r,s,o,u,a,f;if(!/(38|40|27)/.test(n.keyCode))return;r=e(this),n.preventDefault(),n.stopPropagation();if(r.is(".disabled, :disabled"))return;u=i(r),a=u.hasClass("open");if(!a||a&&n.keyCode==27)return n.which==27&&u.find(t).focus(),r.click();s=e("[role=menu] li:not(.divider):visible a",u);if(!s.length)return;f=s.index(s.filter(":focus")),n.keyCode==38&&f>0&&f--,n.keyCode==40&&f').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?e.proxy(this.$element[0].focus,this.$element[0]):e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!t)return;i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,t):t()):t&&t()}};var n=e.fn.modal;e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e.fn.modal.noConflict=function(){return e.fn.modal=n,this},e(document).on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s,o,u,a;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,o=this.options.trigger.split(" ");for(a=o.length;a--;)u=o[a],u=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):u!="manual"&&(i=u=="hover"?"mouseenter":"focus",s=u=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this)));this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,this.$element.data(),t),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var t,n,r,i,s,o,u=e.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(u);if(u.isDefaultPrevented())return;t=this.tip(),this.setContent(),this.options.animation&&t.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,t[0],this.$element[0]):this.options.placement,t.detach().css({top:0,left:0,display:"block"}),this.options.container?t.appendTo(this.options.container):t.insertAfter(this.$element),n=this.getPosition(),r=t[0].offsetWidth,i=t[0].offsetHeight;switch(s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}this.applyPlacement(o,s),this.$element.trigger("shown")}},applyPlacement:function(e,t){var n=this.tip(),r=n[0].offsetWidth,i=n[0].offsetHeight,s,o,u,a;n.offset(e).addClass(t).addClass("in"),s=n[0].offsetWidth,o=n[0].offsetHeight,t=="top"&&o!=i&&(e.top=e.top+i-o,a=!0),t=="bottom"||t=="top"?(u=0,e.left<0&&(u=e.left*-2,e.left=0,n.offset(e),s=n[0].offsetWidth,o=n[0].offsetHeight),this.replaceArrow(u-r+s,s,"left")):this.replaceArrow(o-i,o,"top"),a&&n.offset(e)},replaceArrow:function(e,t,n){this.arrow().css(n,e?50*(1-e/t)+"%":"")},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function i(){var t=setTimeout(function(){n.off(e.support.transition.end).detach()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.detach()})}var t=this,n=this.tip(),r=e.Event("hide");this.$element.trigger(r);if(r.isDefaultPrevented())return;return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?i():n.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var t=this.$element[0];return e.extend({},typeof t.getBoundingClientRect=="function"?t.getBoundingClientRect():{width:t.offsetWidth,height:t.offsetHeight},this.$element.offset())},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(t){var n=t?e(t.currentTarget)[this.type](this._options).data(this.type):this;n.tip().hasClass("in")?n.hide():n.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var n=e.fn.tooltip;e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'
    ',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},e.fn.tooltip.noConflict=function(){return e.fn.tooltip=n,this}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=(typeof n.content=="function"?n.content.call(t[0]):n.content)||t.attr("data-content"),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var n=e.fn.popover;e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'

    '}),e.fn.popover.noConflict=function(){return e.fn.popover=n,this}}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var n=e(this),r=n.data("target")||n.attr("href"),i=/^#\w/.test(r)&&e(r);return i&&i.length&&[[i.position().top+(!e.isWindow(t.$scrollElement.get(0))&&t.$scrollElement.scrollTop()),r]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}};var n=e.fn.scrollspy;e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e.fn.scrollspy.noConflict=function(){return e.fn.scrollspy=n,this},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active:last a")[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}};var n=e.fn.tab;e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e.fn.tab.noConflict=function(){return e.fn.tab=n,this},e(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=e(this.options.menu),this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:t.top+t.height,left:t.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length"+t+""})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("focus",e.proxy(this.focus,this)).on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this)).on("mouseleave","li",e.proxy(this.mouseleave,this))},eventSupported:function(e){var t=e in this.$element;return t||(this.$element.setAttribute(e,"return;"),t=typeof this.$element[e]=="function"),t},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},focus:function(e){this.focused=!0},blur:function(e){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(e){e.stopPropagation(),e.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(t){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")},mouseleave:function(e){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var n=e.fn.typeahead;e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'',item:'
  • ',minLength:1},e.fn.typeahead.Constructor=t,e.fn.typeahead.noConflict=function(){return e.fn.typeahead=n,this},e(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;n.typeahead(n.data())})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)).on("click.affix.data-api",e.proxy(function(){setTimeout(e.proxy(this.checkPosition,this),1)},this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))};var n=e.fn.affix;e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e.fn.affix.noConflict=function(){return e.fn.affix=n,this},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);python-social-auth-0.2.13/social/000077500000000000000000000000001260133235600166005ustar00rootroot00000000000000python-social-auth-0.2.13/social/__init__.py000066400000000000000000000003241260133235600207100ustar00rootroot00000000000000""" python-social-auth application, allows OpenId or OAuth user registration/authentication just adding a few configurations. """ version = (0, 2, 13) extra = '' __version__ = '.'.join(map(str, version)) + extra python-social-auth-0.2.13/social/actions.py000066400000000000000000000112641260133235600206160ustar00rootroot00000000000000from social.p3 import quote from social.utils import sanitize_redirect, user_is_authenticated, \ user_is_active, partial_pipeline_data, setting_url def do_auth(backend, redirect_name='next'): # Clean any partial pipeline data backend.strategy.clean_partial_pipeline() # Save any defined next value into session data = backend.strategy.request_data(merge=False) # Save extra data into session. for field_name in backend.setting('FIELDS_STORED_IN_SESSION', []): if field_name in data: backend.strategy.session_set(field_name, data[field_name]) if redirect_name in data: # Check and sanitize a user-defined GET/POST next field value redirect_uri = data[redirect_name] if backend.setting('SANITIZE_REDIRECTS', True): redirect_uri = sanitize_redirect(backend.strategy.request_host(), redirect_uri) backend.strategy.session_set( redirect_name, redirect_uri or backend.setting('LOGIN_REDIRECT_URL') ) return backend.start() def do_complete(backend, login, user=None, redirect_name='next', *args, **kwargs): data = backend.strategy.request_data() is_authenticated = user_is_authenticated(user) user = is_authenticated and user or None partial = partial_pipeline_data(backend, user, *args, **kwargs) if partial: xargs, xkwargs = partial user = backend.continue_pipeline(*xargs, **xkwargs) else: user = backend.complete(user=user, *args, **kwargs) # pop redirect value before the session is trashed on login(), but after # the pipeline so that the pipeline can change the redirect if needed redirect_value = backend.strategy.session_get(redirect_name, '') or \ data.get(redirect_name, '') user_model = backend.strategy.storage.user.user_model() if user and not isinstance(user, user_model): return user if is_authenticated: if not user: url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL') else: url = setting_url(backend, redirect_value, 'NEW_ASSOCIATION_REDIRECT_URL', 'LOGIN_REDIRECT_URL') elif user: if user_is_active(user): # catch is_new/social_user in case login() resets the instance is_new = getattr(user, 'is_new', False) social_user = user.social_user login(backend, user, social_user) # store last login backend name in session backend.strategy.session_set('social_auth_last_login_backend', social_user.provider) if is_new: url = setting_url(backend, 'NEW_USER_REDIRECT_URL', redirect_value, 'LOGIN_REDIRECT_URL') else: url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL') else: if backend.setting('INACTIVE_USER_LOGIN', False): social_user = user.social_user login(backend, user, social_user) url = setting_url(backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL', 'LOGIN_URL') else: url = setting_url(backend, 'LOGIN_ERROR_URL', 'LOGIN_URL') if redirect_value and redirect_value != url: redirect_value = quote(redirect_value) url += ('?' in url and '&' or '?') + \ '{0}={1}'.format(redirect_name, redirect_value) if backend.setting('SANITIZE_REDIRECTS', True): url = sanitize_redirect(backend.strategy.request_host(), url) or \ backend.setting('LOGIN_REDIRECT_URL') return backend.strategy.redirect(url) def do_disconnect(backend, user, association_id=None, redirect_name='next', *args, **kwargs): partial = partial_pipeline_data(backend, user, *args, **kwargs) if partial: xargs, xkwargs = partial if association_id and not xkwargs.get('association_id'): xkwargs['association_id'] = association_id response = backend.disconnect(*xargs, **xkwargs) else: response = backend.disconnect(user=user, association_id=association_id, *args, **kwargs) if isinstance(response, dict): response = backend.strategy.redirect( backend.strategy.request_data().get(redirect_name, '') or backend.setting('DISCONNECT_REDIRECT_URL') or backend.setting('LOGIN_REDIRECT_URL') ) return response python-social-auth-0.2.13/social/apps/000077500000000000000000000000001260133235600175435ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/__init__.py000066400000000000000000000000001260133235600216420ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/cherrypy_app/000077500000000000000000000000001260133235600222505ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/cherrypy_app/__init__.py000066400000000000000000000000001260133235600243470ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/cherrypy_app/models.py000066400000000000000000000033421260133235600241070ustar00rootroot00000000000000"""Flask SQLAlchemy ORM models for Social Auth""" import cherrypy from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ BaseSQLAlchemyStorage SocialBase = declarative_base() DB_SESSION_ATTR = cherrypy.config.get(setting_name('DB_SESSION_ATTR'), 'db') UID_LENGTH = cherrypy.config.get(setting_name('UID_LENGTH'), 255) User = module_member(cherrypy.config[setting_name('USER_MODEL')]) class CherryPySocialBase(object): @classmethod def _session(cls): return getattr(cherrypy.request, DB_SESSION_ATTR) class UserSocialAuth(CherryPySocialBase, SQLAlchemyUserMixin, SocialBase): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref='social_auth') @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(CherryPySocialBase, SQLAlchemyNonceMixin, SocialBase): """One use numbers""" pass class Association(CherryPySocialBase, SQLAlchemyAssociationMixin, SocialBase): """OpenId account association""" pass class CherryPyStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association python-social-auth-0.2.13/social/apps/cherrypy_app/utils.py000066400000000000000000000032711260133235600237650ustar00rootroot00000000000000import warnings from functools import wraps import cherrypy from social.utils import setting_name, module_member from social.strategies.utils import get_strategy from social.backends.utils import get_backend, user_backends_data DEFAULTS = { 'STRATEGY': 'social.strategies.cherrypy_strategy.CherryPyStrategy', 'STORAGE': 'social.apps.cherrypy_app.models.CherryPyStorage' } def get_helper(name): return cherrypy.config.get(setting_name(name), DEFAULTS.get(name, None)) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend=None, *args, **kwargs): uri = redirect_uri if uri and backend and '%(backend)s' in uri: uri = uri % {'backend': backend} self.strategy = get_strategy(get_helper('STRATEGY'), get_helper('STORAGE')) self.backend = load_backend(self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def backends(user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return user_backends_data(user, get_helper('AUTHENTICATION_BACKENDS'), module_member(get_helper('STORAGE'))) def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/apps/cherrypy_app/views.py000066400000000000000000000020051260133235600237540ustar00rootroot00000000000000import cherrypy from social.utils import setting_name, module_member from social.actions import do_auth, do_complete, do_disconnect from social.apps.cherrypy_app.utils import psa class CherryPyPSAViews(object): @cherrypy.expose @psa('/complete/%(backend)s') def login(self, backend): return do_auth(self.backend) @cherrypy.expose @psa('/complete/%(backend)s') def complete(self, backend, *args, **kwargs): login = cherrypy.config.get(setting_name('LOGIN_METHOD')) do_login = module_member(login) if login else self.do_login user = getattr(cherrypy.request, 'user', None) return do_complete(self.backend, do_login, user=user, *args, **kwargs) @cherrypy.expose @psa() def disconnect(self, backend, association_id=None): user = getattr(cherrypy.request, 'user', None) return do_disconnect(self.backend, user, association_id) def do_login(self, backend, user, social_user): backend.strategy.session_set('user_id', user.id) python-social-auth-0.2.13/social/apps/django_app/000077500000000000000000000000001260133235600216455ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/__init__.py000066400000000000000000000015711260133235600237620ustar00rootroot00000000000000""" Django framework support. To use this: * Add 'social.apps.django_app.default' if using default ORM, or 'social.apps.django_app.me' if using mongoengine * Add url('', include('social.apps.django_app.urls', namespace='social')) to urls.py * Define SOCIAL_AUTH_STORAGE and SOCIAL_AUTH_STRATEGY, default values: SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy' SOCIAL_AUTH_STORAGE = 'social.apps.django_app.default.models.DjangoStorage' """ import django if django.VERSION[0] == 1 and django.VERSION[1] < 7: from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter needed on # get_user() method on authentication backends when working with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/django_app/context_processors.py000066400000000000000000000030041260133235600261620ustar00rootroot00000000000000from django.contrib.auth import REDIRECT_FIELD_NAME from django.utils.functional import SimpleLazyObject try: from django.utils.functional import empty as _empty empty = _empty except ImportError: # django < 1.4 empty = None from social.backends.utils import user_backends_data from social.apps.django_app.utils import Storage, BACKENDS class LazyDict(SimpleLazyObject): """Lazy dict initialization.""" def __getitem__(self, name): if self._wrapped is empty: self._setup() return self._wrapped[name] def __setitem__(self, name, value): if self._wrapped is empty: self._setup() self._wrapped[name] = value def backends(request): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return {'backends': LazyDict(lambda: user_backends_data(request.user, BACKENDS, Storage))} def login_redirect(request): """Load current redirect to context.""" value = request.method == 'POST' and \ request.POST.get(REDIRECT_FIELD_NAME) or \ request.GET.get(REDIRECT_FIELD_NAME) querystring = value and (REDIRECT_FIELD_NAME + '=' + value) or '' return { 'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME, 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': querystring } python-social-auth-0.2.13/social/apps/django_app/default/000077500000000000000000000000001260133235600232715ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/default/__init__.py000066400000000000000000000004421260133235600254020ustar00rootroot00000000000000""" Django default ORM backend support. To enable this app: * Add 'social.apps.django_app.default' to INSTALLED_APPS * In urls.py include url('', include('social.apps.django_app.urls')) """ default_app_config = \ 'social.apps.django_app.default.config.PythonSocialAuthConfig' python-social-auth-0.2.13/social/apps/django_app/default/admin.py000066400000000000000000000032671260133235600247430ustar00rootroot00000000000000"""Admin settings""" from django.conf import settings from django.contrib import admin from social.utils import setting_name from social.apps.django_app.default.models import UserSocialAuth, Nonce, \ Association class UserSocialAuthOption(admin.ModelAdmin): """Social Auth user options""" list_display = ('user', 'id', 'provider', 'uid') list_filter = ('provider',) raw_id_fields = ('user',) list_select_related = True def get_search_fields(self, request=None): search_fields = getattr( settings, setting_name('ADMIN_USER_SEARCH_FIELDS'), None ) if search_fields is None: _User = UserSocialAuth.user_model() username = getattr(_User, 'USERNAME_FIELD', None) or \ hasattr(_User, 'username') and 'username' or \ None fieldnames = ('first_name', 'last_name', 'email', username) all_names = _User._meta.get_all_field_names() search_fields = [name for name in fieldnames if name and name in all_names] return ['user__' + name for name in search_fields] class NonceOption(admin.ModelAdmin): """Nonce options""" list_display = ('id', 'server_url', 'timestamp', 'salt') search_fields = ('server_url',) class AssociationOption(admin.ModelAdmin): """Association options""" list_display = ('id', 'server_url', 'assoc_type') list_filter = ('assoc_type',) search_fields = ('server_url',) admin.site.register(UserSocialAuth, UserSocialAuthOption) admin.site.register(Nonce, NonceOption) admin.site.register(Association, AssociationOption) python-social-auth-0.2.13/social/apps/django_app/default/config.py000066400000000000000000000010401260133235600251030ustar00rootroot00000000000000from django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): name = 'social.apps.django_app.default' verbose_name = 'Python Social Auth' def ready(self): from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter # needed on get_user() method on authentication backends when working # with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/django_app/default/fields.py000066400000000000000000000047211260133235600251150ustar00rootroot00000000000000import json import six from django.core.exceptions import ValidationError from django.db import models try: from django.utils.encoding import smart_unicode as smart_text smart_text # placate pyflakes except ImportError: from django.utils.encoding import smart_text class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)): """Simple JSON field that stores python structures as JSON strings on database. """ def __init__(self, *args, **kwargs): kwargs.setdefault('default', '{}') super(JSONField, self).__init__(*args, **kwargs) def to_python(self, value): """ Convert the input JSON value into python structures, raises django.core.exceptions.ValidationError if the data can't be converted. """ if self.blank and not value: return {} value = value or '{}' if isinstance(value, six.binary_type): value = six.text_type(value, 'utf-8') if isinstance(value, six.string_types): try: # with django 1.6 i have '"{}"' as default value here if value[0] == value[-1] == '"': value = value[1:-1] return json.loads(value) except Exception as err: raise ValidationError(str(err)) else: return value def validate(self, value, model_instance): """Check value is a valid JSON string, raise ValidationError on error.""" if isinstance(value, six.string_types): super(JSONField, self).validate(value, model_instance) try: json.loads(value) except Exception as err: raise ValidationError(str(err)) def get_prep_value(self, value): """Convert value to JSON string before save""" try: return json.dumps(value) except Exception as err: raise ValidationError(str(err)) def value_to_string(self, obj): """Return value from object converted to string properly""" return smart_text(self.get_prep_value(self._get_val_from_obj(obj))) def value_from_object(self, obj): """Return value dumped to string.""" return self.get_prep_value(self._get_val_from_obj(obj)) try: from south.modelsinspector import add_introspection_rules add_introspection_rules( [], ["^social\.apps\.django_app\.default\.fields\.JSONField"] ) except: pass python-social-auth-0.2.13/social/apps/django_app/default/managers.py000066400000000000000000000006011260133235600254350ustar00rootroot00000000000000from django.db import models class UserSocialAuthManager(models.Manager): """Manager for the UserSocialAuth django model.""" def get_social_auth(self, provider, uid): try: return self.select_related('user').get(provider=provider, uid=uid) except self.model.DoesNotExist: return None python-social-auth-0.2.13/social/apps/django_app/default/migrations/000077500000000000000000000000001260133235600254455ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/default/migrations/0001_initial.py000066400000000000000000000073651260133235600301230ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations import social.apps.django_app.default.fields from django.conf import settings import social.storage.django_orm from social.utils import setting_name user_model = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(user_model), ] operations = [ migrations.CreateModel( name='Association', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('server_url', models.CharField(max_length=255)), ('handle', models.CharField(max_length=255)), ('secret', models.CharField(max_length=255)), ('issued', models.IntegerField()), ('lifetime', models.IntegerField()), ('assoc_type', models.CharField(max_length=64)), ], options={ 'db_table': 'social_auth_association', }, bases=( models.Model, social.storage.django_orm.DjangoAssociationMixin ), ), migrations.CreateModel( name='Code', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('email', models.EmailField(max_length=75)), ('code', models.CharField(max_length=32, db_index=True)), ('verified', models.BooleanField(default=False)), ], options={ 'db_table': 'social_auth_code', }, bases=(models.Model, social.storage.django_orm.DjangoCodeMixin), ), migrations.CreateModel( name='Nonce', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('server_url', models.CharField(max_length=255)), ('timestamp', models.IntegerField()), ('salt', models.CharField(max_length=65)), ], options={ 'db_table': 'social_auth_nonce', }, bases=(models.Model, social.storage.django_orm.DjangoNonceMixin), ), migrations.CreateModel( name='UserSocialAuth', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('provider', models.CharField(max_length=32)), ('uid', models.CharField(max_length=255)), ('extra_data', social.apps.django_app.default.fields.JSONField( default='{}')), ('user', models.ForeignKey( related_name='social_auth', to=user_model)), ], options={ 'db_table': 'social_auth_usersocialauth', }, bases=(models.Model, social.storage.django_orm.DjangoUserMixin), ), migrations.AlterUniqueTogether( name='usersocialauth', unique_together=set([('provider', 'uid')]), ), migrations.AlterUniqueTogether( name='code', unique_together=set([('email', 'code')]), ), migrations.AlterUniqueTogether( name='nonce', unique_together=set([('server_url', 'timestamp', 'salt')]), ), ] python-social-auth-0.2.13/social/apps/django_app/default/migrations/0002_add_related_name.py000066400000000000000000000007301260133235600317100ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations from django.conf import settings class Migration(migrations.Migration): dependencies = [ ('default', '0001_initial'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='user', field=models.ForeignKey(related_name='social_auth', to=settings.AUTH_USER_MODEL) ), ] python-social-auth-0.2.13/social/apps/django_app/default/migrations/0003_alter_email_max_length.py000066400000000000000000000006171260133235600331510ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals from django.db import models, migrations class Migration(migrations.Migration): dependencies = [ ('default', '0002_add_related_name'), ] operations = [ migrations.AlterField( model_name='code', name='email', field=models.EmailField(max_length=254), ), ] python-social-auth-0.2.13/social/apps/django_app/default/migrations/__init__.py000066400000000000000000000000001260133235600275440ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/default/models.py000066400000000000000000000076251260133235600251400ustar00rootroot00000000000000"""Django ORM models for Social Auth""" import six from django.db import models from django.conf import settings from django.db.utils import IntegrityError from social.utils import setting_name from social.storage.django_orm import DjangoUserMixin, \ DjangoAssociationMixin, \ DjangoNonceMixin, \ DjangoCodeMixin, \ BaseDjangoStorage from social.apps.django_app.default.fields import JSONField from social.apps.django_app.default.managers import UserSocialAuthManager USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) NONCE_SERVER_URL_LENGTH = getattr( settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255) ASSOCIATION_SERVER_URL_LENGTH = getattr( settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255) ASSOCIATION_HANDLE_LENGTH = getattr( settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255) class AbstractUserSocialAuth(models.Model, DjangoUserMixin): """Abstract Social Auth association model""" user = models.ForeignKey(USER_MODEL, related_name='social_auth') provider = models.CharField(max_length=32) uid = models.CharField(max_length=UID_LENGTH) extra_data = JSONField() objects = UserSocialAuthManager() def __str__(self): return str(self.user) class Meta: abstract = True @classmethod def get_social_auth(cls, provider, uid): try: return cls.objects.select_related('user').get(provider=provider, uid=uid) except UserSocialAuth.DoesNotExist: return None @classmethod def username_max_length(cls): username_field = cls.username_field() field = UserSocialAuth.user_model()._meta.get_field(username_field) return field.max_length @classmethod def user_model(cls): user_model = UserSocialAuth._meta.get_field('user').rel.to if isinstance(user_model, six.string_types): app_label, model_name = user_model.split('.') return models.get_model(app_label, model_name) return user_model class UserSocialAuth(AbstractUserSocialAuth): """Social Auth association model""" class Meta: """Meta data""" unique_together = ('provider', 'uid') db_table = 'social_auth_usersocialauth' class Nonce(models.Model, DjangoNonceMixin): """One use numbers""" server_url = models.CharField(max_length=NONCE_SERVER_URL_LENGTH) timestamp = models.IntegerField() salt = models.CharField(max_length=65) class Meta: unique_together = ('server_url', 'timestamp', 'salt') db_table = 'social_auth_nonce' class Association(models.Model, DjangoAssociationMixin): """OpenId account association""" server_url = models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH) handle = models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH) secret = models.CharField(max_length=255) # Stored base64 encoded issued = models.IntegerField() lifetime = models.IntegerField() assoc_type = models.CharField(max_length=64) class Meta: db_table = 'social_auth_association' class Code(models.Model, DjangoCodeMixin): email = models.EmailField(max_length=254) code = models.CharField(max_length=32, db_index=True) verified = models.BooleanField(default=False) class Meta: db_table = 'social_auth_code' unique_together = ('email', 'code') class DjangoStorage(BaseDjangoStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError python-social-auth-0.2.13/social/apps/django_app/default/south_migrations/000077500000000000000000000000001260133235600266675ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/default/south_migrations/0001_initial.py000066400000000000000000000253721260133235600313430ustar00rootroot00000000000000# -*- coding: utf-8 -*- from south.db import db from south.v2 import SchemaMigration from . import get_custom_user_model_for_migrations, custom_user_frozen_models USER_MODEL = get_custom_user_model_for_migrations() class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'UserSocialAuth' db.create_table('social_auth_usersocialauth', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('user', self.gf('django.db.models.fields.related.ForeignKey')( related_name='social_auth', to=orm[USER_MODEL])), ('provider', self.gf('django.db.models.fields.CharField')( max_length=32)), ('uid', self.gf('django.db.models.fields.CharField')( max_length=255)), ('extra_data', self.gf( 'social.apps.django_app.default.fields.JSONField' )(default='{}')), )) db.send_create_signal(u'default', ['UserSocialAuth']) # Adding unique constraint on 'UserSocialAuth', # fields ['provider', 'uid'] db.create_unique('social_auth_usersocialauth', ['provider', 'uid']) # Adding model 'Nonce' db.create_table('social_auth_nonce', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('server_url', self.gf('django.db.models.fields.CharField')( max_length=255)), ('timestamp', self.gf('django.db.models.fields.IntegerField')()), ('salt', self.gf('django.db.models.fields.CharField')( max_length=65)), )) db.send_create_signal(u'default', ['Nonce']) # Adding model 'Association' db.create_table('social_auth_association', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('server_url', self.gf('django.db.models.fields.CharField')( max_length=255)), ('handle', self.gf('django.db.models.fields.CharField')( max_length=255)), ('secret', self.gf('django.db.models.fields.CharField')( max_length=255)), ('issued', self.gf('django.db.models.fields.IntegerField')()), ('lifetime', self.gf('django.db.models.fields.IntegerField')()), ('assoc_type', self.gf('django.db.models.fields.CharField')( max_length=64)), )) db.send_create_signal(u'default', ['Association']) # Adding model 'Code' db.create_table('social_auth_code', ( (u'id', self.gf('django.db.models.fields.AutoField')( primary_key=True)), ('email', self.gf('django.db.models.fields.EmailField')( max_length=75)), ('code', self.gf('django.db.models.fields.CharField')( max_length=32, db_index=True)), ('verified', self.gf('django.db.models.fields.BooleanField')( default=False)), )) db.send_create_signal(u'default', ['Code']) # Adding unique constraint on 'Code', fields ['email', 'code'] db.create_unique('social_auth_code', ['email', 'code']) def backwards(self, orm): # Removing unique constraint on 'Code', fields ['email', 'code'] db.delete_unique('social_auth_code', ['email', 'code']) # Removing unique constraint on 'UserSocialAuth', # fields ['provider', 'uid'] db.delete_unique('social_auth_usersocialauth', ['provider', 'uid']) # Deleting model 'UserSocialAuth' db.delete_table('social_auth_usersocialauth') # Deleting model 'Nonce' db.delete_table('social_auth_nonce') # Deleting model 'Association' db.delete_table('social_auth_association') # Deleting model 'Code' db.delete_table('social_auth_code') models = { u'auth.group': { 'Meta': {'object_name': 'Group'}, u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, u'auth.permission': { 'Meta': { 'ordering': "(u'content_type__app_label', " "u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission' }, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, u'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ( 'django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, u'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, u'default.association': { 'Meta': {'object_name': 'Association', 'db_table': "'social_auth_association'"}, 'assoc_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}), 'handle': ('django.db.models.fields.CharField', [], {'max_length': '255'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'issued': ('django.db.models.fields.IntegerField', [], {}), 'lifetime': ('django.db.models.fields.IntegerField', [], {}), 'secret': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}) }, u'default.code': { 'Meta': {'unique_together': "(('email', 'code'),)", 'object_name': 'Code', 'db_table': "'social_auth_code'"}, 'code': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) }, u'default.nonce': { 'Meta': {'object_name': 'Nonce', 'db_table': "'social_auth_nonce'"}, u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'salt': ('django.db.models.fields.CharField', [], {'max_length': '65'}), 'server_url': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'timestamp': ('django.db.models.fields.IntegerField', [], {}) }, u'default.usersocialauth': { 'Meta': {'unique_together': "(('provider', 'uid'),)", 'object_name': 'UserSocialAuth', 'db_table': "'social_auth_usersocialauth'"}, 'extra_data': ('social.apps.django_app.default.fields.JSONField', [], {'default': "'{}'"}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'provider': ('django.db.models.fields.CharField', [], {'max_length': '32'}), 'uid': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'social_auth'", 'to': u"orm['auth.User']"}) } } models.update(custom_user_frozen_models(USER_MODEL)) complete_apps = ['default'] python-social-auth-0.2.13/social/apps/django_app/default/south_migrations/__init__.py000066400000000000000000000025721260133235600310060ustar00rootroot00000000000000from django.conf import settings from django.db.models.loading import get_model def get_custom_user_model_for_migrations(): user_model = getattr(settings, 'SOCIAL_AUTH_USER_MODEL', None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' if user_model != 'auth.User': # In case of having a proxy model defined as USER_MODEL # We use auth.User instead to prevent migration errors # Since proxy models aren't present in migrations if get_model(*user_model.split('.'))._meta.proxy: user_model = 'auth.User' return user_model def custom_user_frozen_models(user_model): migration_name = getattr(settings, 'INITIAL_CUSTOM_USER_MIGRATION', '0001_initial.py') if user_model != 'auth.User': from south.migration.base import Migrations from south.exceptions import NoMigrations from south.creator.freezer import freeze_apps user_app, user_model = user_model.split('.') try: user_migrations = Migrations(user_app) except NoMigrations: extra_model = freeze_apps(user_app) else: initial_user_migration = user_migrations.migration(migration_name) extra_model = initial_user_migration.migration_class().models else: extra_model = {} return extra_model python-social-auth-0.2.13/social/apps/django_app/default/tests.py000066400000000000000000000000531260133235600250030ustar00rootroot00000000000000from social.apps.django_app.tests import * python-social-auth-0.2.13/social/apps/django_app/me/000077500000000000000000000000001260133235600222465ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/django_app/me/__init__.py000066400000000000000000000004201260133235600243530ustar00rootroot00000000000000""" Mongoengine backend support. To enable this app: * Add 'social.apps.django_app.me' to INSTALLED_APPS * In urls.py include url('', include('social.apps.django_app.urls')) """ default_app_config = \ 'social.apps.django_app.me.config.PythonSocialAuthConfig' python-social-auth-0.2.13/social/apps/django_app/me/config.py000066400000000000000000000010331260133235600240620ustar00rootroot00000000000000from django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): name = 'social.apps.django_app.me' verbose_name = 'Python Social Auth' def ready(self): from social.strategies.utils import set_current_strategy_getter from social.apps.django_app.utils import load_strategy # Set strategy loader method to workaround current strategy getter # needed on get_user() method on authentication backends when working # with Django set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/django_app/me/models.py000066400000000000000000000041141260133235600241030ustar00rootroot00000000000000""" MongoEngine Django models for Social Auth. Requires MongoEngine 0.8.6 or higher. """ from django.conf import settings from mongoengine import Document, ReferenceField from mongoengine.queryset import OperationError from social.utils import setting_name, module_member from social.storage.django_orm import BaseDjangoStorage from social.storage.mongoengine_orm import MongoengineUserMixin, \ MongoengineNonceMixin, \ MongoengineAssociationMixin, \ MongoengineCodeMixin UNUSABLE_PASSWORD = '!' # Borrowed from django 1.4 def _get_user_model(): """ Get the User Document class user for MongoEngine authentication. Use the model defined in SOCIAL_AUTH_USER_MODEL if defined, or defaults to MongoEngine's configured user document class. """ custom_model = getattr(settings, setting_name('USER_MODEL'), None) if custom_model: return module_member(custom_model) try: # Custom user model support with MongoEngine 0.8 from mongoengine.django.mongo_auth.models import get_user_document return get_user_document() except ImportError: return module_member('mongoengine.django.auth.User') USER_MODEL = _get_user_model() class UserSocialAuth(Document, MongoengineUserMixin): """Social Auth association model""" user = ReferenceField(USER_MODEL) @classmethod def user_model(cls): return USER_MODEL class Nonce(Document, MongoengineNonceMixin): """One use numbers""" pass class Association(Document, MongoengineAssociationMixin): """OpenId account association""" pass class Code(Document, MongoengineCodeMixin): """Mail validation single one time use code""" pass class DjangoStorage(BaseDjangoStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code @classmethod def is_integrity_error(cls, exception): return exception.__class__ is OperationError and \ 'E11000' in exception.message python-social-auth-0.2.13/social/apps/django_app/me/tests.py000066400000000000000000000000531260133235600237600ustar00rootroot00000000000000from social.apps.django_app.tests import * python-social-auth-0.2.13/social/apps/django_app/middleware.py000066400000000000000000000043551260133235600243430ustar00rootroot00000000000000# -*- coding: utf-8 -*- import six from django.conf import settings from django.contrib import messages from django.contrib.messages.api import MessageFailure from django.shortcuts import redirect from django.utils.http import urlquote from social.exceptions import SocialAuthBaseException from social.utils import social_logger class SocialAuthExceptionMiddleware(object): """Middleware that handles Social Auth AuthExceptions by providing the user with a message, logging an error, and redirecting to some next location. By default, the exception message itself is sent to the user and they are redirected to the location specified in the SOCIAL_AUTH_LOGIN_ERROR_URL setting. This middleware can be extended by overriding the get_message or get_redirect_uri methods, which each accept request and exception. """ def process_exception(self, request, exception): strategy = getattr(request, 'social_strategy', None) if strategy is None or self.raise_exception(request, exception): return if isinstance(exception, SocialAuthBaseException): backend = getattr(request, 'backend', None) backend_name = getattr(backend, 'name', 'unknown-backend') message = self.get_message(request, exception) social_logger.error(message) url = self.get_redirect_uri(request, exception) try: messages.error(request, message, extra_tags='social-auth ' + backend_name) except MessageFailure: url += ('?' in url and '&' or '?') + \ 'message={0}&backend={1}'.format(urlquote(message), backend_name) return redirect(url) def raise_exception(self, request, exception): strategy = getattr(request, 'social_strategy', None) if strategy is not None: return strategy.setting('RAISE_EXCEPTIONS', settings.DEBUG) def get_message(self, request, exception): return six.text_type(exception) def get_redirect_uri(self, request, exception): strategy = getattr(request, 'social_strategy', None) return strategy.setting('LOGIN_ERROR_URL') python-social-auth-0.2.13/social/apps/django_app/tests.py000066400000000000000000000045601260133235600233660ustar00rootroot00000000000000from social.tests.test_exceptions import * from social.tests.test_pipeline import * from social.tests.test_storage import * from social.tests.test_utils import * from social.tests.actions.test_associate import * from social.tests.actions.test_disconnect import * from social.tests.actions.test_login import * from social.tests.backends.test_amazon import * from social.tests.backends.test_angel import * from social.tests.backends.test_behance import * from social.tests.backends.test_bitbucket import * from social.tests.backends.test_box import * from social.tests.backends.test_broken import * from social.tests.backends.test_coinbase import * from social.tests.backends.test_dailymotion import * from social.tests.backends.test_disqus import * from social.tests.backends.test_dropbox import * from social.tests.backends.test_dummy import * from social.tests.backends.test_email import * from social.tests.backends.test_evernote import * from social.tests.backends.test_facebook import * from social.tests.backends.test_fitbit import * from social.tests.backends.test_flickr import * from social.tests.backends.test_foursquare import * from social.tests.backends.test_google import * from social.tests.backends.test_instagram import * from social.tests.backends.test_linkedin import * from social.tests.backends.test_live import * from social.tests.backends.test_livejournal import * from social.tests.backends.test_mixcloud import * from social.tests.backends.test_podio import * from social.tests.backends.test_readability import * from social.tests.backends.test_reddit import * from social.tests.backends.test_skyrock import * from social.tests.backends.test_soundcloud import * from social.tests.backends.test_stackoverflow import * from social.tests.backends.test_steam import * from social.tests.backends.test_stocktwits import * from social.tests.backends.test_stripe import * from social.tests.backends.test_thisismyjam import * from social.tests.backends.test_tripit import * from social.tests.backends.test_tumblr import * from social.tests.backends.test_twitter import * from social.tests.backends.test_username import * from social.tests.backends.test_utils import * from social.tests.backends.test_vk import * from social.tests.backends.test_xing import * from social.tests.backends.test_yahoo import * from social.tests.backends.test_yammer import * from social.tests.backends.test_yandex import * python-social-auth-0.2.13/social/apps/django_app/urls.py000066400000000000000000000015401260133235600232040ustar00rootroot00000000000000"""URLs module""" from django.conf import settings try: from django.conf.urls import patterns, url except ImportError: # Django < 1.4 from django.conf.urls.defaults import patterns, url from social.utils import setting_name extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' urlpatterns = patterns('social.apps.django_app.views', # authentication / association url(r'^login/(?P[^/]+){0}$'.format(extra), 'auth', name='begin'), url(r'^complete/(?P[^/]+){0}$'.format(extra), 'complete', name='complete'), # disconnection url(r'^disconnect/(?P[^/]+){0}$'.format(extra), 'disconnect', name='disconnect'), url(r'^disconnect/(?P[^/]+)/(?P[^/]+){0}$' .format(extra), 'disconnect', name='disconnect_individual'), ) python-social-auth-0.2.13/social/apps/django_app/utils.py000066400000000000000000000045421260133235600233640ustar00rootroot00000000000000import warnings from functools import wraps from django.conf import settings from django.core.urlresolvers import reverse from django.http import Http404 from social.utils import setting_name, module_member from social.exceptions import MissingBackend from social.strategies.utils import get_strategy from social.backends.utils import get_backend BACKENDS = settings.AUTHENTICATION_BACKENDS STRATEGY = getattr(settings, setting_name('STRATEGY'), 'social.strategies.django_strategy.DjangoStrategy') STORAGE = getattr(settings, setting_name('STORAGE'), 'social.apps.django_app.default.models.DjangoStorage') Strategy = module_member(STRATEGY) Storage = module_member(STORAGE) def load_strategy(request=None): return get_strategy(STRATEGY, STORAGE, request) def load_backend(strategy, name, redirect_uri): Backend = get_backend(BACKENDS, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None, load_strategy=load_strategy): def decorator(func): @wraps(func) def wrapper(request, backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = reverse(redirect_uri, args=(backend,)) request.social_strategy = load_strategy(request) # backward compatibility in attribute name, only if not already # defined if not hasattr(request, 'strategy'): request.strategy = request.social_strategy try: request.backend = load_backend(request.social_strategy, backend, uri) except MissingBackend: raise Http404('Backend not found') return func(request, backend, *args, **kwargs) return wrapper return decorator def setting(name, default=None): try: return getattr(settings, setting_name(name)) except AttributeError: return getattr(settings, name, default) class BackendWrapper(object): # XXX: Deprecated, restored to avoid session issues def authenticate(self, *args, **kwargs): return None def get_user(self, user_id): return Strategy(storage=Storage).get_user(user_id) def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/apps/django_app/views.py000066400000000000000000000042051260133235600233550ustar00rootroot00000000000000from django.conf import settings from django.contrib.auth import login, REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.http import require_POST from django.views.decorators.cache import never_cache from social.utils import setting_name from social.actions import do_auth, do_complete, do_disconnect from social.apps.django_app.utils import psa NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social' @never_cache @psa('{0}:complete'.format(NAMESPACE)) def auth(request, backend): return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME) @never_cache @csrf_exempt @psa('{0}:complete'.format(NAMESPACE)) def complete(request, backend, *args, **kwargs): """Authentication complete view""" return do_complete(request.backend, _do_login, request.user, redirect_name=REDIRECT_FIELD_NAME, *args, **kwargs) @never_cache @login_required @psa() @require_POST @csrf_protect def disconnect(request, backend, association_id=None): """Disconnects given backend from current logged in user.""" return do_disconnect(request.backend, request.user, association_id, redirect_name=REDIRECT_FIELD_NAME) def _do_login(backend, user, social_user): user.backend = '{0}.{1}'.format(backend.__module__, backend.__class__.__name__) login(backend.strategy.request, user) if backend.setting('SESSION_EXPIRATION', False): # Set session expiration date if present and enabled # by setting. Use last social-auth instance for current # provider, users can associate several accounts with # a same provider. expiration = social_user.expiration_datetime() if expiration: try: backend.strategy.request.session.set_expiry( expiration.seconds + expiration.days * 86400 ) except OverflowError: # Handle django time zone overflow backend.strategy.request.session.set_expiry(None) python-social-auth-0.2.13/social/apps/flask_app/000077500000000000000000000000001260133235600215035ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/flask_app/__init__.py000066400000000000000000000002431260133235600236130ustar00rootroot00000000000000from social.strategies.utils import set_current_strategy_getter from social.apps.flask_app.utils import load_strategy set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/flask_app/default/000077500000000000000000000000001260133235600231275ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/flask_app/default/__init__.py000066400000000000000000000000001260133235600252260ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/flask_app/default/models.py000066400000000000000000000045011260133235600247640ustar00rootroot00000000000000"""Flask SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from sqlalchemy.schema import UniqueConstraint from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage PSABase = declarative_base() class _AppSession(PSABase): __abstract__ = True @classmethod def _set_session(cls, app_session): cls.app_session = app_session @classmethod def _session(cls): return cls.app_session class UserSocialAuth(_AppSession, SQLAlchemyUserMixin): """Social Auth association model""" # Temporary override of constraints to avoid an error on the still-to-be # missing column uid. __table_args__ = () @classmethod def user_model(cls): return cls.user.property.argument @classmethod def username_max_length(cls): user_model = cls.user_model() return user_model.__table__.columns.get('username').type.length class Nonce(_AppSession, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, SQLAlchemyCodeMixin): pass class FlaskStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code def init_social(app, session): UID_LENGTH = app.config.get(setting_name('UID_LENGTH'), 255) User = module_member(app.config[setting_name('USER_MODEL')]) _AppSession._set_session(session) UserSocialAuth.__table_args__ = (UniqueConstraint('provider', 'uid'),) UserSocialAuth.uid = Column(String(UID_LENGTH)) UserSocialAuth.user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) UserSocialAuth.user = relationship(User, backref=backref('social_auth', lazy='dynamic')) python-social-auth-0.2.13/social/apps/flask_app/me/000077500000000000000000000000001260133235600221045ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/flask_app/me/__init__.py000066400000000000000000000000001260133235600242030ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/flask_app/me/models.py000066400000000000000000000025501260133235600237430ustar00rootroot00000000000000"""Flask SQLAlchemy ORM models for Social Auth""" from mongoengine import ReferenceField from social.utils import setting_name, module_member from social.storage.mongoengine_orm import MongoengineUserMixin, \ MongoengineAssociationMixin, \ MongoengineNonceMixin, \ MongoengineCodeMixin, \ BaseMongoengineStorage class FlaskStorage(BaseMongoengineStorage): user = None nonce = None association = None code = None def init_social(app, db): User = module_member(app.config[setting_name('USER_MODEL')]) class UserSocialAuth(db.Document, MongoengineUserMixin): """Social Auth association model""" user = ReferenceField(User) @classmethod def user_model(cls): return User class Nonce(db.Document, MongoengineNonceMixin): """One use numbers""" pass class Association(db.Document, MongoengineAssociationMixin): """OpenId account association""" pass class Code(db.Document, MongoengineCodeMixin): pass # Set the references in the storage class FlaskStorage.user = UserSocialAuth FlaskStorage.nonce = Nonce FlaskStorage.association = Association FlaskStorage.code = Code python-social-auth-0.2.13/social/apps/flask_app/routes.py000066400000000000000000000031161260133235600233770ustar00rootroot00000000000000from flask import g, Blueprint, request from flask.ext.login import login_required, login_user from social.actions import do_auth, do_complete, do_disconnect from social.apps.flask_app.utils import psa social_auth = Blueprint('social', __name__) @social_auth.route('/login//', methods=('GET', 'POST')) @psa('social.complete') def auth(backend): return do_auth(g.backend) @social_auth.route('/complete//', methods=('GET', 'POST')) @psa('social.complete') def complete(backend, *args, **kwargs): """Authentication complete view, override this view if transaction management doesn't suit your needs.""" return do_complete(g.backend, login=do_login, user=g.user, *args, **kwargs) @social_auth.route('/disconnect//', methods=('POST',)) @social_auth.route('/disconnect///', methods=('POST',)) @social_auth.route('/disconnect///', methods=('POST',)) @login_required @psa() def disconnect(backend, association_id=None): """Disconnects given backend from current logged in user.""" return do_disconnect(g.backend, g.user, association_id) def do_login(backend, user, social_user): name = backend.strategy.setting('REMEMBER_SESSION_NAME', 'keep') remember = backend.strategy.session_get(name) or \ request.cookies.get(name) or \ request.args.get(name) or \ request.form.get(name) or \ False return login_user(user, remember=remember) python-social-auth-0.2.13/social/apps/flask_app/template_filters.py000066400000000000000000000015161260133235600254230ustar00rootroot00000000000000from flask import g, request from social.backends.utils import user_backends_data from social.apps.flask_app.utils import get_helper def backends(): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return { 'backends': user_backends_data(g.user, get_helper('AUTHENTICATION_BACKENDS'), get_helper('STORAGE', do_import=True)) } def login_redirect(): """Load current redirect to context.""" value = request.form.get('next', '') or \ request.args.get('next', '') return { 'REDIRECT_FIELD_NAME': 'next', 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': value and ('next=' + value) or '' } python-social-auth-0.2.13/social/apps/flask_app/utils.py000066400000000000000000000031331260133235600232150ustar00rootroot00000000000000import warnings from functools import wraps from flask import current_app, url_for, g from social.utils import module_member, setting_name from social.strategies.utils import get_strategy from social.backends.utils import get_backend DEFAULTS = { 'STORAGE': 'social.apps.flask_app.default.models.FlaskStorage', 'STRATEGY': 'social.strategies.flask_strategy.FlaskStrategy' } def get_helper(name, do_import=False): config = current_app.config.get(setting_name(name), DEFAULTS.get(name, None)) return do_import and module_member(config) or config def load_strategy(): strategy = get_helper('STRATEGY') storage = get_helper('STORAGE') return get_strategy(strategy, storage) def load_backend(strategy, name, redirect_uri, *args, **kwargs): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = url_for(uri, backend=backend) g.strategy = load_strategy() g.backend = load_backend(g.strategy, backend, redirect_uri=uri, *args, **kwargs) return func(backend, *args, **kwargs) return wrapper return decorator def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/apps/pyramid_app/000077500000000000000000000000001260133235600220505ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/pyramid_app/__init__.py000066400000000000000000000007551260133235600241700ustar00rootroot00000000000000from social.strategies.utils import set_current_strategy_getter from social.apps.pyramid_app.utils import load_strategy def includeme(config): config.add_route('social.auth', '/login/{backend}') config.add_route('social.complete', '/complete/{backend}') config.add_route('social.disconnect', '/disconnect/{backend}') config.add_route('social.disconnect_association', '/disconnect/{backend}/{association_id}') set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/pyramid_app/models.py000066400000000000000000000041071260133235600237070ustar00rootroot00000000000000"""Pyramid SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage class PyramidStorage(BaseSQLAlchemyStorage): user = None nonce = None association = None def init_social(config, Base, session): if hasattr(config, 'registry'): config = config.registry.settings UID_LENGTH = config.get(setting_name('UID_LENGTH'), 255) User = module_member(config[setting_name('USER_MODEL')]) app_session = session class _AppSession(object): @classmethod def _session(cls): return app_session class UserSocialAuth(_AppSession, Base, SQLAlchemyUserMixin): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref=backref('social_auth', lazy='dynamic')) @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(_AppSession, Base, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, Base, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, Base, SQLAlchemyCodeMixin): pass # Set the references in the storage class PyramidStorage.user = UserSocialAuth PyramidStorage.nonce = Nonce PyramidStorage.association = Association PyramidStorage.code = Code python-social-auth-0.2.13/social/apps/pyramid_app/utils.py000066400000000000000000000046321260133235600235670ustar00rootroot00000000000000import warnings from functools import wraps from pyramid.threadlocal import get_current_registry from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden from social.utils import setting_name, module_member from social.strategies.utils import get_strategy from social.backends.utils import get_backend, user_backends_data DEFAULTS = { 'STORAGE': 'social.apps.pyramid_app.models.PyramidStorage', 'STRATEGY': 'social.strategies.pyramid_strategy.PyramidStrategy' } def get_helper(name): settings = get_current_registry().settings return settings.get(setting_name(name), DEFAULTS.get(name, None)) def load_strategy(request): return get_strategy( get_helper('STRATEGY'), get_helper('STORAGE'), request ) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy=strategy, redirect_uri=redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): backend = request.matchdict.get('backend') if not backend: return HTTPNotFound('Missing backend') uri = redirect_uri if uri and not uri.startswith('/'): uri = request.route_url(uri, backend=backend) request.strategy = load_strategy(request) request.backend = load_backend(request.strategy, backend, uri) return func(request, *args, **kwargs) return wrapper return decorator def login_required(func): @wraps(func) def wrapper(request, *args, **kwargs): is_logged_in = module_member( request.backend.setting('LOGGEDIN_FUNCTION') ) if not is_logged_in(request): raise HTTPForbidden('Not authorized user') return func(request, *args, **kwargs) return wrapper def backends(request, user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" storage = module_member(get_helper('STORAGE')) return { 'backends': user_backends_data( user, get_helper('AUTHENTICATION_BACKENDS'), storage ) } def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/apps/pyramid_app/views.py000066400000000000000000000021031260133235600235530ustar00rootroot00000000000000from pyramid.view import view_config from social.utils import module_member from social.actions import do_auth, do_complete, do_disconnect from social.apps.pyramid_app.utils import psa, login_required @view_config(route_name='social.auth', request_method='GET') @psa('social.complete') def auth(request): return do_auth(request.backend, redirect_name='next') @view_config(route_name='social.complete', request_method=('GET', 'POST')) @psa('social.complete') def complete(request, *args, **kwargs): do_login = module_member(request.backend.setting('LOGIN_FUNCTION')) return do_complete(request.backend, do_login, request.user, redirect_name='next', *args, **kwargs) @view_config(route_name='social.disconnect', request_method=('POST',)) @view_config(route_name='social.disconnect_association', request_method=('POST',)) @psa() @login_required def disconnect(request): return do_disconnect(request.backend, request.user, request.matchdict.get('association_id'), redirect_name='next') python-social-auth-0.2.13/social/apps/tornado_app/000077500000000000000000000000001260133235600220515ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/tornado_app/__init__.py000066400000000000000000000000001260133235600241500ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/tornado_app/handlers.py000066400000000000000000000022611260133235600242240ustar00rootroot00000000000000from tornado.web import RequestHandler from social.apps.tornado_app.utils import psa from social.actions import do_auth, do_complete, do_disconnect class BaseHandler(RequestHandler): def user_id(self): return self.get_secure_cookie('user_id') def get_current_user(self): user_id = self.user_id() if user_id: return self.backend.strategy.get_user(int(user_id)) def login_user(self, user): self.set_secure_cookie('user_id', str(user.id)) class AuthHandler(BaseHandler): def get(self, backend): self._auth(backend) def post(self, backend): self._auth(backend) @psa('complete') def _auth(self, backend): do_auth(self.backend) class CompleteHandler(BaseHandler): def get(self, backend): self._complete(backend) def post(self, backend): self._complete(backend) @psa('complete') def _complete(self, backend): do_complete( self.backend, login=lambda backend, user, social_user: self.login_user(user), user=self.get_current_user() ) class DisconnectHandler(BaseHandler): def post(self): do_disconnect() python-social-auth-0.2.13/social/apps/tornado_app/models.py000066400000000000000000000040171260133235600237100ustar00rootroot00000000000000"""Tornado SQLAlchemy ORM models for Social Auth""" from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship, backref from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage class TornadoStorage(BaseSQLAlchemyStorage): user = None nonce = None association = None code = None def init_social(Base, session, settings): UID_LENGTH = settings.get(setting_name('UID_LENGTH'), 255) User = module_member(settings[setting_name('USER_MODEL')]) app_session = session class _AppSession(object): @classmethod def _session(cls): return app_session class UserSocialAuth(_AppSession, Base, SQLAlchemyUserMixin): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref=backref('social_auth', lazy='dynamic')) @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(_AppSession, Base, SQLAlchemyNonceMixin): """One use numbers""" pass class Association(_AppSession, Base, SQLAlchemyAssociationMixin): """OpenId account association""" pass class Code(_AppSession, Base, SQLAlchemyCodeMixin): pass # Set the references in the storage class TornadoStorage.user = UserSocialAuth TornadoStorage.nonce = Nonce TornadoStorage.association = Association TornadoStorage.code = Code python-social-auth-0.2.13/social/apps/tornado_app/routes.py000066400000000000000000000007521260133235600237500ustar00rootroot00000000000000from tornado.web import url from .handlers import AuthHandler, CompleteHandler, DisconnectHandler SOCIAL_AUTH_ROUTES = [ url(r'/login/(?P[^/]+)/?', AuthHandler, name='begin'), url(r'/complete/(?P[^/]+)/?', CompleteHandler, name='complete'), url(r'/disconnect/(?P[^/]+)/?', DisconnectHandler, name='disconnect'), url(r'/disconnect/(?P[^/]+)/(?P\d+)/?', DisconnectHandler, name='disconect_individual'), ] python-social-auth-0.2.13/social/apps/tornado_app/utils.py000066400000000000000000000030121260133235600235570ustar00rootroot00000000000000import warnings from functools import wraps from social.utils import setting_name from social.strategies.utils import get_strategy from social.backends.utils import get_backend DEFAULTS = { 'STORAGE': 'social.apps.tornado_app.models.TornadoStorage', 'STRATEGY': 'social.strategies.tornado_strategy.TornadoStrategy' } def get_helper(request_handler, name): return request_handler.settings.get(setting_name(name), DEFAULTS.get(name, None)) def load_strategy(request_handler): strategy = get_helper(request_handler, 'STRATEGY') storage = get_helper(request_handler, 'STORAGE') return get_strategy(strategy, storage, request_handler) def load_backend(request_handler, strategy, name, redirect_uri): backends = get_helper(request_handler, 'AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = self.reverse_url(uri, backend) self.strategy = load_strategy(self) self.backend = load_backend(self, self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/apps/webpy_app/000077500000000000000000000000001260133235600215315ustar00rootroot00000000000000python-social-auth-0.2.13/social/apps/webpy_app/__init__.py000066400000000000000000000002431260133235600236410ustar00rootroot00000000000000from social.strategies.utils import set_current_strategy_getter from social.apps.webpy_app.utils import load_strategy set_current_strategy_getter(load_strategy) python-social-auth-0.2.13/social/apps/webpy_app/app.py000066400000000000000000000041511260133235600226640ustar00rootroot00000000000000import web from social.actions import do_auth, do_complete, do_disconnect from social.apps.webpy_app.utils import psa, load_strategy urls = ( '/login/(?P[^/]+)/?', 'auth', '/complete/(?P[^/]+)/?', 'complete', '/disconnect/(?P[^/]+)/?', 'disconnect', '/disconnect/(?P[^/]+)/(?P\d+)/?', 'disconnect', ) class BaseViewClass(object): def __init__(self, *args, **kwargs): self.session = web.web_session method = web.ctx.method == 'POST' and 'post' or 'get' self.strategy = load_strategy() self.data = web.input(_method=method) super(BaseViewClass, self).__init__(*args, **kwargs) def get_current_user(self): if not hasattr(self, '_user'): if self.session.get('logged_in'): self._user = self.strategy.get_user( self.session.get('user_id') ) else: self._user = None return self._user def login_user(self, user): self.session['logged_in'] = True self.session['user_id'] = user.id class auth(BaseViewClass): def GET(self, backend): return self._auth(backend) def POST(self, backend): return self._auth(backend) @psa('/complete/%(backend)s/') def _auth(self, backend): return do_auth(self.backend) class complete(BaseViewClass): def GET(self, backend, *args, **kwargs): return self._complete(backend, *args, **kwargs) def POST(self, backend, *args, **kwargs): return self._complete(backend, *args, **kwargs) @psa('/complete/%(backend)s/') def _complete(self, backend, *args, **kwargs): return do_complete( self.backend, login=lambda backend, user, social_user: self.login_user(user), user=self.get_current_user(), *args, **kwargs ) class disconnect(BaseViewClass): @psa() def POST(self, backend, association_id=None): return do_disconnect(self.backend, self.get_current_user(), association_id) app_social = web.application(urls, locals()) python-social-auth-0.2.13/social/apps/webpy_app/models.py000066400000000000000000000033651260133235600233750ustar00rootroot00000000000000"""Flask SQLAlchemy ORM models for Social Auth""" import web from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base from social.utils import setting_name, module_member from social.storage.sqlalchemy_orm import SQLAlchemyUserMixin, \ SQLAlchemyAssociationMixin, \ SQLAlchemyNonceMixin, \ SQLAlchemyCodeMixin, \ BaseSQLAlchemyStorage SocialBase = declarative_base() UID_LENGTH = web.config.get(setting_name('UID_LENGTH'), 255) User = module_member(web.config[setting_name('USER_MODEL')]) class WebpySocialBase(object): @classmethod def _session(cls): return web.db_session class UserSocialAuth(WebpySocialBase, SQLAlchemyUserMixin, SocialBase): """Social Auth association model""" uid = Column(String(UID_LENGTH)) user_id = Column(Integer, ForeignKey(User.id), nullable=False, index=True) user = relationship(User, backref='social_auth') @classmethod def username_max_length(cls): return User.__table__.columns.get('username').type.length @classmethod def user_model(cls): return User class Nonce(WebpySocialBase, SQLAlchemyNonceMixin, SocialBase): """One use numbers""" pass class Association(WebpySocialBase, SQLAlchemyAssociationMixin, SocialBase): """OpenId account association""" pass class Code(WebpySocialBase, SQLAlchemyCodeMixin, SocialBase): pass class WebpyStorage(BaseSQLAlchemyStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code python-social-auth-0.2.13/social/apps/webpy_app/utils.py000066400000000000000000000041361260133235600232470ustar00rootroot00000000000000import warnings from functools import wraps import web from social.utils import setting_name, module_member from social.backends.utils import get_backend, user_backends_data from social.strategies.utils import get_strategy DEFAULTS = { 'STRATEGY': 'social.strategies.webpy_strategy.WebpyStrategy', 'STORAGE': 'social.apps.webpy_app.models.WebpyStorage' } def get_helper(name, do_import=False): config = web.config.get(setting_name(name), DEFAULTS.get(name, None)) return do_import and module_member(config) or config def load_strategy(): return get_strategy(get_helper('STRATEGY'), get_helper('STORAGE')) def load_backend(strategy, name, redirect_uri): backends = get_helper('AUTHENTICATION_BACKENDS') Backend = get_backend(backends, name) return Backend(strategy, redirect_uri) def psa(redirect_uri=None): def decorator(func): @wraps(func) def wrapper(self, backend, *args, **kwargs): uri = redirect_uri if uri and backend and '%(backend)s' in uri: uri = uri % {'backend': backend} self.strategy = load_strategy() self.backend = load_backend(self.strategy, backend, uri) return func(self, backend, *args, **kwargs) return wrapper return decorator def backends(user): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social.backends.utils.user_backends_data.""" return user_backends_data(user, get_helper('AUTHENTICATION_BACKENDS'), get_helper('STORAGE', do_import=True)) def login_redirect(): """Load current redirect to context.""" method = web.ctx.method == 'POST' and 'post' or 'get' data = web.input(_method=method) value = data.get('next') return { 'REDIRECT_FIELD_NAME': 'next', 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': value and ('next=' + value) or '' } def strategy(*args, **kwargs): warnings.warn('@strategy decorator is deprecated, use @psa instead') return psa(*args, **kwargs) python-social-auth-0.2.13/social/backends/000077500000000000000000000000001260133235600203525ustar00rootroot00000000000000python-social-auth-0.2.13/social/backends/__init__.py000066400000000000000000000000001260133235600224510ustar00rootroot00000000000000python-social-auth-0.2.13/social/backends/amazon.py000066400000000000000000000030401260133235600222060ustar00rootroot00000000000000""" Amazon OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/amazon.html """ import ssl from social.backends.oauth import BaseOAuth2 class AmazonOAuth2(BaseOAuth2): name = 'amazon' ID_KEY = 'user_id' AUTHORIZATION_URL = 'http://www.amazon.com/ap/oa' ACCESS_TOKEN_URL = 'https://api.amazon.com/auth/o2/token' DEFAULT_SCOPE = ['profile'] REDIRECT_STATE = False ACCESS_TOKEN_METHOD = 'POST' SSL_PROTOCOL = ssl.PROTOCOL_TLSv1 EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('user_id', 'user_id'), ('postal_code', 'postal_code') ] def get_user_details(self, response): """Return user details from amazon account""" name = response.get('name') or '' fullname, first_name, last_name = self.get_user_names(name) return {'username': name, 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Grab user profile information from amazon.""" response = self.get_json('https://www.amazon.com/ap/user/profile', params={'access_token': access_token}) if 'Profile' in response: response = { 'user_id': response['Profile']['CustomerId'], 'name': response['Profile']['Name'], 'email': response['Profile']['PrimaryEmail'] } return response python-social-auth-0.2.13/social/backends/angel.py000066400000000000000000000020511260133235600220100ustar00rootroot00000000000000""" Angel OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/angel.html """ from social.backends.oauth import BaseOAuth2 class AngelOAuth2(BaseOAuth2): name = 'angel' AUTHORIZATION_URL = 'https://angel.co/api/oauth/authorize/' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://angel.co/api/oauth/token/' REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Angel account""" username = response['angellist_url'].split('/')[-1] email = response.get('email', '') fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.angel.co/1/me/', params={ 'access_token': access_token }) python-social-auth-0.2.13/social/backends/aol.py000066400000000000000000000003361260133235600215010ustar00rootroot00000000000000""" AOL OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/aol.html """ from social.backends.open_id import OpenIdAuth class AOLOpenId(OpenIdAuth): name = 'aol' URL = 'http://openid.aol.com' python-social-auth-0.2.13/social/backends/appsfuel.py000066400000000000000000000027361260133235600225530ustar00rootroot00000000000000""" Appsfueld OAuth2 backend (with sandbox mode support), docs at: http://psa.matiasaguirre.net/docs/backends/appsfuel.html """ from social.backends.oauth import BaseOAuth2 class AppsfuelOAuth2(BaseOAuth2): name = 'appsfuel' ID_KEY = 'user_id' AUTHORIZATION_URL = 'http://app.appsfuel.com/content/permission' ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/live/oauth/token' ACCESS_TOKEN_METHOD = 'POST' USER_DETAILS_URL = 'https://api.appsfuel.com/v1/live/user' def get_user_details(self, response): """Return user details from Appsfuel account""" email = response.get('email', '') username = email.split('@')[0] if email else '' fullname, first_name, last_name = self.get_user_names( response.get('display_name', '') ) return { 'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json(self.USER_DETAILS_URL, params={ 'access_token': access_token }) class AppsfuelOAuth2Sandbox(AppsfuelOAuth2): name = 'appsfuel-sandbox' AUTHORIZATION_URL = 'https://api.appsfuel.com/v1/sandbox/choose' ACCESS_TOKEN_URL = 'https://api.appsfuel.com/v1/sandbox/oauth/token' USER_DETAILS_URL = 'https://api.appsfuel.com/v1/sandbox/user' python-social-auth-0.2.13/social/backends/azuread.py000066400000000000000000000106471260133235600223670ustar00rootroot00000000000000""" Copyright (c) 2015 Microsoft Open Technologies, Inc. All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ """ Azure AD OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/azuread.html """ import time from jwt import DecodeError, ExpiredSignature, decode as jwt_decode from social.exceptions import AuthTokenError from social.backends.oauth import BaseOAuth2 class AzureADOAuth2(BaseOAuth2): name = 'azuread-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://login.windows.net/common/oauth2/authorize' ACCESS_TOKEN_URL = 'https://login.windows.net/common/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False DEFAULT_SCOPE = ['openid', 'profile', 'user_impersonation'] EXTRA_DATA = [ ('access_token', 'access_token'), ('id_token', 'id_token'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires'), ('expires_on', 'expires_on'), ('not_before', 'not_before'), ('given_name', 'first_name'), ('family_name', 'last_name'), ('token_type', 'token_type') ] def get_user_id(self, details, response): """Use upn as unique id""" return response.get('upn') def get_user_details(self, response): """Return user details from Azure AD account""" fullname, first_name, last_name = ( response.get('name', ''), response.get('given_name', ''), response.get('family_name', '') ) return {'username': fullname, 'email': response.get('upn'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): response = kwargs.get('response') id_token = response.get('id_token') try: decoded_id_token = jwt_decode(id_token, verify=False) except (DecodeError, ExpiredSignature) as de: raise AuthTokenError(self, de) return decoded_id_token def auth_extra_arguments(self): """Return extra arguments needed on auth process. The defaults can be overriden by GET parameters.""" extra_arguments = {} resource = self.setting('RESOURCE') if resource: extra_arguments = {'resource': resource} return extra_arguments def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(AzureADOAuth2, self).extra_data(user, uid, response, details, *args, **kwargs) data['resource'] = self.setting('RESOURCE') return data def refresh_token_params(self, token, *args, **kwargs): return { 'refresh_token': token, 'grant_type': 'refresh_token', 'resource': self.setting('RESOURCE') } def get_auth_token(self, user_id): """Return the access token for the given user, after ensuring that it has not expired, or refreshing it if so.""" user = self.get_user(user_id=user_id) access_token = user.social_user.access_token expires_on = user.social_user.extra_data['expires_on'] if expires_on <= int(time.time()): new_token_response = self.refresh_token(token=access_token) access_token = new_token_response['access_token'] return access_token python-social-auth-0.2.13/social/backends/base.py000066400000000000000000000230331260133235600216370ustar00rootroot00000000000000from requests import request, ConnectionError from social.utils import SSLHttpAdapter, module_member, parse_qs, user_agent from social.exceptions import AuthFailed class BaseAuth(object): """A django.contrib.auth backend that authenticates the user based on a authentication provider response""" name = '' # provider name, it's stored in database supports_inactive_user = False # Django auth ID_KEY = None EXTRA_DATA = None REQUIRES_EMAIL_VALIDATION = False SEND_USER_AGENT = False SSL_PROTOCOL = None def __init__(self, strategy=None, redirect_uri=None): self.strategy = strategy self.redirect_uri = redirect_uri self.data = {} if strategy: self.data = self.strategy.request_data() self.redirect_uri = self.strategy.absolute_uri( self.redirect_uri ) def setting(self, name, default=None): """Return setting value from strategy""" return self.strategy.setting(name, default=default, backend=self) def start(self): # Clean any partial pipeline info before starting the process self.strategy.clean_partial_pipeline() if self.uses_redirect(): return self.strategy.redirect(self.auth_url()) else: return self.strategy.html(self.auth_html()) def complete(self, *args, **kwargs): return self.auth_complete(*args, **kwargs) def auth_url(self): """Must return redirect URL to auth provider""" raise NotImplementedError('Implement in subclass') def auth_html(self): """Must return login HTML content returned by provider""" raise NotImplementedError('Implement in subclass') def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" raise NotImplementedError('Implement in subclass') def process_error(self, data): """Process data for errors, raise exception if needed. Call this method on any override of auth_complete.""" pass def authenticate(self, *args, **kwargs): """Authenticate user using social credentials Authentication is made if this is the correct backend, backend verification is made by kwargs inspection for current backend name presence. """ # Validate backend and arguments. Require that the Social Auth # response be passed in as a keyword argument, to make sure we # don't match the username/password calling conventions of # authenticate. if 'backend' not in kwargs or kwargs['backend'].name != self.name or \ 'strategy' not in kwargs or 'response' not in kwargs: return None self.strategy = self.strategy or kwargs.get('strategy') self.redirect_uri = self.redirect_uri or kwargs.get('redirect_uri') self.data = self.strategy.request_data() pipeline = self.strategy.get_pipeline() kwargs.setdefault('is_new', False) if 'pipeline_index' in kwargs: pipeline = pipeline[kwargs['pipeline_index']:] return self.pipeline(pipeline, *args, **kwargs) def pipeline(self, pipeline, pipeline_index=0, *args, **kwargs): out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs) if not isinstance(out, dict): return out user = out.get('user') if user: user.social_user = out.get('social') user.is_new = out.get('is_new') return user def disconnect(self, *args, **kwargs): pipeline = self.strategy.get_disconnect_pipeline() if 'pipeline_index' in kwargs: pipeline = pipeline[kwargs['pipeline_index']:] kwargs['name'] = self.name kwargs['user_storage'] = self.strategy.storage.user return self.run_pipeline(pipeline, *args, **kwargs) def run_pipeline(self, pipeline, pipeline_index=0, *args, **kwargs): out = kwargs.copy() out.setdefault('strategy', self.strategy) out.setdefault('backend', out.pop(self.name, None) or self) out.setdefault('request', self.strategy.request_data()) out.setdefault('details', {}) for idx, name in enumerate(pipeline): out['pipeline_index'] = pipeline_index + idx func = module_member(name) result = func(*args, **out) or {} if not isinstance(result, dict): return result out.update(result) self.strategy.clean_partial_pipeline() return out def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return deafault extra data to store in extra_data field""" data = {} for entry in (self.EXTRA_DATA or []) + self.setting('EXTRA_DATA', []): if not isinstance(entry, (list, tuple)): entry = (entry,) size = len(entry) if size >= 1 and size <= 3: if size == 3: name, alias, discard = entry elif size == 2: (name, alias), discard = entry, False elif size == 1: name = alias = entry[0] discard = False value = response.get(name) or details.get(name) if discard and not value: continue data[alias] = value return data def auth_allowed(self, response, details): """Return True if the user should be allowed to authenticate, by default check if email is whitelisted (if there's a whitelist)""" emails = self.setting('WHITELISTED_EMAILS', []) domains = self.setting('WHITELISTED_DOMAINS', []) email = details.get('email') allowed = True if email and (emails or domains): domain = email.split('@', 1)[1] allowed = email in emails or domain in domains return allowed def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return response.get(self.ID_KEY) def get_user_details(self, response): """Must return user details in a know internal struct: {'username': , 'email': , 'fullname': , 'first_name': , 'last_name': } """ raise NotImplementedError('Implement in subclass') def get_user_names(self, fullname='', first_name='', last_name=''): # Avoid None values fullname = fullname or '' first_name = first_name or '' last_name = last_name or '' if fullname and not (first_name or last_name): try: first_name, last_name = fullname.split(' ', 1) except ValueError: first_name = first_name or fullname or '' last_name = last_name or '' fullname = fullname or ' '.join((first_name, last_name)) return fullname.strip(), first_name.strip(), last_name.strip() def get_user(self, user_id): """ Return user with given ID from the User model used by this backend. This is called by django.contrib.auth.middleware. """ from social.strategies.utils import get_current_strategy strategy = self.strategy or get_current_strategy() return strategy.get_user(user_id) def continue_pipeline(self, *args, **kwargs): """Continue previous halted pipeline""" kwargs.update({'backend': self, 'strategy': self.strategy}) return self.authenticate(*args, **kwargs) def auth_extra_arguments(self): """Return extra arguments needed on auth process. The defaults can be overriden by GET parameters.""" extra_arguments = self.setting('AUTH_EXTRA_ARGUMENTS', {}).copy() extra_arguments.update((key, self.data[key]) for key in extra_arguments if key in self.data) return extra_arguments def uses_redirect(self): """Return True if this provider uses redirect url method, otherwise return false.""" return True def request(self, url, method='GET', *args, **kwargs): kwargs.setdefault('headers', {}) if self.setting('VERIFY_SSL') is not None: kwargs.setdefault('verify', self.setting('VERIFY_SSL')) kwargs.setdefault('timeout', self.setting('REQUESTS_TIMEOUT') or self.setting('URLOPEN_TIMEOUT')) if self.SEND_USER_AGENT and 'User-Agent' not in kwargs['headers']: kwargs['headers']['User-Agent'] = user_agent() try: if self.SSL_PROTOCOL: session = SSLHttpAdapter.ssl_adapter_session(self.SSL_PROTOCOL) response = session.request(method, url, *args, **kwargs) else: response = request(method, url, *args, **kwargs) except ConnectionError as err: raise AuthFailed(self, str(err)) response.raise_for_status() return response def get_json(self, url, *args, **kwargs): return self.request(url, *args, **kwargs).json() def get_querystring(self, url, *args, **kwargs): return parse_qs(self.request(url, *args, **kwargs).text) def get_key_and_secret(self): """Return tuple with Consumer Key and Consumer Secret for current service provider. Must return (key, secret), order *must* be respected. """ return self.setting('KEY'), self.setting('SECRET') python-social-auth-0.2.13/social/backends/battlenet.py000066400000000000000000000034441260133235600227130ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 # This provides a backend for python-social-auth. This should not be confused # with officially battle.net offerings. This piece of code is not officially # affiliated with Blizzard Entertainment, copyrights to their respective # owners. See http://us.battle.net/en/forum/topic/13979588015 for more details. class BattleNetOAuth2(BaseOAuth2): """ battle.net Oauth2 backend""" name = 'battlenet-oauth2' ID_KEY = 'accountId' REDIRECT_STATE = False AUTHORIZATION_URL = 'https://eu.battle.net/oauth/authorize' ACCESS_TOKEN_URL = 'https://eu.battle.net/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_METHOD = 'GET' DEFAULT_SCOPE = ['wow.profile'] EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('token_type', 'token_type', True) ] def get_characters(self, access_token): """ Fetches the character list from the battle.net API. Returns list of characters or empty list if the request fails. """ params = {'access_token': access_token} if self.setting('API_LOCALE'): params['locale'] = self.setting('API_LOCALE') response = self.get_json( 'https://eu.api.battle.net/wow/user/characters', params=params ) return response.get('characters') or [] def get_user_details(self, response): """ Return user details from Battle.net account """ return {'battletag': response.get('battletag')} def user_data(self, access_token, *args, **kwargs): """ Loads user data from service """ return self.get_json( 'https://eu.api.battle.net/account/user/battletag', params={'access_token': access_token} ) python-social-auth-0.2.13/social/backends/beats.py000066400000000000000000000043311260133235600220230ustar00rootroot00000000000000""" Beats backend, docs at: https://developer.beatsmusic.com/docs """ import base64 from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 class BeatsOAuth2(BaseOAuth2): name = 'beats' SCOPE_SEPARATOR = ' ' ID_KEY = 'user_context' AUTHORIZATION_URL = \ 'https://partner.api.beatsmusic.com/v1/oauth2/authorize' ACCESS_TOKEN_URL = 'https://partner.api.beatsmusic.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['result'][BeatsOAuth2.ID_KEY] def auth_headers(self): return { 'Authorization': 'Basic {0}'.format(base64.urlsafe_b64encode( ('{0}:{1}'.format(*self.get_key_and_secret()).encode()) )) } @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) # mashery wraps in jsonrpc if response.get('jsonrpc', None): response = response.get('result', None) return self.do_auth(response['access_token'], response=response, *args, **kwargs) def get_user_details(self, response): """Return user details from Beats account""" response = response['result'] fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('id'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://partner.api.beatsmusic.com/v1/api/me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/behance.py000066400000000000000000000030551260133235600223140ustar00rootroot00000000000000""" Behance OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/behance.html """ from social.backends.oauth import BaseOAuth2 class BehanceOAuth2(BaseOAuth2): """Behance OAuth authentication backend""" name = 'behance' AUTHORIZATION_URL = 'https://www.behance.net/v2/oauth/authenticate' ACCESS_TOKEN_URL = 'https://www.behance.net/v2/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = '|' EXTRA_DATA = [('username', 'username')] REDIRECT_STATE = False def get_user_id(self, details, response): return response['user']['id'] def get_user_details(self, response): """Return user details from Behance account""" user = response['user'] fullname, first_name, last_name = self.get_user_names( user['display_name'], user['first_name'], user['last_name'] ) return {'username': user['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': ''} def extra_data(self, user, uid, response, details=None, *args, **kwargs): # Pull up the embedded user attributes so they can be found as extra # data. See the example token response for possible attributes: # http://www.behance.net/dev/authentication#step-by-step data = response.copy() data.update(response['user']) return super(BehanceOAuth2, self).extra_data(user, uid, data, details, *args, **kwargs) python-social-auth-0.2.13/social/backends/belgiumeid.py000066400000000000000000000005231260133235600230320ustar00rootroot00000000000000""" Belgium EID OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/belgium_eid.html """ from social.backends.open_id import OpenIdAuth class BelgiumEIDOpenId(OpenIdAuth): """Belgium e-ID OpenID authentication backend""" name = 'belgiumeid' URL = 'https://www.e-contract.be/eid-idp/endpoints/openid/auth' python-social-auth-0.2.13/social/backends/bitbucket.py000066400000000000000000000071371260133235600227100ustar00rootroot00000000000000""" Bitbucket OAuth2 and OAuth1 backends, docs at: http://psa.matiasaguirre.net/docs/backends/bitbucket.html """ from social.exceptions import AuthForbidden from social.backends.oauth import BaseOAuth1, BaseOAuth2 class BitbucketOAuthBase(object): ID_KEY = 'uuid' def get_user_id(self, details, response): id_key = self.ID_KEY if self.setting('USERNAME_AS_ID', False): id_key = 'username' return response.get(id_key) def get_user_details(self, response): """Return user details from Bitbucket account""" fullname, first_name, last_name = self.get_user_names( response['display_name'] ) return {'username': response.get('username', ''), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" emails = self._get_emails(access_token) email = None for address in reversed(emails['values']): email = address['email'] if address['is_primary']: break if self.setting('VERIFIED_EMAILS_ONLY', False) and \ not address['is_confirmed']: raise AuthForbidden(self, 'Bitbucket account has no verified email') user = self._get_user(access_token) if email: user['email'] = email return user def _get_user(self, access_token=None): raise NotImplementedError('Implement in subclass') def _get_emails(self, access_token=None): raise NotImplementedError('Implement in subclass') class BitbucketOAuth2(BitbucketOAuthBase, BaseOAuth2): name = 'bitbucket-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://bitbucket.org/site/oauth2/authorize' ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('scopes', 'scopes'), ('expires_in', 'expires'), ('token_type', 'token_type'), ('refresh_token', 'refresh_token') ] def auth_complete_credentials(self): return self.get_key_and_secret() def _get_user(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user', params={'access_token': access_token}) def _get_emails(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user/emails', params={'access_token': access_token}) def refresh_token(self, *args, **kwargs): raise NotImplementedError('Refresh tokens for Bitbucket have ' 'not been implemented') class BitbucketOAuth(BitbucketOAuthBase, BaseOAuth1): """Bitbucket OAuth authentication backend""" name = 'bitbucket' AUTHORIZATION_URL = 'https://bitbucket.org/api/1.0/oauth/authenticate' REQUEST_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/request_token' ACCESS_TOKEN_URL = 'https://bitbucket.org/api/1.0/oauth/access_token' def oauth_auth(self, *args, **kwargs): return super(BitbucketOAuth, self).oauth_auth(*args, **kwargs) def _get_user(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user', auth=self.oauth_auth(access_token)) def _get_emails(self, access_token=None): return self.get_json('https://api.bitbucket.org/2.0/user/emails', auth=self.oauth_auth(access_token)) python-social-auth-0.2.13/social/backends/box.py000066400000000000000000000042211260133235600215130ustar00rootroot00000000000000""" Box.net OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/box.html """ from social.backends.oauth import BaseOAuth2 class BoxOAuth2(BaseOAuth2): """Box.net OAuth authentication backend""" name = 'box' AUTHORIZATION_URL = 'https://www.box.com/api/oauth2/authorize' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://www.box.com/api/oauth2/token' REVOKE_TOKEN_URL = 'https://www.box.com/api/oauth2/revoke' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('id', 'id'), ('expires', 'expires'), ] def do_auth(self, access_token, response=None, *args, **kwargs): response = response or {} data = self.user_data(access_token) data['access_token'] = response.get('access_token') data['refresh_token'] = response.get('refresh_token') data['expires'] = response.get('expires_in') kwargs.update({'backend': self, 'response': data}) return self.strategy.authenticate(*args, **kwargs) def get_user_details(self, response): """Return user details Box.net account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('login'), 'email': response.get('login') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" params = self.setting('PROFILE_EXTRA_PARAMS', {}) params['access_token'] = access_token return self.get_json('https://api.box.com/2.0/users/me', params=params) def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) request = self.request(self.REFRESH_TOKEN_URL or self.ACCESS_TOKEN_URL, data=params, headers=self.auth_headers(), method='POST') return self.process_refresh_token_response(request, *args, **kwargs) python-social-auth-0.2.13/social/backends/changetip.py000066400000000000000000000015661260133235600226760ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class ChangeTipOAuth2(BaseOAuth2): """ChangeTip OAuth authentication backend https://www.changetip.com/api """ name = 'changetip' AUTHORIZATION_URL = 'https://www.changetip.com/o/authorize/' ACCESS_TOKEN_URL = 'https://www.changetip.com/o/token/' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' def get_user_details(self, response): """Return user details from ChangeTip account""" return { 'username': response['username'], 'email': response.get('email', ''), 'first_name': '', 'last_name': '', } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.changetip.com/v2/me/', params={ 'access_token': access_token }) python-social-auth-0.2.13/social/backends/clef.py000066400000000000000000000033501260133235600216360ustar00rootroot00000000000000""" Clef OAuth support. This contribution adds support for Clef OAuth service. The settings SOCIAL_AUTH_CLEF_KEY and SOCIAL_AUTH_CLEF_SECRET must be defined with the values given by Clef application registration process. """ from social.backends.oauth import BaseOAuth2 class ClefOAuth2(BaseOAuth2): """Clef OAuth authentication backend""" name = 'clef' AUTHORIZATION_URL = 'https://clef.io/iframes/qr' ACCESS_TOKEN_URL = 'https://clef.io/api/v1/authorize' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' def auth_params(self, *args, **kwargs): params = super(ClefOAuth2, self).auth_params(*args, **kwargs) params['app_id'] = params.pop('client_id') params['redirect_url'] = params.pop('redirect_uri') return params def get_user_id(self, response, details): return details.get('info').get('id') def get_user_details(self, response): """Return user details from Github account""" info = response.get('info') fullname, first_name, last_name = self.get_user_names( first_name=info.get('first_name'), last_name=info.get('last_name') ) email = info.get('email', '') if email: username = email.split('@', 1)[0] else: username = info.get('id') return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'phone_number': info.get('phone_number', '') } def user_data(self, access_token, *args, **kwargs): return self.get_json('https://clef.io/api/v1/info', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/coinbase.py000066400000000000000000000023561260133235600225150ustar00rootroot00000000000000""" Coinbase OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/coinbase.html """ from social.backends.oauth import BaseOAuth2 class CoinbaseOAuth2(BaseOAuth2): name = 'coinbase' SCOPE_SEPARATOR = '+' DEFAULT_SCOPE = ['user', 'balance'] AUTHORIZATION_URL = 'https://coinbase.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://coinbase.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['users'][0]['user']['id'] def get_user_details(self, response): """Return user details from Coinbase account""" user_data = response['users'][0]['user'] email = user_data.get('email', '') name = user_data['name'] fullname, first_name, last_name = self.get_user_names(name) return {'username': name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://coinbase.com/api/v1/users', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/coursera.py000066400000000000000000000024501260133235600225500ustar00rootroot00000000000000""" Coursera OAuth2 backend, docs at: https://tech.coursera.org/app-platform/oauth2/ """ from social.backends.oauth import BaseOAuth2 class CourseraOAuth2(BaseOAuth2): """Coursera OAuth2 authentication backend""" name = 'coursera' ID_KEY = 'username' AUTHORIZATION_URL = 'https://accounts.coursera.org/oauth2/v1/auth' ACCESS_TOKEN_URL = 'https://accounts.coursera.org/oauth2/v1/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['view_profile'] def _get_username_from_response(self, response): elements = response.get('elements', []) for element in elements: if 'id' in element: return element.get('id') return None def get_user_details(self, response): """Return user details from Coursera account""" return {'username': self._get_username_from_response(response)} def user_data(self, access_token, *args, **kwargs): """Load user data from the service""" return self.get_json( 'https://api.coursera.org/api/externalBasicProfiles.v1?q=me', headers=self.get_auth_header(access_token) ) def get_auth_header(self, access_token): return {'Authorization': 'Bearer {0}'.format(access_token)} python-social-auth-0.2.13/social/backends/dailymotion.py000066400000000000000000000015671260133235600232650ustar00rootroot00000000000000""" DailyMotion OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dailymotion.html """ from social.backends.oauth import BaseOAuth2 class DailymotionOAuth2(BaseOAuth2): """Dailymotion OAuth authentication backend""" name = 'dailymotion' EXTRA_DATA = [('id', 'id')] ID_KEY = 'username' AUTHORIZATION_URL = 'https://api.dailymotion.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.dailymotion.com/oauth/token' ACCESS_TOKEN_URL = 'https://api.dailymotion.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def get_user_details(self, response): return {'username': response.get('screenname')} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json('https://api.dailymotion.com/me/', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/digitalocean.py000066400000000000000000000026401260133235600233510ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class DigitalOceanOAuth(BaseOAuth2): """ DigitalOcean OAuth authentication backend. Docs: https://developers.digitalocean.com/documentation/oauth/ """ name = 'digitalocean' AUTHORIZATION_URL = 'https://cloud.digitalocean.com/v1/oauth/authorize' ACCESS_TOKEN_URL = 'https://cloud.digitalocean.com/v1/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' EXTRA_DATA = [ ('expires_in', 'expires_in') ] def get_user_id(self, details, response): """Return user unique id provided by service""" return response['account'].get('uuid') def get_user_details(self, response): """Return user details from DigitalOcean account""" fullname, first_name, last_name = self.get_user_names( response.get('name') or '') return {'username': response['account'].get('email'), 'email': response['account'].get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, token, *args, **kwargs): """Loads user data from service""" url = 'https://api.digitalocean.com/v2/account' auth_header = {"Authorization": "Bearer %s" % token} try: return self.get_json(url, headers=auth_header) except ValueError: return None python-social-auth-0.2.13/social/backends/disqus.py000066400000000000000000000034231260133235600222360ustar00rootroot00000000000000""" Disqus OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/disqus.html """ from social.backends.oauth import BaseOAuth2 class DisqusOAuth2(BaseOAuth2): name = 'disqus' AUTHORIZATION_URL = 'https://disqus.com/api/oauth/2.0/authorize/' ACCESS_TOKEN_URL = 'https://disqus.com/api/oauth/2.0/access_token/' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('avatar', 'avatar'), ('connections', 'connections'), ('user_id', 'user_id'), ('email', 'email'), ('email_hash', 'emailHash'), ('expires', 'expires'), ('location', 'location'), ('meta', 'response'), ('name', 'name'), ('username', 'username'), ] def get_user_id(self, details, response): return response['response']['id'] def get_user_details(self, response): """Return user details from Disqus account""" rr = response.get('response', {}) return { 'username': rr.get('username', ''), 'user_id': response.get('user_id', ''), 'email': rr.get('email', ''), 'name': rr.get('name', ''), } def extra_data(self, user, uid, response, details=None, *args, **kwargs): meta_response = dict(response, **response.get('response', {})) return super(DisqusOAuth2, self).extra_data(user, uid, meta_response, details, *args, **kwargs) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" key, secret = self.get_key_and_secret() return self.get_json( 'https://disqus.com/api/3.0/users/details.json', params={'access_token': access_token, 'api_secret': secret} ) python-social-auth-0.2.13/social/backends/docker.py000066400000000000000000000031171260133235600221750ustar00rootroot00000000000000""" Docker Hub OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/docker.html """ from social.backends.oauth import BaseOAuth2 class DockerOAuth2(BaseOAuth2): name = 'docker' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://hub.docker.com/api/v1.1/o/authorize/' ACCESS_TOKEN_URL = 'https://hub.docker.com/api/v1.1/o/token/' REFRESH_TOKEN_URL = 'https://hub.docker.com/api/v1.1/o/token/' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('user_id', 'user_id'), ('email', 'email'), ('full_name', 'fullname'), ('location', 'location'), ('url', 'url'), ('company', 'company'), ('gravatar_email', 'gravatar_email'), ] def get_user_details(self, response): """Return user details from Docker Hub account""" fullname, first_name, last_name = self.get_user_names( response.get('full_name') or response.get('username') or '' ) return { 'username': response.get('username'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': response.get('email', '') } def user_data(self, access_token, *args, **kwargs): """Grab user profile information from Docker Hub.""" username = kwargs['response']['username'] return self.get_json( 'https://hub.docker.com/api/v1.1/users/%s/' % username, headers={'Authorization': 'Bearer %s' % access_token} ) python-social-auth-0.2.13/social/backends/douban.py000066400000000000000000000040641260133235600222000ustar00rootroot00000000000000""" Douban OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/douban.html """ from social.backends.oauth import BaseOAuth2, BaseOAuth1 class DoubanOAuth(BaseOAuth1): """Douban OAuth authentication backend""" name = 'douban' EXTRA_DATA = [('id', 'id')] AUTHORIZATION_URL = 'http://www.douban.com/service/auth/authorize' REQUEST_TOKEN_URL = 'http://www.douban.com/service/auth/request_token' ACCESS_TOKEN_URL = 'http://www.douban.com/service/auth/access_token' def get_user_id(self, details, response): return response['db:uid']['$t'] def get_user_details(self, response): """Return user details from Douban""" return {'username': response["db:uid"]["$t"], 'email': ''} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json('http://api.douban.com/people/%40me?&alt=json', auth=self.oauth_auth(access_token)) class DoubanOAuth2(BaseOAuth2): """Douban OAuth authentication backend""" name = 'douban-oauth2' AUTHORIZATION_URL = 'https://www.douban.com/service/auth2/auth' ACCESS_TOKEN_URL = 'https://www.douban.com/service/auth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('uid', 'username'), ('refresh_token', 'refresh_token'), ] def get_user_details(self, response): """Return user details from Douban""" fullname, first_name, last_name = self.get_user_names( response.get('name', '') ) return {'username': response.get('uid', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': ''} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.douban.com/v2/user/~me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/dribbble.py000066400000000000000000000042521260133235600224740ustar00rootroot00000000000000""" Dribbble OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dribbble.html http://developer.dribbble.com/v1/oauth/ """ from social.backends.oauth import BaseOAuth2 class DribbbleOAuth2(BaseOAuth2): """Dribbble OAuth authentication backend""" name = 'dribbble' AUTHORIZATION_URL = 'https://dribbble.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://dribbble.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('name', 'name'), ('html_url', 'html_url'), ('avatar_url', 'avatar_url'), ('bio', 'bio'), ('location', 'location'), ('links', 'links'), ('buckets_count', 'buckets_count'), ('comments_received_count', 'comments_received_count'), ('followers_count', 'followers_count'), ('followings_count', 'followings_count'), ('likes_count', 'likes_count'), ('likes_received_count', 'likes_received_count'), ('projects_count', 'projects_count'), ('rebounds_received_count', 'rebounds_received_count'), ('shots_count', 'shots_count'), ('teams_count', 'teams_count'), ('pro', 'pro'), ('buckets_url', 'buckets_url'), ('followers_url', 'followers_url'), ('following_url', 'following_url'), ('likes_url', 'shots_url'), ('teams_url', 'teams_url'), ('created_at', 'created_at'), ('updated_at', 'updated_at'), ] def get_user_details(self, response): """Return user details from Dribbble account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('username'), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.dribbble.com/v1/user', headers={ 'Authorization': ' Bearer {0}'.format(access_token) }) python-social-auth-0.2.13/social/backends/dropbox.py000066400000000000000000000045211260133235600224030ustar00rootroot00000000000000""" Dropbox OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/dropbox.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class DropboxOAuth(BaseOAuth1): """Dropbox OAuth authentication backend""" name = 'dropbox' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://www.dropbox.com/1/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.dropbox.com/1/oauth/request_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://api.dropbox.com/1/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Dropbox account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': str(response.get('uid')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.dropbox.com/1/account/info', auth=self.oauth_auth(access_token)) class DropboxOAuth2(BaseOAuth2): name = 'dropbox-oauth2' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://www.dropbox.com/1/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.dropbox.com/1/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('uid', 'username'), ] def get_user_details(self, response): """Return user details from Dropbox account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': str(response.get('uid')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.dropbox.com/1/account/info', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/echosign.py000066400000000000000000000015331260133235600225250ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class EchosignOAuth2(BaseOAuth2): name = 'echosign' REDIRECT_STATE = False ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' REVOKE_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://secure.echosign.com/public/oauth' ACCESS_TOKEN_URL = 'https://secure.echosign.com/oauth/token' REFRESH_TOKEN_URL = 'https://secure.echosign.com/oauth/refresh' REVOKE_TOKEN_URL = 'https://secure.echosign.com/oauth/revoke' def get_user_details(self, response): return response def get_user_id(self, details, response): return details['userInfoList'][0]['userId'] def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.echosign.com/api/rest/v3/users', headers={'Access-Token': access_token}) python-social-auth-0.2.13/social/backends/email.py000066400000000000000000000004261260133235600220150ustar00rootroot00000000000000""" Legacy Email backend, docs at: http://psa.matiasaguirre.net/docs/backends/email.html """ from social.backends.legacy import LegacyAuth class EmailAuth(LegacyAuth): name = 'email' ID_KEY = 'email' REQUIRES_EMAIL_VALIDATION = True EXTRA_DATA = ['email'] python-social-auth-0.2.13/social/backends/eveonline.py000066400000000000000000000026051260133235600227130ustar00rootroot00000000000000""" EVE Online Single Sign-On (SSO) OAuth2 backend Documentation at https://developers.eveonline.com/resource/single-sign-on """ from social.backends.oauth import BaseOAuth2 class EVEOnlineOAuth2(BaseOAuth2): """EVE Online OAuth authentication backend""" name = 'eveonline' AUTHORIZATION_URL = 'https://login.eveonline.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.eveonline.com/oauth/token' ID_KEY = 'CharacterID' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('CharacterID', 'id'), ('ExpiresOn', 'expires'), ('CharacterOwnerHash', 'owner_hash', True), ('refresh_token', 'refresh_token', True), ] def get_user_details(self, response): """Return user details from EVE Online account""" user_data = self.user_data(response['access_token']) fullname, first_name, last_name = self.get_user_names( user_data['CharacterName'] ) return { 'email': '', 'username': fullname, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Get Character data from EVE server""" return self.get_json( 'https://login.eveonline.com/oauth/verify', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/evernote.py000066400000000000000000000052201260133235600225520ustar00rootroot00000000000000""" Evernote OAuth1 backend (with sandbox mode support), docs at: http://psa.matiasaguirre.net/docs/backends/evernote.html """ from requests import HTTPError from social.exceptions import AuthCanceled from social.backends.oauth import BaseOAuth1 class EvernoteOAuth(BaseOAuth1): """ Evernote OAuth authentication backend. Possible Values: {'edam_expires': ['1367525289541'], 'edam_noteStoreUrl': [ 'https://sandbox.evernote.com/shard/s1/notestore' ], 'edam_shard': ['s1'], 'edam_userId': ['123841'], 'edam_webApiUrlPrefix': ['https://sandbox.evernote.com/shard/s1/'], 'oauth_token': [ 'S=s1:U=1e3c1:E=13e66dbee45:C=1370f2ac245:P=185:A=my_user:' \ 'H=411443c5e8b20f8718ed382a19d4ae38' ]} """ name = 'evernote' ID_KEY = 'edam_userId' AUTHORIZATION_URL = 'https://www.evernote.com/OAuth.action' REQUEST_TOKEN_URL = 'https://www.evernote.com/oauth' ACCESS_TOKEN_URL = 'https://www.evernote.com/oauth' EXTRA_DATA = [ ('access_token', 'access_token'), ('oauth_token', 'oauth_token'), ('edam_noteStoreUrl', 'store_url'), ('edam_expires', 'expires') ] def get_user_details(self, response): """Return user details from Evernote account""" return {'username': response['edam_userId'], 'email': ''} def access_token(self, token): """Return request for access token value""" try: return self.get_querystring(self.ACCESS_TOKEN_URL, auth=self.oauth_auth(token)) except HTTPError as err: # Evernote returns a 401 error when AuthCanceled if err.response.status_code == 401: raise AuthCanceled(self) else: raise def extra_data(self, user, uid, response, details=None, *args, **kwargs): data = super(EvernoteOAuth, self).extra_data(user, uid, response, details, *args, **kwargs) # Evernote returns expiration timestamp in miliseconds, so it needs to # be normalized. if 'expires' in data: data['expires'] = int(data['expires']) / 1000 return data def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return access_token.copy() class EvernoteSandboxOAuth(EvernoteOAuth): name = 'evernote-sandbox' AUTHORIZATION_URL = 'https://sandbox.evernote.com/OAuth.action' REQUEST_TOKEN_URL = 'https://sandbox.evernote.com/oauth' ACCESS_TOKEN_URL = 'https://sandbox.evernote.com/oauth' python-social-auth-0.2.13/social/backends/exacttarget.py000066400000000000000000000076751260133235600232560ustar00rootroot00000000000000""" ExactTarget OAuth support. Support Authentication from IMH using JWT token and pre-shared key. Requires package pyjwt """ from datetime import timedelta, datetime import jwt from social.exceptions import AuthFailed, AuthCanceled from social.backends.oauth import BaseOAuth2 class ExactTargetOAuth2(BaseOAuth2): name = 'exacttarget' def get_user_details(self, response): """Use the email address of the user, suffixed by _et""" user = response.get('token', {})\ .get('request', {})\ .get('user', {}) if 'email' in user: user['username'] = user['email'] return user def get_user_id(self, details, response): """ Create a user ID from the ET user ID. Uses details rather than the default response, as only the token is available in response. details is much richer: { 'expiresIn': 1200, 'username': 'example@example.com', 'refreshToken': '1234567890abcdef', 'internalOauthToken': 'jwttoken.......', 'oauthToken': 'yetanothertoken', 'id': 123456, 'culture': 'en-US', 'timezone': { 'shortName': 'CST', 'offset': -6.0, 'dst': False, 'longName': '(GMT-06:00) Central Time (No Daylight Saving)' }, 'email': 'example@example.com' } """ return '{0}'.format(details.get('id')) def uses_redirect(self): return False def auth_url(self): return None def process_error(self, data): if data.get('error'): error = self.data.get('error_description') or self.data['error'] raise AuthFailed(self, error) def do_auth(self, token, *args, **kwargs): dummy, secret = self.get_key_and_secret() try: # Decode the token, using the Application Signature from settings decoded = jwt.decode(token, secret, algorithms=['HS256']) except jwt.DecodeError: # Wrong signature, fail authentication raise AuthCanceled(self) kwargs.update({'response': {'token': decoded}, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" token = self.data.get('jwt', {}) if not token: raise AuthFailed(self, 'Authentication Failed') return self.do_auth(token, *args, **kwargs) def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Load extra details from the JWT token""" data = { 'id': details.get('id'), 'email': details.get('email'), # OAuth token, for use with legacy SOAP API calls: # http://bit.ly/13pRHfo 'internalOauthToken': details.get('internalOauthToken'), # Token for use with the Application ClientID for the FUEL API 'oauthToken': details.get('oauthToken'), # If the token has expired, use the FUEL API to get a new token see # http://bit.ly/10v1K5l and http://bit.ly/11IbI6F - set legacy=1 'refreshToken': details.get('refreshToken'), } # The expiresIn value determines how long the tokens are valid for. # Take a bit off, then convert to an int timestamp expiresSeconds = details.get('expiresIn', 0) - 30 expires = datetime.utcnow() + timedelta(seconds=expiresSeconds) data['expires'] = (expires - datetime(1970, 1, 1)).total_seconds() if response.get('token'): token = response['token'] org = token.get('request', {}).get('organization') if org: data['stack'] = org.get('stackKey') data['enterpriseId'] = org.get('enterpriseId') return data python-social-auth-0.2.13/social/backends/facebook.py000066400000000000000000000176561260133235600225140ustar00rootroot00000000000000""" Facebook OAuth2 and Canvas Application backends, docs at: http://psa.matiasaguirre.net/docs/backends/facebook.html """ import hmac import time import json import base64 import hashlib from social.utils import parse_qs, constant_time_compare, handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthException, AuthCanceled, AuthUnknownError, \ AuthMissingParameter class FacebookOAuth2(BaseOAuth2): """Facebook OAuth2 authentication backend""" name = 'facebook' RESPONSE_TYPE = None SCOPE_SEPARATOR = ',' AUTHORIZATION_URL = 'https://www.facebook.com/v2.3/dialog/oauth' ACCESS_TOKEN_URL = 'https://graph.facebook.com/v2.3/oauth/access_token' REVOKE_TOKEN_URL = 'https://graph.facebook.com/v2.3/{uid}/permissions' REVOKE_TOKEN_METHOD = 'DELETE' USER_DATA_URL = 'https://graph.facebook.com/v2.3/me' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Facebook account""" fullname, first_name, last_name = self.get_user_names( response.get('name', ''), response.get('first_name', ''), response.get('last_name', '') ) return {'username': response.get('username', response.get('name')), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" params = self.setting('PROFILE_EXTRA_PARAMS', {}) params['access_token'] = access_token if self.setting('APPSECRET_PROOF', True): _, secret = self.get_key_and_secret() params['appsecret_proof'] = hmac.new( secret.encode('utf8'), msg=access_token.encode('utf8'), digestmod=hashlib.sha256 ).hexdigest() return self.get_json(self.USER_DATA_URL, params=params) def process_error(self, data): super(FacebookOAuth2, self).process_error(data) if data.get('error_code'): raise AuthCanceled(self, data.get('error_message') or data.get('error_code')) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) if not self.data.get('code'): raise AuthMissingParameter(self, 'code') state = self.validate_state() key, secret = self.get_key_and_secret() response = self.request(self.ACCESS_TOKEN_URL, params={ 'client_id': key, 'redirect_uri': self.get_redirect_uri(state), 'client_secret': secret, 'code': self.data['code'] }) # API v2.3 returns a JSON, according to the documents linked at issue # #592, but it seems that this needs to be enabled(?), otherwise the # usual querystring type response is returned. try: response = response.json() except ValueError: response = parse_qs(response.text) access_token = response['access_token'] return self.do_auth(access_token, response, *args, **kwargs) def process_refresh_token_response(self, response, *args, **kwargs): return parse_qs(response.content) def refresh_token_params(self, token, *args, **kwargs): client_id, client_secret = self.get_key_and_secret() return { 'fb_exchange_token': token, 'grant_type': 'fb_exchange_token', 'client_id': client_id, 'client_secret': client_secret } def do_auth(self, access_token, response=None, *args, **kwargs): response = response or {} data = self.user_data(access_token) if not isinstance(data, dict): # From time to time Facebook responds back a JSON with just # False as value, the reason is still unknown, but since the # data is needed (it contains the user ID used to identify the # account on further logins), this app cannot allow it to # continue with the auth process. raise AuthUnknownError(self, 'An error ocurred while retrieving ' 'users Facebook data') data['access_token'] = access_token if 'expires' in response: data['expires'] = response['expires'] kwargs.update({'backend': self, 'response': data}) return self.strategy.authenticate(*args, **kwargs) def revoke_token_url(self, token, uid): return self.REVOKE_TOKEN_URL.format(uid=uid) def revoke_token_params(self, token, uid): return {'access_token': token} def process_revoke_token_response(self, response): return super(FacebookOAuth2, self).process_revoke_token_response( response ) and response.content == 'true' class FacebookAppOAuth2(FacebookOAuth2): """Facebook Application Authentication support""" name = 'facebook-app' def uses_redirect(self): return False def auth_complete(self, *args, **kwargs): access_token = None response = {} if 'signed_request' in self.data: key, secret = self.get_key_and_secret() response = self.load_signed_request(self.data['signed_request']) if 'user_id' not in response and 'oauth_token' not in response: raise AuthException(self) if response is not None: access_token = response.get('access_token') or \ response.get('oauth_token') or \ self.data.get('access_token') if access_token is None: if self.data.get('error') == 'access_denied': raise AuthCanceled(self) else: raise AuthException(self) return self.do_auth(access_token, response, *args, **kwargs) def auth_html(self): key, secret = self.get_key_and_secret() namespace = self.setting('NAMESPACE', None) scope = self.setting('SCOPE', '') if scope: scope = self.SCOPE_SEPARATOR.join(scope) ctx = { 'FACEBOOK_APP_NAMESPACE': namespace or key, 'FACEBOOK_KEY': key, 'FACEBOOK_EXTENDED_PERMISSIONS': scope, 'FACEBOOK_COMPLETE_URI': self.redirect_uri, } tpl = self.setting('LOCAL_HTML', 'facebook.html') return self.strategy.render_html(tpl=tpl, context=ctx) def load_signed_request(self, signed_request): def base64_url_decode(data): data = data.encode('ascii') data += '=' * (4 - (len(data) % 4)) return base64.urlsafe_b64decode(data) key, secret = self.get_key_and_secret() try: sig, payload = signed_request.split('.', 1) except ValueError: pass # ignore if can't split on dot else: sig = base64_url_decode(sig) data = json.loads(base64_url_decode(payload)) expected_sig = hmac.new(secret, msg=payload, digestmod=hashlib.sha256).digest() # allow the signed_request to function for upto 1 day if constant_time_compare(sig, expected_sig) and \ data['issued_at'] > (time.time() - 86400): return data class Facebook2OAuth2(FacebookOAuth2): """Facebook OAuth2 authentication backend using Facebook Open Graph 2.0""" AUTHORIZATION_URL = 'https://www.facebook.com/v2.0/dialog/oauth' ACCESS_TOKEN_URL = 'https://graph.facebook.com/v2.0/oauth/access_token' REVOKE_TOKEN_URL = 'https://graph.facebook.com/v2.0/{uid}/permissions' USER_DATA_URL = 'https://graph.facebook.com/v2.0/me' class Facebook2AppOAuth2(Facebook2OAuth2, FacebookAppOAuth2): pass python-social-auth-0.2.13/social/backends/fedora.py000066400000000000000000000004171260133235600221660ustar00rootroot00000000000000""" Fedora OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/fedora.html """ from social.backends.open_id import OpenIdAuth class FedoraOpenId(OpenIdAuth): name = 'fedora' URL = 'https://id.fedoraproject.org' USERNAME_KEY = 'nickname' python-social-auth-0.2.13/social/backends/fitbit.py000066400000000000000000000017411260133235600222100ustar00rootroot00000000000000""" Fitbit OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/fitbit.html """ from social.backends.oauth import BaseOAuth1 class FitbitOAuth(BaseOAuth1): """Fitbit OAuth authentication backend""" name = 'fitbit' AUTHORIZATION_URL = 'https://www.fitbit.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.fitbit.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.fitbit.com/oauth/access_token' ID_KEY = 'encodedId' EXTRA_DATA = [('encodedId', 'id'), ('displayName', 'username')] def get_user_details(self, response): """Return user details from Fitbit account""" return {'username': response.get('displayName'), 'email': ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.fitbit.com/1/user/-/profile.json', auth=self.oauth_auth(access_token) )['user'] python-social-auth-0.2.13/social/backends/flickr.py000066400000000000000000000027301260133235600222000ustar00rootroot00000000000000""" Flickr OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/flickr.html """ from social.backends.oauth import BaseOAuth1 class FlickrOAuth(BaseOAuth1): """Flickr OAuth authentication backend""" name = 'flickr' AUTHORIZATION_URL = 'https://www.flickr.com/services/oauth/authorize' REQUEST_TOKEN_URL = 'https://www.flickr.com/services/oauth/request_token' ACCESS_TOKEN_URL = 'https://www.flickr.com/services/oauth/access_token' EXTRA_DATA = [ ('id', 'id'), ('username', 'username'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Flickr account""" fullname, first_name, last_name = self.get_user_names( response.get('fullname') ) return {'username': response.get('username') or response.get('id'), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return { 'id': access_token['user_nsid'], 'username': access_token['username'], 'fullname': access_token.get('fullname', ''), } def auth_extra_arguments(self): params = super(FlickrOAuth, self).auth_extra_arguments() or {} if 'perms' not in params: params['perms'] = 'read' return params python-social-auth-0.2.13/social/backends/foursquare.py000066400000000000000000000025401260133235600231210ustar00rootroot00000000000000""" Foursquare OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/foursquare.html """ from social.backends.oauth import BaseOAuth2 class FoursquareOAuth2(BaseOAuth2): name = 'foursquare' AUTHORIZATION_URL = 'https://foursquare.com/oauth2/authenticate' ACCESS_TOKEN_URL = 'https://foursquare.com/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' API_VERSION = '20140128' def get_user_id(self, details, response): return response['response']['user']['id'] def get_user_details(self, response): """Return user details from Foursquare account""" info = response['response']['user'] email = info['contact']['email'] fullname, first_name, last_name = self.get_user_names( first_name=info.get('firstName', ''), last_name=info.get('lastName', '') ) return {'username': first_name + ' ' + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.foursquare.com/v2/users/self', params={'oauth_token': access_token, 'v': self.API_VERSION}) python-social-auth-0.2.13/social/backends/gae.py000066400000000000000000000024141260133235600214610ustar00rootroot00000000000000""" Google App Engine support using User API """ from __future__ import absolute_import from google.appengine.api import users from social.backends.base import BaseAuth from social.exceptions import AuthException class GoogleAppEngineAuth(BaseAuth): """GoogleAppengine authentication backend""" name = 'google-appengine' def get_user_id(self, details, response): """Return current user id.""" user = users.get_current_user() if user: return user.user_id() def get_user_details(self, response): """Return user basic information (id and email only).""" user = users.get_current_user() return {'username': user.user_id(), 'email': user.email(), 'fullname': '', 'first_name': '', 'last_name': ''} def auth_url(self): """Build and return complete URL.""" return users.create_login_url(self.redirect_uri) def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance.""" if not users.get_current_user(): raise AuthException('Authentication error') kwargs.update({'response': '', 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.13/social/backends/github.py000066400000000000000000000075521260133235600222170ustar00rootroot00000000000000""" Github OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/github.html """ from requests import HTTPError from six.moves.urllib.parse import urljoin from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed class GithubOAuth2(BaseOAuth2): """Github OAuth authentication backend""" name = 'github' API_URL = 'https://api.github.com/' AUTHORIZATION_URL = 'https://github.com/login/oauth/authorize' ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires'), ('login', 'login') ] def api_url(self): return self.API_URL def get_user_details(self, response): """Return user details from Github account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('login'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" data = self._user_data(access_token) if not data.get('email'): try: emails = self._user_data(access_token, '/emails') except (HTTPError, ValueError, TypeError): emails = [] if emails: email = emails[0] primary_emails = [ e for e in emails if not isinstance(e, dict) or e.get('primary') ] if primary_emails: email = primary_emails[0] if isinstance(email, dict): email = email.get('email', '') data['email'] = email return data def _user_data(self, access_token, path=None): url = urljoin(self.api_url(), 'user{0}'.format(path or '')) return self.get_json(url, params={'access_token': access_token}) class GithubMemberOAuth2(GithubOAuth2): no_member_string = '' def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" user_data = super(GithubMemberOAuth2, self).user_data( access_token, *args, **kwargs ) try: self.request(self.member_url(user_data), params={ 'access_token': access_token }) except HTTPError as err: # if the user is a member of the organization, response code # will be 204, see http://bit.ly/ZS6vFl if err.response.status_code != 204: raise AuthFailed(self, 'User doesn\'t belong to the organization') return user_data def member_url(self, user_data): raise NotImplementedError('Implement in subclass') class GithubOrganizationOAuth2(GithubMemberOAuth2): """Github OAuth2 authentication backend for organizations""" name = 'github-org' no_member_string = 'User doesn\'t belong to the organization' def member_url(self, user_data): return urljoin( self.api_url(), 'orgs/{org}/members/{username}'.format( org=self.setting('NAME'), username=user_data.get('login') ) ) class GithubTeamOAuth2(GithubMemberOAuth2): """Github OAuth2 authentication backend for teams""" name = 'github-team' no_member_string = 'User doesn\'t belong to the team' def member_url(self, user_data): return urljoin( self.api_url(), 'teams/{team_id}/members/{username}'.format( team_id=self.setting('ID'), username=user_data.get('login') ) ) python-social-auth-0.2.13/social/backends/github_enterprise.py000066400000000000000000000024621260133235600244520ustar00rootroot00000000000000""" Github Enterprise OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/github_enterprise.html """ from six.moves.urllib.parse import urljoin from social.utils import append_slash from social.backends.github import GithubOAuth2, GithubOrganizationOAuth2, \ GithubTeamOAuth2 class GithubEnterpriseMixin(object): def api_url(self): return append_slash(self.setting('API_URL')) def authorization_url(self): return self._url('login/oauth/authorize') def access_token_url(self): return self._url('login/oauth/access_token') def _url(self, path): return urljoin(append_slash(self.setting('URL')), path) class GithubEnterpriseOAuth2(GithubEnterpriseMixin, GithubOAuth2): """Github Enterprise OAuth authentication backend""" name = 'github-enterprise' class GithubEnterpriseOrganizationOAuth2(GithubEnterpriseMixin, GithubOrganizationOAuth2): """Github Enterprise OAuth2 authentication backend for organizations""" name = 'github-enterprise-org' DEFAULT_SCOPE = ['read:org'] class GithubEnterpriseTeamOAuth2(GithubEnterpriseMixin, GithubTeamOAuth2): """Github Enterprise OAuth2 authentication backend for teams""" name = 'github-enterprise-team' DEFAULT_SCOPE = ['read:org'] python-social-auth-0.2.13/social/backends/goclio.py000066400000000000000000000023371260133235600222050ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class GoClioOAuth2(BaseOAuth2): name = 'goclio' AUTHORIZATION_URL = 'https://app.goclio.com/oauth/authorize/' ACCESS_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'https://app.goclio.com/oauth/token/' REDIRECT_STATE = False STATE_PARAMETER = False def get_user_details(self, response): """Return user details from GoClio account""" user = response.get('user', {}) username = user.get('id', None) email = user.get('email', None) first_name, last_name = (user.get('first_name', None), user.get('last_name', None)) fullname = '%s %s' % (first_name, last_name) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://app.goclio.com/api/v2/users/who_am_i', params={'access_token': access_token} ) def get_user_id(self, details, response): return response.get('user', {}).get('id') python-social-auth-0.2.13/social/backends/goclioeu.py000066400000000000000000000007421260133235600225350ustar00rootroot00000000000000from social.backends.goclio import GoClioOAuth2 class GoClioEuOAuth2(GoClioOAuth2): name = 'goclioeu' AUTHORIZATION_URL = 'https://app.goclio.eu/oauth/authorize/' ACCESS_TOKEN_URL = 'https://app.goclio.eu/oauth/token/' def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://app.goclio.eu/api/v2/users/who_am_i', params={'access_token': access_token} ) python-social-auth-0.2.13/social/backends/google.py000066400000000000000000000175571260133235600222170ustar00rootroot00000000000000""" Google OpenId, OAuth2, OAuth1, Google+ Sign-in backends, docs at: http://psa.matiasaguirre.net/docs/backends/google.html """ from social.utils import handle_http_errors from social.backends.open_id import OpenIdAuth, OpenIdConnectAuth from social.backends.oauth import BaseOAuth2, BaseOAuth1 from social.exceptions import AuthMissingParameter class BaseGoogleAuth(object): def get_user_id(self, details, response): """Use google email as unique id""" if self.setting('USE_UNIQUE_USER_ID', False): return response['id'] else: return details['email'] def get_user_details(self, response): """Return user details from Google API account""" if 'email' in response: email = response['email'] elif 'emails' in response: email = response['emails'][0]['value'] else: email = '' if isinstance(response.get('name'), dict): names = response.get('name') or {} name, given_name, family_name = ( response.get('displayName', ''), names.get('givenName', ''), names.get('familyName', '') ) else: name, given_name, family_name = ( response.get('name', ''), response.get('given_name', ''), response.get('family_name', '') ) fullname, first_name, last_name = self.get_user_names( name, given_name, family_name ) return {'username': email.split('@', 1)[0], 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} class BaseGoogleOAuth2API(BaseGoogleAuth): def get_scope(self): """Return list with needed access scope""" scope = self.setting('SCOPE', []) if not self.setting('IGNORE_DEFAULT_SCOPE', False): default_scope = [] if self.setting('USE_DEPRECATED_API', False): default_scope = self.DEPRECATED_DEFAULT_SCOPE else: default_scope = self.DEFAULT_SCOPE scope = scope + (default_scope or []) return scope def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" if self.setting('USE_DEPRECATED_API', False): url = 'https://www.googleapis.com/oauth2/v1/userinfo' else: url = 'https://www.googleapis.com/plus/v1/people/me' return self.get_json(url, params={ 'access_token': access_token, 'alt': 'json' }) def revoke_token_params(self, token, uid): return {'token': token} def revoke_token_headers(self, token, uid): return {'Content-type': 'application/json'} class GoogleOAuth2(BaseGoogleOAuth2API, BaseOAuth2): """Google OAuth2 authentication backend""" name = 'google-oauth2' REDIRECT_STATE = False AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke' REVOKE_TOKEN_METHOD = 'GET' # The order of the default scope is important DEFAULT_SCOPE = ['openid', 'email', 'profile'] DEPRECATED_DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('token_type', 'token_type', True) ] class GooglePlusAuth(BaseGoogleOAuth2API, BaseOAuth2): name = 'google-plus' REDIRECT_STATE = False STATE_PARAMETER = False AUTHORIZATION_URL = 'https://accounts.google.com/o/oauth2/auth' ACCESS_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' REVOKE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/revoke' REVOKE_TOKEN_METHOD = 'GET' DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/plus.me', ] DEPRECATED_DEFAULT_SCOPE = [ 'https://www.googleapis.com/auth/plus.login', 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile' ] EXTRA_DATA = [ ('id', 'user_id'), ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ('access_type', 'access_type', True), ('code', 'code') ] def auth_complete_params(self, state=None): params = super(GooglePlusAuth, self).auth_complete_params(state) if self.data.get('access_token'): # Don't add postmessage if this is plain server-side workflow params['redirect_uri'] = 'postmessage' return params @handle_http_errors def auth_complete(self, *args, **kwargs): if 'access_token' in self.data: # Client-side workflow token = self.data.get('access_token') response = self.get_json( 'https://www.googleapis.com/oauth2/v1/tokeninfo', params={'access_token': token} ) self.process_error(response) return self.do_auth(token, response=response, *args, **kwargs) elif 'code' in self.data: # Server-side workflow response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) else: raise AuthMissingParameter(self, 'access_token or code') class GoogleOAuth(BaseGoogleAuth, BaseOAuth1): """Google OAuth authorization mechanism""" name = 'google-oauth' AUTHORIZATION_URL = 'https://www.google.com/accounts/OAuthAuthorizeToken' REQUEST_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetRequestToken' ACCESS_TOKEN_URL = 'https://www.google.com/accounts/OAuthGetAccessToken' DEFAULT_SCOPE = ['https://www.googleapis.com/auth/userinfo#email'] def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" return self.get_querystring( 'https://www.googleapis.com/userinfo/email', auth=self.oauth_auth(access_token) ) def get_key_and_secret(self): """Return Google OAuth Consumer Key and Consumer Secret pair, uses anonymous by default, beware that this marks the application as not registered and a security badge is displayed on authorization page. http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth """ key_secret = super(GoogleOAuth, self).get_key_and_secret() if key_secret == (None, None): key_secret = ('anonymous', 'anonymous') return key_secret class GoogleOpenId(OpenIdAuth): name = 'google' URL = 'https://www.google.com/accounts/o8/id' def get_user_id(self, details, response): """ Return user unique id provided by service. For google user email is unique enought to flag a single user. Email comes from schema: http://axschema.org/contact/email """ return details['email'] class GoogleOpenIdConnect(GoogleOAuth2, OpenIdConnectAuth): name = 'google-openidconnect' ID_TOKEN_ISSUER = "accounts.google.com" def user_data(self, access_token, *args, **kwargs): """Return user data from Google API""" return self.get_json( 'https://www.googleapis.com/plus/v1/people/me/openIdConnect', params={'access_token': access_token, 'alt': 'json'} ) python-social-auth-0.2.13/social/backends/instagram.py000066400000000000000000000031131260133235600227070ustar00rootroot00000000000000""" Instagram OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/instagram.html """ from social.backends.oauth import BaseOAuth2 class InstagramOAuth2(BaseOAuth2): name = 'instagram' AUTHORIZATION_URL = 'https://instagram.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://instagram.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' def get_user_id(self, details, response): # Sometimes Instagram returns 'user', sometimes 'data', but API docs # says 'data' http://instagram.com/developer/endpoints/users/#get_users user = response.get('user') or response.get('data') or {} return user.get('id') def get_user_details(self, response): """Return user details from Instagram account""" # Sometimes Instagram returns 'user', sometimes 'data', but API docs # says 'data' http://instagram.com/developer/endpoints/users/#get_users user = response.get('user') or response.get('data') or {} username = user['username'] email = user.get('email', '') fullname, first_name, last_name = self.get_user_names( user.get('full_name', '') ) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.instagram.com/v1/users/self', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/jawbone.py000066400000000000000000000053521260133235600223560ustar00rootroot00000000000000""" Jawbone OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/jawbone.html """ from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthCanceled, AuthUnknownError class JawboneOAuth2(BaseOAuth2): name = 'jawbone' AUTHORIZATION_URL = 'https://jawbone.com/auth/oauth2/auth' ACCESS_TOKEN_URL = 'https://jawbone.com/auth/oauth2/token' SCOPE_SEPARATOR = ' ' REDIRECT_STATE = False def get_user_id(self, details, response): return response['data']['xid'] def get_user_details(self, response): """Return user details from Jawbone account""" data = response['data'] fullname, first_name, last_name = self.get_user_names( first_name=data.get('first', ''), last_name=data.get('last', '') ) return { 'username': first_name + ' ' + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'dob': data.get('dob', ''), 'gender': data.get('gender', ''), 'height': data.get('height', ''), 'weight': data.get('weight', '') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://jawbone.com/nudge/api/users/@me', headers={'Authorization': 'Bearer ' + access_token}, ) def process_error(self, data): error = data.get('error') if error: if error == 'access_denied': raise AuthCanceled(self) else: raise AuthUnknownError(self, 'Jawbone error was {0}'.format( error )) return super(JawboneOAuth2, self).process_error(data) def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'client_id': client_id, 'client_secret': client_secret, } @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, params=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) python-social-auth-0.2.13/social/backends/kakao.py000066400000000000000000000024051260133235600220130ustar00rootroot00000000000000""" Kakao OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/kakao.html """ from social.backends.oauth import BaseOAuth2 class KakaoOAuth2(BaseOAuth2): """Kakao OAuth authentication backend""" name = 'kakao' AUTHORIZATION_URL = 'https://kauth.kakao.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://kauth.kakao.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): """Return user details from Kakao account""" nickname = response['properties']['nickname'] return { 'username': nickname, 'email': '', 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://kapi.kakao.com/v1/user/me', params={'access_token': access_token}) def auth_complete_params(self, state=None): return { 'grant_type': 'authorization_code', 'code': self.data.get('code', ''), 'client_id': self.get_key_and_secret()[0], } python-social-auth-0.2.13/social/backends/khanacademy.py000066400000000000000000000116571260133235600232030ustar00rootroot00000000000000""" Khan Academy OAuth backend, docs at: https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication """ import six from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_TYPE_QUERY from requests_oauthlib import OAuth1 from social.backends.oauth import BaseOAuth1 from social.p3 import urlencode class BrowserBasedOAuth1(BaseOAuth1): """Browser based mechanism OAuth authentication, fill the needed parameters to communicate properly with authentication service. REQUEST_TOKEN_URL Request token URL (opened in web browser) ACCESS_TOKEN_URL Access token URL """ REQUEST_TOKEN_URL = '' OAUTH_TOKEN_PARAMETER_NAME = 'oauth_token' REDIRECT_URI_PARAMETER_NAME = 'redirect_uri' ACCESS_TOKEN_URL = '' def auth_url(self): """Return redirect url""" return self.unauthorized_token_request() def get_unauthorized_token(self): return self.strategy.request_data() def unauthorized_token_request(self): """Return request for unauthorized token (first stage)""" params = self.request_token_extra_arguments() params.update(self.get_scope_argument()) key, secret = self.get_key_and_secret() # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() auth = OAuth1( key, secret, callback_uri=self.get_redirect_uri(state), decoding=decoding, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY ) url = self.REQUEST_TOKEN_URL + '?' + urlencode(params) url, _, _ = auth.client.sign(url) return url def oauth_auth(self, token=None, oauth_verifier=None): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') token = token or {} # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=token.get('oauth_token'), resource_owner_secret=token.get('oauth_token_secret'), callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_method=SIGNATURE_HMAC, signature_type=SIGNATURE_TYPE_QUERY, decoding=decoding) class KhanAcademyOAuth1(BrowserBasedOAuth1): """ Class used for autorising with Khan Academy. Flow of Khan Academy is a bit different than most OAuth 1.0 and consinsts of the following steps: 1. Create signed params to attach to the REQUEST_TOKEN_URL 2. Redirect user to the REQUEST_TOKEN_URL that will respond with oauth_secret, oauth_token, oauth_verifier that should be used with ACCESS_TOKEN_URL 3. Go to ACCESS_TOKEN_URL and grab oauth_token_secret. Note that we don't use the AUTHORIZATION_URL. REQUEST_TOKEN_URL requires the following arguments: oauth_consumer_key - Your app's consumer key oauth_nonce - Random 64-bit, unsigned number encoded as an ASCII string in decimal format. The nonce/timestamp pair should always be unique. oauth_version - OAuth version used by your app. Must be "1.0" for now. oauth_signature - String generated using the referenced signature method. oauth_signature_method - Signature algorithm (currently only support "HMAC-SHA1") oauth_timestamp - Integer representing the time the request is sent. The timestamp should be expressed in number of seconds after January 1, 1970 00:00:00 GMT. oauth_callback (optional) - URL to redirect to after request token is received and authorized by the user's chosen identity provider. """ name = 'khanacademy-oauth1' ID_KEY = 'user_id' REQUEST_TOKEN_URL = 'http://www.khanacademy.org/api/auth/request_token' ACCESS_TOKEN_URL = 'https://www.khanacademy.org/api/auth/access_token' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' USER_DATA_URL = 'https://www.khanacademy.org/api/v1/user' EXTRA_DATA = [('user_id', 'user_id')] def get_user_details(self, response): """Return user details from Khan Academy account""" return { 'username': response.get('key_email'), 'email': response.get('key_email'), 'fullname': '', 'first_name': '', 'last_name': '', 'user_id': response.get('user_id') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" auth = self.oauth_auth(access_token) url, _, _ = auth.client.sign(self.USER_DATA_URL) return self.get_json(url) python-social-auth-0.2.13/social/backends/lastfm.py000066400000000000000000000035401260133235600222140ustar00rootroot00000000000000import hashlib from social.utils import handle_http_errors from social.backends.base import BaseAuth class LastFmAuth(BaseAuth): """ Last.Fm authentication backend. Requires two settings: SOCIAL_AUTH_LASTFM_KEY SOCIAL_AUTH_LASTFM_SECRET Don't forget to set the Last.fm callback to something sensible like http://your.site/lastfm/complete """ name = 'lastfm' AUTH_URL = 'http://www.last.fm/api/auth/?api_key={api_key}' EXTRA_DATA = [ ('key', 'session_key') ] def auth_url(self): return self.AUTH_URL.format(api_key=self.setting('KEY')) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" key, secret = self.get_key_and_secret() token = self.data['token'] signature = hashlib.md5(''.join( ('api_key', key, 'methodauth.getSession', 'token', token, secret) ).encode()).hexdigest() response = self.get_json('http://ws.audioscrobbler.com/2.0/', data={ 'method': 'auth.getSession', 'api_key': key, 'token': token, 'api_sig': signature, 'format': 'json' }, method='POST') kwargs.update({'response': response['session'], 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return response.get('name') def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names(response['name']) return { 'username': response['name'], 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } python-social-auth-0.2.13/social/backends/launchpad.py000066400000000000000000000003231260133235600226610ustar00rootroot00000000000000""" Launchpad OpenId backend """ from social.backends.open_id import OpenIdAuth class LaunchpadOpenId(OpenIdAuth): name = 'launchpad' URL = 'https://login.launchpad.net' USERNAME_KEY = 'nickname' python-social-auth-0.2.13/social/backends/legacy.py000066400000000000000000000027571260133235600222030ustar00rootroot00000000000000from social.backends.base import BaseAuth from social.exceptions import AuthMissingParameter class LegacyAuth(BaseAuth): def get_user_id(self, details, response): return details.get(self.ID_KEY) or \ response.get(self.ID_KEY) def auth_url(self): return self.setting('FORM_URL') def auth_html(self): return self.strategy.render_html(tpl=self.setting('FORM_HTML')) def uses_redirect(self): return self.setting('FORM_URL') and not \ self.setting('FORM_HTML') def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if self.ID_KEY not in self.data: raise AuthMissingParameter(self, self.ID_KEY) kwargs.update({'response': self.data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_user_details(self, response): """Return user details""" email = response.get('email', '') username = response.get('username', '') fullname, first_name, last_name = self.get_user_names( response.get('fullname', ''), response.get('first_name', ''), response.get('last_name', '') ) if email and not username: username = email.split('@', 1)[0] return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } python-social-auth-0.2.13/social/backends/linkedin.py000066400000000000000000000073001260133235600225210ustar00rootroot00000000000000""" LinkedIn OAuth1 and OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/linkedin.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class BaseLinkedinAuth(object): EXTRA_DATA = [('id', 'id'), ('first-name', 'first_name', True), ('last-name', 'last_name', True), ('firstName', 'first_name', True), ('lastName', 'last_name', True)] USER_DETAILS = 'https://api.linkedin.com/v1/people/~:({0})' def get_user_details(self, response): """Return user details from Linkedin account""" fullname, first_name, last_name = self.get_user_names( first_name=response['firstName'], last_name=response['lastName'] ) email = response.get('emailAddress', '') return {'username': first_name + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_details_url(self): # use set() since LinkedIn fails when values are duplicated fields_selectors = list(set(['first-name', 'id', 'last-name'] + self.setting('FIELD_SELECTORS', []))) # user sort to ease the tests URL mocking fields_selectors.sort() fields_selectors = ','.join(fields_selectors) return self.USER_DETAILS.format(fields_selectors) def user_data_headers(self): lang = self.setting('FORCE_PROFILE_LANGUAGE') if lang: return { 'Accept-Language': lang if lang is not True else self.strategy.get_language() } class LinkedinOAuth(BaseLinkedinAuth, BaseOAuth1): """Linkedin OAuth authentication backend""" name = 'linkedin' SCOPE_SEPARATOR = '+' AUTHORIZATION_URL = 'https://www.linkedin.com/uas/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.linkedin.com/uas/oauth/requestToken' ACCESS_TOKEN_URL = 'https://api.linkedin.com/uas/oauth/accessToken' def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( self.user_details_url(), params={'format': 'json'}, auth=self.oauth_auth(access_token), headers=self.user_data_headers() ) def unauthorized_token(self): """Makes first request to oauth. Returns an unauthorized Token.""" scope = self.get_scope() or '' if scope: scope = '?scope=' + self.SCOPE_SEPARATOR.join(scope) return self.request(self.REQUEST_TOKEN_URL + scope, params=self.request_token_extra_arguments(), auth=self.oauth_auth()).text class LinkedinOAuth2(BaseLinkedinAuth, BaseOAuth2): name = 'linkedin-oauth2' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://www.linkedin.com/uas/oauth2/authorization' ACCESS_TOKEN_URL = 'https://www.linkedin.com/uas/oauth2/accessToken' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def user_data(self, access_token, *args, **kwargs): return self.get_json( self.user_details_url(), params={'oauth2_access_token': access_token, 'format': 'json'}, headers=self.user_data_headers() ) def request_access_token(self, *args, **kwargs): # LinkedIn expects a POST request with querystring parameters, despite # the spec http://tools.ietf.org/html/rfc6749#section-4.1.3 kwargs['params'] = kwargs.pop('data') return super(LinkedinOAuth2, self).request_access_token( *args, **kwargs ) python-social-auth-0.2.13/social/backends/live.py000066400000000000000000000030351260133235600216640ustar00rootroot00000000000000""" Live OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/live.html """ from social.backends.oauth import BaseOAuth2 class LiveOAuth2(BaseOAuth2): name = 'live' AUTHORIZATION_URL = 'https://login.live.com/oauth20_authorize.srf' ACCESS_TOKEN_URL = 'https://login.live.com/oauth20_token.srf' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['wl.basic', 'wl.emails'] EXTRA_DATA = [ ('id', 'id'), ('access_token', 'access_token'), ('authentication_token', 'authentication_token'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires'), ('email', 'email'), ('first_name', 'first_name'), ('last_name', 'last_name'), ('token_type', 'token_type'), ] REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Live Connect account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name'), last_name=response.get('last_name') ) return {'username': response.get('name'), 'email': response.get('emails', {}).get('account', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://apis.live.net/v5.0/me', params={ 'access_token': access_token }) python-social-auth-0.2.13/social/backends/livejournal.py000066400000000000000000000017371260133235600232660ustar00rootroot00000000000000""" LiveJournal OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/livejournal.html """ from social.p3 import urlsplit from social.backends.open_id import OpenIdAuth from social.exceptions import AuthMissingParameter class LiveJournalOpenId(OpenIdAuth): """LiveJournal OpenID authentication backend""" name = 'livejournal' def get_user_details(self, response): """Generate username from identity url""" values = super(LiveJournalOpenId, self).get_user_details(response) values['username'] = values.get('username') or \ urlsplit(response.identity_url)\ .netloc.split('.', 1)[0] return values def openid_url(self): """Returns LiveJournal authentication URL""" if not self.data.get('openid_lj_user'): raise AuthMissingParameter(self, 'openid_lj_user') return 'http://{0}.livejournal.com'.format(self.data['openid_lj_user']) python-social-auth-0.2.13/social/backends/loginradius.py000066400000000000000000000050711260133235600232470ustar00rootroot00000000000000""" LoginRadius BaseOAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/loginradius.html """ from social.backends.oauth import BaseOAuth2 class LoginRadiusAuth(BaseOAuth2): """LoginRadius BaseOAuth2 authentication backend.""" name = 'loginradius' ID_KEY = 'ID' ACCESS_TOKEN_URL = 'https://api.loginradius.com/api/v2/access_token' PROFILE_URL = 'https://api.loginradius.com/api/v2/userprofile' ACCESS_TOKEN_METHOD = 'GET' REDIRECT_STATE = False STATE_PARAMETER = False def uses_redirect(self): """Return False because we return HTML instead.""" return False def auth_html(self): key, secret = self.get_key_and_secret() tpl = self.setting('TEMPLATE', 'loginradius.html') return self.strategy.render_html(tpl=tpl, context={ 'backend': self, 'LOGINRADIUS_KEY': key, 'LOGINRADIUS_REDIRECT_URL': self.get_redirect_uri() }) def request_access_token(self, *args, **kwargs): return self.get_json(params={ 'token': self.data.get('token'), 'secret': self.setting('SECRET') }, *args, **kwargs) def user_data(self, access_token, *args, **kwargs): """Loads user data from service. Implement in subclass.""" return self.get_json( self.PROFILE_URL, params={'access_token': access_token}, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) def get_user_details(self, response): """Must return user details in a know internal struct: {'username': , 'email': , 'fullname': , 'first_name': , 'last_name': } """ profile = { 'username': response['NickName'] or '', 'email': response['Email'][0]['Value'] or '', 'fullname': response['FullName'] or '', 'first_name': response['FirstName'] or '', 'last_name': response['LastName'] or '' } return profile def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response. Since LoginRadius handles multiple providers, we need to distinguish them to prevent conflicts.""" return '{0}-{1}'.format(response.get('Provider'), response.get(self.ID_KEY)) python-social-auth-0.2.13/social/backends/mailru.py000066400000000000000000000032351260133235600222200ustar00rootroot00000000000000""" Mail.ru OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mailru.html """ from hashlib import md5 from social.p3 import unquote from social.backends.oauth import BaseOAuth2 class MailruOAuth2(BaseOAuth2): """Mail.ru authentication backend""" name = 'mailru-oauth2' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://connect.mail.ru/oauth/authorize' ACCESS_TOKEN_URL = 'https://connect.mail.ru/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [('refresh_token', 'refresh_token'), ('expires_in', 'expires')] def get_user_details(self, response): """Return user details from Mail.ru request""" fullname, first_name, last_name = self.get_user_names( first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return {'username': unquote(response['nick']), 'email': unquote(response['email']), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data from Mail.ru REST API""" key, secret = self.get_key_and_secret() data = {'method': 'users.getInfo', 'session_key': access_token, 'app_id': key, 'secure': '1'} param_list = sorted(list(item + '=' + data[item] for item in data)) data['sig'] = md5( (''.join(param_list) + secret).encode('utf-8') ).hexdigest() return self.get_json('http://www.appsmail.ru/platform/api', params=data)[0] python-social-auth-0.2.13/social/backends/mapmyfitness.py000066400000000000000000000027651260133235600234550ustar00rootroot00000000000000""" MapMyFitness OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mapmyfitness.html """ from social.backends.oauth import BaseOAuth2 class MapMyFitnessOAuth2(BaseOAuth2): """MapMyFitness OAuth authentication backend""" name = 'mapmyfitness' AUTHORIZATION_URL = 'https://www.mapmyfitness.com/v7.0/oauth2/authorize' ACCESS_TOKEN_URL = \ 'https://oauth2-api.mapmyapi.com/v7.0/oauth2/access_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ] def auth_headers(self): key = self.get_key_and_secret()[0] return { 'Api-Key': key } def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): first = response.get('first_name', '') last = response.get('last_name', '') full = (first + last).strip() return { 'username': response['username'], 'email': response['email'], 'fullname': full, 'first_name': first, 'last_name': last, } def user_data(self, access_token, *args, **kwargs): key = self.get_key_and_secret()[0] url = 'https://oauth2-api.mapmyapi.com/v7.0/user/self/' headers = { 'Authorization': 'Bearer {0}'.format(access_token), 'Api-Key': key } return self.get_json(url, headers=headers) python-social-auth-0.2.13/social/backends/meetup.py000066400000000000000000000022521260133235600222240ustar00rootroot00000000000000""" Meetup OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/meetup.html """ from social.backends.oauth import BaseOAuth2 class MeetupOAuth2(BaseOAuth2): """Meetup OAuth2 authentication backend""" name = 'meetup' AUTHORIZATION_URL = 'https://secure.meetup.com/oauth2/authorize' ACCESS_TOKEN_URL = 'https://secure.meetup.com/oauth2/access' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['basic'] SCOPE_SEPARATOR = ',' REDIRECT_STATE = False STATE_PARAMETER = 'state' def get_user_details(self, response): """Return user details from Meetup account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': response.get('username'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.meetup.com/2/member/self', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/mendeley.py000066400000000000000000000043051260133235600225300ustar00rootroot00000000000000""" Mendeley OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mendeley.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2 class MendeleyMixin(object): SCOPE_SEPARATOR = '+' EXTRA_DATA = [('profile_id', 'profile_id'), ('name', 'name'), ('bio', 'bio')] def get_user_id(self, details, response): return response['id'] def get_user_details(self, response): """Return user details from Mendeley account""" profile_id = response['id'] name = response['display_name'] bio = response['link'] return {'profile_id': profile_id, 'name': name, 'bio': bio} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" values = self.get_user_data(access_token) values.update(values) return values def get_user_data(self, access_token): raise NotImplementedError('Implement in subclass') class MendeleyOAuth(MendeleyMixin, BaseOAuth1): name = 'mendeley' AUTHORIZATION_URL = 'http://api.mendeley.com/oauth/authorize/' REQUEST_TOKEN_URL = 'http://api.mendeley.com/oauth/request_token/' ACCESS_TOKEN_URL = 'http://api.mendeley.com/oauth/access_token/' def get_user_data(self, access_token): return self.get_json( 'http://api.mendeley.com/oapi/profiles/info/me/', auth=self.oauth_auth(access_token) ) class MendeleyOAuth2(MendeleyMixin, BaseOAuth2): name = 'mendeley-oauth2' AUTHORIZATION_URL = 'https://api-oauth2.mendeley.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api-oauth2.mendeley.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['all'] REDIRECT_STATE = False EXTRA_DATA = MendeleyMixin.EXTRA_DATA + [ ('refresh_token', 'refresh_token'), ('expires_in', 'expires_in'), ('token_type', 'token_type'), ] def get_user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.mendeley.com/profiles/me/', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/mineid.py000066400000000000000000000023511260133235600221720ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class MineIDOAuth2(BaseOAuth2): """MineID OAuth2 authentication backend""" name = 'mineid' _AUTHORIZATION_URL = '%(scheme)s://%(host)s/oauth/authorize' _ACCESS_TOKEN_URL = '%(scheme)s://%(host)s/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ] def get_user_details(self, response): """Return user details""" return {'email': response.get('email'), 'username': response.get('email')} def user_data(self, access_token, *args, **kwargs): return self._user_data(access_token) def _user_data(self, access_token, path=None): url = '%(scheme)s://%(host)s/api/user' % self.get_mineid_url_params() return self.get_json(url, params={'access_token': access_token}) @property def AUTHORIZATION_URL(self): return self._AUTHORIZATION_URL % self.get_mineid_url_params() @property def ACCESS_TOKEN_URL(self): return self._ACCESS_TOKEN_URL % self.get_mineid_url_params() def get_mineid_url_params(self): return { 'host': self.setting('HOST', 'www.mineid.org'), 'scheme': self.setting('SCHEME', 'https'), } python-social-auth-0.2.13/social/backends/mixcloud.py000066400000000000000000000017011260133235600225470ustar00rootroot00000000000000""" Mixcloud OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/mixcloud.html """ from social.backends.oauth import BaseOAuth2 class MixcloudOAuth2(BaseOAuth2): name = 'mixcloud' ID_KEY = 'username' AUTHORIZATION_URL = 'https://www.mixcloud.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.mixcloud.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['username'], 'email': None, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): return self.get_json('https://api.mixcloud.com/me/', params={'access_token': access_token, 'alt': 'json'}) python-social-auth-0.2.13/social/backends/moves.py000066400000000000000000000017631260133235600220640ustar00rootroot00000000000000""" Moves OAuth2 backend, docs at: https://dev.moves-app.com/docs/authentication Written by Avi Alkalay Certified to work with Django 1.6 """ from social.backends.oauth import BaseOAuth2 class MovesOAuth2(BaseOAuth2): """Moves OAuth authentication backend""" name = 'moves' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://api.moves-app.com/oauth/v1/authorize' ACCESS_TOKEN_URL = 'https://api.moves-app.com/oauth/v1/access_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('refresh_token', 'refresh_token', True), ('expires_in', 'expires'), ] def get_user_details(self, response): """Return user details Moves account""" return {'username': response.get('user_id')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.moves-app.com/api/1.1/user/profile', params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/nationbuilder.py000066400000000000000000000031221260133235600235610ustar00rootroot00000000000000""" NationBuilder OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/nationbuilder.html """ from social.backends.oauth import BaseOAuth2 class NationBuilderOAuth2(BaseOAuth2): """NationBuilder OAuth2 authentication backend""" name = 'nationbuilder' AUTHORIZATION_URL = 'https://{slug}.nationbuilder.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://{slug}.nationbuilder.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def authorization_url(self): return self.AUTHORIZATION_URL.format(slug=self.slug) def access_token_url(self): return self.ACCESS_TOKEN_URL.format(slug=self.slug) @property def slug(self): return self.setting('SLUG') def get_user_details(self, response): """Return user details from Github account""" email = response.get('email') or '' username = email.split('@')[0] if email else '' return {'username': username, 'email': email, 'fullname': response.get('full_name') or '', 'first_name': response.get('first_name') or '', 'last_name': response.get('last_name') or ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://{slug}.nationbuilder.com/api/v1/people/me'.format( slug=self.slug ) return self.get_json(url, params={ 'access_token': access_token })['person'] python-social-auth-0.2.13/social/backends/nk.py000066400000000000000000000052431260133235600213400ustar00rootroot00000000000000from urllib import urlencode import six from requests_oauthlib import OAuth1 from social.backends.oauth import BaseOAuth2 class NKOAuth2(BaseOAuth2): """NK OAuth authentication backend""" name = 'nk' AUTHORIZATION_URL = 'https://nk.pl/oauth2/login' ACCESS_TOKEN_URL = 'https://nk.pl/oauth2/token' SCOPE_SEPARATOR = ',' ACCESS_TOKEN_METHOD = 'POST' SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER' EXTRA_DATA = [ ('id', 'id'), ] def get_user_details(self, response): """Return user details from NK account""" entry = response['entry'] return { 'username': entry.get('displayName'), 'email': entry['emails'][0]['value'], 'first_name': entry.get('displayName').split(' ')[0], 'id': entry.get('id') } def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': self.get_redirect_uri(state), 'scope': self.get_scope_argument() } def get_user_id(self, details, response): """Return a unique ID for the current user, by default from server response.""" return details.get(self.ID_KEY) def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'http://opensocial.nk-net.pl/v09/social/rest/people/@me?' + urlencode({ 'nk_token': access_token, 'fields': 'name,surname,avatar,localization,age,gender,emails,birthdate' }) return self.get_json( url, auth=self.oauth_auth(access_token) ) def oauth_auth(self, token=None, oauth_verifier=None, signature_type=SIGNATURE_TYPE_AUTH_HEADER): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') token = token or {} # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=None, resource_owner_secret=None, callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_type=signature_type, decoding=decoding) python-social-auth-0.2.13/social/backends/oauth.py000066400000000000000000000403651260133235600220540ustar00rootroot00000000000000import six from requests_oauthlib import OAuth1 from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER from social.p3 import urlencode, unquote from social.utils import url_add_parameters, parse_qs, handle_http_errors from social.exceptions import AuthFailed, AuthCanceled, AuthUnknownError, \ AuthMissingParameter, AuthStateMissing, \ AuthStateForbidden, AuthTokenError from social.backends.base import BaseAuth class OAuthAuth(BaseAuth): """OAuth authentication backend base class. Also settings will be inspected to get more values names that should be stored on extra_data field. Setting name is created from current backend name (all uppercase) plus _EXTRA_DATA. access_token is always stored. URLs settings: AUTHORIZATION_URL Authorization service url ACCESS_TOKEN_URL Access token URL """ AUTHORIZATION_URL = '' ACCESS_TOKEN_URL = '' ACCESS_TOKEN_METHOD = 'GET' REVOKE_TOKEN_URL = None REVOKE_TOKEN_METHOD = 'POST' ID_KEY = 'id' SCOPE_PARAMETER_NAME = 'scope' DEFAULT_SCOPE = None SCOPE_SEPARATOR = ' ' REDIRECT_STATE = False STATE_PARAMETER = False def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(OAuthAuth, self).extra_data(user, uid, response, details, *args, **kwargs) data['access_token'] = response.get('access_token', '') or \ kwargs.get('access_token') return data def state_token(self): """Generate csrf token to include as state parameter.""" return self.strategy.random_string(32) def get_or_create_state(self): if self.STATE_PARAMETER or self.REDIRECT_STATE: # Store state in session for further request validation. The state # value is passed as state parameter (as specified in OAuth2 spec), # but also added to redirect, that way we can still verify the # request if the provider doesn't implement the state parameter. # Reuse token if any. name = self.name + '_state' state = self.strategy.session_get(name) if state is None: state = self.state_token() self.strategy.session_set(name, state) else: state = None return state def get_session_state(self): return self.strategy.session_get(self.name + '_state') def get_request_state(self): request_state = self.data.get('state') or \ self.data.get('redirect_state') if request_state and isinstance(request_state, list): request_state = request_state[0] return request_state def validate_state(self): """Validate state value. Raises exception on error, returns state value if valid.""" if not self.STATE_PARAMETER and not self.REDIRECT_STATE: return None state = self.get_session_state() request_state = self.get_request_state() if not request_state: raise AuthMissingParameter(self, 'state') elif not state: raise AuthStateMissing(self, 'state') elif not request_state == state: raise AuthStateForbidden(self) else: return state def get_redirect_uri(self, state=None): """Build redirect with redirect_state parameter.""" uri = self.redirect_uri if self.REDIRECT_STATE and state: uri = url_add_parameters(uri, {'redirect_state': state}) return uri def get_scope(self): """Return list with needed access scope""" scope = self.setting('SCOPE', []) if not self.setting('IGNORE_DEFAULT_SCOPE', False): scope = scope + (self.DEFAULT_SCOPE or []) return scope def get_scope_argument(self): param = {} scope = self.get_scope() if scope: param[self.SCOPE_PARAMETER_NAME] = self.SCOPE_SEPARATOR.join(scope) return param def user_data(self, access_token, *args, **kwargs): """Loads user data from service. Implement in subclass""" return {} def authorization_url(self): return self.AUTHORIZATION_URL def access_token_url(self): return self.ACCESS_TOKEN_URL def revoke_token_url(self, token, uid): return self.REVOKE_TOKEN_URL def revoke_token_params(self, token, uid): return {} def revoke_token_headers(self, token, uid): return {} def process_revoke_token_response(self, response): return response.status_code == 200 def revoke_token(self, token, uid): if self.REVOKE_TOKEN_URL: url = self.revoke_token_url(token, uid) params = self.revoke_token_params(token, uid) headers = self.revoke_token_headers(token, uid) data = urlencode(params) if self.REVOKE_TOKEN_METHOD != 'GET' \ else None response = self.request(url, params=params, headers=headers, data=data, method=self.REVOKE_TOKEN_METHOD) return self.process_revoke_token_response(response) class BaseOAuth1(OAuthAuth): """Consumer based mechanism OAuth authentication, fill the needed parameters to communicate properly with authentication service. URLs settings: REQUEST_TOKEN_URL Request token URL """ REQUEST_TOKEN_URL = '' REQUEST_TOKEN_METHOD = 'GET' OAUTH_TOKEN_PARAMETER_NAME = 'oauth_token' REDIRECT_URI_PARAMETER_NAME = 'redirect_uri' UNATHORIZED_TOKEN_SUFIX = 'unauthorized_token_name' def auth_url(self): """Return redirect url""" token = self.set_unauthorized_token() return self.oauth_authorization_request(token) def process_error(self, data): if 'oauth_problem' in data: if data['oauth_problem'] == 'user_refused': raise AuthCanceled(self, 'User refused the access') raise AuthUnknownError(self, 'Error was ' + data['oauth_problem']) @handle_http_errors def auth_complete(self, *args, **kwargs): """Return user, might be logged in""" # Multiple unauthorized tokens are supported (see #521) self.process_error(self.data) self.validate_state() token = self.get_unauthorized_token() access_token = self.access_token(token) return self.do_auth(access_token, *args, **kwargs) @handle_http_errors def do_auth(self, access_token, *args, **kwargs): """Finish the auth process once the access_token was retrieved""" if not isinstance(access_token, dict): access_token = parse_qs(access_token) data = self.user_data(access_token) if data is not None and 'access_token' not in data: data['access_token'] = access_token kwargs.update({'response': data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def get_unauthorized_token(self): name = self.name + self.UNATHORIZED_TOKEN_SUFIX unauthed_tokens = self.strategy.session_get(name, []) if not unauthed_tokens: raise AuthTokenError(self, 'Missing unauthorized token') data_token = self.data.get(self.OAUTH_TOKEN_PARAMETER_NAME) if data_token is None: raise AuthTokenError(self, 'Missing unauthorized token') token = None for utoken in unauthed_tokens: orig_utoken = utoken if not isinstance(utoken, dict): utoken = parse_qs(utoken) if utoken.get(self.OAUTH_TOKEN_PARAMETER_NAME) == data_token: self.strategy.session_set(name, list(set(unauthed_tokens) - set([orig_utoken]))) token = utoken break else: raise AuthTokenError(self, 'Incorrect tokens') return token def set_unauthorized_token(self): token = self.unauthorized_token() name = self.name + self.UNATHORIZED_TOKEN_SUFIX tokens = self.strategy.session_get(name, []) + [token] self.strategy.session_set(name, tokens) return token def request_token_extra_arguments(self): """Return extra arguments needed on request-token process""" return self.setting('REQUEST_TOKEN_EXTRA_ARGUMENTS', {}) def unauthorized_token(self): """Return request for unauthorized token (first stage)""" params = self.request_token_extra_arguments() params.update(self.get_scope_argument()) key, secret = self.get_key_and_secret() # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() response = self.request( self.REQUEST_TOKEN_URL, params=params, auth=OAuth1(key, secret, callback_uri=self.get_redirect_uri(state), decoding=decoding), method=self.REQUEST_TOKEN_METHOD ) content = response.content if response.encoding or response.apparent_encoding: content = content.decode(response.encoding or response.apparent_encoding) else: content = response.content.decode() return content def oauth_authorization_request(self, token): """Generate OAuth request to authorize token.""" if not isinstance(token, dict): token = parse_qs(token) params = self.auth_extra_arguments() or {} params.update(self.get_scope_argument()) params[self.OAUTH_TOKEN_PARAMETER_NAME] = token.get( self.OAUTH_TOKEN_PARAMETER_NAME ) state = self.get_or_create_state() params[self.REDIRECT_URI_PARAMETER_NAME] = self.get_redirect_uri(state) return '{0}?{1}'.format(self.authorization_url(), urlencode(params)) def oauth_auth(self, token=None, oauth_verifier=None, signature_type=SIGNATURE_TYPE_AUTH_HEADER): key, secret = self.get_key_and_secret() oauth_verifier = oauth_verifier or self.data.get('oauth_verifier') if token: resource_owner_key = token.get('oauth_token') resource_owner_secret = token.get('oauth_token_secret') if not resource_owner_key: raise AuthTokenError(self, 'Missing oauth_token') if not resource_owner_secret: raise AuthTokenError(self, 'Missing oauth_token_secret') else: resource_owner_key = None resource_owner_secret = None # decoding='utf-8' produces errors with python-requests on Python3 # since the final URL will be of type bytes decoding = None if six.PY3 else 'utf-8' state = self.get_or_create_state() return OAuth1(key, secret, resource_owner_key=resource_owner_key, resource_owner_secret=resource_owner_secret, callback_uri=self.get_redirect_uri(state), verifier=oauth_verifier, signature_type=signature_type, decoding=decoding) def oauth_request(self, token, url, params=None, method='GET'): """Generate OAuth request, setups callback url""" return self.request(url, method=method, params=params, auth=self.oauth_auth(token)) def access_token(self, token): """Return request for access token value""" return self.get_querystring(self.access_token_url(), auth=self.oauth_auth(token), method=self.ACCESS_TOKEN_METHOD) class BaseOAuth2(OAuthAuth): """Base class for OAuth2 providers. OAuth2 draft details at: http://tools.ietf.org/html/draft-ietf-oauth-v2-10 """ REFRESH_TOKEN_URL = None REFRESH_TOKEN_METHOD = 'POST' RESPONSE_TYPE = 'code' REDIRECT_STATE = True STATE_PARAMETER = True def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() params = { 'client_id': client_id, 'redirect_uri': self.get_redirect_uri(state) } if self.STATE_PARAMETER and state: params['state'] = state if self.RESPONSE_TYPE: params['response_type'] = self.RESPONSE_TYPE return params def auth_url(self): """Return redirect url""" state = self.get_or_create_state() params = self.auth_params(state) params.update(self.get_scope_argument()) params.update(self.auth_extra_arguments()) params = urlencode(params) if not self.REDIRECT_STATE: # redirect_uri matching is strictly enforced, so match the # providers value exactly. params = unquote(params) return '{0}?{1}'.format(self.authorization_url(), params) def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'client_id': client_id, 'client_secret': client_secret, 'redirect_uri': self.get_redirect_uri(state) } def auth_complete_credentials(self): return None def auth_headers(self): return {'Content-Type': 'application/x-www-form-urlencoded', 'Accept': 'application/json'} def request_access_token(self, *args, **kwargs): return self.get_json(*args, **kwargs) def process_error(self, data): if data.get('error'): if data['error'] == 'denied' or data['error'] == 'access_denied': raise AuthCanceled(self, data.get('error_description', '')) raise AuthFailed(self, data.get('error_description') or data['error']) elif 'denied' in data: raise AuthCanceled(self, data['denied']) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" state = self.validate_state() self.process_error(self.data) response = self.request_access_token( self.access_token_url(), data=self.auth_complete_params(state), headers=self.auth_headers(), auth=self.auth_complete_credentials(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) @handle_http_errors def do_auth(self, access_token, *args, **kwargs): """Finish the auth process once the access_token was retrieved""" data = self.user_data(access_token, *args, **kwargs) response = kwargs.get('response') or {} response.update(data or {}) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def refresh_token_params(self, token, *args, **kwargs): client_id, client_secret = self.get_key_and_secret() return { 'refresh_token': token, 'grant_type': 'refresh_token', 'client_id': client_id, 'client_secret': client_secret } def process_refresh_token_response(self, response, *args, **kwargs): return response.json() def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) url = self.refresh_token_url() method = self.REFRESH_TOKEN_METHOD key = 'params' if method == 'GET' else 'data' request_args = {'headers': self.auth_headers(), 'method': method, key: params} request = self.request(url, **request_args) return self.process_refresh_token_response(request, *args, **kwargs) def refresh_token_url(self): return self.REFRESH_TOKEN_URL or self.access_token_url() python-social-auth-0.2.13/social/backends/odnoklassniki.py000066400000000000000000000156121260133235600236010ustar00rootroot00000000000000""" Odnoklassniki OAuth2 and Iframe Application backends, docs at: http://psa.matiasaguirre.net/docs/backends/odnoklassnikiru.html """ from hashlib import md5 from social.p3 import unquote from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed class OdnoklassnikiOAuth2(BaseOAuth2): """Odnoklassniki authentication backend""" name = 'odnoklassniki-oauth2' ID_KEY = 'uid' ACCESS_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'http://www.odnoklassniki.ru/oauth/authorize' ACCESS_TOKEN_URL = 'http://api.odnoklassniki.ru/oauth/token.do' EXTRA_DATA = [('refresh_token', 'refresh_token'), ('expires_in', 'expires')] def get_user_details(self, response): """Return user details from Odnoklassniki request""" fullname, first_name, last_name = self.get_user_names( fullname=unquote(response['name']), first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return { 'username': response['uid'], 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Return user data from Odnoklassniki REST API""" data = {'access_token': access_token, 'method': 'users.getCurrentUser'} key, secret = self.get_key_and_secret() public_key = self.setting('PUBLIC_NAME') return odnoklassniki_api(self, data, 'http://api.odnoklassniki.ru/', public_key, secret, 'oauth') class OdnoklassnikiApp(BaseAuth): """Odnoklassniki iframe app authentication backend""" name = 'odnoklassniki-app' ID_KEY = 'uid' def extra_data(self, user, uid, response, details=None, *args, **kwargs): return dict([(key, value) for key, value in response.items() if key in response['extra_data_list']]) def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( fullname=unquote(response['name']), first_name=unquote(response['first_name']), last_name=unquote(response['last_name']) ) return { 'username': response['uid'], 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def auth_complete(self, *args, **kwargs): self.verify_auth_sig() response = self.get_response() fields = ('uid', 'first_name', 'last_name', 'name') + \ self.setting('EXTRA_USER_DATA_LIST', ()) data = { 'method': 'users.getInfo', 'uids': '{0}'.format(response['logged_user_id']), 'fields': ','.join(fields), } client_key, client_secret = self.get_key_and_secret() public_key = self.setting('PUBLIC_NAME') details = odnoklassniki_api(self, data, response['api_server'], public_key, client_secret, 'iframe_nosession') if len(details) == 1 and 'uid' in details[0]: details = details[0] auth_data_fields = self.setting('EXTRA_AUTH_DATA_LIST', ('api_server', 'apiconnection', 'session_key', 'authorized', 'session_secret_key')) for field in auth_data_fields: details[field] = response[field] details['extra_data_list'] = fields + auth_data_fields kwargs.update({'backend': self, 'response': details}) else: raise AuthFailed(self, 'Cannot get user details: API error') return self.strategy.authenticate(*args, **kwargs) def get_auth_sig(self): secret_key = self.setting('SECRET') hash_source = '{0:s}{1:s}{2:s}'.format(self.data['logged_user_id'], self.data['session_key'], secret_key) return md5(hash_source.encode('utf-8')).hexdigest() def get_response(self): fields = ('logged_user_id', 'api_server', 'application_key', 'session_key', 'session_secret_key', 'authorized', 'apiconnection') return dict((name, self.data[name]) for name in fields if name in self.data) def verify_auth_sig(self): correct_key = self.get_auth_sig() key = self.data['auth_sig'].lower() if correct_key != key: raise AuthFailed(self, 'Wrong authorization key') def odnoklassniki_oauth_sig(data, client_secret): """ Calculates signature of request data access_token value must be included Algorithm is described at http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=12878032, search for "little bit different way" """ suffix = md5( '{0:s}{1:s}'.format(data['access_token'], client_secret).encode('utf-8') ).hexdigest() check_list = sorted(['{0:s}={1:s}'.format(key, value) for key, value in data.items() if key != 'access_token']) return md5((''.join(check_list) + suffix).encode('utf-8')).hexdigest() def odnoklassniki_iframe_sig(data, client_secret_or_session_secret): """ Calculates signature as described at: http://dev.odnoklassniki.ru/wiki/display/ok/ Authentication+and+Authorization If API method requires session context, request is signed with session secret key. Otherwise it is signed with application secret key """ param_list = sorted(['{0:s}={1:s}'.format(key, value) for key, value in data.items()]) return md5( (''.join(param_list) + client_secret_or_session_secret).encode('utf-8') ).hexdigest() def odnoklassniki_api(backend, data, api_url, public_key, client_secret, request_type='oauth'): """Calls Odnoklassniki REST API method http://dev.odnoklassniki.ru/wiki/display/ok/Odnoklassniki+Rest+API""" data.update({ 'application_key': public_key, 'format': 'JSON' }) if request_type == 'oauth': data['sig'] = odnoklassniki_oauth_sig(data, client_secret) elif request_type == 'iframe_session': data['sig'] = odnoklassniki_iframe_sig(data, data['session_secret_key']) elif request_type == 'iframe_nosession': data['sig'] = odnoklassniki_iframe_sig(data, client_secret) else: msg = 'Unknown request type {0}. How should it be signed?' raise AuthFailed(backend, msg.format(request_type)) return backend.get_json(api_url + 'fb.do', params=data) python-social-auth-0.2.13/social/backends/open_id.py000066400000000000000000000337421260133235600223520ustar00rootroot00000000000000import datetime from calendar import timegm from jwt import InvalidTokenError, decode as jwt_decode from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE from openid.consumer.discover import DiscoveryFailure from openid.extensions import sreg, ax, pape from social.utils import url_add_parameters from social.exceptions import AuthException, AuthFailed, AuthCanceled, \ AuthUnknownError, AuthMissingParameter, \ AuthTokenError from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 # OpenID configuration OLD_AX_ATTRS = [ ('http://schema.openid.net/contact/email', 'old_email'), ('http://schema.openid.net/namePerson', 'old_fullname'), ('http://schema.openid.net/namePerson/friendly', 'old_nickname') ] AX_SCHEMA_ATTRS = [ # Request both the full name and first/last components since some # providers offer one but not the other. ('http://axschema.org/contact/email', 'email'), ('http://axschema.org/namePerson', 'fullname'), ('http://axschema.org/namePerson/first', 'first_name'), ('http://axschema.org/namePerson/last', 'last_name'), ('http://axschema.org/namePerson/friendly', 'nickname'), ] SREG_ATTR = [ ('email', 'email'), ('fullname', 'fullname'), ('nickname', 'nickname') ] OPENID_ID_FIELD = 'openid_identifier' SESSION_NAME = 'openid' class OpenIdAuth(BaseAuth): """Generic OpenID authentication backend""" name = 'openid' URL = None USERNAME_KEY = 'username' def get_user_id(self, details, response): """Return user unique id provided by service""" return response.identity_url def get_ax_attributes(self): attrs = self.setting('AX_SCHEMA_ATTRS', []) if attrs and self.setting('IGNORE_DEFAULT_AX_ATTRS', True): return attrs return attrs + AX_SCHEMA_ATTRS + OLD_AX_ATTRS def get_sreg_attributes(self): return self.setting('SREG_ATTR') or SREG_ATTR def values_from_response(self, response, sreg_names=None, ax_names=None): """Return values from SimpleRegistration response or AttributeExchange response if present. @sreg_names and @ax_names must be a list of name and aliases for such name. The alias will be used as mapping key. """ values = {} # Use Simple Registration attributes if provided if sreg_names: resp = sreg.SRegResponse.fromSuccessResponse(response) if resp: values.update((alias, resp.get(name) or '') for name, alias in sreg_names) # Use Attribute Exchange attributes if provided if ax_names: resp = ax.FetchResponse.fromSuccessResponse(response) if resp: for src, alias in ax_names: name = alias.replace('old_', '') values[name] = resp.getSingle(src, '') or values.get(name) return values def get_user_details(self, response): """Return user details from an OpenID request""" values = {'username': '', 'email': '', 'fullname': '', 'first_name': '', 'last_name': ''} # update values using SimpleRegistration or AttributeExchange # values values.update(self.values_from_response( response, self.get_sreg_attributes(), self.get_ax_attributes() )) fullname = values.get('fullname') or '' first_name = values.get('first_name') or '' last_name = values.get('last_name') or '' email = values.get('email') or '' if not fullname and first_name and last_name: fullname = first_name + ' ' + last_name elif fullname: try: first_name, last_name = fullname.rsplit(' ', 1) except ValueError: last_name = fullname username_key = self.setting('USERNAME_KEY') or self.USERNAME_KEY values.update({'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'username': values.get(username_key) or (first_name.title() + last_name.title()), 'email': email}) return values def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return defined extra data names to store in extra_data field. Settings will be inspected to get more values names that should be stored on extra_data field. Setting name is created from current backend name (all uppercase) plus _SREG_EXTRA_DATA and _AX_EXTRA_DATA because values can be returned by SimpleRegistration or AttributeExchange schemas. Both list must be a value name and an alias mapping similar to SREG_ATTR, OLD_AX_ATTRS or AX_SCHEMA_ATTRS """ sreg_names = self.setting('SREG_EXTRA_DATA') ax_names = self.setting('AX_EXTRA_DATA') values = self.values_from_response(response, sreg_names, ax_names) from_details = super(OpenIdAuth, self).extra_data( user, uid, {}, details, *args, **kwargs ) values.update(from_details) return values def auth_url(self): """Return auth URL returned by service""" openid_request = self.setup_request(self.auth_extra_arguments()) # Construct completion URL, including page we should redirect to return_to = self.strategy.absolute_uri(self.redirect_uri) return openid_request.redirectURL(self.trust_root(), return_to) def auth_html(self): """Return auth HTML returned by service""" openid_request = self.setup_request(self.auth_extra_arguments()) return_to = self.strategy.absolute_uri(self.redirect_uri) form_tag = {'id': 'openid_message'} return openid_request.htmlMarkup(self.trust_root(), return_to, form_tag_attrs=form_tag) def trust_root(self): """Return trust-root option""" return self.setting('OPENID_TRUST_ROOT') or \ self.strategy.absolute_uri('/') def continue_pipeline(self, *args, **kwargs): """Continue previous halted pipeline""" response = self.consumer().complete(dict(self.data.items()), self.strategy.absolute_uri( self.redirect_uri )) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def auth_complete(self, *args, **kwargs): """Complete auth process""" response = self.consumer().complete(dict(self.data.items()), self.strategy.absolute_uri( self.redirect_uri )) self.process_error(response) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def process_error(self, data): if not data: raise AuthException(self, 'OpenID relying party endpoint') elif data.status == FAILURE: raise AuthFailed(self, data.message) elif data.status == CANCEL: raise AuthCanceled(self) elif data.status != SUCCESS: raise AuthUnknownError(self, data.status) def setup_request(self, params=None): """Setup request""" request = self.openid_request(params) # Request some user details. Use attribute exchange if provider # advertises support. if request.endpoint.supportsType(ax.AXMessage.ns_uri): fetch_request = ax.FetchRequest() # Mark all attributes as required, Google ignores optional ones for attr, alias in self.get_ax_attributes(): fetch_request.add(ax.AttrInfo(attr, alias=alias, required=True)) else: fetch_request = sreg.SRegRequest( optional=list(dict(self.get_sreg_attributes()).keys()) ) request.addExtension(fetch_request) # Add PAPE Extension for if configured preferred_policies = self.setting( 'OPENID_PAPE_PREFERRED_AUTH_POLICIES' ) preferred_level_types = self.setting( 'OPENID_PAPE_PREFERRED_AUTH_LEVEL_TYPES' ) max_age = self.setting('OPENID_PAPE_MAX_AUTH_AGE') if max_age is not None: try: max_age = int(max_age) except (ValueError, TypeError): max_age = None if max_age is not None or preferred_policies or preferred_level_types: pape_request = pape.Request( max_auth_age=max_age, preferred_auth_policies=preferred_policies, preferred_auth_level_types=preferred_level_types ) request.addExtension(pape_request) return request def consumer(self): """Create an OpenID Consumer object for the given Django request.""" if not hasattr(self, '_consumer'): self._consumer = self.create_consumer(self.strategy.openid_store()) return self._consumer def create_consumer(self, store=None): return Consumer(self.strategy.openid_session_dict(SESSION_NAME), store) def uses_redirect(self): """Return true if openid request will be handled with redirect or HTML content will be returned. """ return self.openid_request().shouldSendRedirect() def openid_request(self, params=None): """Return openid request""" try: return self.consumer().begin(url_add_parameters(self.openid_url(), params)) except DiscoveryFailure as err: raise AuthException(self, 'OpenID discovery error: {0}'.format( err )) def openid_url(self): """Return service provider URL. This base class is generic accepting a POST parameter that specifies provider URL.""" if self.URL: return self.URL elif OPENID_ID_FIELD in self.data: return self.data[OPENID_ID_FIELD] else: raise AuthMissingParameter(self, OPENID_ID_FIELD) class OpenIdConnectAssociation(object): """ Use Association model to save the nonce by force. """ def __init__(self, handle, secret='', issued=0, lifetime=0, assoc_type=''): self.handle = handle # as nonce self.secret = secret.encode() # not use self.issued = issued # not use self.lifetime = lifetime # not use self.assoc_type = assoc_type # as state class OpenIdConnectAuth(BaseOAuth2): """ Base class for Open ID Connect backends. Currently only the code response type is supported. """ ID_TOKEN_ISSUER = None DEFAULT_SCOPE = ['openid'] EXTRA_DATA = ['id_token', 'refresh_token', ('sub', 'id')] # Set after access_token is retrieved id_token = None def auth_params(self, state=None): """Return extra arguments needed on auth process.""" params = super(OpenIdConnectAuth, self).auth_params(state) params['nonce'] = self.get_and_store_nonce( self.AUTHORIZATION_URL, state ) return params def auth_complete_params(self, state=None): params = super(OpenIdConnectAuth, self).auth_complete_params(state) # Add a nonce to the request so that to help counter CSRF params['nonce'] = self.get_and_store_nonce( self.ACCESS_TOKEN_URL, state ) return params def get_and_store_nonce(self, url, state): # Create a nonce nonce = self.strategy.random_string(64) # Store the nonce association = OpenIdConnectAssociation(nonce, assoc_type=state) self.strategy.storage.association.store(url, association) return nonce def get_nonce(self, nonce): try: return self.strategy.storage.association.get( server_url=self.ACCESS_TOKEN_URL, handle=nonce )[0] except IndexError: pass def remove_nonce(self, nonce_id): self.strategy.storage.association.remove([nonce_id]) def validate_and_return_id_token(self, id_token): """ Validates the id_token according to the steps at http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. """ client_id, _client_secret = self.get_key_and_secret() decryption_key = self.setting('ID_TOKEN_DECRYPTION_KEY') try: # Decode the JWT and raise an error if the secret is invalid or # the response has expired. id_token = jwt_decode(id_token, decryption_key, audience=client_id, issuer=self.ID_TOKEN_ISSUER, algorithms=['HS256']) except InvalidTokenError as err: raise AuthTokenError(self, err) # Verify the token was issued in the last 10 minutes utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple()) if id_token['iat'] < (utc_timestamp - 600): raise AuthTokenError(self, 'Incorrect id_token: iat') # Validate the nonce to ensure the request was not modified nonce = id_token.get('nonce') if not nonce: raise AuthTokenError(self, 'Incorrect id_token: nonce') nonce_obj = self.get_nonce(nonce) if nonce_obj: self.remove_nonce(nonce_obj.id) else: raise AuthTokenError(self, 'Incorrect id_token: nonce') return id_token def request_access_token(self, *args, **kwargs): """ Retrieve the access token. Also, validate the id_token and store it (temporarily). """ response = self.get_json(*args, **kwargs) self.id_token = self.validate_and_return_id_token(response['id_token']) return response python-social-auth-0.2.13/social/backends/openstreetmap.py000066400000000000000000000036001260133235600236110ustar00rootroot00000000000000""" OpenStreetMap OAuth support. This adds support for OpenStreetMap OAuth service. An application must be registered first on OpenStreetMap and the settings SOCIAL_AUTH_OPENSTREETMAP_KEY and SOCIAL_AUTH_OPENSTREETMAP_SECRET must be defined with the corresponding values. More info: http://wiki.openstreetmap.org/wiki/OAuth """ from xml.dom import minidom from social.backends.oauth import BaseOAuth1 class OpenStreetMapOAuth(BaseOAuth1): """OpenStreetMap OAuth authentication backend""" name = 'openstreetmap' AUTHORIZATION_URL = 'http://www.openstreetmap.org/oauth/authorize' REQUEST_TOKEN_URL = 'http://www.openstreetmap.org/oauth/request_token' ACCESS_TOKEN_URL = 'http://www.openstreetmap.org/oauth/access_token' EXTRA_DATA = [ ('id', 'id'), ('avatar', 'avatar'), ('account_created', 'account_created') ] def get_user_details(self, response): """Return user details from OpenStreetMap account""" return { 'username': response['username'], 'email': '', 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Return user data provided""" response = self.oauth_request( access_token, 'http://api.openstreetmap.org/api/0.6/user/details' ) try: dom = minidom.parseString(response.content) except ValueError: return None user = dom.getElementsByTagName('user')[0] try: avatar = dom.getElementsByTagName('img')[0].getAttribute('href') except IndexError: avatar = None return { 'id': user.getAttribute('id'), 'username': user.getAttribute('display_name'), 'account_created': user.getAttribute('account_created'), 'avatar': avatar } python-social-auth-0.2.13/social/backends/orbi.py000066400000000000000000000024031260133235600216560ustar00rootroot00000000000000""" Orbi OAuth2 backend """ from social.backends.oauth import BaseOAuth2 class OrbiOAuth2(BaseOAuth2): """Orbi OAuth2 authentication backend""" name = 'orbi' AUTHORIZATION_URL = 'https://login.orbi.kr/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.orbi.kr/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('imin', 'imin'), ('nick', 'nick'), ('photo', 'photo'), ('sex', 'sex'), ('birth', 'birth'), ] def get_user_id(self, details, response): return response.get('id') def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('name', ''), response.get('first_name', ''), response.get('last_name', '') ) return { 'username': response.get('username', response.get('name')), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, } def user_data(self, access_token, *args, **kwargs): """Load user data from orbi""" return self.get_json('https://login.orbi.kr/oauth/user/get', params={ 'access_token': access_token }) python-social-auth-0.2.13/social/backends/persona.py000066400000000000000000000034651260133235600224030ustar00rootroot00000000000000""" Mozilla Persona authentication backend, docs at: http://psa.matiasaguirre.net/docs/backends/persona.html """ from social.utils import handle_http_errors from social.backends.base import BaseAuth from social.exceptions import AuthFailed, AuthMissingParameter class PersonaAuth(BaseAuth): """BrowserID authentication backend""" name = 'persona' def get_user_id(self, details, response): """Use BrowserID email as ID""" return details['email'] def get_user_details(self, response): """Return user details, BrowserID only provides Email.""" # {'status': 'okay', # 'audience': 'localhost:8000', # 'expires': 1328983575529, # 'email': 'name@server.com', # 'issuer': 'browserid.org'} email = response['email'] return {'username': email.split('@', 1)[0], 'email': email, 'fullname': '', 'first_name': '', 'last_name': ''} def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return users extra data""" return {'audience': response['audience'], 'issuer': response['issuer']} @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" if 'assertion' not in self.data: raise AuthMissingParameter(self, 'assertion') response = self.get_json('https://browserid.org/verify', data={ 'assertion': self.data['assertion'], 'audience': self.strategy.request_host() }, method='POST') if response.get('status') == 'failure': raise AuthFailed(self) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.13/social/backends/pixelpin.py000066400000000000000000000022511260133235600225540ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class PixelPinOAuth2(BaseOAuth2): """PixelPin OAuth authentication backend""" name = 'pixelpin-oauth2' ID_KEY = 'id' AUTHORIZATION_URL = 'https://login.pixelpin.co.uk/OAuth2/Flogin.aspx' ACCESS_TOKEN_URL = 'https://ws3.pixelpin.co.uk/index.php/api/token' ACCESS_TOKEN_METHOD = 'POST' REQUIRES_EMAIL_VALIDATION = False EXTRA_DATA = [ ('id', 'id'), ] def get_user_details(self, response): """Return user details from PixelPin account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('firstName'), last_name=response.get('lastName') ) return {'username': response.get('firstName'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://ws3.pixelpin.co.uk/index.php/api/userdata', params={'access_token': access_token} ) python-social-auth-0.2.13/social/backends/pocket.py000066400000000000000000000033021260133235600222070ustar00rootroot00000000000000""" Pocket OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/pocket.html """ from social.backends.base import BaseAuth from social.utils import handle_http_errors class PocketAuth(BaseAuth): name = 'pocket' AUTHORIZATION_URL = 'https://getpocket.com/auth/authorize' ACCESS_TOKEN_URL = 'https://getpocket.com/v3/oauth/authorize' REQUEST_TOKEN_URL = 'https://getpocket.com/v3/oauth/request' ID_KEY = 'username' def get_json(self, url, *args, **kwargs): headers = {'X-Accept': 'application/json'} kwargs.update({'method': 'POST', 'headers': headers}) return super(PocketAuth, self).get_json(url, *args, **kwargs) def get_user_details(self, response): return {'username': response['username']} def extra_data(self, user, uid, response, details=None, *args, **kwargs): return response def auth_url(self): data = { 'consumer_key': self.setting('POCKET_CONSUMER_KEY'), 'redirect_uri': self.redirect_uri, } token = self.get_json(self.REQUEST_TOKEN_URL, data=data)['code'] self.strategy.session_set('pocket_request_token', token) bits = (self.AUTHORIZATION_URL, token, self.redirect_uri) return '%s?request_token=%s&redirect_uri=%s' % bits @handle_http_errors def auth_complete(self, *args, **kwargs): data = { 'consumer_key': self.setting('POCKET_CONSUMER_KEY'), 'code': self.strategy.session_get('pocket_request_token'), } response = self.get_json(self.ACCESS_TOKEN_URL, data=data) kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.13/social/backends/podio.py000066400000000000000000000023361260133235600220420ustar00rootroot00000000000000""" Podio OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/podio.html """ from social.backends.oauth import BaseOAuth2 class PodioOAuth2(BaseOAuth2): """Podio OAuth authentication backend""" name = 'podio' AUTHORIZATION_URL = 'https://podio.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://podio.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('access_token', 'access_token'), ('token_type', 'token_type'), ('expires_in', 'expires'), ('refresh_token', 'refresh_token'), ] def get_user_id(self, details, response): return response['ref']['id'] def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response['profile']['name'] ) return { 'username': 'user_%d' % response['user']['user_id'], 'email': response['user']['mail'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, } def user_data(self, access_token, *args, **kwargs): return self.get_json('https://api.podio.com/user/status', headers={'Authorization': 'OAuth2 ' + access_token}) python-social-auth-0.2.13/social/backends/professionali.py000066400000000000000000000035631260133235600236100ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Professionaly OAuth 2.0 support. This contribution adds support for professionaly.ru OAuth 2.0. Username is retrieved from the identity returned by server. """ from time import time from social.utils import parse_qs from social.backends.oauth import BaseOAuth2 class ProfessionaliOAuth2(BaseOAuth2): name = 'professionali' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://api.professionali.ru/oauth/authorize.html' ACCESS_TOKEN_URL = 'https://api.professionali.ru/oauth/getToken.json' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('avatar_big', 'avatar_big'), ('link', 'link') ] def get_user_details(self, response): first_name, last_name = map(response.get, ('firstname', 'lastname')) email = '' if self.setting('FAKE_EMAIL'): email = '{0}@professionali.ru'.format(time()) return { 'username': '{0}_{1}'.format(last_name, first_name), 'first_name': first_name, 'last_name': last_name, 'email': email } def user_data(self, access_token, response, *args, **kwargs): url = 'https://api.professionali.ru/v6/users/get.json' fields = list(set(['firstname', 'lastname', 'avatar_big', 'link'] + self.setting('EXTRA_DATA', []))) params = { 'fields': ','.join(fields), 'access_token': access_token, 'ids[]': response['user_id'] } try: return self.get_json(url, params)[0] except (TypeError, KeyError, IOError, ValueError, IndexError): return None def get_json(self, url, *args, **kwargs): return self.request(url, verify=False, *args, **kwargs).json() def get_querystring(self, url, *args, **kwargs): return parse_qs(self.request(url, verify=False, *args, **kwargs).text) python-social-auth-0.2.13/social/backends/pushbullet.py000066400000000000000000000015151260133235600231150ustar00rootroot00000000000000import base64 from social.backends.oauth import BaseOAuth2 class PushbulletOAuth2(BaseOAuth2): """pushbullet OAuth authentication backend""" name = 'pushbullet' EXTRA_DATA = [('id', 'id')] ID_KEY = 'username' AUTHORIZATION_URL = 'https://www.pushbullet.com/authorize' REQUEST_TOKEN_URL = 'https://api.pushbullet.com/oauth2/token' ACCESS_TOKEN_URL = 'https://api.pushbullet.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' STATE_PARAMETER = False def get_user_details(self, response): return {'username': response.get('access_token')} def get_user_id(self, details, response): auth = 'Basic {0}'.format(base64.b64encode(details['username'])) return self.get_json('https://api.pushbullet.com/v2/users/me', headers={'Authorization': auth})['iden'] python-social-auth-0.2.13/social/backends/qiita.py000066400000000000000000000042341260133235600220360ustar00rootroot00000000000000""" Qiita OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/qiita.html http://qiita.com/api/v2/docs#get-apiv2oauthauthorize """ import json from social.backends.oauth import BaseOAuth2 class QiitaOAuth2(BaseOAuth2): """Qiita OAuth authentication backend""" name = 'qiita' AUTHORIZATION_URL = 'https://qiita.com/api/v2/oauth/authorize' ACCESS_TOKEN_URL = 'https://qiita.com/api/v2/access_tokens' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' REDIRECT_STATE = True EXTRA_DATA = [ ('description', 'description'), ('facebook_id', 'facebook_id'), ('followees_count', 'followees_count'), ('followers_count', 'followers_count'), ('github_login_name', 'github_login_name'), ('id', 'id'), ('items_count', 'items_count'), ('linkedin_id', 'linkedin_id'), ('location', 'location'), ('name', 'name'), ('organization', 'organization'), ('profile_image_url', 'profile_image_url'), ('twitter_screen_name', 'twitter_screen_name'), ('website_url', 'website_url'), ] def auth_complete_params(self, state=None): data = super(QiitaOAuth2, self).auth_complete_params(state) if "grant_type" in data: del data["grant_type"] if "redirect_uri" in data: del data["redirect_uri"] return json.dumps(data) def auth_headers(self): return {'Content-Type': 'application/json'} def request_access_token(self, *args, **kwargs): data = super(QiitaOAuth2, self).request_access_token(*args, **kwargs) data.update({'access_token': data['token']}) return data def get_user_details(self, response): """Return user details from Qiita account""" return { 'username': response['id'], 'fullname': response['name'], } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://qiita.com/api/v2/authenticated_user', headers={ 'Authorization': ' Bearer {0}'.format(access_token) }) python-social-auth-0.2.13/social/backends/qq.py000066400000000000000000000031061260133235600213450ustar00rootroot00000000000000""" Created on May 13, 2014 @author: Yong Zhang (zyfyfe@gmail.com) """ import json from social.utils import parse_qs from social.backends.oauth import BaseOAuth2 class QQOAuth2(BaseOAuth2): name = 'qq' ID_KEY = 'openid' AUTHORIZE_URL = 'https://graph.qq.com/oauth2.0/authorize' ACCESS_TOKEN_URL = 'https://graph.qq.com/oauth2.0/token' AUTHORIZATION_URL = 'https://graph.qq.com/oauth2.0/authorize' OPENID_URL = 'https://graph.qq.com/oauth2.0/me' REDIRECT_STATE = False EXTRA_DATA = [ ('nickname', 'username'), ('figureurl_qq_1', 'profile_image_url'), ('gender', 'gender') ] def get_user_details(self, response): return { 'username': response.get('nickname', '') } def get_openid(self, access_token): response = self.request(self.OPENID_URL, params={ 'access_token': access_token }) data = json.loads(response.content[10:-3]) return data['openid'] def user_data(self, access_token, *args, **kwargs): openid = self.get_openid(access_token) response = self.get_json( 'https://graph.qq.com/user/get_user_info', params={ 'access_token': access_token, 'oauth_consumer_key': self.setting('SOCIAL_AUTH_QQ_KEY'), 'openid': openid } ) response['openid'] = openid return response def request_access_token(self, url, data, *args, **kwargs): response = self.request(url, params=data, *args, **kwargs) return parse_qs(response.content) python-social-auth-0.2.13/social/backends/rdio.py000066400000000000000000000046441260133235600216710ustar00rootroot00000000000000""" Rdio OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/rdio.html """ from social.backends.oauth import BaseOAuth1, BaseOAuth2, OAuthAuth RDIO_API = 'https://www.rdio.com/api/1/' class BaseRdio(OAuthAuth): ID_KEY = 'key' def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( fullname=response['displayName'], first_name=response['firstName'], last_name=response['lastName'] ) return { 'username': response['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } class RdioOAuth1(BaseRdio, BaseOAuth1): """Rdio OAuth authentication backend""" name = 'rdio-oauth1' REQUEST_TOKEN_URL = 'http://api.rdio.com/oauth/request_token' AUTHORIZATION_URL = 'https://www.rdio.com/oauth/authorize' ACCESS_TOKEN_URL = 'http://api.rdio.com/oauth/access_token' EXTRA_DATA = [ ('key', 'rdio_id'), ('icon', 'rdio_icon_url'), ('url', 'rdio_profile_url'), ('username', 'rdio_username'), ('streamRegion', 'rdio_stream_region'), ] def user_data(self, access_token, *args, **kwargs): """Return user data provided""" params = {'method': 'currentUser', 'extras': 'username,displayName,streamRegion'} request = self.oauth_request(access_token, RDIO_API, params, method='POST') return self.get_json(request.url, method='POST', data=request.to_postdata())['result'] class RdioOAuth2(BaseRdio, BaseOAuth2): name = 'rdio-oauth2' AUTHORIZATION_URL = 'https://www.rdio.com/oauth2/authorize' ACCESS_TOKEN_URL = 'https://www.rdio.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('key', 'rdio_id'), ('icon', 'rdio_icon_url'), ('url', 'rdio_profile_url'), ('username', 'rdio_username'), ('streamRegion', 'rdio_stream_region'), ('refresh_token', 'refresh_token', True), ('token_type', 'token_type', True), ] def user_data(self, access_token, *args, **kwargs): return self.get_json(RDIO_API, method='POST', data={ 'method': 'currentUser', 'extras': 'username,displayName,streamRegion', 'access_token': access_token })['result'] python-social-auth-0.2.13/social/backends/readability.py000066400000000000000000000025071260133235600232210ustar00rootroot00000000000000""" Readability OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/readability.html """ from social.backends.oauth import BaseOAuth1 READABILITY_API = 'https://www.readability.com/api/rest/v1' class ReadabilityOAuth(BaseOAuth1): """Readability OAuth authentication backend""" name = 'readability' ID_KEY = 'username' AUTHORIZATION_URL = '{0}/oauth/authorize/'.format(READABILITY_API) REQUEST_TOKEN_URL = '{0}/oauth/request_token/'.format(READABILITY_API) ACCESS_TOKEN_URL = '{0}/oauth/access_token/'.format(READABILITY_API) EXTRA_DATA = [('date_joined', 'date_joined'), ('kindle_email_address', 'kindle_email_address'), ('avatar_url', 'avatar_url'), ('email_into_address', 'email_into_address')] def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( first_name=response['first_name'], last_name=response['last_name'] ) return {'username': response['username'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): return self.get_json(READABILITY_API + '/users/_current', auth=self.oauth_auth(access_token)) python-social-auth-0.2.13/social/backends/reddit.py000066400000000000000000000033521260133235600222020ustar00rootroot00000000000000""" Reddit OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/reddit.html """ import base64 from social.backends.oauth import BaseOAuth2 class RedditOAuth2(BaseOAuth2): """Reddit OAuth2 authentication backend""" name = 'reddit' AUTHORIZATION_URL = 'https://ssl.reddit.com/api/v1/authorize' ACCESS_TOKEN_URL = 'https://ssl.reddit.com/api/v1/access_token' ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' REDIRECT_STATE = False SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['identity'] SEND_USER_AGENT = True EXTRA_DATA = [ ('id', 'id'), ('name', 'username'), ('link_karma', 'link_karma'), ('comment_karma', 'comment_karma'), ('refresh_token', 'refresh_token'), ('expires_in', 'expires') ] def get_user_details(self, response): """Return user details from Reddit account""" return {'username': response.get('name'), 'email': '', 'fullname': '', 'first_name': '', 'last_name': ''} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://oauth.reddit.com/api/v1/me.json', headers={'Authorization': 'bearer ' + access_token} ) def auth_headers(self): return { 'Authorization': 'Basic {0}'.format(base64.urlsafe_b64encode( ('{0}:{1}'.format(*self.get_key_and_secret()).encode()) )) } def refresh_token_params(self, token, redirect_uri=None, *args, **kwargs): params = super(RedditOAuth2, self).refresh_token_params(token) params['redirect_uri'] = self.redirect_uri or redirect_uri return params python-social-auth-0.2.13/social/backends/runkeeper.py000066400000000000000000000034171260133235600227310ustar00rootroot00000000000000""" RunKeeper OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/runkeeper.html """ from social.backends.oauth import BaseOAuth2 class RunKeeperOAuth2(BaseOAuth2): """RunKeeper OAuth authentication backend""" name = 'runkeeper' AUTHORIZATION_URL = 'https://runkeeper.com/apps/authorize' ACCESS_TOKEN_URL = 'https://runkeeper.com/apps/token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('userID', 'id'), ] def get_user_id(self, details, response): return response['userID'] def get_user_details(self, response): """Parse username from profile link""" username = None profile_url = response.get('profile') if len(profile_url): profile_url_parts = profile_url.split('http://runkeeper.com/user/') if len(profile_url_parts) > 1 and len(profile_url_parts[1]): username = profile_url_parts[1] fullname, first_name, last_name = self.get_user_names( fullname=response.get('name') ) return {'username': username, 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): # We need to use the /user endpoint to get the user id, the /profile # endpoint contains name, user name, location, gender user_data = self._user_data(access_token, '/user') profile_data = self._user_data(access_token, '/profile') return dict(user_data, **profile_data) def _user_data(self, access_token, path): url = 'https://api.runkeeper.com{0}'.format(path) return self.get_json(url, params={'access_token': access_token}) python-social-auth-0.2.13/social/backends/salesforce.py000066400000000000000000000034561260133235600230620ustar00rootroot00000000000000from urllib import urlencode from social.backends.oauth import BaseOAuth2 class SalesforceOAuth2(BaseOAuth2): """Salesforce OAuth2 authentication backend""" name = 'salesforce-oauth2' AUTHORIZATION_URL = \ 'https://login.salesforce.com/services/oauth2/authorize' ACCESS_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/token' REVOKE_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/revoke' ACCESS_TOKEN_METHOD = 'POST' REFRESH_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ' ' EXTRA_DATA = [ ('id', 'id'), ('instance_url', 'instance_url'), ('issued_at', 'issued_at'), ('signature', 'signature'), ('refresh_token', 'refresh_token'), ] def get_user_details(self, response): """Return user details from a Salesforce account""" return { 'username': response.get('username'), 'email': response.get('email') or '', 'first_name': response.get('first_name'), 'last_name': response.get('last_name'), 'fullname': response.get('display_name') } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" user_id_url = kwargs.get('response').get('id') url = user_id_url + '?' + urlencode({'access_token': access_token}) try: return self.get_json(url) except ValueError: return None class SalesforceOAuth2Sandbox(SalesforceOAuth2): """Salesforce OAuth2 authentication testing backend""" name = 'salesforce-oauth2-sandbox' AUTHORIZATION_URL = 'https://test.salesforce.com/services/oauth2/authorize' ACCESS_TOKEN_URL = 'https://test.salesforce.com/services/oauth2/token' REVOKE_TOKEN_URL = 'https://test.salesforce.com/services/oauth2/revoke' python-social-auth-0.2.13/social/backends/saml.py000066400000000000000000000302201260133235600216550ustar00rootroot00000000000000""" Backend for SAML 2.0 support Terminology: "Service Provider" (SP): Your web app "Identity Provider" (IdP): The third-party site that is authenticating users via SAML """ from onelogin.saml2.auth import OneLogin_Saml2_Auth from onelogin.saml2.settings import OneLogin_Saml2_Settings from social.backends.base import BaseAuth from social.exceptions import AuthFailed # Helpful constants: OID_COMMON_NAME = "urn:oid:2.5.4.3" OID_EDU_PERSON_PRINCIPAL_NAME = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6" OID_EDU_PERSON_ENTITLEMENT = "urn:oid:1.3.6.1.4.1.5923.1.1.1.7" OID_GIVEN_NAME = "urn:oid:2.5.4.42" OID_MAIL = "urn:oid:0.9.2342.19200300.100.1.3" OID_SURNAME = "urn:oid:2.5.4.4" OID_USERID = "urn:oid:0.9.2342.19200300.100.1.1" class SAMLIdentityProvider(object): """Wrapper around configuration for a SAML Identity provider""" def __init__(self, name, **kwargs): """Load and parse configuration""" self.name = name # name should be a slug and must not contain a colon, which # could conflict with uid prefixing: assert ':' not in self.name and ' ' not in self.name, \ 'IdP "name" should be a slug (short, no spaces)' self.conf = kwargs def get_user_permanent_id(self, attributes): """ The most important method: Get a permanent, unique identifier for this user from the attributes supplied by the IdP. If you want to use the NameID, it's available via attributes['name_id'] """ return attributes[ self.conf.get('attr_user_permanent_id', OID_USERID) ][0] # Attributes processing: def get_user_details(self, attributes): """ Given the SAML attributes extracted from the SSO response, get the user data like name. """ return { 'fullname': self.get_attr(attributes, 'attr_full_name', OID_COMMON_NAME), 'first_name': self.get_attr(attributes, 'attr_first_name', OID_GIVEN_NAME), 'last_name': self.get_attr(attributes, 'attr_last_name', OID_SURNAME), 'username': self.get_attr(attributes, 'attr_username', OID_USERID), 'email': self.get_attr(attributes, 'attr_email', OID_MAIL), } def get_attr(self, attributes, conf_key, default_attribute): """ Internal helper method. Get the attribute 'default_attribute' out of the attributes, unless self.conf[conf_key] overrides the default by specifying another attribute to use. """ key = self.conf.get(conf_key, default_attribute) return attributes[key][0] if key in attributes else None @property def entity_id(self): """Get the entity ID for this IdP""" # Required. e.g. "https://idp.testshib.org/idp/shibboleth" return self.conf['entity_id'] @property def sso_url(self): """Get the SSO URL for this IdP""" # Required. e.g. # "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO" return self.conf['url'] @property def x509cert(self): """X.509 Public Key Certificate for this IdP""" return self.conf['x509cert'] @property def saml_config_dict(self): """Get the IdP configuration dict in the format required by python-saml""" return { 'entityId': self.entity_id, 'singleSignOnService': { 'url': self.sso_url, # python-saml only supports Redirect 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect' }, 'x509cert': self.x509cert, } class DummySAMLIdentityProvider(SAMLIdentityProvider): """ A placeholder IdP used when we must specify something, e.g. when generating SP metadata. If OneLogin_Saml2_Auth is modified to not always require IdP config, this can be removed. """ def __init__(self): super(DummySAMLIdentityProvider, self).__init__( 'dummy', entity_id='https://dummy.none/saml2', url='https://dummy.none/SSO', x509cert='' ) class SAMLAuth(BaseAuth): """ PSA Backend that implements SAML 2.0 Service Provider (SP) functionality. Unlike all of the other backends, this one can be configured to work with many identity providers (IdPs). For example, a University that belongs to a Shibboleth federation may support authentication via ~100 partner universities. Also, the IdP configuration can be changed at runtime if you require that functionality - just subclass this and override `get_idp()`. Several settings are required. Here's an example: SOCIAL_AUTH_SAML_SP_ENTITY_ID = "https://saml.example.com/" SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = "... X.509 certificate string ..." SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = "... private key ..." SOCIAL_AUTH_SAML_ORG_INFO = { "en-US": { "name": "example", "displayname": "Example Inc.", "url": "http://example.com" } } SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = { "givenName": "Tech Gal", "emailAddress": "technical@example.com" } SOCIAL_AUTH_SAML_SUPPORT_CONTACT = { "givenName": "Support Guy", "emailAddress": "support@example.com" } SOCIAL_AUTH_SAML_ENABLED_IDPS = { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0B... ...8Bbnl+ev0peYzxFyF5sQA==", } } Optional settings: SOCIAL_AUTH_SAML_SP_EXTRA = {} SOCIAL_AUTH_SAML_SECURITY_CONFIG = {} """ name = "saml" def get_idp(self, idp_name): """Given the name of an IdP, get a SAMLIdentityProvider instance""" idp_config = self.setting('ENABLED_IDPS')[idp_name] return SAMLIdentityProvider(idp_name, **idp_config) def generate_saml_config(self, idp): """ Generate the configuration required to instantiate OneLogin_Saml2_Auth """ # The shared absolute URL that all IdPs redirect back to - # this is specified in our metadata.xml: abs_completion_url = self.redirect_uri config = { 'contactPerson': { 'technical': self.setting('TECHNICAL_CONTACT'), 'support': self.setting('SUPPORT_CONTACT') }, 'debug': True, 'idp': idp.saml_config_dict, 'organization': self.setting('ORG_INFO'), 'security': { 'metadataValidUntil': '', 'metadataCacheDuration': 'P10D', # metadata valid for ten days }, 'sp': { 'assertionConsumerService': { 'url': abs_completion_url, # python-saml only supports HTTP-POST 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' }, 'entityId': self.setting('SP_ENTITY_ID'), 'x509cert': self.setting('SP_PUBLIC_CERT'), 'privateKey': self.setting('SP_PRIVATE_KEY'), }, 'strict': True, # We must force strict mode - for security } config["security"].update(self.setting("SECURITY_CONFIG", {})) config["sp"].update(self.setting("SP_EXTRA", {})) return config def generate_metadata_xml(self): """ Helper method that can be used from your web app to generate the XML metadata required to link your web app as a Service Provider with each IdP you wish to use. Returns (metadata XML string, list of errors) Example usage (Django): from social.apps.django_app.utils import load_strategy, \ load_backend def saml_metadata_view(request): complete_url = reverse('social:complete', args=("saml", )) saml_backend = load_backend(load_strategy(request), "saml", complete_url) metadata, errors = saml_backend.generate_metadata_xml() if not errors: return HttpResponse(content=metadata, content_type='text/xml') return HttpResponseServerError(content=', '.join(errors)) """ # python-saml requires us to specify something here even # though it's not used idp = DummySAMLIdentityProvider() config = self.generate_saml_config(idp) saml_settings = OneLogin_Saml2_Settings(config) metadata = saml_settings.get_sp_metadata() errors = saml_settings.validate_metadata(metadata) return metadata, errors def _create_saml_auth(self, idp): """Get an instance of OneLogin_Saml2_Auth""" config = self.generate_saml_config(idp) request_info = { 'https': 'on' if self.strategy.request_is_secure() else 'off', 'http_host': self.strategy.request_host(), 'script_name': self.strategy.request_path(), 'server_port': self.strategy.request_port(), 'get_data': self.strategy.request_get(), 'post_data': self.strategy.request_post(), } return OneLogin_Saml2_Auth(request_info, config) def auth_url(self): """Get the URL to which we must redirect in order to authenticate the user""" idp_name = self.strategy.request_data()['idp'] auth = self._create_saml_auth(idp=self.get_idp(idp_name)) # Below, return_to sets the RelayState, which can contain # arbitrary data. We use it to store the specific SAML IdP # name, since we multiple IdPs share the same auth_complete # URL. return auth.login(return_to=idp_name) def get_user_details(self, response): """Get user details like full name, email, etc. from the response - see auth_complete""" idp = self.get_idp(response['idp_name']) return idp.get_user_details(response['attributes']) def get_user_id(self, details, response): """ Get the permanent ID for this user from the response. We prefix each ID with the name of the IdP so that we can connect multiple IdPs to this user. """ idp = self.get_idp(response['idp_name']) uid = idp.get_user_permanent_id(response['attributes']) return '{}:{}'.format(idp.name, uid) def auth_complete(self, *args, **kwargs): """ The user has been redirected back from the IdP and we should now log them in, if everything checks out. """ idp_name = self.strategy.request_data()['RelayState'] idp = self.get_idp(idp_name) auth = self._create_saml_auth(idp) auth.process_response() errors = auth.get_errors() if errors or not auth.is_authenticated(): reason = auth.get_last_error_reason() raise AuthFailed( self, 'SAML login failed: {} ({})'.format(errors, reason) ) attributes = auth.get_attributes() attributes['name_id'] = auth.get_nameid() self._check_entitlements(idp, attributes) response = { 'idp_name': idp_name, 'attributes': attributes, 'session_index': auth.get_session_index(), } kwargs.update({'response': response, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) def _check_entitlements(self, idp, attributes): """ Additional verification of a SAML response before authenticating the user. Subclasses can override this method if they need custom validation code, such as requiring the presence of an eduPersonEntitlement. raise social.exceptions.AuthForbidden if the user should not be authenticated, or do nothing to allow the login pipeline to continue. """ pass python-social-auth-0.2.13/social/backends/shopify.py000066400000000000000000000064311260133235600224110ustar00rootroot00000000000000""" Shopify OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/shopify.html """ import imp import six from social.utils import handle_http_errors from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthFailed, AuthCanceled class ShopifyOAuth2(BaseOAuth2): """Shopify OAuth2 authentication backend""" name = 'shopify' ID_KEY = 'shop' EXTRA_DATA = [ ('shop', 'shop'), ('website', 'website'), ('expires', 'expires') ] @property def shopifyAPI(self): if not hasattr(self, '_shopify_api'): fp, pathname, description = imp.find_module('shopify') self._shopify_api = imp.load_module('shopify', fp, pathname, description) return self._shopify_api def get_user_details(self, response): """Use the shopify store name as the username""" return { 'username': six.text_type(response.get('shop', '')).replace( '.myshopify.com', '' ) } def extra_data(self, user, uid, response, details=None, *args, **kwargs): """Return access_token and extra defined names to store in extra_data field""" data = super(ShopifyOAuth2, self).extra_data(user, uid, response, details, *args, **kwargs) session = self.shopifyAPI.Session(self.data.get('shop').strip()) # Get, and store the permanent token token = session.request_token(data['access_token']) data['access_token'] = token return dict(data) def auth_url(self): key, secret = self.get_key_and_secret() self.shopifyAPI.Session.setup(api_key=key, secret=secret) scope = self.get_scope() state = self.state_token() self.strategy.session_set(self.name + '_state', state) redirect_uri = self.get_redirect_uri(state) session = self.shopifyAPI.Session(self.data.get('shop').strip()) return session.create_permission_url( scope=scope, redirect_uri=redirect_uri ) @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes login process, must return user instance""" self.process_error(self.data) access_token = None key, secret = self.get_key_and_secret() try: shop_url = self.data.get('shop') self.shopifyAPI.Session.setup(api_key=key, secret=secret) shopify_session = self.shopifyAPI.Session(shop_url, self.data) access_token = shopify_session.token except self.shopifyAPI.ValidationException: raise AuthCanceled(self) else: if not access_token: raise AuthFailed(self, 'Authentication Failed') return self.do_auth(access_token, shop_url, shopify_session.url, *args, **kwargs) def do_auth(self, access_token, shop_url, website, *args, **kwargs): kwargs.update({ 'backend': self, 'response': { 'shop': shop_url, 'website': 'http://{0}'.format(website), 'access_token': access_token } }) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.13/social/backends/skyrock.py000066400000000000000000000022531260133235600224130ustar00rootroot00000000000000""" Skyrock OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/skyrock.html """ from social.backends.oauth import BaseOAuth1 class SkyrockOAuth(BaseOAuth1): """Skyrock OAuth authentication backend""" name = 'skyrock' ID_KEY = 'id_user' AUTHORIZATION_URL = 'https://api.skyrock.com/v2/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.skyrock.com/v2/oauth/initiate' ACCESS_TOKEN_URL = 'https://api.skyrock.com/v2/oauth/token' EXTRA_DATA = [('id', 'id')] def get_user_details(self, response): """Return user details from Skyrock account""" fullname, first_name, last_name = self.get_user_names( first_name=response['firstname'], last_name=response['name'] ) return {'username': response['username'], 'email': response['email'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): """Return user data provided""" return self.get_json('https://api.skyrock.com/v2/user/get.json', auth=self.oauth_auth(access_token)) python-social-auth-0.2.13/social/backends/slack.py000066400000000000000000000045561260133235600220330ustar00rootroot00000000000000""" Slack OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/slack.html https://api.slack.com/docs/oauth """ import re from social.backends.oauth import BaseOAuth2 class SlackOAuth2(BaseOAuth2): """Slack OAuth authentication backend""" name = 'slack' AUTHORIZATION_URL = 'https://slack.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://slack.com/api/oauth.access' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('name', 'name'), ('real_name', 'real_name') ] def get_user_details(self, response): """Return user details from Slack account""" # Build the username with the team $username@$team_url # Necessary to get unique names for all of slack username = response.get('user') if self.setting('USERNAME_WITH_TEAM', True): match = re.search(r'//([^.]+)\.slack\.com', response['url']) username = '{0}@{1}'.format(username, match.group(1)) out = {'username': username} if 'profile' in response: out.update({ 'email': response['profile'].get('email'), 'fullname': response['profile'].get('real_name'), 'first_name': response['profile'].get('first_name'), 'last_name': response['profile'].get('last_name') }) return out def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" # Has to be two calls, because the users.info requires a username, # And we want the team information. Check auth.test details at: # https://api.slack.com/methods/auth.test auth_test = self.get_json('https://slack.com/api/auth.test', params={ 'token': access_token }) # https://api.slack.com/methods/users.info user_info = self.get_json('https://slack.com/api/users.info', params={ 'token': access_token, 'user': auth_test.get('user_id') }) if user_info.get('user'): # Capture the user data, if available based on the scope auth_test.update(user_info['user']) # Clean up user_id vs id auth_test['id'] = auth_test['user_id'] auth_test.pop('ok', None) auth_test.pop('user_id', None) return auth_test python-social-auth-0.2.13/social/backends/soundcloud.py000066400000000000000000000041541260133235600231070ustar00rootroot00000000000000""" Soundcloud OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/soundcloud.html """ from social.p3 import urlencode from social.backends.oauth import BaseOAuth2 class SoundcloudOAuth2(BaseOAuth2): """Soundcloud OAuth authentication backend""" name = 'soundcloud' AUTHORIZATION_URL = 'https://soundcloud.com/connect' ACCESS_TOKEN_URL = 'https://api.soundcloud.com/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('refresh_token', 'refresh_token'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Soundcloud account""" fullname, first_name, last_name = self.get_user_names( response.get('full_name') ) return {'username': response.get('username'), 'email': response.get('email') or '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://api.soundcloud.com/me.json', params={'oauth_token': access_token}) def auth_url(self): """Return redirect url""" state = None if self.STATE_PARAMETER or self.REDIRECT_STATE: # Store state in session for further request validation. The state # value is passed as state parameter (as specified in OAuth2 spec), # but also added to redirect_uri, that way we can still verify the # request if the provider doesn't implement the state parameter. # Reuse token if any. name = self.name + '_state' state = self.strategy.session_get(name) or self.state_token() self.strategy.session_set(name, state) params = self.auth_params(state) params.update(self.get_scope_argument()) params.update(self.auth_extra_arguments()) return self.AUTHORIZATION_URL + '?' + urlencode(params) python-social-auth-0.2.13/social/backends/spotify.py000066400000000000000000000027561260133235600224330ustar00rootroot00000000000000""" Spotify backend, docs at: https://developer.spotify.com/spotify-web-api/ https://developer.spotify.com/spotify-web-api/authorization-guide/ """ import base64 from social.backends.oauth import BaseOAuth2 class SpotifyOAuth2(BaseOAuth2): name = 'spotify' SCOPE_SEPARATOR = ' ' ID_KEY = 'id' AUTHORIZATION_URL = 'https://accounts.spotify.com/authorize' ACCESS_TOKEN_URL = 'https://accounts.spotify.com/api/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ] def auth_headers(self): auth_str = '{0}:{1}'.format(*self.get_key_and_secret()) b64_auth_str = base64.urlsafe_b64encode(auth_str.encode()).decode() return { 'Authorization': 'Basic {0}'.format(b64_auth_str) } def get_user_details(self, response): """Return user details from Spotify account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('id'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.spotify.com/v1/me', headers={'Authorization': 'Bearer {0}'.format(access_token)} ) python-social-auth-0.2.13/social/backends/stackoverflow.py000066400000000000000000000026601260133235600236210ustar00rootroot00000000000000""" Stackoverflow OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stackoverflow.html """ from social.backends.oauth import BaseOAuth2 class StackoverflowOAuth2(BaseOAuth2): """Stackoverflow OAuth2 authentication backend""" name = 'stackoverflow' ID_KEY = 'user_id' AUTHORIZATION_URL = 'https://stackexchange.com/oauth' ACCESS_TOKEN_URL = 'https://stackexchange.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Stackoverflow account""" fullname, first_name, last_name = self.get_user_names( response.get('display_name') ) return {'username': response.get('link').rsplit('/', 1)[-1], 'full_name': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.stackexchange.com/2.1/me', params={ 'site': 'stackoverflow', 'access_token': access_token, 'key': self.setting('API_KEY') } )['items'][0] def request_access_token(self, *args, **kwargs): return self.get_querystring(*args, **kwargs) python-social-auth-0.2.13/social/backends/steam.py000066400000000000000000000030211260133235600220310ustar00rootroot00000000000000""" Steam OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/steam.html """ from social.backends.open_id import OpenIdAuth from social.exceptions import AuthFailed USER_INFO = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' class SteamOpenId(OpenIdAuth): name = 'steam' URL = 'https://steamcommunity.com/openid' def get_user_id(self, details, response): """Return user unique id provided by service""" return self._user_id(response) def get_user_details(self, response): player = self.get_json(USER_INFO, params={ 'key': self.setting('API_KEY'), 'steamids': self._user_id(response) }) if len(player['response']['players']) > 0: player = player['response']['players'][0] details = {'username': player.get('personaname'), 'email': '', 'fullname': '', 'first_name': '', 'last_name': '', 'player': player} else: details = {} return details def consumer(self): # Steam seems to support stateless mode only, ignore store if not hasattr(self, '_consumer'): self._consumer = self.create_consumer() return self._consumer def _user_id(self, response): user_id = response.identity_url.rsplit('/', 1)[-1] if not user_id.isdigit(): raise AuthFailed(self, 'Missing Steam Id') return user_id python-social-auth-0.2.13/social/backends/stocktwits.py000066400000000000000000000025151260133235600231450ustar00rootroot00000000000000""" Stocktwits OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stocktwits.html """ from social.backends.oauth import BaseOAuth2 class StocktwitsOAuth2(BaseOAuth2): """Stockwiths OAuth2 backend""" name = 'stocktwits' AUTHORIZATION_URL = 'https://api.stocktwits.com/api/2/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.stocktwits.com/api/2/oauth/token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' DEFAULT_SCOPE = ['read', 'publish_messages', 'publish_watch_lists', 'follow_users', 'follow_stocks'] def get_user_id(self, details, response): return response['user']['id'] def get_user_details(self, response): """Return user details from Stocktwits account""" fullname, first_name, last_name = self.get_user_names( response['user']['name'] ) return {'username': response['user']['username'], 'email': '', # not supplied 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json( 'https://api.stocktwits.com/api/2/account/verify.json', params={'access_token': access_token} ) python-social-auth-0.2.13/social/backends/strava.py000066400000000000000000000034721260133235600222320ustar00rootroot00000000000000""" Strava OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/strava.html """ from social.backends.oauth import BaseOAuth2 class StravaOAuth(BaseOAuth2): name = 'strava' AUTHORIZATION_URL = 'https://www.strava.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.strava.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' # Strava doesn't check for parameters in redirect_uri and directly appends # the auth parameters to it, ending with an URL like: # http://example.com/complete/strava?redirect_state=xxx?code=xxx&state=xxx # Check issue #259 for details. REDIRECT_STATE = False REVOKE_TOKEN_URL = 'https://www.strava.com/oauth/deauthorize' def get_user_id(self, details, response): return response['athlete']['id'] def get_user_details(self, response): """Return user details from Strava account""" # because there is no usernames on strava username = response['athlete']['id'] email = response['athlete'].get('email', '') fullname, first_name, last_name = self.get_user_names( first_name=response['athlete'].get('firstname', ''), last_name=response['athlete'].get('lastname', ''), ) return {'username': str(username), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('https://www.strava.com/api/v3/athlete', params={'access_token': access_token}) def revoke_token_params(self, token, uid): params = super(StravaOAuth, self).revoke_token_params(token, uid) params['access_token'] = token return params python-social-auth-0.2.13/social/backends/stripe.py000066400000000000000000000035551260133235600222420ustar00rootroot00000000000000""" Stripe OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/stripe.html """ from social.backends.oauth import BaseOAuth2 class StripeOAuth2(BaseOAuth2): """Stripe OAuth2 authentication backend""" name = 'stripe' ID_KEY = 'stripe_user_id' AUTHORIZATION_URL = 'https://connect.stripe.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://connect.stripe.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('stripe_publishable_key', 'stripe_publishable_key'), ('access_token', 'access_token'), ('livemode', 'livemode'), ('token_type', 'token_type'), ('refresh_token', 'refresh_token'), ('stripe_user_id', 'stripe_user_id'), ] def get_user_details(self, response): """Return user details from Stripe account""" return {'username': response.get('stripe_user_id'), 'email': ''} def auth_params(self, state=None): client_id, client_secret = self.get_key_and_secret() params = {'response_type': 'code', 'client_id': client_id} if state: params['state'] = state return params def auth_complete_params(self, state=None): client_id, client_secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', 'client_id': client_id, 'scope': self.SCOPE_SEPARATOR.join(self.get_scope()), 'code': self.data['code'] } def auth_headers(self): client_id, client_secret = self.get_key_and_secret() return {'Accept': 'application/json', 'Authorization': 'Bearer {0}'.format(client_secret)} def refresh_token_params(self, refresh_token, *args, **kwargs): return {'refresh_token': refresh_token, 'grant_type': 'refresh_token'} python-social-auth-0.2.13/social/backends/suse.py000066400000000000000000000007131260133235600217040ustar00rootroot00000000000000""" Open Suse OpenId backend, docs at: http://psa.matiasaguirre.net/docs/backends/suse.html """ from social.backends.open_id import OpenIdAuth class OpenSUSEOpenId(OpenIdAuth): name = 'opensuse' URL = 'https://www.opensuse.org/openid/user/' def get_user_id(self, details, response): """ Return user unique id provided by service. For openSUSE the nickname is original. """ return details['nickname'] python-social-auth-0.2.13/social/backends/taobao.py000066400000000000000000000015741260133235600222000ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class TAOBAOAuth(BaseOAuth2): """Taobao OAuth authentication mechanism""" name = 'taobao' ID_KEY = 'taobao_user_id' ACCESS_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://oauth.taobao.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.taobao.com/token' def user_data(self, access_token, *args, **kwargs): """Return user data provided""" try: return self.get_json('https://eco.taobao.com/router/rest', params={ 'method': 'taobao.user.get', 'fomate': 'json', 'v': '2.0', 'access_token': access_token }) except ValueError: return None def get_user_details(self, response): """Return user details from Taobao account""" return {'username': response.get('taobao_user_nick')} python-social-auth-0.2.13/social/backends/thisismyjam.py000066400000000000000000000023031260133235600232630ustar00rootroot00000000000000""" ThisIsMyJam OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/thisismyjam.html """ from social.backends.oauth import BaseOAuth1 class ThisIsMyJamOAuth1(BaseOAuth1): """ThisIsMyJam OAuth1 authentication backend""" name = 'thisismyjam' REQUEST_TOKEN_URL = 'http://www.thisismyjam.com/oauth/request_token' AUTHORIZATION_URL = 'http://www.thisismyjam.com/oauth/authorize' ACCESS_TOKEN_URL = 'http://www.thisismyjam.com/oauth/access_token' REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' def get_user_details(self, response): """Return user details from ThisIsMyJam account""" info = response.get('person') fullname, first_name, last_name = self.get_user_names( info.get('fullname') ) return { 'username': info.get('name'), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('http://api.thisismyjam.com/1/verify.json', auth=self.oauth_auth(access_token)) python-social-auth-0.2.13/social/backends/trello.py000066400000000000000000000027511260133235600222320ustar00rootroot00000000000000""" Trello OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/trello.html """ from social.backends.oauth import BaseOAuth1 class TrelloOAuth(BaseOAuth1): """Trello OAuth authentication backend""" name = 'trello' ID_KEY = 'username' AUTHORIZATION_URL = 'https://trello.com/1/OAuthAuthorizeToken' REQUEST_TOKEN_URL = 'https://trello.com/1/OAuthGetRequestToken' ACCESS_TOKEN_URL = 'https://trello.com/1/OAuthGetAccessToken' EXTRA_DATA = [ ('username', 'username'), ('email', 'email'), ('fullName', 'fullName') ] def get_user_details(self, response): """Return user details from Trello account""" fullname, first_name, last_name = self.get_user_names( response.get('fullName') ) return {'username': response.get('username'), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token): """Return user data provided""" url = 'https://trello.com/1/members/me' try: return self.get_json(url, auth=self.oauth_auth(access_token)) except ValueError: return None def auth_extra_arguments(self): return { 'name': self.setting('APP_NAME', ''), # trello default expiration is '30days' 'expiration': self.setting('EXPIRATION', 'never') } python-social-auth-0.2.13/social/backends/tripit.py000066400000000000000000000033601260133235600222410ustar00rootroot00000000000000""" Tripit OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/tripit.html """ from xml.dom import minidom from social.backends.oauth import BaseOAuth1 class TripItOAuth(BaseOAuth1): """TripIt OAuth authentication backend""" name = 'tripit' AUTHORIZATION_URL = 'https://www.tripit.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://api.tripit.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.tripit.com/oauth/access_token' EXTRA_DATA = [('screen_name', 'screen_name')] def get_user_details(self, response): """Return user details from TripIt account""" fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['screen_name'], 'email': response['email'], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" dom = minidom.parseString(self.oauth_request( access_token, 'https://api.tripit.com/v1/get/profile' ).content) return { 'id': dom.getElementsByTagName('Profile')[0].getAttribute('ref'), 'name': dom.getElementsByTagName('public_display_name')[0] .childNodes[0].data, 'screen_name': dom.getElementsByTagName('screen_name')[0] .childNodes[0].data, 'email': dom.getElementsByTagName('is_primary')[0] .parentNode .getElementsByTagName('address')[0] .childNodes[0].data } python-social-auth-0.2.13/social/backends/tumblr.py000066400000000000000000000021241260133235600222300ustar00rootroot00000000000000""" Tumblr OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/tumblr.html """ from social.utils import first from social.backends.oauth import BaseOAuth1 class TumblrOAuth(BaseOAuth1): name = 'tumblr' ID_KEY = 'name' AUTHORIZATION_URL = 'http://www.tumblr.com/oauth/authorize' REQUEST_TOKEN_URL = 'http://www.tumblr.com/oauth/request_token' REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_URL = 'http://www.tumblr.com/oauth/access_token' def get_user_id(self, details, response): return response['response']['user'][self.ID_KEY] def get_user_details(self, response): # http://www.tumblr.com/docs/en/api/v2#user-methods user_info = response['response']['user'] data = {'username': user_info['name']} blog = first(lambda blog: blog['primary'], user_info['blogs']) if blog: data['fullname'] = blog['title'] return data def user_data(self, access_token): return self.get_json('http://api.tumblr.com/v2/user/info', auth=self.oauth_auth(access_token)) python-social-auth-0.2.13/social/backends/twilio.py000066400000000000000000000025501260133235600222350ustar00rootroot00000000000000""" Amazon auth backend, docs at: http://psa.matiasaguirre.net/docs/backends/twilio.html """ from re import sub from social.p3 import urlencode from social.backends.base import BaseAuth class TwilioAuth(BaseAuth): name = 'twilio' ID_KEY = 'AccountSid' def get_user_details(self, response): """Return twilio details, Twilio only provides AccountSID as parameters.""" # /complete/twilio/?AccountSid=ACc65ea16c9ebd4d4684edf814995b27e return {'username': response['AccountSid'], 'email': '', 'fullname': '', 'first_name': '', 'last_name': ''} def auth_url(self): """Return authorization redirect url.""" key, secret = self.get_key_and_secret() callback = self.strategy.absolute_uri(self.redirect_uri) callback = sub(r'^https', 'http', callback) query = urlencode({'cb': callback}) return 'https://www.twilio.com/authorize/{0}?{1}'.format(key, query) def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" account_sid = self.data.get('AccountSid') if not account_sid: raise ValueError('No AccountSid returned') kwargs.update({'response': self.data, 'backend': self}) return self.strategy.authenticate(*args, **kwargs) python-social-auth-0.2.13/social/backends/twitch.py000066400000000000000000000016161260133235600222320ustar00rootroot00000000000000""" Twitch OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/twitch.html """ from social.backends.oauth import BaseOAuth2 class TwitchOAuth2(BaseOAuth2): """Twitch OAuth authentication backend""" name = 'twitch' ID_KEY = '_id' AUTHORIZATION_URL = 'https://api.twitch.tv/kraken/oauth2/authorize' ACCESS_TOKEN_URL = 'https://api.twitch.tv/kraken/oauth2/token' ACCESS_TOKEN_METHOD = 'POST' DEFAULT_SCOPE = ['user_read'] REDIRECT_STATE = False def get_user_details(self, response): return { 'username': response.get('name'), 'email': response.get('email'), 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): return self.get_json( 'https://api.twitch.tv/kraken/user/', params={'oauth_token': access_token} ) python-social-auth-0.2.13/social/backends/twitter.py000066400000000000000000000026541260133235600224350ustar00rootroot00000000000000""" Twitter OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/twitter.html """ from social.backends.oauth import BaseOAuth1 from social.exceptions import AuthCanceled class TwitterOAuth(BaseOAuth1): """Twitter OAuth authentication backend""" name = 'twitter' EXTRA_DATA = [('id', 'id')] REQUEST_TOKEN_METHOD = 'POST' ACCESS_TOKEN_METHOD = 'POST' AUTHORIZATION_URL = 'https://api.twitter.com/oauth/authenticate' REQUEST_TOKEN_URL = 'https://api.twitter.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://api.twitter.com/oauth/access_token' REDIRECT_STATE = True def process_error(self, data): if 'denied' in data: raise AuthCanceled(self) else: super(TwitterOAuth, self).process_error(data) def get_user_details(self, response): """Return user details from Twitter account""" fullname, first_name, last_name = self.get_user_names(response['name']) return {'username': response['screen_name'], 'email': '', # not supplied 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.twitter.com/1.1/account/verify_credentials.json', auth=self.oauth_auth(access_token) ) python-social-auth-0.2.13/social/backends/uber.py000066400000000000000000000025561260133235600216710ustar00rootroot00000000000000""" Uber OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/uber.html """ from social.backends.oauth import BaseOAuth2 class UberOAuth2(BaseOAuth2): name = 'uber' ID_KEY='uuid' SCOPE_SEPARATOR = ' ' AUTHORIZATION_URL = 'https://login.uber.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://login.uber.com/oauth/token' ACCESS_TOKEN_METHOD = 'POST' def auth_complete_credentials(self): return self.get_key_and_secret() def get_user_details(self, response): """Return user details from Uber account""" email = response.get('email', '') fullname, first_name, last_name = self.get_user_names() return {'username': email, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" client_id, client_secret = self.get_key_and_secret() response = kwargs.pop('response') return self.get_json('https://api.uber.com/v1/me', headers={ 'Authorization': '{0} {1}'.format( response.get('token_type'), access_token ) } ) python-social-auth-0.2.13/social/backends/ubuntu.py000066400000000000000000000006011260133235600222430ustar00rootroot00000000000000""" Ubuntu One OpenId backend """ from social.backends.open_id import OpenIdAuth class UbuntuOpenId(OpenIdAuth): name = 'ubuntu' URL = 'https://login.ubuntu.com' def get_user_id(self, details, response): """ Return user unique id provided by service. For Ubuntu One the nickname should be original. """ return details['nickname'] python-social-auth-0.2.13/social/backends/username.py000066400000000000000000000004031260133235600225400ustar00rootroot00000000000000""" Legacy Username backend, docs at: http://psa.matiasaguirre.net/docs/backends/username.html """ from social.backends.legacy import LegacyAuth class UsernameAuth(LegacyAuth): name = 'username' ID_KEY = 'username' EXTRA_DATA = ['username'] python-social-auth-0.2.13/social/backends/utils.py000066400000000000000000000061061260133235600220670ustar00rootroot00000000000000from social.exceptions import MissingBackend from social.backends.base import BaseAuth from social.utils import module_member, user_is_authenticated # Cache for discovered backends. BACKENDSCACHE = {} def load_backends(backends, force_load=False): """ Load backends defined on SOCIAL_AUTH_AUTHENTICATION_BACKENDS, backends will be imported and cached on BACKENDSCACHE. The key in that dict will be the backend name, and the value is the backend class. Only subclasses of BaseAuth (and sub-classes) are considered backends. Previously there was a BACKENDS attribute expected on backends modules, this is not needed anymore since it's enough with the AUTHENTICATION_BACKENDS setting. BACKENDS was used because backends used to be split on two classes the authentication backend and another class that dealt with the auth mechanism with the provider, those classes are joined now. A force_load boolean argument is also provided so that get_backend below can retry a requested backend that may not yet be discovered. """ global BACKENDSCACHE if force_load: BACKENDSCACHE = {} if not BACKENDSCACHE: for auth_backend in backends: backend = module_member(auth_backend) if issubclass(backend, BaseAuth): BACKENDSCACHE[backend.name] = backend return BACKENDSCACHE def get_backend(backends, name): """Returns a backend by name. Backends are stored in the BACKENDSCACHE cache dict. If not found, each of the modules referenced in AUTHENTICATION_BACKENDS is imported and checked for a BACKENDS definition. If the named backend is found in the module's BACKENDS definition, it's then stored in the cache for future access. """ try: # Cached backend which has previously been discovered return BACKENDSCACHE[name] except KeyError: # Reload BACKENDS to ensure a missing backend hasn't been missed load_backends(backends, force_load=True) try: return BACKENDSCACHE[name] except KeyError: raise MissingBackend(name) def user_backends_data(user, backends, storage): """ Will return backends data for given user, the return value will have the following keys: associated: UserSocialAuth model instances for currently associated accounts not_associated: Not associated (yet) backend names backends: All backend names. If user is not authenticated, then 'associated' list is empty, and there's no difference between 'not_associated' and 'backends'. """ available = list(load_backends(backends).keys()) values = {'associated': [], 'not_associated': available, 'backends': available} if user_is_authenticated(user): associated = storage.user.get_social_auth_for_user(user) not_associated = list(set(available) - set(assoc.provider for assoc in associated)) values['associated'] = associated values['not_associated'] = not_associated return values python-social-auth-0.2.13/social/backends/vend.py000066400000000000000000000023321260133235600216600ustar00rootroot00000000000000""" Vend OAuth2 backend: """ from social.backends.oauth import BaseOAuth2 class VendOAuth2(BaseOAuth2): name = 'vend' AUTHORIZATION_URL = 'https://secure.vendhq.com/connect' ACCESS_TOKEN_URL = 'https://{0}.vendhq.com/api/1.0/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('refresh_token', 'refresh_token'), ('domain_prefix', 'domain_prefix') ] def access_token_url(self): return self.ACCESS_TOKEN_URL.format(self.data['domain_prefix']) def get_user_details(self, response): email = response['email'] username = response.get('username') or email.split('@', 1)[0] return { 'username': username, 'email': email, 'fullname': '', 'first_name': '', 'last_name': '' } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" prefix = kwargs['response']['domain_prefix'] url = 'https://{0}.vendhq.com/api/users'.format(prefix) data = self.get_json(url, headers={ 'Authorization': 'Bearer {0}'.format(access_token) }) return data['users'][0] if data.get('users') else {} python-social-auth-0.2.13/social/backends/vimeo.py000066400000000000000000000054041260133235600220460ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth1, BaseOAuth2 class VimeoOAuth1(BaseOAuth1): """Vimeo OAuth authentication backend""" name = 'vimeo' AUTHORIZATION_URL = 'https://vimeo.com/oauth/authorize' REQUEST_TOKEN_URL = 'https://vimeo.com/oauth/request_token' ACCESS_TOKEN_URL = 'https://vimeo.com/oauth/access_token' def get_user_id(self, details, response): return response.get('person', {}).get('id') def get_user_details(self, response): """Return user details from Twitter account""" person = response.get('person', {}) fullname, first_name, last_name = self.get_user_names( person.get('display_name', '') ) return {'username': person.get('username', ''), 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://vimeo.com/api/rest/v2', params={'format': 'json', 'method': 'vimeo.people.getInfo'}, auth=self.oauth_auth(access_token) ) class VimeoOAuth2(BaseOAuth2): """Vimeo OAuth2 authentication backend""" name = 'vimeo-oauth2' AUTHORIZATION_URL = 'https://api.vimeo.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://api.vimeo.com/oauth/access_token' REFRESH_TOKEN_URL = 'https://api.vimeo.com/oauth/request_token' ACCESS_TOKEN_METHOD = 'POST' SCOPE_SEPARATOR = ',' API_ACCEPT_HEADER = {'Accept': 'application/vnd.vimeo.*+json;version=3.0'} def get_redirect_uri(self, state=None): """ Build redirect with redirect_state parameter. @Vimeo API 3 requires exact redirect uri without additional additional state parameter included """ return self.redirect_uri def get_user_id(self, details, response): """Return user id""" try: user_id = response.get('user', {})['uri'].split('/')[-1] except KeyError: user_id = None return user_id def get_user_details(self, response): """Return user details from account""" user = response.get('user', {}) fullname, first_name, last_name = self.get_user_names( user.get('name', '') ) return {'username': fullname, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" return self.get_json( 'https://api.vimeo.com/me', params={'access_token': access_token}, headers=VimeoOAuth2.API_ACCEPT_HEADER, ) python-social-auth-0.2.13/social/backends/vk.py000066400000000000000000000167511260133235600213560ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ VK.com OpenAPI, OAuth2 and Iframe application OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/vk.html """ from time import time from hashlib import md5 from social.utils import parse_qs from social.backends.base import BaseAuth from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthTokenRevoked, AuthException class VKontakteOpenAPI(BaseAuth): """VK.COM OpenAPI authentication backend""" name = 'vk-openapi' ID_KEY = 'id' def get_user_details(self, response): """Return user details from VK.com request""" nickname = response.get('nickname') or '' fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name', [''])[0], last_name=response.get('last_name', [''])[0] ) return { 'username': response['id'] if len(nickname) == 0 else nickname, 'email': '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): return self.data def auth_html(self): """Returns local VK authentication page, not necessary for VK to authenticate. """ ctx = {'VK_APP_ID': self.setting('APP_ID'), 'VK_COMPLETE_URL': self.redirect_uri} local_html = self.setting('LOCAL_HTML', 'vkontakte.html') return self.strategy.render_html(tpl=local_html, context=ctx) def auth_complete(self, *args, **kwargs): """Performs check of authentication in VKontakte, returns User if succeeded""" session_value = self.strategy.session_get( 'vk_app_' + self.setting('APP_ID') ) if 'id' not in self.data or not session_value: raise ValueError('VK.com authentication is not completed') mapping = parse_qs(session_value) check_str = ''.join(item + '=' + mapping[item] for item in ['expire', 'mid', 'secret', 'sid']) key, secret = self.get_key_and_secret() hash = md5((check_str + secret).encode('utf-8')).hexdigest() if hash != mapping['sig'] or int(mapping['expire']) < time(): raise ValueError('VK.com authentication failed: Invalid Hash') kwargs.update({'backend': self, 'response': self.user_data(mapping['mid'])}) return self.strategy.authenticate(*args, **kwargs) def uses_redirect(self): """VK.com does not require visiting server url in order to do authentication, so auth_xxx methods are not needed to be called. Their current implementation is just an example""" return False class VKOAuth2(BaseOAuth2): """VKOAuth2 authentication backend""" name = 'vk-oauth2' ID_KEY = 'user_id' AUTHORIZATION_URL = 'http://oauth.vk.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.vk.com/access_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('id', 'id'), ('expires_in', 'expires') ] def get_user_id(self, details, response): return response['uid'] def get_user_details(self, response): """Return user details from VK.com account""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('first_name'), last_name=response.get('last_name') ) return {'username': response.get('screen_name'), 'email': response.get('email', ''), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" request_data = ['first_name', 'last_name', 'screen_name', 'nickname', 'photo'] + self.setting('EXTRA_DATA', []) fields = ','.join(set(request_data)) data = vk_api(self, 'users.get', { 'access_token': access_token, 'fields': fields, }) if data and data.get('error'): error = data['error'] msg = error.get('error_msg', 'Unknown error') if error.get('error_code') == 5: raise AuthTokenRevoked(self, msg) else: raise AuthException(self, msg) if data: data = data.get('response')[0] data['user_photo'] = data.get('photo') # Backward compatibility return data or {} class VKAppOAuth2(VKOAuth2): """VK.com Application Authentication support""" name = 'vk-app' def user_profile(self, user_id, access_token=None): request_data = ['first_name', 'last_name', 'screen_name', 'nickname', 'photo'] + self.setting('EXTRA_DATA', []) fields = ','.join(set(request_data)) data = {'uids': user_id, 'fields': fields} if access_token: data['access_token'] = access_token profiles = vk_api(self, 'getProfiles', data).get('response') if profiles: return profiles[0] def auth_complete(self, *args, **kwargs): required_params = ('is_app_user', 'viewer_id', 'access_token', 'api_id') if not all(param in self.data for param in required_params): return None auth_key = self.data.get('auth_key') # Verify signature, if present key, secret = self.get_key_and_secret() if auth_key: check_key = md5('_'.join([key, self.data.get('viewer_id'), secret]).encode('utf-8')).hexdigest() if check_key != auth_key: raise ValueError('VK.com authentication failed: invalid ' 'auth key') user_check = self.setting('USERMODE') user_id = self.data.get('viewer_id') if user_check is not None: user_check = int(user_check) if user_check == 1: is_user = self.data.get('is_app_user') elif user_check == 2: is_user = vk_api(self, 'isAppUser', {'uid': user_id}).get('response', 0) if not int(is_user): return None auth_data = { 'auth': self, 'backend': self, 'request': self.strategy.request_data(), 'response': { 'user_id': user_id, } } auth_data['response'].update(self.user_profile(user_id)) return self.strategy.authenticate(*args, **auth_data) def vk_api(backend, method, data): """ Calls VK.com OpenAPI method, check: https://vk.com/apiclub http://goo.gl/yLcaa """ # We need to perform server-side call if no access_token data['v'] = backend.setting('API_VERSION', '3.0') if 'access_token' not in data: key, secret = backend.get_key_and_secret() if 'api_id' not in data: data['api_id'] = key data['method'] = method data['format'] = 'json' url = 'http://api.vk.com/api.php' param_list = sorted(list(item + '=' + data[item] for item in data)) data['sig'] = md5( (''.join(param_list) + secret).encode('utf-8') ).hexdigest() else: url = 'https://api.vk.com/method/' + method try: return backend.get_json(url, params=data) except (TypeError, KeyError, IOError, ValueError, IndexError): return None python-social-auth-0.2.13/social/backends/weibo.py000066400000000000000000000042231260133235600220320ustar00rootroot00000000000000# coding:utf8 # author:hepochen@gmail.com https://github.com/hepochen """ Weibo OAuth2 backend, docs at: http://psa.matiasaguirre.net/docs/backends/weibo.html """ from social.backends.oauth import BaseOAuth2 class WeiboOAuth2(BaseOAuth2): """Weibo (of sina) OAuth authentication backend""" name = 'weibo' ID_KEY = 'uid' AUTHORIZATION_URL = 'https://api.weibo.com/oauth2/authorize' REQUEST_TOKEN_URL = 'https://api.weibo.com/oauth2/request_token' ACCESS_TOKEN_URL = 'https://api.weibo.com/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('id', 'id'), ('name', 'username'), ('profile_image_url', 'profile_image_url'), ('gender', 'gender') ] def get_user_details(self, response): """Return user details from Weibo. API URL is: https://api.weibo.com/2/users/show.json/?uid=&access_token= """ if self.setting('DOMAIN_AS_USERNAME'): username = response.get('domain', '') else: username = response.get('name', '') fullname, first_name, last_name = self.get_user_names( first_name=response.get('screen_name', '') ) return {'username': username, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def get_uid(self, access_token): """Return uid by access_token""" data = self.get_json( 'https://api.weibo.com/oauth2/get_token_info', method='POST', params={'access_token': access_token} ) return data['uid'] def user_data(self, access_token, response=None, *args, **kwargs): """Return user data""" # If user id was not retrieved in the response, then get it directly # from weibo get_token_info endpoint uid = response and response.get('uid') or self.get_uid(access_token) user_data = self.get_json( 'https://api.weibo.com/2/users/show.json', params={'access_token': access_token, 'uid': uid} ) user_data['uid'] = uid return user_data python-social-auth-0.2.13/social/backends/weixin.py000066400000000000000000000067151260133235600222400ustar00rootroot00000000000000# -*- coding: utf-8 -*- # author:duoduo3369@gmail.com https://github.com/duoduo369 """ Weixin OAuth2 backend """ from requests import HTTPError from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthCanceled, AuthUnknownError class WeixinOAuth2(BaseOAuth2): """Weixin OAuth authentication backend""" name = 'weixin' ID_KEY = 'openid' AUTHORIZATION_URL = 'https://open.weixin.qq.com/connect/qrconnect' ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False EXTRA_DATA = [ ('nickname', 'username'), ('headimgurl', 'profile_image_url'), ] def get_user_details(self, response): """Return user details from Weixin. API URL is: https://api.weixin.qq.com/sns/userinfo """ if self.setting('DOMAIN_AS_USERNAME'): username = response.get('domain', '') else: username = response.get('nickname', '') return { 'username': username, 'profile_image_url': response.get('headimgurl', '') } def user_data(self, access_token, *args, **kwargs): data = self.get_json('https://api.weixin.qq.com/sns/userinfo', params={ 'access_token': access_token, 'openid': kwargs['response']['openid'] }) nickname = data.get('nickname') if nickname: # weixin api has some encode bug, here need handle data['nickname'] = nickname.encode('raw_unicode_escape').decode('utf-8') return data def auth_params(self, state=None): appid, secret = self.get_key_and_secret() params = { 'appid': appid, 'redirect_uri': self.get_redirect_uri(state) } if self.STATE_PARAMETER and state: params['state'] = state if self.RESPONSE_TYPE: params['response_type'] = self.RESPONSE_TYPE return params def auth_complete_params(self, state=None): appid, secret = self.get_key_and_secret() return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'appid': appid, 'secret': secret, 'redirect_uri': self.get_redirect_uri(state) } def refresh_token_params(self, token, *args, **kwargs): appid, secret = self.get_key_and_secret() return { 'refresh_token': token, 'grant_type': 'refresh_token', 'appid': appid, 'secret': secret } def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) try: response = self.request_access_token( self.ACCESS_TOKEN_URL, data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) except HTTPError as err: if err.response.status_code == 400: raise AuthCanceled(self) else: raise except KeyError: raise AuthUnknownError(self) if 'errcode' in response: raise AuthCanceled(self) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) python-social-auth-0.2.13/social/backends/withings.py000066400000000000000000000010251260133235600225560ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth1 class WithingsOAuth(BaseOAuth1): name = 'withings' AUTHORIZATION_URL = 'https://oauth.withings.com/account/authorize' REQUEST_TOKEN_URL = 'https://oauth.withings.com/account/request_token' ACCESS_TOKEN_URL = 'https://oauth.withings.com/account/access_token' ID_KEY = 'userid' def get_user_details(self, response): """Return user details from Withings account""" return {'userid': response['access_token']['userid'], 'email': ''} python-social-auth-0.2.13/social/backends/wunderlist.py000066400000000000000000000021111260133235600231170ustar00rootroot00000000000000from social.backends.oauth import BaseOAuth2 class WunderlistOAuth2(BaseOAuth2): """Wunderlist OAuth2 authentication backend""" name = 'wunderlist' AUTHORIZATION_URL = 'https://www.wunderlist.com/oauth/authorize' ACCESS_TOKEN_URL = 'https://www.wunderlist.com/oauth/access_token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): """Return user details from Wunderlist account""" fullname, first_name, last_name = self.get_user_names( response.get('name') ) return {'username': str(response.get('id')), 'email': response.get('email'), 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" headers = { 'X-Access-Token': access_token, 'X-Client-ID': self.setting('KEY')} return self.get_json( 'https://a.wunderlist.com/api/v1/user', headers=headers) python-social-auth-0.2.13/social/backends/xing.py000066400000000000000000000027571260133235600217040ustar00rootroot00000000000000""" XING OAuth1 backend, docs at: http://psa.matiasaguirre.net/docs/backends/xing.html """ from social.backends.oauth import BaseOAuth1 class XingOAuth(BaseOAuth1): """Xing OAuth authentication backend""" name = 'xing' AUTHORIZATION_URL = 'https://api.xing.com/v1/authorize' REQUEST_TOKEN_URL = 'https://api.xing.com/v1/request_token' ACCESS_TOKEN_URL = 'https://api.xing.com/v1/access_token' SCOPE_SEPARATOR = '+' EXTRA_DATA = [ ('id', 'id'), ('user_id', 'user_id') ] def get_user_details(self, response): """Return user details from Xing account""" email = response.get('email', '') fullname, first_name, last_name = self.get_user_names( first_name=response['first_name'], last_name=response['last_name'] ) return {'username': first_name + last_name, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'email': email} def user_data(self, access_token, *args, **kwargs): """Return user data provided""" profile = self.get_json( 'https://api.xing.com/v1/users/me.json', auth=self.oauth_auth(access_token) )['users'][0] return { 'user_id': profile['id'], 'id': profile['id'], 'first_name': profile['first_name'], 'last_name': profile['last_name'], 'email': profile['active_email'] } python-social-auth-0.2.13/social/backends/yahoo.py000066400000000000000000000135601260133235600220500ustar00rootroot00000000000000""" Yahoo OpenId, OAuth1 and OAuth2 backends, docs at: http://psa.matiasaguirre.net/docs/backends/yahoo.html """ from requests.auth import HTTPBasicAuth from social.utils import handle_http_errors from social.backends.open_id import OpenIdAuth from social.backends.oauth import BaseOAuth2, BaseOAuth1 class YahooOpenId(OpenIdAuth): """Yahoo OpenID authentication backend""" name = 'yahoo' URL = 'http://me.yahoo.com' class YahooOAuth(BaseOAuth1): """Yahoo OAuth authentication backend. DEPRECATED""" name = 'yahoo-oauth' ID_KEY = 'guid' AUTHORIZATION_URL = 'https://api.login.yahoo.com/oauth/v2/request_auth' REQUEST_TOKEN_URL = \ 'https://api.login.yahoo.com/oauth/v2/get_request_token' ACCESS_TOKEN_URL = 'https://api.login.yahoo.com/oauth/v2/get_token' EXTRA_DATA = [ ('guid', 'id'), ('access_token', 'access_token'), ('expires', 'expires') ] def get_user_details(self, response): """Return user details from Yahoo Profile""" fullname, first_name, last_name = self.get_user_names( first_name=response.get('givenName'), last_name=response.get('familyName') ) emails = [email for email in response.get('emails', []) if email.get('handle')] emails.sort(key=lambda e: e.get('primary', False), reverse=True) return {'username': response.get('nickname'), 'email': emails[0]['handle'] if emails else '', 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://social.yahooapis.com/v1/user/{0}/profile?format=json' return self.get_json( url.format(self._get_guid(access_token)), auth=self.oauth_auth(access_token) )['profile'] def _get_guid(self, access_token): """ Beause you have to provide GUID for every API request it's also returned during one of OAuth calls """ return self.get_json( 'https://social.yahooapis.com/v1/me/guid?format=json', auth=self.oauth_auth(access_token) )['guid']['value'] class YahooOAuth2(BaseOAuth2): """Yahoo OAuth2 authentication backend""" name = 'yahoo-oauth2' ID_KEY = 'guid' AUTHORIZATION_URL = 'https://api.login.yahoo.com/oauth2/request_auth' ACCESS_TOKEN_URL = 'https://api.login.yahoo.com/oauth2/get_token' ACCESS_TOKEN_METHOD = 'POST' EXTRA_DATA = [ ('xoauth_yahoo_guid', 'id'), ('access_token', 'access_token'), ('expires_in', 'expires'), ('refresh_token', 'refresh_token'), ('token_type', 'token_type'), ] def get_user_names(self, first_name, last_name): if first_name or last_name: return ' '.join((first_name, last_name)), first_name, last_name return None, None, None def get_user_details(self, response): """ Return user details from Yahoo Profile. To Get user email you need the profile private read permission. """ fullname, first_name, last_name = self.get_user_names( first_name=response.get('givenName'), last_name=response.get('familyName') ) emails = [email for email in response.get('emails', []) if 'handle' in email] emails.sort(key=lambda e: e.get('primary', False), reverse=True) email = emails[0]['handle'] if emails else response.get('guid', '') return { 'username': response.get('nickname'), 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name } def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" url = 'https://social.yahooapis.com/v1/user/{0}/profile?format=json' \ .format(kwargs['response']['xoauth_yahoo_guid']) return self.get_json(url, headers={ 'Authorization': 'Bearer {0}'.format(access_token) }, method='GET')['profile'] @handle_http_errors def auth_complete(self, *args, **kwargs): """Completes loging process, must return user instance""" self.process_error(self.data) response = self.request_access_token( self.ACCESS_TOKEN_URL, auth=HTTPBasicAuth(*self.get_key_and_secret()), data=self.auth_complete_params(self.validate_state()), headers=self.auth_headers(), method=self.ACCESS_TOKEN_METHOD ) self.process_error(response) return self.do_auth(response['access_token'], response=response, *args, **kwargs) def refresh_token_params(self, token, *args, **kwargs): return { 'refresh_token': token, 'grant_type': 'refresh_token', 'redirect_uri': 'oob', # out of bounds } def refresh_token(self, token, *args, **kwargs): params = self.refresh_token_params(token, *args, **kwargs) url = self.REFRESH_TOKEN_URL or self.ACCESS_TOKEN_URL method = self.REFRESH_TOKEN_METHOD key = 'params' if method == 'GET' else 'data' request_args = { 'headers': self.auth_headers(), 'method': method, key: params } request = self.request( url, auth=HTTPBasicAuth(*self.get_key_and_secret()), **request_args ) return self.process_refresh_token_response(request, *args, **kwargs) def auth_complete_params(self, state=None): return { 'grant_type': 'authorization_code', # request auth code 'code': self.data.get('code', ''), # server response code 'redirect_uri': self.get_redirect_uri(state) } python-social-auth-0.2.13/social/backends/yammer.py000066400000000000000000000030001260133235600222070ustar00rootroot00000000000000""" Yammer OAuth2 production and staging backends, docs at: http://psa.matiasaguirre.net/docs/backends/yammer.html """ from social.backends.oauth import BaseOAuth2 class YammerOAuth2(BaseOAuth2): name = 'yammer' AUTHORIZATION_URL = 'https://www.yammer.com/dialog/oauth' ACCESS_TOKEN_URL = 'https://www.yammer.com/oauth2/access_token' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires'), ('mugshot_url', 'mugshot_url') ] def get_user_id(self, details, response): return response['user']['id'] def get_user_details(self, response): username = response['user']['name'] fullname, first_name, last_name = self.get_user_names( fullname=response['user']['full_name'], first_name=response['user']['first_name'], last_name=response['user']['last_name'] ) email = response['user']['contact']['email_addresses'][0]['address'] mugshot_url = response['user']['mugshot_url'] return { 'username': username, 'email': email, 'fullname': fullname, 'first_name': first_name, 'last_name': last_name, 'picture_url': mugshot_url } class YammerStagingOAuth2(YammerOAuth2): name = 'yammer-staging' AUTHORIZATION_URL = 'https://www.staging.yammer.com/dialog/oauth' ACCESS_TOKEN_URL = 'https://www.staging.yammer.com/oauth2/access_token' REQUEST_TOKEN_URL = 'https://www.staging.yammer.com/oauth2/request_token' python-social-auth-0.2.13/social/backends/yandex.py000066400000000000000000000057161260133235600222250ustar00rootroot00000000000000""" Yandex OpenID and OAuth2 support. This contribution adds support for Yandex.ru OpenID service in the form openid.yandex.ru/user. Username is retrieved from the identity url. If username is not specified, OpenID 2.0 url used for authentication. """ from social.p3 import urlsplit from social.backends.open_id import OpenIdAuth from social.backends.oauth import BaseOAuth2 class YandexOpenId(OpenIdAuth): """Yandex OpenID authentication backend""" name = 'yandex-openid' URL = 'http://openid.yandex.ru' def get_user_id(self, details, response): return details['email'] or response.identity_url def get_user_details(self, response): """Generate username from identity url""" values = super(YandexOpenId, self).get_user_details(response) values['username'] = values.get('username') or\ urlsplit(response.identity_url)\ .path.strip('/') values['email'] = values.get('email', '') return values class YandexOAuth2(BaseOAuth2): """Legacy Yandex OAuth2 authentication backend""" name = 'yandex-oauth2' AUTHORIZATION_URL = 'https://oauth.yandex.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.yandex.com/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('real_name') or response.get('display_name') or '' ) return {'username': response.get('display_name'), 'email': response.get('default_email') or response.get('emails', [''])[0], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, response, *args, **kwargs): return self.get_json('https://login.yandex.ru/info', params={'oauth_token': access_token, 'format': 'json'}) class YaruOAuth2(BaseOAuth2): name = 'yaru' AUTHORIZATION_URL = 'https://oauth.yandex.com/authorize' ACCESS_TOKEN_URL = 'https://oauth.yandex.com/token' ACCESS_TOKEN_METHOD = 'POST' REDIRECT_STATE = False def get_user_details(self, response): fullname, first_name, last_name = self.get_user_names( response.get('real_name') or response.get('display_name') or '' ) return {'username': response.get('display_name'), 'email': response.get('default_email') or response.get('emails', [''])[0], 'fullname': fullname, 'first_name': first_name, 'last_name': last_name} def user_data(self, access_token, response, *args, **kwargs): return self.get_json('https://login.yandex.ru/info', params={'oauth_token': access_token, 'format': 'json'}) python-social-auth-0.2.13/social/backends/zotero.py000066400000000000000000000016551260133235600222550ustar00rootroot00000000000000""" Zotero OAuth1 backends, docs at: http://psa.matiasaguirre.net/docs/backends/zotero.html """ from social.backends.oauth import BaseOAuth1 class ZoteroOAuth(BaseOAuth1): """Zotero OAuth authorization mechanism""" name = 'zotero' AUTHORIZATION_URL = 'https://www.zotero.org/oauth/authorize' REQUEST_TOKEN_URL = 'https://www.zotero.org/oauth/request' ACCESS_TOKEN_URL = 'https://www.zotero.org/oauth/access' def get_user_id(self, details, response): """ Return user unique id provided by service. For Ubuntu One the nickname should be original. """ return details['userID'] def get_user_details(self, response): """Return user details from Zotero API account""" access_token = response.get('access_token', {}) return { 'username': access_token.get('username', ''), 'userID': access_token.get('userID', '') } python-social-auth-0.2.13/social/exceptions.py000066400000000000000000000061071260133235600213370ustar00rootroot00000000000000class SocialAuthBaseException(ValueError): """Base class for pipeline exceptions.""" pass class WrongBackend(SocialAuthBaseException): def __init__(self, backend_name): self.backend_name = backend_name def __str__(self): return 'Incorrect authentication service "{0}"'.format( self.backend_name ) class MissingBackend(WrongBackend): def __str__(self): return 'Missing backend "{0}" entry'.format(self.backend_name) class NotAllowedToDisconnect(SocialAuthBaseException): """User is not allowed to disconnect it's social account.""" pass class AuthException(SocialAuthBaseException): """Auth process exception.""" def __init__(self, backend, *args, **kwargs): self.backend = backend super(AuthException, self).__init__(*args, **kwargs) class AuthFailed(AuthException): """Auth process failed for some reason.""" def __str__(self): msg = super(AuthFailed, self).__str__() if msg == 'access_denied': return 'Authentication process was canceled' return 'Authentication failed: {0}'.format(msg) class AuthCanceled(AuthException): """Auth process was canceled by user.""" def __str__(self): return 'Authentication process canceled' class AuthUnknownError(AuthException): """Unknown auth process error.""" def __str__(self): msg = super(AuthUnknownError, self).__str__() return 'An unknown error happened while authenticating {0}'.format(msg) class AuthTokenError(AuthException): """Auth token error.""" def __str__(self): msg = super(AuthTokenError, self).__str__() return 'Token error: {0}'.format(msg) class AuthMissingParameter(AuthException): """Missing parameter needed to start or complete the process.""" def __init__(self, backend, parameter, *args, **kwargs): self.parameter = parameter super(AuthMissingParameter, self).__init__(backend, *args, **kwargs) def __str__(self): return 'Missing needed parameter {0}'.format(self.parameter) class AuthStateMissing(AuthException): """State parameter is incorrect.""" def __str__(self): return 'Session value state missing.' class AuthStateForbidden(AuthException): """State parameter is incorrect.""" def __str__(self): return 'Wrong state parameter given.' class AuthAlreadyAssociated(AuthException): """A different user has already associated the target social account""" pass class AuthTokenRevoked(AuthException): """User revoked the access_token in the provider.""" def __str__(self): return 'User revoke access to the token' class AuthForbidden(AuthException): """Authentication for this user is forbidden""" def __str__(self): return 'Your credentials aren\'t allowed' class AuthUnreachableProvider(AuthException): """Cannot reach the provider""" def __str__(self): return 'The authentication provider could not be reached' class InvalidEmail(AuthException): def __str__(self): return 'Email couldn\'t be validated' python-social-auth-0.2.13/social/p3.py000066400000000000000000000011271260133235600174750ustar00rootroot00000000000000# Python3 support, keep import hacks here import six if six.PY3: from urllib.parse import parse_qs, urlparse, urlunparse, quote, \ urlsplit, urlencode, unquote from io import StringIO else: try: from urlparse import parse_qs except ImportError: # fall back for Python 2.5 from cgi import parse_qs from urlparse import urlparse, urlunparse, urlsplit from urllib import urlencode, unquote, quote from StringIO import StringIO # Placate pyflakes parse_qs, urlparse, urlunparse, quote, urlsplit, urlencode, unquote, StringIO python-social-auth-0.2.13/social/pipeline/000077500000000000000000000000001260133235600204055ustar00rootroot00000000000000python-social-auth-0.2.13/social/pipeline/__init__.py000066400000000000000000000045221260133235600225210ustar00rootroot00000000000000DEFAULT_AUTH_PIPELINE = ( # Get the information we can about the user and return it in a simple # format to create the user instance later. On some cases the details are # already part of the auth response from the provider, but sometimes this # could hit a provider API. 'social.pipeline.social_auth.social_details', # Get the social uid from whichever service we're authing thru. The uid is # the unique identifier of the given user in the provider. 'social.pipeline.social_auth.social_uid', # Verifies that the current auth process is valid within the current # project, this is were emails and domains whitelists are applied (if # defined). 'social.pipeline.social_auth.auth_allowed', # Checks if the current social-account is already associated in the site. 'social.pipeline.social_auth.social_user', # Make up a username for this person, appends a random string at the end if # there's any collision. 'social.pipeline.user.get_username', # Send a validation email to the user to verify its email address. # 'social.pipeline.mail.mail_validation', # Associates the current social details with another user account with # a similar email address. # 'social.pipeline.social_auth.associate_by_email', # Create a user account if we haven't found one yet. 'social.pipeline.user.create_user', # Create the record that associated the social account with this user. 'social.pipeline.social_auth.associate_user', # Populate the extra_data field in the social record with the values # specified by settings (and the default ones like access_token, etc). 'social.pipeline.social_auth.load_extra_data', # Update the user record with any changed info from the auth service. 'social.pipeline.user.user_details' ) DEFAULT_DISCONNECT_PIPELINE = ( # Verifies that the social association can be disconnected from the current # user (ensure that the user login mechanism is not compromised by this # disconnection). 'social.pipeline.disconnect.allowed_to_disconnect', # Collects the social associations to disconnect. 'social.pipeline.disconnect.get_entries', # Revoke any access_token when possible. 'social.pipeline.disconnect.revoke_tokens', # Removes the social associations. 'social.pipeline.disconnect.disconnect' ) python-social-auth-0.2.13/social/pipeline/debug.py000066400000000000000000000003741260133235600220510ustar00rootroot00000000000000from pprint import pprint def debug(response, details, *args, **kwargs): print('=' * 80) pprint(response) print('=' * 80) pprint(details) print('=' * 80) pprint(args) print('=' * 80) pprint(kwargs) print('=' * 80) python-social-auth-0.2.13/social/pipeline/disconnect.py000066400000000000000000000020721260133235600231110ustar00rootroot00000000000000from social.exceptions import NotAllowedToDisconnect def allowed_to_disconnect(strategy, user, name, user_storage, association_id=None, *args, **kwargs): if not user_storage.allowed_to_disconnect(user, name, association_id): raise NotAllowedToDisconnect() def get_entries(strategy, user, name, user_storage, association_id=None, *args, **kwargs): return { 'entries': user_storage.get_social_auth_for_user( user, name, association_id ) } def revoke_tokens(strategy, entries, *args, **kwargs): revoke_tokens = strategy.setting('REVOKE_TOKENS_ON_DISCONNECT', False) if revoke_tokens: for entry in entries: if 'access_token' in entry.extra_data: backend = entry.get_backend(strategy)(strategy) backend.revoke_token(entry.extra_data['access_token'], entry.uid) def disconnect(strategy, entries, user_storage, *args, **kwargs): for entry in entries: user_storage.disconnect(entry) python-social-auth-0.2.13/social/pipeline/mail.py000066400000000000000000000022131260133235600216770ustar00rootroot00000000000000from social.exceptions import InvalidEmail from social.pipeline.partial import partial @partial def mail_validation(backend, details, is_new=False, *args, **kwargs): requires_validation = backend.REQUIRES_EMAIL_VALIDATION or \ backend.setting('FORCE_EMAIL_VALIDATION', False) send_validation = details.get('email') and \ (is_new or backend.setting('PASSWORDLESS', False)) if requires_validation and send_validation: data = backend.strategy.request_data() if 'verification_code' in data: backend.strategy.session_pop('email_validation_address') if not backend.strategy.validate_email(details['email'], data['verification_code']): raise InvalidEmail(backend) else: backend.strategy.send_email_validation(backend, details['email']) backend.strategy.session_set('email_validation_address', details['email']) return backend.strategy.redirect( backend.strategy.setting('EMAIL_VALIDATION_URL') ) python-social-auth-0.2.13/social/pipeline/partial.py000066400000000000000000000014671260133235600224230ustar00rootroot00000000000000from functools import wraps def save_status_to_session(strategy, pipeline_index, *args, **kwargs): """Saves current social-auth status to session.""" strategy.session_set('partial_pipeline', strategy.partial_to_session(pipeline_index + 1, *args, **kwargs)) def partial(func): @wraps(func) def wrapper(strategy, pipeline_index, *args, **kwargs): out = func(strategy=strategy, pipeline_index=pipeline_index, *args, **kwargs) or {} if not isinstance(out, dict): values = strategy.partial_to_session(pipeline_index, *args, **kwargs) strategy.session_set('partial_pipeline', values) return out return wrapper python-social-auth-0.2.13/social/pipeline/social_auth.py000066400000000000000000000063751260133235600232650ustar00rootroot00000000000000from social.exceptions import AuthAlreadyAssociated, AuthException, \ AuthForbidden def social_details(backend, details, response, *args, **kwargs): return {'details': dict(backend.get_user_details(response), **details)} def social_uid(backend, details, response, *args, **kwargs): return {'uid': backend.get_user_id(details, response)} def auth_allowed(backend, details, response, *args, **kwargs): if not backend.auth_allowed(response, details): raise AuthForbidden(backend) def social_user(backend, uid, user=None, *args, **kwargs): provider = backend.name social = backend.strategy.storage.user.get_social_auth(provider, uid) if social: if user and social.user != user: msg = 'This {0} account is already in use.'.format(provider) raise AuthAlreadyAssociated(backend, msg) elif not user: user = social.user return {'social': social, 'user': user, 'is_new': user is None, 'new_association': False} def associate_user(backend, uid, user=None, social=None, *args, **kwargs): if user and not social: try: social = backend.strategy.storage.user.create_social_auth( user, uid, backend.name ) except Exception as err: if not backend.strategy.storage.is_integrity_error(err): raise # Protect for possible race condition, those bastard with FTL # clicking capabilities, check issue #131: # https://github.com/omab/django-social-auth/issues/131 return social_user(backend, uid, user, *args, **kwargs) else: return {'social': social, 'user': social.user, 'new_association': True} def associate_by_email(backend, details, user=None, *args, **kwargs): """ Associate current auth with a user with the same email address in the DB. This pipeline entry is not 100% secure unless you know that the providers enabled enforce email verification on their side, otherwise a user can attempt to take over another user account by using the same (not validated) email address on some provider. This pipeline entry is disabled by default. """ if user: return None email = details.get('email') if email: # Try to associate accounts registered with the same email address, # only if it's a single object. AuthException is raised if multiple # objects are returned. users = list(backend.strategy.storage.user.get_users_by_email(email)) if len(users) == 0: return None elif len(users) > 1: raise AuthException( backend, 'The given email address is associated with another account' ) else: return {'user': users[0]} def load_extra_data(backend, details, response, uid, user, *args, **kwargs): social = kwargs.get('social') or \ backend.strategy.storage.user.get_social_auth(backend.name, uid) if social: extra_data = backend.extra_data(user, uid, response, details, *args, **kwargs) social.set_extra_data(extra_data) python-social-auth-0.2.13/social/pipeline/user.py000066400000000000000000000066461260133235600217510ustar00rootroot00000000000000from uuid import uuid4 from social.utils import slugify, module_member USER_FIELDS = ['username', 'email'] def get_username(strategy, details, user=None, *args, **kwargs): if 'username' not in strategy.setting('USER_FIELDS', USER_FIELDS): return storage = strategy.storage if not user: email_as_username = strategy.setting('USERNAME_IS_FULL_EMAIL', False) uuid_length = strategy.setting('UUID_LENGTH', 16) max_length = storage.user.username_max_length() do_slugify = strategy.setting('SLUGIFY_USERNAMES', False) do_clean = strategy.setting('CLEAN_USERNAMES', True) if do_clean: clean_func = storage.user.clean_username else: clean_func = lambda val: val if do_slugify: override_slug = strategy.setting('SLUGIFY_FUNCTION') if override_slug: slug_func = module_member(override_slug) else: slug_func = slugify else: slug_func = lambda val: val if email_as_username and details.get('email'): username = details['email'] elif details.get('username'): username = details['username'] else: username = uuid4().hex short_username = username[:max_length - uuid_length] final_username = slug_func(clean_func(username[:max_length])) # Generate a unique username for current user using username # as base but adding a unique hash at the end. Original # username is cut to avoid any field max_length. # The final_username may be empty and will skip the loop. while not final_username or \ storage.user.user_exists(username=final_username): username = short_username + uuid4().hex[:uuid_length] final_username = slug_func(clean_func(username[:max_length])) else: final_username = storage.user.get_username(user) return {'username': final_username} def create_user(strategy, details, user=None, *args, **kwargs): if user: return {'is_new': False} fields = dict((name, kwargs.get(name) or details.get(name)) for name in strategy.setting('USER_FIELDS', USER_FIELDS)) if not fields: return return { 'is_new': True, 'user': strategy.create_user(**fields) } def user_details(strategy, details, user=None, *args, **kwargs): """Update user details using data from provider.""" if user: changed = False # flag to track changes protected = ('username', 'id', 'pk', 'email') + \ tuple(strategy.setting('PROTECTED_USER_FIELDS', [])) # Update user model attributes with the new data sent by the current # provider. Update on some attributes is disabled by default, for # example username and id fields. It's also possible to disable update # on fields defined in SOCIAL_AUTH_PROTECTED_FIELDS. for name, value in details.items(): if value and hasattr(user, name): # Check https://github.com/omab/python-social-auth/issues/671 current_value = getattr(user, name, None) if not current_value or name not in protected: changed |= current_value != value setattr(user, name, value) if changed: strategy.storage.user.changed(user) python-social-auth-0.2.13/social/pipeline/utils.py000066400000000000000000000040111260133235600221130ustar00rootroot00000000000000import six SERIALIZABLE_TYPES = (dict, list, tuple, set, bool, type(None)) + \ six.integer_types + six.string_types + \ (six.text_type, six.binary_type,) def partial_to_session(strategy, next, backend, request=None, *args, **kwargs): user = kwargs.get('user') social = kwargs.get('social') clean_kwargs = { 'response': kwargs.get('response') or {}, 'details': kwargs.get('details') or {}, 'username': kwargs.get('username'), 'uid': kwargs.get('uid'), 'is_new': kwargs.get('is_new') or False, 'new_association': kwargs.get('new_association') or False, 'user': user and user.id or None, 'social': social and { 'provider': social.provider, 'uid': social.uid } or None } kwargs.update(clean_kwargs) # Clean any MergeDict data type from the values new_kwargs = {} for name, value in kwargs.items(): # Check for class name to avoid importing Django MergeDict or # Werkzeug MultiDict if isinstance(value, dict) or \ value.__class__.__name__ in ('MergeDict', 'MultiDict'): value = dict(value) if isinstance(value, SERIALIZABLE_TYPES): new_kwargs[name] = strategy.to_session_value(value) return { 'next': next, 'backend': backend.name, 'args': tuple(map(strategy.to_session_value, args)), 'kwargs': new_kwargs } def partial_from_session(strategy, session): kwargs = session['kwargs'].copy() user = kwargs.get('user') social = kwargs.get('social') if isinstance(social, dict): kwargs['social'] = strategy.storage.user.get_social_auth(**social) if user: kwargs['user'] = strategy.storage.user.get_user(user) return ( session['next'], session['backend'], list(map(strategy.from_session_value, session['args'])), dict((key, strategy.from_session_value(val)) for key, val in kwargs.items()) ) python-social-auth-0.2.13/social/storage/000077500000000000000000000000001260133235600202445ustar00rootroot00000000000000python-social-auth-0.2.13/social/storage/__init__.py000066400000000000000000000000001260133235600223430ustar00rootroot00000000000000python-social-auth-0.2.13/social/storage/base.py000066400000000000000000000201121260133235600215240ustar00rootroot00000000000000"""Models mixins for Social Auth""" import re import time import base64 import uuid import warnings from datetime import datetime, timedelta import six from openid.association import Association as OpenIdAssociation from social.backends.utils import get_backend from social.strategies.utils import get_current_strategy CLEAN_USERNAME_REGEX = re.compile(r'[^\w.@+_-]+', re.UNICODE) class UserMixin(object): user = '' provider = '' uid = None extra_data = None def get_backend(self, strategy=None): strategy = strategy or get_current_strategy() if strategy: return get_backend(strategy.get_backends(), self.provider) def get_backend_instance(self, strategy=None): strategy = strategy or get_current_strategy() Backend = self.get_backend(strategy) if Backend: return Backend(strategy=strategy) @property def access_token(self): """Return access_token stored in extra_data or None""" return self.extra_data.get('access_token') @property def tokens(self): warnings.warn('tokens is deprecated, use access_token instead') return self.access_token def refresh_token(self, strategy, *args, **kwargs): token = self.extra_data.get('refresh_token') or \ self.extra_data.get('access_token') backend = self.get_backend(strategy) if token and backend and hasattr(backend, 'refresh_token'): backend = backend(strategy=strategy) response = backend.refresh_token(token, *args, **kwargs) access_token = response.get('access_token') refresh_token = response.get('refresh_token') if access_token or refresh_token: if access_token: self.extra_data['access_token'] = access_token if refresh_token: self.extra_data['refresh_token'] = refresh_token self.save() def expiration_datetime(self): """Return provider session live seconds. Returns a timedelta ready to use with session.set_expiry(). If provider returns a timestamp instead of session seconds to live, the timedelta is inferred from current time (using UTC timezone). None is returned if there's no value stored or it's invalid. """ if self.extra_data and 'expires' in self.extra_data: try: expires = int(self.extra_data.get('expires')) except (ValueError, TypeError): return None now = datetime.utcnow() # Detect if expires is a timestamp if expires > time.mktime(now.timetuple()): # expires is a datetime return datetime.fromtimestamp(expires) - now else: # expires is a timedelta return timedelta(seconds=expires) def set_extra_data(self, extra_data=None): if extra_data and self.extra_data != extra_data: if self.extra_data: self.extra_data.update(extra_data) else: self.extra_data = extra_data return True @classmethod def clean_username(cls, value): """Clean username removing any unsupported character""" return CLEAN_USERNAME_REGEX.sub('', value) @classmethod def changed(cls, user): """The given user instance is ready to be saved""" raise NotImplementedError('Implement in subclass') @classmethod def get_username(cls, user): """Return the username for given user""" raise NotImplementedError('Implement in subclass') @classmethod def user_model(cls): """Return the user model""" raise NotImplementedError('Implement in subclass') @classmethod def username_max_length(cls): """Return the max length for username""" raise NotImplementedError('Implement in subclass') @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): """Return if it's safe to disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def disconnect(cls, entry): """Disconnect the social account for the given user""" raise NotImplementedError('Implement in subclass') @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ raise NotImplementedError('Implement in subclass') @classmethod def create_user(cls, *args, **kwargs): """Create a user instance""" raise NotImplementedError('Implement in subclass') @classmethod def get_user(cls, pk): """Return user instance for given id""" raise NotImplementedError('Implement in subclass') @classmethod def get_users_by_email(cls, email): """Return users instances for given email address""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth(cls, provider, uid): """Return UserSocialAuth for given provider and uid""" raise NotImplementedError('Implement in subclass') @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): """Return all the UserSocialAuth instances for given user""" raise NotImplementedError('Implement in subclass') @classmethod def create_social_auth(cls, user, uid, provider): """Create a UserSocialAuth instance for given user""" raise NotImplementedError('Implement in subclass') class NonceMixin(object): """One use numbers""" server_url = '' timestamp = 0 salt = '' @classmethod def use(cls, server_url, timestamp, salt): """Create a Nonce instance""" raise NotImplementedError('Implement in subclass') class AssociationMixin(object): """OpenId account association""" server_url = '' handle = '' secret = '' issued = 0 lifetime = 0 assoc_type = '' @classmethod def oids(cls, server_url, handle=None): kwargs = {'server_url': server_url} if handle is not None: kwargs['handle'] = handle return sorted([(assoc.id, cls.openid_association(assoc)) for assoc in cls.get(**kwargs) ], key=lambda x: x[1].issued, reverse=True) @classmethod def openid_association(cls, assoc): secret = assoc.secret if not isinstance(secret, six.binary_type): secret = secret.encode() return OpenIdAssociation(assoc.handle, base64.decodestring(secret), assoc.issued, assoc.lifetime, assoc.assoc_type) @classmethod def store(cls, server_url, association): """Create an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def get(cls, *args, **kwargs): """Get an Association instance""" raise NotImplementedError('Implement in subclass') @classmethod def remove(cls, ids_to_delete): """Remove an Association instance""" raise NotImplementedError('Implement in subclass') class CodeMixin(object): email = '' code = '' verified = False def verify(self): self.verified = True self.save() @classmethod def generate_code(cls): return uuid.uuid4().hex @classmethod def make_code(cls, email): code = cls() code.email = email code.code = cls.generate_code() code.verified = False code.save() return code @classmethod def get_code(cls, code): raise NotImplementedError('Implement in subclass') class BaseStorage(object): user = UserMixin nonce = NonceMixin association = AssociationMixin code = CodeMixin @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.13/social/storage/django_orm.py000066400000000000000000000112131260133235600227330ustar00rootroot00000000000000"""Django ORM models for Social Auth""" import base64 import six from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage class DjangoUserMixin(UserMixin): """Social Auth association model""" @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super(DjangoUserMixin, self).set_extra_data(extra_data): self.save() @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: qs = cls.objects.exclude(id=association_id) else: qs = cls.objects.exclude(provider=backend_name) qs = qs.filter(user=user) if hasattr(user, 'has_usable_password'): valid_password = user.has_usable_password() else: valid_password = True return valid_password or qs.count() > 0 @classmethod def disconnect(cls, entry): entry.delete() @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ if 'username' in kwargs: kwargs[cls.username_field()] = kwargs.pop('username') return cls.user_model().objects.filter(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def create_user(cls, *args, **kwargs): username_field = cls.username_field() if 'username' in kwargs and username_field not in kwargs: kwargs[username_field] = kwargs.pop('username') return cls.user_model().objects.create_user(*args, **kwargs) @classmethod def get_user(cls, pk=None, **kwargs): if pk: kwargs = {'pk': pk} try: return cls.user_model().objects.get(**kwargs) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): user_model = cls.user_model() email_field = getattr(user_model, 'EMAIL_FIELD', 'email') return user_model.objects.filter(**{email_field + '__iexact': email}) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls.objects.get(provider=provider, uid=uid) except cls.DoesNotExist: return None @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = user.social_auth.all() if provider: qs = qs.filter(provider=provider) if id: qs = qs.filter(id=id) return qs @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, six.string_types): uid = str(uid) return cls.objects.create(user=user, uid=uid, provider=provider) class DjangoNonceMixin(NonceMixin): @classmethod def use(cls, server_url, timestamp, salt): return cls.objects.get_or_create(server_url=server_url, timestamp=timestamp, salt=salt)[1] class DjangoAssociationMixin(AssociationMixin): @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls.objects.get(server_url=server_url, handle=association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): return cls.objects.filter(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls.objects.filter(pk__in=ids_to_delete).delete() class DjangoCodeMixin(CodeMixin): @classmethod def get_code(cls, code): try: return cls.objects.get(code=code) except cls.DoesNotExist: return None class BaseDjangoStorage(BaseStorage): user = DjangoUserMixin nonce = DjangoNonceMixin association = DjangoAssociationMixin code = DjangoCodeMixin python-social-auth-0.2.13/social/storage/mongoengine_orm.py000066400000000000000000000134321260133235600240030ustar00rootroot00000000000000import base64 import six from mongoengine import DictField, IntField, StringField, \ EmailField, BooleanField from mongoengine.queryset import OperationError from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage UNUSABLE_PASSWORD = '!' # Borrowed from django 1.4 class MongoengineUserMixin(UserMixin): """Social Auth association model""" user = None provider = StringField(max_length=32) uid = StringField(max_length=255, unique_with='provider') extra_data = DictField() def str_id(self): return str(self.id) @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = cls.objects if provider: qs = qs.filter(provider=provider) if id: qs = qs.filter(id=id) return qs.filter(user=user.id) @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(type(uid), six.string_types): uid = str(uid) return cls.objects.create(user=user.id, uid=uid, provider=provider) @classmethod def username_max_length(cls): username_field = cls.username_field() field = getattr(cls.user_model(), username_field) return field.max_length @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def create_user(cls, *args, **kwargs): kwargs['password'] = UNUSABLE_PASSWORD if 'email' in kwargs: # Empty string makes email regex validation fail kwargs['email'] = kwargs['email'] or None return cls.user_model().objects.create(*args, **kwargs) @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: qs = cls.objects.filter(id__ne=association_id) else: qs = cls.objects.filter(provider__ne=backend_name) qs = qs.filter(user=user) if hasattr(user, 'has_usable_password'): valid_password = user.has_usable_password() else: valid_password = True return valid_password or qs.count() > 0 @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super(MongoengineUserMixin, self).set_extra_data(extra_data): self.save() @classmethod def disconnect(cls, entry): entry.delete() @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ if 'username' in kwargs: kwargs[cls.username_field()] = kwargs.pop('username') return cls.user_model().objects.filter(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def get_user(cls, pk): try: return cls.user_model().objects.get(id=pk) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): return cls.user_model().objects.filter(email__iexact=email) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls.objects.get(provider=provider, uid=uid) except cls.DoesNotExist: return None class MongoengineNonceMixin(NonceMixin): """One use numbers""" server_url = StringField(max_length=255) timestamp = IntField() salt = StringField(max_length=40) @classmethod def use(cls, server_url, timestamp, salt): return cls.objects.get_or_create(server_url=server_url, timestamp=timestamp, salt=salt)[1] class MongoengineAssociationMixin(AssociationMixin): """OpenId account association""" server_url = StringField(max_length=255) handle = StringField(max_length=255) secret = StringField(max_length=255) # Stored base64 encoded issued = IntField() lifetime = IntField() assoc_type = StringField(max_length=64) @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls.objects.get(server_url=server_url, handle=association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): return cls.objects.filter(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls.objects.filter(pk__in=ids_to_delete).delete() class MongoengineCodeMixin(CodeMixin): email = EmailField() code = StringField(max_length=32) verified = BooleanField(default=False) @classmethod def get_code(cls, code): try: return cls.objects.get(code=code) except cls.DoesNotExist: return None class BaseMongoengineStorage(BaseStorage): user = MongoengineUserMixin nonce = MongoengineNonceMixin association = MongoengineAssociationMixin code = MongoengineCodeMixin @classmethod def is_integrity_error(cls, exception): return exception.__class__ is OperationError and \ 'E11000' in exception.message python-social-auth-0.2.13/social/storage/sqlalchemy_orm.py000066400000000000000000000156061260133235600236450ustar00rootroot00000000000000"""SQLAlchemy models for Social Auth""" import base64 import six import json try: import transaction except ImportError: transaction = None from sqlalchemy import Column, Integer, String from sqlalchemy.exc import IntegrityError from sqlalchemy.types import PickleType, Text from sqlalchemy.schema import UniqueConstraint from social.storage.base import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, BaseStorage # JSON type field class JSONType(PickleType): impl = Text def __init__(self, *args, **kwargs): kwargs['pickler'] = json super(JSONType, self).__init__(*args, **kwargs) class SQLAlchemyMixin(object): @classmethod def _session(cls): raise NotImplementedError('Implement in subclass') @classmethod def _query(cls): return cls._session().query(cls) @classmethod def _new_instance(cls, model, *args, **kwargs): return cls._save_instance(model(*args, **kwargs)) @classmethod def _save_instance(cls, instance): cls._session().add(instance) cls._flush() return instance @classmethod def _flush(cls): try: cls._session().flush() except AssertionError: if transaction: with transaction.manager as manager: manager.commit() else: cls._session().commit() def save(self): self._save_instance(self) class SQLAlchemyUserMixin(SQLAlchemyMixin, UserMixin): """Social Auth association model""" __tablename__ = 'social_auth_usersocialauth' __table_args__ = (UniqueConstraint('provider', 'uid'),) id = Column(Integer, primary_key=True) provider = Column(String(32)) extra_data = Column(JSONType) uid = None user_id = None user = None @classmethod def changed(cls, user): cls._save_instance(user) def set_extra_data(self, extra_data=None): if super(SQLAlchemyUserMixin, self).set_extra_data(extra_data): self._save_instance(self) @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: qs = cls._query().filter(cls.id != association_id) else: qs = cls._query().filter(cls.provider != backend_name) qs = qs.filter(cls.user == user) if hasattr(user, 'has_usable_password'): # TODO valid_password = user.has_usable_password() else: valid_password = True return valid_password or qs.count() > 0 @classmethod def disconnect(cls, entry): cls._session().delete(entry) cls._flush() @classmethod def user_query(cls): return cls._session().query(cls.user_model()) @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ return cls.user_query().filter_by(*args, **kwargs).count() > 0 @classmethod def get_username(cls, user): return getattr(user, 'username', None) @classmethod def create_user(cls, *args, **kwargs): return cls._new_instance(cls.user_model(), *args, **kwargs) @classmethod def get_user(cls, pk): return cls.user_query().get(pk) @classmethod def get_users_by_email(cls, email): return cls.user_query().filter_by(email=email) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, six.string_types): uid = str(uid) try: return cls._query().filter_by(provider=provider, uid=uid)[0] except IndexError: return None @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = cls._query().filter_by(user_id=user.id) if provider: qs = qs.filter_by(provider=provider) if id: qs = qs.filter_by(id=id) return qs @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, six.string_types): uid = str(uid) return cls._new_instance(cls, user=user, uid=uid, provider=provider) class SQLAlchemyNonceMixin(SQLAlchemyMixin, NonceMixin): __tablename__ = 'social_auth_nonce' __table_args__ = (UniqueConstraint('server_url', 'timestamp', 'salt'),) id = Column(Integer, primary_key=True) server_url = Column(String(255)) timestamp = Column(Integer) salt = Column(String(40)) @classmethod def use(cls, server_url, timestamp, salt): kwargs = {'server_url': server_url, 'timestamp': timestamp, 'salt': salt} try: return cls._query().filter_by(**kwargs)[0] except IndexError: return cls._new_instance(cls, **kwargs) class SQLAlchemyAssociationMixin(SQLAlchemyMixin, AssociationMixin): __tablename__ = 'social_auth_association' __table_args__ = (UniqueConstraint('server_url', 'handle'),) id = Column(Integer, primary_key=True) server_url = Column(String(255)) handle = Column(String(255)) secret = Column(String(255)) # base64 encoded issued = Column(Integer) lifetime = Column(Integer) assoc_type = Column(String(64)) @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls._query().filter_by(server_url=server_url, handle=association.handle)[0] except IndexError: assoc = cls(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret).decode() assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type cls._save_instance(assoc) @classmethod def get(cls, *args, **kwargs): return cls._query().filter_by(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls._query().filter(cls.id.in_(ids_to_delete)).delete( synchronize_session='fetch' ) class SQLAlchemyCodeMixin(SQLAlchemyMixin, CodeMixin): __tablename__ = 'social_auth_code' __table_args__ = (UniqueConstraint('code', 'email'),) id = Column(Integer, primary_key=True) email = Column(String(200)) code = Column(String(32), index=True) @classmethod def get_code(cls, code): return cls._query().filter(cls.code == code).first() class BaseSQLAlchemyStorage(BaseStorage): user = SQLAlchemyUserMixin nonce = SQLAlchemyNonceMixin association = SQLAlchemyAssociationMixin code = SQLAlchemyCodeMixin @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError python-social-auth-0.2.13/social/store.py000066400000000000000000000052701260133235600203120ustar00rootroot00000000000000import time try: import cPickle as pickle except ImportError: import pickle from openid.store.interface import OpenIDStore as BaseOpenIDStore from openid.store.nonce import SKEW class OpenIdStore(BaseOpenIDStore): """Storage class""" def __init__(self, strategy): """Init method""" super(OpenIdStore, self).__init__() self.strategy = strategy self.storage = strategy.storage self.assoc = self.storage.association self.nonce = self.storage.nonce self.max_nonce_age = 6 * 60 * 60 # Six hours def storeAssociation(self, server_url, association): """Store new assocition if doesn't exist""" self.assoc.store(server_url, association) def removeAssociation(self, server_url, handle): """Remove association""" associations_ids = list(dict(self.assoc.oids(server_url, handle)).keys()) if associations_ids: self.assoc.remove(associations_ids) def expiresIn(self, assoc): if hasattr(assoc, 'getExpiresIn'): return assoc.getExpiresIn() else: # python3-openid 3.0.2 return assoc.expiresIn def getAssociation(self, server_url, handle=None): """Return stored assocition""" associations, expired = [], [] for assoc_id, association in self.assoc.oids(server_url, handle): expires = self.expiresIn(association) if expires > 0: associations.append(association) elif expires == 0: expired.append(assoc_id) if expired: # clear expired associations self.assoc.remove(expired) if associations: # return most recet association return associations[0] def useNonce(self, server_url, timestamp, salt): """Generate one use number and return *if* it was created""" if abs(timestamp - time.time()) > SKEW: return False return self.nonce.use(server_url, timestamp, salt) class OpenIdSessionWrapper(dict): pickle_instances = ( '_yadis_services__openid_consumer_', '_openid_consumer_last_token' ) def __getitem__(self, name): value = super(OpenIdSessionWrapper, self).__getitem__(name) if name in self.pickle_instances: value = pickle.loads(value) return value def __setitem__(self, name, value): if name in self.pickle_instances: value = pickle.dumps(value, 0) super(OpenIdSessionWrapper, self).__setitem__(name, value) def get(self, name, default=None): try: return self[name] except KeyError: return default python-social-auth-0.2.13/social/strategies/000077500000000000000000000000001260133235600207525ustar00rootroot00000000000000python-social-auth-0.2.13/social/strategies/__init__.py000066400000000000000000000000001260133235600230510ustar00rootroot00000000000000python-social-auth-0.2.13/social/strategies/base.py000066400000000000000000000165701260133235600222470ustar00rootroot00000000000000import time import random import hashlib from social.utils import setting_name, module_member from social.store import OpenIdStore, OpenIdSessionWrapper from social.pipeline import DEFAULT_AUTH_PIPELINE, DEFAULT_DISCONNECT_PIPELINE from social.pipeline.utils import partial_from_session, partial_to_session class BaseTemplateStrategy(object): def __init__(self, strategy): self.strategy = strategy def render(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} if tpl: return self.render_template(tpl, context) else: return self.render_string(html, context) def render_template(self, tpl, context): raise NotImplementedError('Implement in subclass') def render_string(self, html, context): raise NotImplementedError('Implement in subclass') class BaseStrategy(object): ALLOWED_CHARS = 'abcdefghijklmnopqrstuvwxyz' \ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ '0123456789' DEFAULT_TEMPLATE_STRATEGY = BaseTemplateStrategy def __init__(self, storage=None, tpl=None): self.storage = storage self.tpl = (tpl or self.DEFAULT_TEMPLATE_STRATEGY)(self) def setting(self, name, default=None, backend=None): names = [setting_name(name), name] if backend: names.insert(0, setting_name(backend.name, name)) for name in names: try: return self.get_setting(name) except (AttributeError, KeyError): pass return default def create_user(self, *args, **kwargs): return self.storage.user.create_user(*args, **kwargs) def get_user(self, *args, **kwargs): return self.storage.user.get_user(*args, **kwargs) def session_setdefault(self, name, value): self.session_set(name, value) return self.session_get(name) def openid_session_dict(self, name): # Many frameworks are switching the session serialization from Pickle # to JSON to avoid code execution risks. Flask did this from Flask # 0.10, Django is switching to JSON by default from version 1.6. # # Sadly python-openid stores classes instances in the session which # fails the JSON serialization, the classes are: # # openid.yadis.manager.YadisServiceManager # openid.consumer.discover.OpenIDServiceEndpoint # # This method will return a wrapper over the session value used with # openid (a dict) which will automatically keep a pickled value for the # mentioned classes. return OpenIdSessionWrapper(self.session_setdefault(name, {})) def to_session_value(self, val): return val def from_session_value(self, val): return val def partial_to_session(self, next, backend, request=None, *args, **kwargs): return partial_to_session(self, next, backend, request=request, *args, **kwargs) def partial_from_session(self, session): return partial_from_session(self, session) def clean_partial_pipeline(self, name='partial_pipeline'): self.session_pop(name) def openid_store(self): return OpenIdStore(self) def get_pipeline(self): return self.setting('PIPELINE', DEFAULT_AUTH_PIPELINE) def get_disconnect_pipeline(self): return self.setting('DISCONNECT_PIPELINE', DEFAULT_DISCONNECT_PIPELINE) def random_string(self, length=12, chars=ALLOWED_CHARS): # Implementation borrowed from django 1.4 try: random.SystemRandom() except NotImplementedError: key = self.setting('SECRET_KEY', '') seed = '{0}{1}{2}'.format(random.getstate(), time.time(), key) random.seed(hashlib.sha256(seed.encode()).digest()) return ''.join([random.choice(chars) for i in range(length)]) def absolute_uri(self, path=None): uri = self.build_absolute_uri(path) if uri and self.setting('REDIRECT_IS_HTTPS'): uri = uri.replace('http://', 'https://') return uri def get_language(self): """Return current language""" return '' def send_email_validation(self, backend, email): email_validation = self.setting('EMAIL_VALIDATION_FUNCTION') send_email = module_member(email_validation) code = self.storage.code.make_code(email) send_email(self, backend, code) return code def validate_email(self, email, code): verification_code = self.storage.code.get_code(code) if not verification_code or verification_code.code != code: return False else: verification_code.verify() return True def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" return self.tpl.render(tpl, html, context) def authenticate(self, backend, *args, **kwargs): """Trigger the authentication mechanism tied to the current framework""" kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return backend.authenticate(*args, **kwargs) def get_backends(self): """Return configured backends""" return self.setting('AUTHENTICATION_BACKENDS', []) # Implement the following methods on strategies sub-classes def redirect(self, url): """Return a response redirect to the given URL""" raise NotImplementedError('Implement in subclass') def get_setting(self, name): """Return value for given setting name""" raise NotImplementedError('Implement in subclass') def html(self, content): """Return HTTP response with given content""" raise NotImplementedError('Implement in subclass') def request_data(self, merge=True): """Return current request data (POST or GET)""" raise NotImplementedError('Implement in subclass') def request_host(self): """Return current host value""" raise NotImplementedError('Implement in subclass') def session_get(self, name, default=None): """Return session value for given key""" raise NotImplementedError('Implement in subclass') def session_set(self, name, value): """Set session value for given key""" raise NotImplementedError('Implement in subclass') def session_pop(self, name): """Pop session value for given key""" raise NotImplementedError('Implement in subclass') def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" raise NotImplementedError('Implement in subclass') def request_is_secure(self): """Is the request using HTTPS?""" raise NotImplementedError('Implement in subclass') def request_path(self): """path of the current request""" raise NotImplementedError('Implement in subclass') def request_port(self): """Port in use for this request""" raise NotImplementedError('Implement in subclass') def request_get(self): """Request GET data""" raise NotImplementedError('Implement in subclass') def request_post(self): """Request POST data""" raise NotImplementedError('Implement in subclass') python-social-auth-0.2.13/social/strategies/cherrypy_strategy.py000066400000000000000000000036041260133235600251160ustar00rootroot00000000000000import six import cherrypy from social.strategies.base import BaseStrategy, BaseTemplateStrategy class CherryPyJinja2TemplateStrategy(BaseTemplateStrategy): def __init__(self, strategy): self.strategy = strategy self.env = cherrypy.tools.jinja2env def render_template(self, tpl, context): return self.env.get_template(tpl).render(context) def render_string(self, html, context): return self.env.from_string(html).render(context) class CherryPyStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = CherryPyJinja2TemplateStrategy def get_setting(self, name): return cherrypy.config[name] def request_data(self, merge=True): if merge: data = cherrypy.request.params elif cherrypy.request.method == 'POST': data = cherrypy.body.params else: data = cherrypy.request.params return data def request_host(self): return cherrypy.request.base def redirect(self, url): raise cherrypy.HTTPRedirect(url) def html(self, content): return content def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return backend.authenticate(*args, **kwargs) def session_get(self, name, default=None): return cherrypy.session.get(name, default) def session_set(self, name, value): cherrypy.session[name] = value def session_pop(self, name): cherrypy.session.pop(name, None) def session_setdefault(self, name, value): return cherrypy.session.setdefault(name, value) def build_absolute_uri(self, path=None): return cherrypy.url(path or '') def is_response(self, value): return isinstance(value, six.string_types) or \ isinstance(value, cherrypy.CherryPyException) python-social-auth-0.2.13/social/strategies/django_strategy.py000066400000000000000000000116741260133235600245210ustar00rootroot00000000000000from django.conf import settings from django.http import HttpResponse from django.db.models import Model from django.contrib.contenttypes.models import ContentType from django.contrib.auth import authenticate from django.shortcuts import redirect from django.template import TemplateDoesNotExist, RequestContext, loader from django.utils.encoding import force_text from django.utils.functional import Promise from django.utils.translation import get_language from social.strategies.base import BaseStrategy, BaseTemplateStrategy class DjangoTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): template = loader.get_template(tpl) return template.render(RequestContext(self.strategy.request, context)) def render_string(self, html, context): template = loader.get_template_from_string(html) return template.render(RequestContext(self.strategy.request, context)) class DjangoStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = DjangoTemplateStrategy def __init__(self, storage, request=None, tpl=None): self.request = request self.session = request.session if request else {} super(DjangoStrategy, self).__init__(storage, tpl) def get_setting(self, name): value = getattr(settings, name) # Force text on URL named settings that are instance of Promise if name.endswith('_URL') and isinstance(value, Promise): value = force_text(value) return value def request_data(self, merge=True): if not self.request: return {} if merge: data = self.request.GET.copy() data.update(self.request.POST) elif self.request.method == 'POST': data = self.request.POST else: data = self.request.GET return data def request_host(self): if self.request: return self.request.get_host() def request_is_secure(self): """Is the request using HTTPS?""" return self.request.is_secure() def request_path(self): """path of the current request""" return self.request.path def request_port(self): """Port in use for this request""" return self.request.META['SERVER_PORT'] def request_get(self): """Request GET data""" return self.request.GET.copy() def request_post(self): """Request POST data""" return self.request.POST.copy() def redirect(self, url): return redirect(url) def html(self, content): return HttpResponse(content, content_type='text/html;charset=UTF-8') def render_html(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} try: template = loader.get_template(tpl) except TemplateDoesNotExist: template = loader.get_template_from_string(html) return template.render(RequestContext(self.request, context)) def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return authenticate(*args, **kwargs) def session_get(self, name, default=None): return self.session.get(name, default) def session_set(self, name, value): self.session[name] = value if hasattr(self.session, 'modified'): self.session.modified = True def session_pop(self, name): return self.session.pop(name, None) def session_setdefault(self, name, value): return self.session.setdefault(name, value) def build_absolute_uri(self, path=None): if self.request: return self.request.build_absolute_uri(path) else: return path def random_string(self, length=12, chars=BaseStrategy.ALLOWED_CHARS): try: from django.utils.crypto import get_random_string except ImportError: # django < 1.4 return super(DjangoStrategy, self).random_string(length, chars) else: return get_random_string(length, chars) def to_session_value(self, val): """Converts values that are instance of Model to a dictionary with enough information to retrieve the instance back later.""" if isinstance(val, Model): val = { 'pk': val.pk, 'ctype': ContentType.objects.get_for_model(val).pk } return val def from_session_value(self, val): """Converts back the instance saved by self._ctype function.""" if isinstance(val, dict) and 'pk' in val and 'ctype' in val: ctype = ContentType.objects.get_for_id(val['ctype']) ModelClass = ctype.model_class() val = ModelClass.objects.get(pk=val['pk']) return val def get_language(self): """Return current language""" return get_language() python-social-auth-0.2.13/social/strategies/flask_strategy.py000066400000000000000000000031561260133235600243530ustar00rootroot00000000000000from flask import current_app, request, redirect, make_response, session, \ render_template, render_template_string from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class FlaskTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return render_template(tpl, **context) def render_string(self, html, context): return render_template_string(html, **context) class FlaskStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = FlaskTemplateStrategy def get_setting(self, name): return current_app.config[name] def request_data(self, merge=True): if merge: data = request.form.copy() data.update(request.args) elif request.method == 'POST': data = request.form else: data = request.args return data def request_host(self): return request.host def redirect(self, url): return redirect(url) def html(self, content): response = make_response(content) response.headers['Content-Type'] = 'text/html;charset=UTF-8' return response def session_get(self, name, default=None): return session.get(name, default) def session_set(self, name, value): session[name] = value def session_pop(self, name): return session.pop(name, None) def session_setdefault(self, name, value): return session.setdefault(name, value) def build_absolute_uri(self, path=None): return build_absolute_uri(request.host_url, path) python-social-auth-0.2.13/social/strategies/pyramid_strategy.py000066400000000000000000000052531260133235600247200ustar00rootroot00000000000000from webob.multidict import NoVars from pyramid.response import Response from pyramid.httpexceptions import HTTPFound from pyramid.renderers import render from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class PyramidTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return render(tpl, context, request=self.strategy.request) def render_string(self, html, context): return render(html, context, request=self.strategy.request) class PyramidStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = PyramidTemplateStrategy def __init__(self, storage, request, tpl=None): self.request = request super(PyramidStrategy, self).__init__(storage, tpl) def redirect(self, url): """Return a response redirect to the given URL""" response = getattr(self.request, 'response', None) if response is None: response = HTTPFound(location=url) else: response = HTTPFound(location=url, headers=response.headers) return response def get_setting(self, name): """Return value for given setting name""" return self.request.registry.settings[name] def html(self, content): """Return HTTP response with given content""" response = getattr(self.request, 'response', None) if response is None: response = Response(body=content) else: response = self.request.response response.body = content return response def request_data(self, merge=True): """Return current request data (POST or GET)""" if self.request.method == 'POST': if merge: data = self.request.POST.copy() if not isinstance(self.request.GET, NoVars): data.update(self.request.GET) else: data = self.request.POST else: data = self.request.GET return data def request_host(self): """Return current host value""" return self.request.host def session_get(self, name, default=None): """Return session value for given key""" return self.request.session.get(name, default) def session_set(self, name, value): """Set session value for given key""" self.request.session[name] = value def session_pop(self, name): """Pop session value for given key""" return self.request.session.pop(name, None) def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" return build_absolute_uri(self.request.host_url, path) python-social-auth-0.2.13/social/strategies/tornado_strategy.py000066400000000000000000000046451260133235600247250ustar00rootroot00000000000000import json import six from tornado.template import Loader, Template from social.utils import build_absolute_uri from social.strategies.base import BaseStrategy, BaseTemplateStrategy class TornadoTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): path, tpl = tpl.rsplit('/', 1) return Loader(path).load(tpl).generate(**context) def render_string(self, html, context): return Template(html).generate(**context) class TornadoStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = TornadoTemplateStrategy def __init__(self, storage, request_handler, tpl=None): self.request_handler = request_handler self.request = self.request_handler.request super(TornadoStrategy, self).__init__(storage, tpl) def get_setting(self, name): return self.request_handler.settings[name] def request_data(self, merge=True): # Multiple valued arguments not supported yet return dict((key, val[0].decode()) for key, val in six.iteritems(self.request.arguments)) def request_host(self): return self.request.host def redirect(self, url): return self.request_handler.redirect(url) def html(self, content): self.request_handler.write(content) def session_get(self, name, default=None): value = self.request_handler.get_secure_cookie(name) if value: return json.loads(value.decode()) return default def session_set(self, name, value): self.request_handler.set_secure_cookie(name, json.dumps(value).encode()) def session_pop(self, name): value = self.session_get(name) self.request_handler.clear_cookie(name) return value def session_setdefault(self, name, value): pass def build_absolute_uri(self, path=None): return build_absolute_uri('{0}://{1}'.format(self.request.protocol, self.request.host), path) def partial_to_session(self, next, backend, request=None, *args, **kwargs): return json.dumps(super(TornadoStrategy, self).partial_to_session( next, backend, request=request, *args, **kwargs )) def partial_from_session(self, session): if session: return super(TornadoStrategy, self).partial_to_session( json.loads(session) ) python-social-auth-0.2.13/social/strategies/utils.py000066400000000000000000000015741260133235600224730ustar00rootroot00000000000000from social.utils import module_member # Current strategy getter cache, currently only used by Django to set a method # to get the current strategy which is latter used by backends get_user() # method to retrieve the user saved in the session. Backends need an strategy # to properly access the storage, but Django does not know about that when # creates the backend instance, this method workarounds the problem. _current_strategy_getter = None def get_strategy(strategy, storage, *args, **kwargs): Strategy = module_member(strategy) Storage = module_member(storage) return Strategy(Storage, *args, **kwargs) def set_current_strategy_getter(func): global _current_strategy_getter _current_strategy_getter = func def get_current_strategy(): global _current_strategy_getter if _current_strategy_getter is not None: return _current_strategy_getter() python-social-auth-0.2.13/social/strategies/webpy_strategy.py000066400000000000000000000036141260133235600244000ustar00rootroot00000000000000import web from social.strategies.base import BaseStrategy, BaseTemplateStrategy class WebpyTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return web.template.render(tpl)(**context) def render_string(self, html, context): return web.template.Template(html)(**context) class WebpyStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = WebpyTemplateStrategy def get_setting(self, name): return getattr(web.config, name) def request_data(self, merge=True): if merge: data = web.input(_method='both') elif web.ctx.method == 'POST': data = web.input(_method='post') else: data = web.input(_method='get') return data def request_host(self): return web.ctx.host def redirect(self, url): return web.seeother(url) def html(self, content): web.header('Content-Type', 'text/html;charset=UTF-8') return content def render_html(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} if tpl: tpl = web.template.frender(tpl) else: tpl = web.template.Template(html) return tpl(**context) def session_get(self, name, default=None): return web.web_session.get(name, default) def session_set(self, name, value): web.web_session[name] = value def session_pop(self, name): return web.web_session.pop(name, None) def session_setdefault(self, name, value): return web.web_session.setdefault(name, value) def build_absolute_uri(self, path=None): path = path or '' if path.startswith('http://') or path.startswith('https://'): return path return web.ctx.protocol + '://' + web.ctx.host + path python-social-auth-0.2.13/social/tests/000077500000000000000000000000001260133235600177425ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/README.rst000066400000000000000000000026031260133235600214320ustar00rootroot00000000000000Testing python-social-auth ========================== Testing the application is fairly simple. Just meet the dependencies and run the testing suite. The testing suite uses HTTPretty_ to mock server responses. It's not a live test against the provider's API. To do it that way, a browser and a tool like Selenium are needed. That's slow, it's prone to errors in some cases, and some of the application examples must be running to perform the testing. Plus, it requires real Key and Secret pairs, in the end it's a mess to test functionality which is the real point. By mocking the server responses, we can test the backend's functionality (and other areas too) easily and quickly. Installing dependencies ----------------------- Go to the tests_ directory and install the dependencies listed in the requirements.txt_. Then run with ``nosetests`` command. Pending ------- At the moment only OAuth1 and OAuth2 backends are being tested, and only login and partial pipeline features are covered by the test. There's still a lot to work on, like: * OpenId backends * Frameworks support * Failure cases (like authentication canceled, etc) * Extra data saving .. _HTTPretty: https://github.com/gabrielfalcao/HTTPretty .. _tests: https://github.com/omab/python-social-auth/tree/master/tests .. _requirements.txt: https://github.com/omab/python-social-auth/blob/master/tests/requirements.txt python-social-auth-0.2.13/social/tests/__init__.py000066400000000000000000000000001260133235600220410ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/actions/000077500000000000000000000000001260133235600214025ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/actions/__init__.py000066400000000000000000000000001260133235600235010ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/actions/actions.py000066400000000000000000000205271260133235600234220ustar00rootroot00000000000000import json import requests import unittest2 as unittest from httpretty import HTTPretty from social.utils import parse_qs, module_member from social.p3 import urlparse from social.actions import do_auth, do_complete from social.tests.models import TestStorage, User, TestUserSocialAuth, \ TestNonce, TestAssociation from social.tests.strategy import TestStrategy class BaseActionTest(unittest.TestCase): user_data_url = 'https://api.github.com/user' login_redirect_url = '/success' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def __init__(self, *args, **kwargs): self.strategy = None super(BaseActionTest, self).__init__(*args, **kwargs) def setUp(self): HTTPretty.enable() User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() Backend = module_member('social.backends.github.GithubOAuth2') self.strategy = self.strategy or TestStrategy(TestStorage) self.backend = Backend(self.strategy, redirect_uri='/complete/github') self.user = None def tearDown(self): self.backend = None self.strategy = None self.user = None User.reset_cache() User.set_active(True) TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() HTTPretty.disable() def do_login(self, after_complete_checks=True, user_data_body=None, expected_username=None): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_KEY': 'a-key', 'SOCIAL_AUTH_GITHUB_SECRET': 'a-secret-key', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': self.login_redirect_url, 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.github.GithubOAuth2', ) }) start_url = do_auth(self.backend).url target_url = self.strategy.build_absolute_uri( '/complete/github/?code=foobar' ) start_query = parse_qs(urlparse(start_url).query) location_url = target_url + ('?' in target_url and '&' or '?') + \ 'state=' + start_query['state'] location_query = parse_qs(urlparse(location_url).query) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=location_url) HTTPretty.register_uri(HTTPretty.GET, location_url, status=200, body='foobar') response = requests.get(start_url) self.assertEqual(response.url, location_url) self.assertEqual(response.text, 'foobar') HTTPretty.register_uri(HTTPretty.POST, uri=self.backend.ACCESS_TOKEN_URL, status=200, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: user_data_body = user_data_body or self.user_data_body or '' HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=user_data_body, content_type='text/json') self.strategy.set_request_data(location_query, self.backend) def _login(backend, user, social_user): backend.strategy.session_set('username', user.username) redirect = do_complete(self.backend, user=self.user, login=_login) if after_complete_checks: self.assertEqual(self.strategy.session_get('username'), expected_username or self.expected_username) self.assertEqual(redirect.url, self.login_redirect_url) return redirect def do_login_with_partial_pipeline(self, before_complete=None): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_KEY': 'a-key', 'SOCIAL_AUTH_GITHUB_SECRET': 'a-secret-key', 'SOCIAL_AUTH_LOGIN_REDIRECT_URL': self.login_redirect_url, 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( 'social.backends.github.GithubOAuth2', ), 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.tests.pipeline.set_password', 'social.pipeline.user.user_details' ) }) start_url = do_auth(self.backend).url target_url = self.strategy.build_absolute_uri( '/complete/github/?code=foobar' ) start_query = parse_qs(urlparse(start_url).query) location_url = target_url + ('?' in target_url and '&' or '?') + \ 'state=' + start_query['state'] location_query = parse_qs(urlparse(location_url).query) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=location_url) HTTPretty.register_uri(HTTPretty.GET, location_url, status=200, body='foobar') response = requests.get(start_url) self.assertEqual(response.url, location_url) self.assertEqual(response.text, 'foobar') HTTPretty.register_uri(HTTPretty.GET, uri=self.backend.ACCESS_TOKEN_URL, status=200, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=self.user_data_body or '', content_type='text/json') self.strategy.set_request_data(location_query, self.backend) def _login(backend, user, social_user): backend.strategy.session_set('username', user.username) redirect = do_complete(self.backend, user=self.user, login=_login) url = self.strategy.build_absolute_uri('/password') self.assertEqual(redirect.url, url) HTTPretty.register_uri(HTTPretty.GET, redirect.url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, redirect.url, status=200) password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) if before_complete: before_complete() redirect = do_complete(self.backend, user=self.user, login=_login) self.assertEqual(self.strategy.session_get('username'), self.expected_username) self.assertEqual(redirect.url, self.login_redirect_url) python-social-auth-0.2.13/social/tests/actions/test_associate.py000066400000000000000000000055641260133235600250000ustar00rootroot00000000000000import json from social.exceptions import AuthAlreadyAssociated from social.tests.models import User from social.tests.actions.actions import BaseActionTest class AssociateActionTest(BaseActionTest): expected_username = 'foobar' def setUp(self): super(AssociateActionTest, self).setUp() self.user = User(username='foobar', email='foo@bar.com') self.backend.strategy.session_set('username', self.user.username) def test_associate(self): self.do_login() self.assertTrue(len(self.user.social), 1) self.assertEqual(self.user.social[0].provider, 'github') def test_associate_with_partial_pipeline(self): self.do_login_with_partial_pipeline() self.assertEqual(len(self.user.social), 1) self.assertEqual(self.user.social[0].provider, 'github') class MultipleAccountsTest(AssociateActionTest): alternative_user_data_body = json.dumps({ 'login': 'foobar2', 'id': 2, 'avatar_url': 'https://github.com/images/error/foobar2_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar2', 'name': 'monalisa foobar2', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar2', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_multiple_social_accounts(self): self.do_login() self.do_login(user_data_body=self.alternative_user_data_body) self.assertEqual(len(self.user.social), 2) self.assertEqual(self.user.social[0].provider, 'github') self.assertEqual(self.user.social[1].provider, 'github') class AlreadyAssociatedErrorTest(BaseActionTest): def setUp(self): super(AlreadyAssociatedErrorTest, self).setUp() self.user1 = User(username='foobar', email='foo@bar.com') self.user = None def tearDown(self): super(AlreadyAssociatedErrorTest, self).tearDown() self.user1 = None self.user = None def test_already_associated_error(self): self.user = self.user1 self.do_login() self.user = User(username='foobar2', email='foo2@bar2.com') with self.assertRaisesRegexp(AuthAlreadyAssociated, 'This github account is already in use.'): self.do_login() python-social-auth-0.2.13/social/tests/actions/test_disconnect.py000066400000000000000000000051401260133235600251440ustar00rootroot00000000000000import requests from httpretty import HTTPretty from social.actions import do_disconnect from social.exceptions import NotAllowedToDisconnect from social.utils import parse_qs from social.tests.models import User, TestUserSocialAuth from social.tests.actions.actions import BaseActionTest class DisconnectActionTest(BaseActionTest): def test_not_allowed_to_disconnect(self): self.do_login() user = User.get(self.expected_username) with self.assertRaises(NotAllowedToDisconnect): do_disconnect(self.backend, user) def test_disconnect(self): self.do_login() user = User.get(self.expected_username) user.password = 'password' do_disconnect(self.backend, user) self.assertEqual(len(user.social), 0) def test_disconnect_with_association_id(self): self.do_login() user = User.get(self.expected_username) user.password = 'password' association_id = user.social[0].id second_usa = TestUserSocialAuth(user, user.social[0].provider, "uid2") self.assertEqual(len(user.social), 2) do_disconnect(self.backend, user, association_id) self.assertEqual(len(user.social), 1) self.assertEqual(user.social[0], second_usa) def test_disconnect_with_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_DISCONNECT_PIPELINE': ( 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.tests.pipeline.set_password', 'social.pipeline.disconnect.allowed_to_disconnect', 'social.pipeline.disconnect.get_entries', 'social.pipeline.disconnect.revoke_tokens', 'social.pipeline.disconnect.disconnect' ) }) self.do_login() user = User.get(self.expected_username) redirect = do_disconnect(self.backend, user) url = self.strategy.build_absolute_uri('/password') self.assertEqual(redirect.url, url) HTTPretty.register_uri(HTTPretty.GET, redirect.url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, redirect.url, status=200) password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) redirect = do_disconnect(self.backend, user) self.assertEqual(len(user.social), 0) python-social-auth-0.2.13/social/tests/actions/test_login.py000066400000000000000000000051171260133235600241270ustar00rootroot00000000000000from social.tests.models import User from social.tests.actions.actions import BaseActionTest class LoginActionTest(BaseActionTest): def test_login(self): self.do_login() def test_login_with_partial_pipeline(self): self.do_login_with_partial_pipeline() def test_fields_stored_in_session(self): self.strategy.set_settings({ 'SOCIAL_AUTH_FIELDS_STORED_IN_SESSION': ['foo', 'bar'] }) self.strategy.set_request_data({'foo': '1', 'bar': '2'}, self.backend) self.do_login() self.assertEqual(self.strategy.session_get('foo'), '1') self.assertEqual(self.strategy.session_get('bar'), '2') def test_redirect_value(self): self.strategy.set_request_data({'next': '/after-login'}, self.backend) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/after-login') def test_login_with_invalid_partial_pipeline(self): def before_complete(): partial = self.strategy.session_get('partial_pipeline') partial['backend'] = 'foobar' self.strategy.session_set('partial_pipeline', partial) self.do_login_with_partial_pipeline(before_complete) def test_new_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NEW_USER_REDIRECT_URL': '/new-user' }) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/new-user') def test_inactive_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_INACTIVE_USER_URL': '/inactive' }) User.set_active(False) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/inactive') def test_invalid_user(self): self.strategy.set_settings({ 'SOCIAL_AUTH_LOGIN_ERROR_URL': '/error', 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details', 'social.tests.pipeline.remove_user' ) }) redirect = self.do_login(after_complete_checks=False) self.assertEqual(redirect.url, '/error') python-social-auth-0.2.13/social/tests/backends/000077500000000000000000000000001260133235600215145ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/backends/__init__.py000066400000000000000000000000001260133235600236130ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/backends/base.py000066400000000000000000000140201260133235600227750ustar00rootroot00000000000000import unittest2 as unittest import requests from httpretty import HTTPretty from social.utils import module_member, parse_qs from social.backends.utils import user_backends_data, load_backends from social.tests.strategy import TestStrategy from social.tests.models import User, TestUserSocialAuth, TestNonce, \ TestAssociation, TestCode, TestStorage class BaseBackendTest(unittest.TestCase): backend = None backend_path = None name = None complete_url = '' raw_complete_url = '/complete/{0}' def setUp(self): HTTPretty.enable() Backend = module_member(self.backend_path) self.strategy = TestStrategy(TestStorage) self.backend = Backend(self.strategy, redirect_uri=self.complete_url) self.name = self.backend.name.upper().replace('-', '_') self.complete_url = self.strategy.build_absolute_uri( self.raw_complete_url.format(self.backend.name) ) backends = (self.backend_path, 'social.tests.backends.test_broken.BrokenBackendAuth') self.strategy.set_settings({ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': backends }) self.strategy.set_settings(self.extra_settings()) # Force backends loading to trash PSA cache load_backends(backends, force_load=True) User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() TestCode.reset_cache() def tearDown(self): HTTPretty.disable() self.backend = None self.strategy = None self.name = None self.complete_url = None User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() TestCode.reset_cache() def extra_settings(self): return {} def do_start(self): raise NotImplementedError('Implement in subclass') def do_login(self): user = self.do_start() username = self.expected_username self.assertEqual(user.username, username) self.assertEqual(self.strategy.session_get('username'), username) self.assertEqual(self.strategy.get_user(user.id), user) self.assertEqual(self.backend.get_user(user.id), user) user_backends = user_backends_data( user, self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'), self.strategy.storage ) self.assertEqual(len(list(user_backends.keys())), 3) self.assertEqual('associated' in user_backends, True) self.assertEqual('not_associated' in user_backends, True) self.assertEqual('backends' in user_backends, True) self.assertEqual(len(user_backends['associated']), 1) self.assertEqual(len(user_backends['not_associated']), 1) self.assertEqual(len(user_backends['backends']), 2) return user def pipeline_settings(self): self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.partial.save_status_to_session', 'social.tests.pipeline.ask_for_password', 'social.tests.pipeline.ask_for_slug', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.tests.pipeline.set_password', 'social.tests.pipeline.set_slug', 'social.pipeline.user.user_details' ) }) def pipeline_handlers(self, url): HTTPretty.register_uri(HTTPretty.GET, url, status=200, body='foobar') HTTPretty.register_uri(HTTPretty.POST, url, status=200) def pipeline_password_handling(self, url): password = 'foobar' requests.get(url) requests.post(url, data={'password': password}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['password'], password) self.strategy.session_set('password', data['password']) return password def pipeline_slug_handling(self, url): slug = 'foo-bar' requests.get(url) requests.post(url, data={'slug': slug}) data = parse_qs(HTTPretty.last_request.body) self.assertEqual(data['slug'], slug) self.strategy.session_set('slug', data['slug']) return slug def do_partial_pipeline(self): url = self.strategy.build_absolute_uri('/password') self.pipeline_settings() redirect = self.do_start() self.assertEqual(redirect.url, url) self.pipeline_handlers(url) password = self.pipeline_password_handling(url) data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.assertEqual(backend, self.backend.name) redirect = self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) url = self.strategy.build_absolute_uri('/slug') self.assertEqual(redirect.url, url) self.pipeline_handlers(url) slug = self.pipeline_slug_handling(url) data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.assertEqual(backend, self.backend.name) user = self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) self.assertEqual(user.username, self.expected_username) self.assertEqual(user.slug, slug) self.assertEqual(user.password, password) return user python-social-auth-0.2.13/social/tests/backends/data/000077500000000000000000000000001260133235600224255ustar00rootroot00000000000000python-social-auth-0.2.13/social/tests/backends/data/saml_config.json000066400000000000000000000100211260133235600255730ustar00rootroot00000000000000{ "SOCIAL_AUTH_SAML_SP_ENTITY_ID": "https://github.com/omab/python-social-auth/saml-test", "SOCIAL_AUTH_SAML_SP_PUBLIC_CERT": "MIICsDCCAhmgAwIBAgIJAO7BwdjDZcUWMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNBMRkwFwYDVQQIExBCcml0aXNoIENvbHVtYmlhMRswGQYDVQQKExJweXRob24tc29jaWFsLWF1dGgwHhcNMTUwNTA4MDc1ODQ2WhcNMjUwNTA3MDc1ODQ2WjBFMQswCQYDVQQGEwJDQTEZMBcGA1UECBMQQnJpdGlzaCBDb2x1bWJpYTEbMBkGA1UEChMScHl0aG9uLXNvY2lhbC1hdXRoMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayyZK4n9qcYkV5MFni1rbjx+Mr5aOEmb5u33amMKLwSTwIDAQABo4GnMIGkMB0GA1UdDgQWBBRRiBR6zS66fKVokp0yJHbgv3RYmjB1BgNVHSMEbjBsgBRRiBR6zS66fKVokp0yJHbgv3RYmqFJpEcwRTELMAkGA1UEBhMCQ0ExGTAXBgNVBAgTEEJyaXRpc2ggQ29sdW1iaWExGzAZBgNVBAoTEnB5dGhvbi1zb2NpYWwtYXV0aIIJAO7BwdjDZcUWMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJwsMU3YSaybVjuJ8US0fUhlPOlM40QFCGL4vB3TEbb24Mq8HrjUwrU0JFPGls9a2OYzN2B3e35NorMuxs+grGtr2yP6LvuX+nV6A93wb4ooGHoGfC7VLlyxSSns937SS5R1pzQ4gWzZma2KGWKICWph5zQ0ARVhL63967mGLmoI=", "SOCIAL_AUTH_SAML_SP_PRIVATE_KEY": "MIICXgIBAAKBgQCq3g1Cl+3uR5vCnN4HbgjTg+m3nHhteEMyb++ycZYre2bxUfsshER6x33l23tHckRYwm7MdBbrp3LrVoiOCdPblTml1IhEPTCwKMhBKvvWqTvgfcSSnRzAWkLlQYSusayyZK4n9qcYkV5MFni1rbjx+Mr5aOEmb5u33amMKLwSTwIDAQABAoGBAIHAg6NJSiYC/NYpVzWfKlasuoNy78R5adXYSNZiCR5V5FNm5OzmODZgXUt6g0A7FomshIT/txQWoV7y5FmwPs8n13JY3Hdt4tJ6MHw2feLo710+OEp9VBQus3JsB2F8ONYrGvs00hPPL7h5av/rzTdE8F67YM1mSgeg7xEF6BghAkEA12OOqSzp2MLTNY7PqOaLDzy4aAMVNN3Ntv2jBN0jq7s1b5ilQ2PGkLwdtkicq/VZcRyUqVbZbMwz05II3nqx3wJBAMsVhRQ5sdFCRBzEbSAm2YEJaFh5u6QT3+zWHMFpPJRnaBAWz3RXKEnleJ+DS2Xz1Jm6ZrmLdZiwMx/8dK5rDZECQQC7GTdWi7ZC3dIcpwaKIGHRhZxmda8ZMkc9Wwwd8H7I8aFUZFPCu0xEc7SXoHHACit8zyfwBYpvMN8gPK3JnOkfAkEAsUSpk0wBMT38one7IZOHzCDgGkq4RbKrhdon45Pus0PIDDM9BrqFimtpbSN4DxhVfZK91DwtfAhhuAvv9cewYQJAPMhpAqv3PBGYmtRDUlWXJQv2JRJJkrvbbqgBed2OX5RRgj5V3SR6PBhLbcTZ+q+1tdPkMFzZo5U6MN5m/6oXvQ==", "SOCIAL_AUTH_SAML_ORG_INFO": { "en-US": {"name": "psa", "displayname": "PSA", "url": "https://github.com/omab/python-social-auth/"} }, "SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {"givenName": "Tech Gal", "emailAddress": "technical@example.com"}, "SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {"givenName": "Support Guy", "emailAddress": "support@example.com"}, "SOCIAL_AUTH_SAML_ENABLED_IDPS": { "testshib": { "entity_id": "https://idp.testshib.org/idp/shibboleth", "url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO", "x509cert": "MIIEDjCCAvagAwIBAgIBADANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzEVMBMGA1UECBMMUGVubnN5bHZhbmlhMRMwEQYDVQQHEwpQaXR0c2J1cmdoMREwDwYDVQQKEwhUZXN0U2hpYjEZMBcGA1UEAxMQaWRwLnRlc3RzaGliLm9yZzAeFw0wNjA4MzAyMTEyMjVaFw0xNjA4MjcyMTEyMjVaMGcxCzAJBgNVBAYTAlVTMRUwEwYDVQQIEwxQZW5uc3lsdmFuaWExEzARBgNVBAcTClBpdHRzYnVyZ2gxETAPBgNVBAoTCFRlc3RTaGliMRkwFwYDVQQDExBpZHAudGVzdHNoaWIub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYkCGuTmJp9eAOSGHwRJo1SNatB5ZOKqDM9ysg7CyVTDClcpu93gSP10nH4gkCZOlnESNgttg0r+MqL8tfJC6ybddEFB3YBo8PZajKSe3OQ01Ow3yT4I+Wdg1tsTpSge9gEz7SrC07EkYmHuPtd71CHiUaCWDv+xVfUQX0aTNPFmDixzUjoYzbGDrtAyCqA8f9CN2txIfJnpHE6q6CmKcoLADS4UrNPlhHSzd614kR/JYiks0K4kbRqCQF0Dv0P5Di+rEfefC6glV8ysC8dB5/9nb0yh/ojRuJGmgMWHgWk6h0ihjihqiu4jACovUZ7vVOCgSE5Ipn7OIwqd93zp2wIDAQABo4HEMIHBMB0GA1UdDgQWBBSsBQ869nh83KqZr5jArr4/7b+QazCBkQYDVR0jBIGJMIGGgBSsBQ869nh83KqZr5jArr4/7b+Qa6FrpGkwZzELMAkGA1UEBhMCVVMxFTATBgNVBAgTDFBlbm5zeWx2YW5pYTETMBEGA1UEBxMKUGl0dHNidXJnaDERMA8GA1UEChMIVGVzdFNoaWIxGTAXBgNVBAMTEGlkcC50ZXN0c2hpYi5vcmeCAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAjR29PhrCbk8qLN5MFfSVk98t3CT9jHZoYxd8QMRLI4j7iYQxXiGJTT1FXs1nd4Rha9un+LqTfeMMYqISdDDI6tv8iNpkOAvZZUosVkUo93pv1T0RPz35hcHHYq2yee59HJOco2bFlcsH8JBXRSRrJ3Q7Eut+z9uo80JdGNJ4/SJy5UorZ8KazGj16lfJhOBXldgrhppQBb0Nq6HKHguqmwRfJ+WkxemZXzhediAjGeka8nz8JjwxpUjAiSWYKLtJhGEaTqCYxCCX2Dw+dOTqUzHOZ7WKv4JXPK5G/Uhr8K/qhmFT2nIQi538n6rVYLeWj8Bbnl+ev0peYzxFyF5sQA==" }, "other": { "entity_id": "https://unused.saml.example.com", "url": "https://unused.saml.example.com/SAML2/Redirect/SSO" } } } python-social-auth-0.2.13/social/tests/backends/data/saml_response.txt000066400000000000000000000421271260133235600260460ustar00rootroot00000000000000http://myapp.com/?RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL215YXBwLmNvbSIgSUQ9Il8yNTk2NTFlOTY3ZGIwOGZjYTQ4MjdkODI3YWY1M2RkMCIgSW5SZXNwb25zZVRvPSJURVNUX0lEIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDUtMDlUMDM6NTc6NDMuNzkyWiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI%2BaHR0cHM6Ly9pZHAudGVzdHNoaWIub3JnL2lkcC9zaGliYm9sZXRoPC9zYW1sMjpJc3N1ZXI%2BPHNhbWwycDpTdGF0dXM%2BPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM%2BPHNhbWwyOkVuY3J5cHRlZEFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIElkPSJfMGM0NzYzNzIyOWFkNmEzMTY1OGU0MDc2ZDNlYzBmNmQiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiLz48ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPHhlbmM6RW5jcnlwdGVkS2V5IElkPSJfYjZmNmU2YWZjMzYyNGI3NmM1N2JmOWZhODA5YzAzNmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiLz48L3hlbmM6RW5jcnlwdGlvbk1ldGhvZD48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE%2BPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDc0RDQ0FobWdBd0lCQWdJSkFPN0J3ZGpEWmNVV01BMEdDU3FHU0liM0RRRUJCUVVBTUVVeEN6QUpCZ05WQkFZVEFrTkJNUmt3CkZ3WURWUVFJRXhCQ2NtbDBhWE5vSUVOdmJIVnRZbWxoTVJzd0dRWURWUVFLRXhKd2VYUm9iMjR0YzI5amFXRnNMV0YxZEdnd0hoY04KTVRVd05UQTRNRGMxT0RRMldoY05NalV3TlRBM01EYzFPRFEyV2pCRk1Rc3dDUVlEVlFRR0V3SkRRVEVaTUJjR0ExVUVDQk1RUW5KcApkR2x6YUNCRGIyeDFiV0pwWVRFYk1Ca0dBMVVFQ2hNU2NIbDBhRzl1TFhOdlkybGhiQzFoZFhSb01JR2ZNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNEdOQURDQmlRS0JnUUNxM2cxQ2wrM3VSNXZDbk40SGJnalRnK20zbkhodGVFTXliKyt5Y1pZcmUyYnhVZnNzaEVSNngzM2wKMjN0SGNrUll3bTdNZEJicnAzTHJWb2lPQ2RQYmxUbWwxSWhFUFRDd0tNaEJLdnZXcVR2Z2ZjU1NuUnpBV2tMbFFZU3VzYXl5Wks0bgo5cWNZa1Y1TUZuaTFyYmp4K01yNWFPRW1iNXUzM2FtTUtMd1NUd0lEQVFBQm80R25NSUdrTUIwR0ExVWREZ1FXQkJSUmlCUjZ6UzY2CmZLVm9rcDB5SkhiZ3YzUlltakIxQmdOVkhTTUViakJzZ0JSUmlCUjZ6UzY2ZktWb2twMHlKSGJndjNSWW1xRkpwRWN3UlRFTE1Ba0cKQTFVRUJoTUNRMEV4R1RBWEJnTlZCQWdURUVKeWFYUnBjMmdnUTI5c2RXMWlhV0V4R3pBWkJnTlZCQW9URW5CNWRHaHZiaTF6YjJOcApZV3d0WVhWMGFJSUpBTzdCd2RqRFpjVVdNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUp3c01VM1lTCmF5YlZqdUo4VVMwZlVobFBPbE00MFFGQ0dMNHZCM1RFYmIyNE1xOEhyalV3clUwSkZQR2xzOWEyT1l6TjJCM2UzNU5vck11eHMrZ3IKR3RyMnlQNkx2dVgrblY2QTkzd2I0b29HSG9HZkM3VkxseXhTU25zOTM3U1M1UjFwelE0Z1d6Wm1hMktHV0tJQ1dwaDV6UTBBUlZoTAo2Mzk2N21HTG1vST08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BTElQdkVNVUVGeXhrVHowQ2N4QVA5TjV4Y3NYT2V4aVV4cXBvR2VIeVFMV0R5RVBBUDVnZ1daL3NLZ1ViL2xWSk92bCtuQXhSdVhXUlc5dGxSWWx3R2orRVhIOWhIbmdEY1BWMDNqSUJMQnFJbElBL1RmMGw4cVliOHFKRy9ZM0RTS2RQNkwvUURtYXBtTXpFM29YOEJxMW5Ea3YrUWh4cmQwMGVGK2ZMYVQ0PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BRVpUWDhHTkM0My9yWStTUVlBMXRudHlUTTVVNkN2dUNCaktsVEVlekZPRjBZZHhCWUdFQVVjYU8xNVNKOXBMemJ1L1h0WGxzTkVMZTdKdEx4RUpwYUxubWFENnIranNWczdLaTBLNHRTMGNBUERDWHV2R1FoMmFOVjVQOGJ3N1JWUGhLOGQwYlJ1RklGR09FOHMwTTZYOUpxWDN4S0MvL1lSbVVoeDlybnU3ZWlwMGh5ZitPaUZiVGR2SDY2NTB2LzQ3aVdKcDNZeFlUV0QyMHBNbVRJMUpwWUEwYjByWVFQRkR0RU93d0JxYktxanRJc3ZYVFJzeXJhQkxvbnFOeHN5dHpEWHEra0JsMXp3WGUvSE5QcUVQblczdnNxaFhZcDVGM3dkWThkKzNCOTRMZlpOdUd4a0p3VDNzdVR0OGY5VHRBSlI4VytBUmtzT2M4eDBVaVNsVG5BNHFHOTBLMTR5dkVoVHcvd2drZjFXV01RT3dpZDNpakFYbUV4MU5MbVZvYUxYb3p4VExkTjN6YnJ6VEJIRXc3R2J3ZEdrdU5pMlhZOW16YUgwaWtGRm51VUxjMHUwc0pycEdGdzlaK0VlUk44RzNVUVZ5MjhtS2g3ZFBwWU5KbzhyajIxZFFaK2JaeUtTUHZablU3REkyakdJRE5US1g2ZkVyVWFINGlOTzN4cUU2Vk90L2d4T3BMNE5VNUhLV0Q0bG93VzcwdUJjVEVQRmhwaThpYUovdTB6YzUvTEhvdVBjMzByc1RLZFc5cmJLL2NWaHNQUHErZzA5WHZpZ0QweTJvN2tOc1pVL25tRXFiSzBKOTBrazhCR3I5cXRSczY4bUJnSURtUHVwUkhwWjM4eXNnU2VZN3V0VlVaSG5tQ0dzTzZ2NDJ6OTVOK05Pb3RCTEVZbFd1ZEdzYnowQWc4VkRDSlY5ak95QW95MDZyL1AyUHBsOFhjdmJza2d2T1BMMWdDNnVYbVJJS1lmOEw4UDJCNXVjN0haK0dtUHNOWXRLS2VKRDFFUHovdCt2NlBIbXNVb3dsSDhSd3FMRHdtMUF4dlNLQTR3UXBlQ0dQd3A5YXRYS0lWMS84NUZzRWMzajVzNjd6VlRybThrVEpydXV2MDZEdFVRZDNMOFdwTkV4cWhQait6RUp6U3RxSG04ckhNMVhNQUVxdVozc0xycTVqLzFSNlpqS0dOdFJCbjhwOE5ERGtrWm0vWTV5TXlJNXJJS3U5bnA3bXdaaEVpeWVHeHdxblV3VVMvUzVDRjNnMHVidnd4eVVnalVvd1ZvTkNqYktBbkdtT2VCSW5abkh0eGdIVUhVOUVlTFdyd2pRc3JtUmpJV0R2RkZQa3l6SzJDL20yaitubmNxc2E1OGRLVXZxcGR1VTRJYnNPQng3UGpXdXRBNmY5bXd6YWxyRU1NK0lGR3VPdk9HMC93eUdzQjZLREV6bldjUC83NkQ4angzaHZFSlAzN3REbFgreGM4Qno5TXdKdkd6VG4xbTdCb2xoR0lzSXlCTys1ZXpXa3RDWVVIUURGVE9wbXA0MDlOWHp6ZUNTUGY1U2NDWG5YYjRPd01ULy9VM1JFUnRRbGMrNmU2WG1JRjhoRkJVc0taUUJsS2ppSDkwZHlzYWlsNmN2V3UyQW55Q3QxbWxXcHFLc0MzU2RTRVZDTG1qRjlUQUFUMEtFSGdZQjg3RjZtZUpTTysvOXkyZkRuYVVvUUlUVzdubnVuSCtkT3dWSGZMU0wyL2N5YTltNlQzR29TSVNMbGJPMVRzalhKclVkZW55OTcvM2tkNmhFQlphdGY1U3NETFQ3SjNsQUVJNDROeXJ0NkIxQWdod2JNdkpqd1JNTXRNdUJLc3ltUytKVzc4UFNEWXQ4MG9waDJQTTc1N0tBNCtUMTAvYnZaQkE5Vk1OdVpqNVV3NXRWMnFIS3dwS0t6ZVVETUFiQlBRaGpYcXlQZzFKa09rd2RQMUpnOHRITjJTelBZQTlmT1htV0pBZGJDS2tMb0F4ZTV6cDZBUzYzS3FXMmFmSUt6SHJ3RTJmS1VtamppeURvMnNuMkJHbWtBaTRzbnpiVzc2SUQvSVgwd044aDBaQ2VRc29vKzdtb1RCMEJxSnBkS1MycXlsUktoc3BSTC9henVQdmxaK1pwckJxdXpJdEZkNFVLMkpzQkp6VXcwZkpxcTV1bk9PZENzVWM3SUU3QTNmZ1NmZ3NBd1R3WFZJMEVoME5ySWZpMkFKV1Z2VFpEMys2eFZ3dS96WWhuVjc0VXkvMFE4Mi8yQWtpSGpFRjNJVGNLWHdTNTB6bWtLakxjZDJqa2h5TUFYMWRoQ0wwZElFMUJoN0RNamVvNC9YbjBqSlpPL3Rrbi9xZmYzc3RNb1BYVG9KTnBIU1RjR2ZheGtaMzJYNCt3Q0xPc0VBRWxlMVZSY0kwUkZyOFhHTSsxWU9BTjBodFdGcFMxaG9kSi9OczJqL1FnUVNEemNpQ1FZeUFDd3lFRWZDZjZybnR0VmJyTlJQZWlmSHhBM3B2UnZ5ZGRhNDE5cXl0ZXI0akJ3cmw3ZUpuVnJ2VEprR2VhU2FRbDdXWk5SQXBscXRnNnZPYmpiMHZDRWlFaFhKbmNzQUhxcXp5QTRGeWFUVGQ2R0FySU9adUNxRWVoWk51T01lOVlrMVpya0VkR3pIalJESWk3Q1BKQk12NEZ4ZHI3bnJvN0I1WEhKb0ZMNE1DSUtOWWU2aWZiTUtYOU5uN1FWdnphUmY2UXlaSW1BWENQZndvU1BkN2x6NXl3UDJLSUIyaGhFMWt5eVZ5YVc5T0praWpUY3dvUnZrSXhIU0RqMXFqeGxueXh0QzhVZ1pNWmlwcGgzQXJpcjRiekIzUDhIbGIzejZ0OW51KzZMemNiN2ZObVo0UHluaU50Vk9OQ0lHbEh4dTBSY3hQK3cwUXNsM1BtTzJLaHBpc2RIanhvSUJ1YVY1NXdoTlFFNmdNNFBrT0xINDc4Rzg4bUxkd2s2RFpkWVl4L2d6RWE3b3ZIL0pReFp2TzRLdFVTNmZjZHJxV2thTFg1cEhkNkdneFBGZ2NFc2Nad1ZqM2hCS0xFQmE5L0dodERINEhzRnNRbmpPZnNDQkNzN0tjRitmTi9oSUdUeHFqTVlKVHJRYmNtdWF5dk9xR3RQMDFPcXltR24rVm5FSVkzKytQcm95SFN3K0Q0b0JIVG1maFNXRmJLZCtuTlVFS3BhRVIxNkdCU256WktQRVRVSmdRWEw5QWJRQ3RXVjFHb0UzRWNnMDZYaVd2aHFHakpGNldtdEU4dHY4Q25rZmxMNm91TDRvNldpbmx2WnNEdkZrS0R6TDkwUTNsWC9NanBtRTFpWU9uYzdISXdEVGwraFRRcHdsYXJiTDVUNGNkZTg1akNwYU0xU3p1TStiQU5zMHlXVDA0ZXJUVFc2cnhlbXFDTHAra202TVVMTlZOcE1CazBiQjJpRU82UlRtc3VpRlhDUU1xdU5xZjdkWXUwTFFCZzQ0MkJzU1pBV1ZrWEVZblduOURLdTRSby8veEFsb2h5VHozWlZmSkhuWVBSdDloSUErRHVUL3c4T2ZzTURIWnlCelUvL0JEa1NiNkxjMHdraVA3QlhIdjBoNVdud2dNWUxlZDBPalR5UWI2aGxpVnQ5b0FjaDRFVy9EZUlBdkpaQ1BYVm1pUFFYTGVsOVJIRko2bXFiYVo0TCtaZG1ONmQwcFZNZ1FveXhmQTR3dEwwYVpiNnFZYkhibjJMd2VBQVZwL3M2TzVlMVExdnZpZDRTWHo0a2l3RW1LSStIeXZEQ1pnekpQQVN5Z1gvWDJFWEZ0NGV3SjVmUFQyVXZmWnhQWlpqMFZGSFpyUFQwWVd2VE16bjUva3hoT09oM2drVGdDSmNwNWVsZnp4cEFPNFl1a0NoNHJXdVNndDRqVUJyaWNYbFdWdWo5U3JSZVhUalNHTktLK202NWovUDllNHRHT0RkMk9BbjNKTVQ3Q3FuaDhreTZpZjVjbmpVMmU3UDhTZnBONGwxWEFiZEZEcGk5bVJYamEyTzR1RWFHNGNvNW4xcWNDT3ZNMWYyblFBY1ZGNUFoSXhueS96TWhmU2l2RXdOQ0Zyd2tBWDRyQVE0WldUNldFakFyUG5jb1Y4Z1VRclhxQVA4NDJmK1lNWWI5RHFncmFicEg1a3ZuMnQzcWRldGJHODJ0QWlTamhPcUxNYW9iU2F4cXdWa1lUOHRTMW9rUUt2MWZoZ2t6elpEOE5IQnVQQzdNVHdXS0VCS2tDRUUzRWRFMXhNQURLd1B1M3NSaGpSaExXZyszZ2srejJtdlU4cTBhTlc0Y3hObUdoekx4eEY0Q3NFNStMQ1cwOWFpUVJOM1VvWmg1aktBZzBiMlh3WHBLS3pycUVTY1BYdnI0L1dWUTMyMm5qRWRvQVdXR0t2WnBKMlRlREo0eDdiT21LVElFc2RHWU1UZzFVaEU2eFFQcnhqS3dWeGFJNVJyaVE4a0xpaGgwa0t0WHQvYTVsSDhzUjVwR0ZISGZ3dlNVb3liQTB1eUVDNnNRVitPbTVReUZmRmpqZHFCOGNpOGxQS1hLTHFCTHJ6bjNmUkh3TmQwbzFiRTg0aGllTkx5UlhZVmhrRCtFNEpGaVd3ZWt3U3VWM3BjQk9ybnRVU3RoWmx6M3hIUURUVGNJNWliOFJyQ2swZEZ6YTgvQmw3VUdtWlUwSXZ2UmdvVXF2TXNHT2dMY3pGWmRpZnJ5aGNiUTY4a2ZzZ3lCMHppdC9MN1BSV3V4RkdYdDFoTVZSVUZ3WXBJS04zVkI3cXVKZlgwamZsU1JaRndMaXdlK3VhYndmTVZ6c2doajUvOXZNNzcwK0JaMGtJcE45NzBTMG5BbHl6R0h0aW1nTUl1RXFhbUt5QTNTQlI1aHZIYmRyNENnTHFUbXIzbFFnWmpnSkNvN1FXYUJWTXdCR0RpdzVOVVhUUnBycWc4U3h2eDlnNWZwbXMrL0o2QjFEelNTM3ZRZzgxdHFRU1ZDWVJpc0Y3M2VqZlFuZk4zcUszd3RJRDkxQnRISmFvMEFaUUdKVFpKOXVsZ0kzV3hzdWR4ejB0VHVpNlJlSWpmSWsxekZRdFpwRExGMnB3NGpTQVdQTlJqNDBYdVIrRzFUVlI3OVFiME9FYkw4RDFoTU5zWmo3MTZNbUhSOTlKaUxNdm1FWHV5a1V4VGhGYjRMTzZVbW1kU3UwTlBpMXQ2NmNkYURpQWhMaVBFTGdUNkZsenA2T2FGSGNSNjRncEtyemtTNDJONEhJeFpNa2R6M0FsYkRhK2pOWHZPR1l3UWl5K0xNNENZWGtrTWtHR3ZTWis5R2xWQ0l5RXBJaXIzbEQ3bmdzZGk4emxGWDYvekNaczlQSUtwZFZlSGJGZi9GS20wV3AreHI0Ykd0R0RrVHR2Nk1Manh2YU8zanFHaUFWeERKVWFkTVBlS2VHSm5uempTdnpKbGdOVHV3c3grRnF5L2dPMkwxMGowWmhDWi92dE9NelVjNjl3cGhKZm9FNzU3V3lOeFJOcThJc0Y1Tkg5Y0x0b3UvbUNxOTc3YnZPSkRrSURCN3lKWEJ6YUhVQkJuSXJra1Qyemg3bGJmUm5SREJUSFZraVZMazVESUxqeC9XL1BSZEZpUUM2SzRmZGx4Y29JbzlMcnM4ZFVWZkt2TTNNYnJ6c1hGT3ZtVVh0K3NsZldvd3UyTC9ndG9mRFhvTUJZZnlEcWIvWlRaRWZ0MC83blliRm1relBEUlZacU5SR0F3YWZVNTU1UjB2SWtNbGR2VjdKUzhNT1BNYWlXQVBpelNLRG4yRzNvcys1MzRFQytaOGZnWmFPVWpZL0xLME9vME9RMmhvNUV6MGNMYWpwUjFINk9FNEhvUm1ydjQzZkFjdGpYc0hYdi81RXg3emdrWk1NZXZhTFNEdjZtcjFGcDk4QXR4L296VTFGVDBoMDUxcVcwR0g2VWpRRXk5aExSZDBBMnFkUTRMZXpReDNvbDFTblhsamt2MG4zTXFlaFozOC94bzZhdHFDdkJtQkc3amlUdXd6YnlVUngzRm1TM0NCNllOYnFON3hPYVRZRnlkOEZDL01nY0xGQmMwS3F4MXllQ2VUd1hucldQb0dvdllVQlYxYjA1cWtIa1d5V0RUaCsveXJFNzF0RjNxbUQvd3F6cUJyNE04NERtWWVuQkdFOWxtb3FIZEMyWnRpK09KVFZKcmlHZWxQQ3RjZnZRaUlQcHdDZ3BFNmg1ekZhRndLajRuZGtBUkRpTC95L1EwWTZxNU5rM1g5RURlTmdjY1pIcFdmOUpKQ3M2a29wdXRtYjdDczIrbVJYdER1S09DaGY5UVUyN3Bmb1NJaklYK3NGdHY1c0hhSms2aHBZMlpzUUhzaTBYbFowc3FMTnQ5ayszdTVnYnBSU1JCczlHaC9BaVY0dkNyYTRkOTh5U0dCdzRSR1FhSStpQ29RaG9YK3lxc3VrYkx6bXJUU3FXMVRXaXJReUlHZ1Q5VnFERE1mUzAxeGdQSlNFSTlIWlp6TGlFVXVGMm1CMi81Y2dqaEFUaWQrdGV1UVB4aldhN2NSc2t5YUhuTENjQURVUU9ESUFPVjJDWXROcnAwY29ZL091S3ZzaXlJT0lacVJ5dE1PMGVNZ1ZJWTBzWmdxeVEycXlubUx0NDBmWmd3SFVyV245Zm9TYTNtMkVRTy9uOS8yU2NuelJWdVZpVnNjM0tCSElQL3AzNlJlSWowTGlNcCtPQ0p3SHlLVW1UeDRBU1V0dXVhWktlRHl1QjlxcXJuUEFNWUVCeElsTGFvdXMzV1pHakIrcW9ub3QvNmk1UE40bUZjbHFDcUxhMGJHbks4ZnJxYy9yd2tuVGV0YUE0c2tXTEw1L21qNEd5MitFQkh3a0x3UXd2K0FKdmZTOXYvNDl1LzY0N1ZFYW15UzdZQ2ZEUHNBQUREQ1FFcWJNQ1h2Ui8xVmEwWi9YUWhoNlkrZUt0MEVpRDdpNmRZODJtQkFoNEJMRmRVV3VGZHVrdUVwaGZ2WXB3N2loVjNxTjB1NFM1NTRXU0dUa0ZsdlpYNG1hbkF4a1g2ekQxS0NWaEFMdEJnSDgzdkhxam9uc0lwOFMydHgwZ0tiYzEreHVaRVppVWlNVVlVdTByQVFsRFcrZHJoN3lVRHZqekFHSnBmTk01eThaMW45em93VzZ5YW5VZWFBNjhSZDd5TUxobFd0NVh6bGhBTVZDZmZYZ0pFelR1YzJEbENVOXNMLzVTVkRaV2N4R1E5aFM1cnJtK2VyQ1Jxd2FJQk1DNUtza0RCZHdOWmh2Q0FCdEpqS2Vla1FUSjd5MFp4SGNhbGVCaU1rbkYwZVRDZzFvUEhPUVZLQ3V3NE94cHRZUS9xS1V0TEFIWFZ2OTlLMGRWcWZDMmpVQWlHQmVYa0t3aGRYTGtJYlZxU0EyZmxraXBBeEhYNnByUEExQjF3eTVab3hPUFg4RVExOW92eXpBbFg1dHU0OXEwWC9PSExFN1o5T1cxenltRXR6ZFpyNXJZbWtFcVdtcHVSNU5jeHFwTWlZam93dUNXZWhubzIyeG5JM09IQ0xDZkFKaHRrcklhL1hPc0tZRFpCRzFJMGJsN2taR2R5cEtUQlhYdXl6WE5WUlU5L005ejhaVytwdG1oZ2NOUzBJS2VaaSs5bFl4cWRlS3lnbldTTTV3czdSYUpmNlRRZTNSaWJZUjFvNkhwRzB2VHpiTEtQZTZnRjJGODdiWlBJei9mcTNLWnZiM3UrSnhZcCtJVjBtQi9VN29YelhRRk1RK3VmWllpNzUxbkx6WlVxRE1ybU53TFJPVUFNUk8rVnJtblkwSVB1cFBVMXc0b0hBb1dnVGRnTk5pNk1uTFQ4V0pmUlhjT0pKMk1lbUc2K2ZNeHNZUU52UVJwa1RGY05vaFV6Y3ZjcHJ3NUV3WEVZQTJzbzczL2MvY3RIRGcreU05YlF4REppUlltRnFydkhYb29hS1JyekxnUjZLVWdoM3ltaWxaQ0lSSm9KbTE3aEtHM1pxTTE0Lzl5OUc5OE9BZjNkVTlqMDk3aUNlaEc3a2VxYXRJQ2hFWmJqbmQ4Y00rS3djN2FtVWp2ekQzQmNvMHl3MDJxT054OWF3OGhSblZiWDZhdkRJbGhySHZ6SU44MzFvUjljRHBwMG1DUEJXZFVDQlNqVGJ1RkZqRC90WElSbGxlT2JraFFKSUdSNlE2U1MxcXkzT29WT1VheFl6THY0U2s3dndrQUMwUitGREVIeVFZbFVhbVVkTWcyUmdwRUdhSVd1V3IxaGNnRm10QmREV2g3ZFBuWTF0U3VKOC95MXp4NkRvN2ZJYmNFenBBK2E0ODNtRG5vemdld3VmaFdqVCsvUS85WlEreFQ5UWJBT1pQSXhHV3VhSXVrVk8zSWxvZDhJM1NGZFJCTHY5ZXBDNzFLeXpSdVlpMktkOHJ5NVNINit1WnMxUHlZUlpRakdDK3Q4VzRtSE82Z1lFRWVXSkJ1UWhnSHdmV2xhZXlWb3hac0NBQVZKRUllT3hPZDZtNW45OHRCUDdHTmgxT1M0eDRCS2FVN1A0UVQzNVVIZW5meE84WWFQUThmbXlobUJhSVJVZklBTVN2ZTJZRFp5SWNNTTkrN0tNSVVabzJ0eXRvYzdCOGVvZzBNaUkrVkpFdFg0c29FRjFSWkhQZVV3NWlCTjI4OTh2MmVTcGNnVUJhWHFzOUN5VlZtTVJQMEtLUDJ1REt4MUdJcUhjS0ZCOXVQVWRkQS9vT3dNa0tVUWsraFZVVDVPbEVMdjd1a0FBUEE0eE4rZkczVmYxeUVKV0FiVGx5dWtGcThjNXBTRkY1cXVHbUgwVmVpQzVvVEFka1VES3Z6WGhWWUs5c3BRYjNVZ1Z0Qld6N1ZScnlOUVVST3BIZU5xeDlhZHA4YWREWCtRSHJUKytYblN4VVI3SVdGanlNTkZJRWlMWmkxdks1UVVrZlRDUU9qdjh2SHdiUi9MRHF3Z3M5bXdsT3pPY0RLdVBVK0dTb2lnVFdRejRWN0N2SHRaVDI3WUdKVG44RFFFM3IzdjB4aWxvODJ2U3VXSDg0WEU3VEJsTUpFb2R5eDNDRngwVUVkc3VhRHBPSEV3UjZYNlUyU0xseERYSXVZeEhlNXh2NjI4bXU0bDRMSnBYUjhkYmljTEZKQW55Q0FVeDJLb2dDamt1cmU4bXNUZktDbG8wamFlN1hNR05PSk15b0ZYbVlHZUh2eGhNUGMzTEtYLy9VY1p0c3p3dFJrQmNFdURXQysvQWNWZVBOSHVOWWI5MEpIcnRucGg1ZDlhL1lpTkpzY1N3QTFwUVZrdW1TQWtPQWdLdWRzcnl3c0N3Zkg1anNydVpHUTJDd1hKRXQzUU4wU2NLUlVnT1NCQ3FYa1BqZDVSVzJuOFZpamt4anovbWptakhCNmk0eHM5NEU2Nzk5STAyaldYNVd3UDZhTFRaTGt5TjhxNDUxT0RmeUZVZEY5WWsyZXQ5VUpsV1NzRFJMSWVCd0ZyQkEyZTdyRWsybWFLVUNCRW5PUWM2bUhVMXQvZ3gzK1VXVVFXbkpMZVUxbWUvbkFEdy96UGUwd3d0Vm9BaERZdDBoR1hQblJydjFoUHRGS01CeWtqckg3a0J5U0R3WDlQMi9XZkNkQlE5K1J4cHRsR2hvRmdpMUs0NVlOeEpEd05wTmd5MDV2WXUzVUtrMkpRYVNGUzcwK0Y1NzluRE5RenZpK0pPRlRsdDFmWDJGNXk5NEV2NHZobWRQSmRVOFVVRjU2Ymx0emxKREVFdmsySlFrOTM0aHpwTXJGZ1d3ZHUxUkxxSEhCN2h2T2hnaHNqV0ZGY01zNjZaRUtWcVhKUytxWWNVMHk0akwySVQrNlF2N2pvQ3BWbUdzUWtGY1FyblhxOUJiOTdaUS96UCtwaldmWTU0UmNRVlMydUU1YURObVVyVkdLK3E0d0xRcUhuRVViT2puSHFFeGlacUtxOVdRaUtUK2c3QS96bVlIQ2k0YzFTejRNVWhHb0t6U2l4aXoxYUNJUEJXdy9vczR2cUVqbXgzOGx6YnV0OWNWbElzeGNkTUpUTERRK3ZOZ0YyY1ZRaVcxRTQ0d3lWcnI3TUFaOE9KRVpFSzlEZWt5MzJQUkFuSkRUVXVqdGFscmJ0T2VOczhyS09uTjcvNFRqUEwvZmRlbEI4bjA4WXdSNXdmbU42VGpGWUhRSDFjbUZmK1AvNUxVMTI4Q1pEYjNQUStxMlFJazV3aE40eGwvcy9lb29pallmeWtDcm5aSEhHWkluTGhoU2pWbk5ISWdTL203VWV0NlhBTDdvZUl5UFRLeHVnbDJzRWtUQzNnZ0tjTnFZR0E5U3ZlYVlaQ00vWHNQRUtQbWs3QmlRNmprWFBKaE1yREd4Vkc0SW9aSDgrYjBrUWJYR2l0Mkw0L3hZdHh1bTVzcFNPSjdsTDltVFpRNnBxM2JOaTEwZU1mZ0ZWaDc3NU5JRlc0SEp3U1FtaTU0bk11blZTQjhxdjZKc0w3SGlsZ2N0ZHFSNThTTjVad1lCa2dOR1hzYjA1QXJWemVXbHh1Y21BSHNPT3dyczFnMzh6bTRZN2ZPZmducmFhV1kxanZZOFlEODZQZThkZzR4cE5paTg3UnNDZk5WK2NKVmMraktFdnpuZVY1Zzd0RmlxZCtsZHp4STlKemdSS2t0WUV6RUpRSVU5M2UvclJaN1lrVkZtNVV1cjVhMWYzcG83T0VtYkJUc2MrQ1FaOGNnYmIvbUphRXJoa3NyL3JURjBNcjNxeDl5SlJWSEJ6YWNWd0dScEFRaURPdnJkWU4xQXBVOTRyR1lrVFVzdWs1YjE1Wll2QVZxRlRzVlVMaS9HY29mbEljMm01Z2RFTFZOblRmdXY1Zlk5S1NlWHFoUU80S0pOYVZmbHAwQ0VKYWFFZFNLUXJJNXRaT2w1RkE4VXZlNmxTWVd5TVk0REl4a1RiT1JoWHVBdzR6b1RTMjgrN3d2TXhydVBkZnlKbUJCTkhQdCtEYmdKNHovcHJZWUhpTmFMTXNZamtQZE44ajNKZDczQXJFZk92Um52MzYxSVVVMFg1RDc1dlRSdlpkbzMzWERzanRlOU4weUo3K2lIQnF1a1FJY2pIVW9ic2RQN0hOajBVYWNSMHIvTmRlVTlGNFBNc1VLY2t6Tk4rZGhyMVI2d1J2R1VZb1pDRWJaWlJMWEt4QnA3SElUNEVQUktHakIvdW1xTFhhMXl6RWx2QW1WQUJhMDFZN3dGdk4wM2Ywb25FbUhTM2w1d1paRmV6cjVibnN5T01XVGxhMU5kaW1ZNXNVeE15VFliZmc4dzB2cXNEc28zWFAxYndLdzZ3M3VIRGQ1UHBSWnVDSnR0eWk0ZzJGeWI0Ymg1UU42ZkdORTI2ekRGN1Y4QmJwZXJLNkFKQ0xTWm5kaDZMMTlPUTBram4xUGpEMGk4c1BZcGFXOWxVeVJkZElPKzRWQS9LemxPUzJ4M2s5VUtUdElsTTBUSVdtZXFIS0dYUVpocGpvVGI2VlNKN203cjZaaVlQMnVsQVVvZmVWL0o2eCtzckxEQXkyQ2ZFNnFrREZ1OU9NWDBBSXVnN3loQUtOMDRyT3hVNk5tcGtjOUZ4bXUvVS9vR3hHdmIzeFVFTDYwdE1sSE9EaWtqY1I5RDJrKzRwbEc1WnV0d0FIY2kwRU02WHRrVEhQOU5QMlRTR1VFN1E5SGYvU0VEc2V0a25hZXhvWmhDczJLWDFMeU5JS0U0N2pkMkR3MTUreDRRVXV0VUFTbzU5Q1lHMVFBeW9BVVhrV3dtbXkzTGdTUWp5T3ZLV25qaE8veWpPd0FyWGd0NFBrSVVnZDQ1N05ReFpMbU41K0J4NVJoQ0FHdkUxYmxOZjlMek9keGJiaG5VZ2Z1RDM5MXVSRkhjS2RYREY3ZmVqb3gveThtaWZJcTRWVzQyajBHQnFOQUtkK0prMnJCMW9hOTRiT2hxcVVzanhqWnlRaGRXTzhNblR6T2tOaGVpZXU2blYxcW5yZ3JHU2huWTNJMlczb29GNFNnczRjZ3drZ2h2dHpFa0xUbU5OUm83RTdudVRuMkxJcmlGSnlvTmZQdUp0aWN0S0JtNzRGZytkWVBTMlIzTzNmOWxBZWxiVWZjbzZGNU9EL3hkS1VuRTh0V3FOMExVcDlWQUptWVZYZFVDaGJ4MjM4MWtDaStLNDJoRzUydFNQYU1hb1dTb0xQY2Zrb24rc1pYdjdEdEtwZi9HTzdhcUMza1pzRGpva29haHJGZGJWSlNTZWhrNGp5K3RzRHplQnJKSjBrMVZrUnJHN1NoVHZjTmd1cjVucVRUTEE5dlJMQmJNTTlhNlI1NEZ0Z1pQOWFKMU1aMEdCcUVpMnF6Ui8yd2tYQlhwcFhZdi9TcU1RV1dhbTVsSHBMVktxaDN4ZHRjNFdmck9mYldsbU1PNXA5Z0JUSFp1YUcxVGFkZXFRVVpKQmZBS01ENFdSR0NsMDFaeDRTVzE0YzZrdnFKdXExL080N215L3RsVHlLWndpYlBkQTNRMVVGd0I3R2Z4anEwaDN2ckxFbUNrS3Vsc0VBUkN6UnZNVjJSVnBVbFpUV240Y1Boc0hjcTNROElHSUYyKy9nOENFSU4vMU8xcVMvMkpXcXlDNmtIb0w4Y2R2R0VHbmkxSTNDTk1JcXhxaHhJL1V0R3REc2VwYmwrSHI0elh4MzZna3BCbXBoT2xkTFVYTHAzVEtibVVZRWJSWHcvZmRmeFQ3WDdZUFhHQ0hHVG1uTzk4WkxDOTA2Zmkvekd2b04rNlpzbCs3MkpWMGxJWEo0V3dZdWxFUmZHbkFDWGNoa0Yzei9ITWR3elcwTUFFaXptQmwvREo2ZUoyU01PSG1Uc25YbElGRDRlcFRrYnFBQ0dpZ2I1UExFdHdQRVRjYkNRckM5YUtTU1FnSTdEZXd1aWlxM2J0Y0RUWkIzeEI5WWxlbmhpU0FXNjIwcmwzc2ZjY3d3eGFSOHBDV2Rzd0x3dmFxcDhjM01PV3RCc2xPcmVTSkNEcWgvdzBYbm1WMFJVWFpNM2JvUmkwVXhsaHVUeDFlM1NTd09pbTlOczNYV3NoTmI4Lzc3VkhnUWhRVFlSUU1NRllYaWRmMElCKzBtSUpocWNoQTlUeUY3dGRjSDhrUUJUSHNEWS96bFpqK3EwNlFMd0JkbTkxc3IyK3VzZmxlaXB3WUMrcmdiNHROVnA3VU5rYkVqTnR6ZWZsTi9VRTlkbHZtT2x6V1dtZkh2NGVkUGkzMmJmeUNRS1d6SGJVVEV3NU0yVFpsZnpNaTFWUjVsaDBxQ1lqaDNITUlmL2MwcHBKd2I1b1lFTnBBenlxbnlmdmlTV3lBYzc2L1l1VWwvb2FVaysrYzBZc2d1TGo5ZGFQdVVvemhoZ3VjSytQRGlNckI0ODU1Mk83VWg0aHRwNmZ3S2dJa1JCTVFIUTd6MmV5WXovV1AwQm9ZZVhjOGc3aUprclhFNzA1bFo1bXhGU0poT3E1WlNleVJSb21pUm41K3VRemM5ZFdWQjBYb2JURXdOc0VRM2FIZ25JY29BczY2UGplUT09PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==python-social-auth-0.2.13/social/tests/backends/legacy.py000066400000000000000000000027421260133235600233370ustar00rootroot00000000000000import requests from httpretty import HTTPretty from social.utils import parse_qs from social.tests.backends.base import BaseBackendTest class BaseLegacyTest(BaseBackendTest): form = '' response_body = '' def setUp(self): super(BaseLegacyTest, self).setUp() self.strategy.set_settings({ 'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): self.strategy.build_absolute_uri('/login/{0}'.format( self.backend.name)) }) def extra_settings(self): return {'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name): '/login/{0}'.format(self.backend.name)} def do_start(self): start_url = self.strategy.build_absolute_uri(self.backend.start().url) HTTPretty.register_uri( HTTPretty.GET, start_url, status=200, body=self.form.format(self.complete_url) ) HTTPretty.register_uri( HTTPretty.POST, self.complete_url, status=200, body=self.response_body, content_type='application/x-www-form-urlencoded' ) response = requests.get(start_url) self.assertEqual(response.text, self.form.format(self.complete_url)) response = requests.post( self.complete_url, data=parse_qs(self.response_body) ) self.strategy.set_request_data(parse_qs(response.text), self.backend) return self.backend.complete() python-social-auth-0.2.13/social/tests/backends/oauth.py000066400000000000000000000106721260133235600232140ustar00rootroot00000000000000import requests from httpretty import HTTPretty from social.p3 import urlparse from social.utils import parse_qs, url_add_parameters from social.tests.models import User from social.tests.backends.base import BaseBackendTest class BaseOAuthTest(BaseBackendTest): backend = None backend_path = None user_data_body = None user_data_url = '' user_data_content_type = 'application/json' access_token_body = None access_token_status = 200 expected_username = '' def extra_settings(self): return {'SOCIAL_AUTH_' + self.name + '_KEY': 'a-key', 'SOCIAL_AUTH_' + self.name + '_SECRET': 'a-secret-key'} def _method(self, method): return {'GET': HTTPretty.GET, 'POST': HTTPretty.POST}[method] def handle_state(self, start_url, target_url): start_query = parse_qs(urlparse(start_url).query) redirect_uri = start_query.get('redirect_uri') if getattr(self.backend, 'STATE_PARAMETER', False): if start_query.get('state'): target_url = url_add_parameters(target_url, { 'state': start_query['state'] }) if redirect_uri and getattr(self.backend, 'REDIRECT_STATE', False): redirect_query = parse_qs(urlparse(redirect_uri).query) if redirect_query.get('redirect_state'): target_url = url_add_parameters(target_url, { 'redirect_state': redirect_query['redirect_state'] }) return target_url def auth_handlers(self, start_url): target_url = self.handle_state(start_url, self.strategy.build_absolute_uri( self.complete_url )) HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=target_url) HTTPretty.register_uri(HTTPretty.GET, target_url, status=200, body='foobar') HTTPretty.register_uri(self._method(self.backend.ACCESS_TOKEN_METHOD), uri=self.backend.access_token_url(), status=self.access_token_status, body=self.access_token_body or '', content_type='text/json') if self.user_data_url: HTTPretty.register_uri(HTTPretty.GET, self.user_data_url, body=self.user_data_body or '', content_type=self.user_data_content_type) return target_url def do_start(self): start_url = self.backend.start().url target_url = self.auth_handlers(start_url) response = requests.get(start_url) self.assertEqual(response.url, target_url) self.assertEqual(response.text, 'foobar') self.strategy.set_request_data(parse_qs(urlparse(target_url).query), self.backend) return self.backend.complete() class OAuth1Test(BaseOAuthTest): request_token_body = None raw_complete_url = '/complete/{0}/?oauth_verifier=bazqux&' \ 'oauth_token=foobar' def request_token_handler(self): HTTPretty.register_uri(self._method(self.backend.REQUEST_TOKEN_METHOD), self.backend.REQUEST_TOKEN_URL, body=self.request_token_body, status=200) def do_start(self): self.request_token_handler() return super(OAuth1Test, self).do_start() class OAuth2Test(BaseOAuthTest): raw_complete_url = '/complete/{0}/?code=foobar' refresh_token_body = '' def refresh_token_arguments(self): return {} def do_refresh_token(self): self.do_login() HTTPretty.register_uri(self._method(self.backend.REFRESH_TOKEN_METHOD), self.backend.refresh_token_url(), status=200, body=self.refresh_token_body) user = list(User.cache.values())[0] social = user.social[0] social.refresh_token(strategy=self.strategy, **self.refresh_token_arguments()) return user, social python-social-auth-0.2.13/social/tests/backends/open_id.py000066400000000000000000000200331260133235600235010ustar00rootroot00000000000000# -*- coding: utf-8 -*- from calendar import timegm import sys import json import datetime import requests import jwt from openid import oidutil PY3 = sys.version_info[0] == 3 if PY3: from html.parser import HTMLParser HTMLParser # placate pyflakes else: from HTMLParser import HTMLParser from httpretty import HTTPretty sys.path.insert(0, '..') from social.utils import parse_qs, module_member from social.backends.utils import load_backends from social.exceptions import AuthTokenError from social.tests.backends.base import BaseBackendTest from social.tests.models import TestStorage, User, TestUserSocialAuth, \ TestNonce, TestAssociation from social.tests.strategy import TestStrategy # Patch to remove the too-verbose output until a new version is released oidutil.log = lambda *args, **kwargs: None class FormHTMLParser(HTMLParser): form = {} inputs = {} def handle_starttag(self, tag, attrs): attrs = dict(attrs) if tag == 'form': self.form.update(attrs) elif tag == 'input' and 'name' in attrs: self.inputs[attrs['name']] = attrs['value'] class OpenIdTest(BaseBackendTest): backend_path = None backend = None access_token_body = None user_data_body = None user_data_url = '' expected_username = '' settings = None partial_login_settings = None raw_complete_url = '/complete/{0}/' def setUp(self): HTTPretty.enable() Backend = module_member(self.backend_path) self.strategy = TestStrategy(TestStorage) self.complete_url = self.raw_complete_url.format(Backend.name) self.backend = Backend(self.strategy, redirect_uri=self.complete_url) self.strategy.set_settings({ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': ( self.backend_path, 'social.tests.backends.test_broken.BrokenBackendAuth' ) }) # Force backends loading to trash PSA cache load_backends( self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'), force_load=True ) def tearDown(self): self.strategy = None User.reset_cache() TestUserSocialAuth.reset_cache() TestNonce.reset_cache() TestAssociation.reset_cache() HTTPretty.disable() def get_form_data(self, html): parser = FormHTMLParser() parser.feed(html) return parser.form, parser.inputs def openid_url(self): return self.backend.openid_url() def post_start(self): pass def do_start(self): HTTPretty.register_uri(HTTPretty.GET, self.openid_url(), status=200, body=self.discovery_body, content_type='application/xrds+xml') start = self.backend.start() self.post_start() form, inputs = self.get_form_data(start) HTTPretty.register_uri(HTTPretty.POST, form.get('action'), status=200, body=self.server_response) response = requests.post(form.get('action'), data=inputs) self.strategy.set_request_data(parse_qs(response.content), self.backend) HTTPretty.register_uri(HTTPretty.POST, form.get('action'), status=200, body='is_valid:true\n') return self.backend.complete() class OpenIdConnectTestMixin(object): """ Mixin to test OpenID Connect consumers. Inheriting classes should also inherit OAuth2Test. """ client_key = 'a-key' client_secret = 'a-secret-key' issuer = None # id_token issuer def extra_settings(self): settings = super(OpenIdConnectTestMixin, self).extra_settings() settings.update({ 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key, 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret, 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): self.client_secret }) return settings def access_token_body(self, request, _url, headers): """ Get the nonce from the request parameters, add it to the id_token, and return the complete response. """ nonce = parse_qs(request.body).get('nonce') body = self.prepare_access_token_body(nonce=nonce) return 200, headers, body def get_id_token(self, client_key=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Return the id_token to be added to the access token body. """ id_token = { 'iss': issuer, 'nonce': nonce, 'aud': client_key, 'azp': client_key, 'exp': expiration_datetime, 'iat': issue_datetime, 'sub': '1234' } return id_token def prepare_access_token_body(self, client_key=None, client_secret=None, expiration_datetime=None, issue_datetime=None, nonce=None, issuer=None): """ Prepares a provider access token response. Arguments: client_id -- (str) OAuth ID for the client that requested authentication. client_secret -- (str) OAuth secret for the client that requested authentication. expiration_time -- (datetime) Date and time after which the response should be considered invalid. """ body = {'access_token': 'foobar', 'token_type': 'bearer'} client_key = client_key or self.client_key client_secret = client_secret or self.client_secret now = datetime.datetime.utcnow() expiration_datetime = expiration_datetime or \ (now + datetime.timedelta(seconds=30)) issue_datetime = issue_datetime or now nonce = nonce or 'a-nonce' issuer = issuer or self.issuer id_token = self.get_id_token( client_key, timegm(expiration_datetime.utctimetuple()), timegm(issue_datetime.utctimetuple()), nonce, issuer) body['id_token'] = jwt.encode(id_token, client_secret, algorithm='HS256').decode('utf-8') return json.dumps(body) def authtoken_raised(self, expected_message, **access_token_kwargs): self.access_token_body = self.prepare_access_token_body( **access_token_kwargs ) with self.assertRaisesRegexp(AuthTokenError, expected_message): self.do_login() def test_invalid_secret(self): self.authtoken_raised( 'Token error: Signature verification failed', client_secret='wrong!' ) def test_expired_signature(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(seconds=30) self.authtoken_raised('Token error: Signature has expired', expiration_datetime=expiration_datetime) def test_invalid_issuer(self): self.authtoken_raised('Token error: Invalid issuer', issuer='someone-else') def test_invalid_audience(self): self.authtoken_raised('Token error: Invalid audience', client_key='someone-else') def test_invalid_issue_time(self): expiration_datetime = datetime.datetime.utcnow() - \ datetime.timedelta(hours=1) self.authtoken_raised('Token error: Incorrect id_token: iat', issue_datetime=expiration_datetime) def test_invalid_nonce(self): self.authtoken_raised( 'Token error: Incorrect id_token: nonce', nonce='something-wrong' ) python-social-auth-0.2.13/social/tests/backends/test_amazon.py000066400000000000000000000024451260133235600244170ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class AmazonOAuth2Test(OAuth2Test): backend_path = 'social.backends.amazon.AmazonOAuth2' user_data_url = 'https://www.amazon.com/ap/user/profile' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'user_id': 'amzn1.account.ABCDE1234', 'email': 'foo@bar.com', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class AmazonOAuth2BrokenServerResponseTest(OAuth2Test): backend_path = 'social.backends.amazon.AmazonOAuth2' user_data_url = 'https://www.amazon.com/ap/user/profile' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'Request-Id': '02GGTU7CWMNFTV3KH3J6', 'Profile': { 'Name': 'Foo Bar', 'CustomerId': 'amzn1.account.ABCDE1234', 'PrimaryEmail': 'foo@bar.com' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_angel.py000066400000000000000000000021211260133235600242070ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class AngelOAuth2Test(OAuth2Test): backend_path = 'social.backends.angel.AngelOAuth2' user_data_url = 'https://api.angel.co/1/me/' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'facebook_url': 'http://www.facebook.com/foobar', 'bio': None, 'name': 'Foo Bar', 'roles': [], 'github_url': None, 'angellist_url': 'https://angel.co/foobar', 'image': 'https://graph.facebook.com/foobar/picture?type=square', 'linkedin_url': None, 'locations': [], 'twitter_url': None, 'what_ive_built': None, 'dribbble_url': None, 'behance_url': None, 'blog_url': None, 'aboutme_url': None, 'follower_count': 0, 'online_bio_url': None, 'id': 101010 }) expected_username = 'foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_azuread.py000066400000000000000000000061541260133235600245660ustar00rootroot00000000000000""" Copyright (c) 2015 Microsoft Open Technologies, Inc. All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ import json from social.tests.backends.oauth import OAuth2Test class AzureADOAuth2Test(OAuth2Test): backend_path = 'social.backends.azuread.AzureADOAuth2' user_data_url = 'https://graph.windows.net/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'id_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL' '3N0cy53aW5kb3dzLm5ldC83Mjc0MDZhYy03MDY4LTQ4ZmEtOTJiOS1jMmQ' '2NzIxMWJjNTAvIiwiaWF0IjpudWxsLCJleHAiOm51bGwsImF1ZCI6IjAyO' 'WNjMDEwLWJiNzQtNGQyYi1hMDQwLWY5Y2VkM2ZkMmM3NiIsInN1YiI6In' 'FVOHhrczltSHFuVjZRMzR6aDdTQVpvY2loOUV6cnJJOW1wVlhPSWJWQTg' 'iLCJ2ZXIiOiIxLjAiLCJ0aWQiOiI3Mjc0MDZhYy03MDY4LTQ4ZmEtOTJi' 'OS1jMmQ2NzIxMWJjNTAiLCJvaWQiOiI3ZjhlMTk2OS04YjgxLTQzOGMtO' 'GQ0ZS1hZDZmNTYyYjI4YmIiLCJ1cG4iOiJmb29iYXJAdGVzdC5vbm1pY3' 'Jvc29mdC5jb20iLCJnaXZlbl9uYW1lIjoiZm9vIiwiZmFtaWx5X25hbWU' 'iOiJiYXIiLCJuYW1lIjoiZm9vIGJhciIsInVuaXF1ZV9uYW1lIjoiZm9v' 'YmFyQHRlc3Qub25taWNyb3NvZnQuY29tIiwicHdkX2V4cCI6IjQ3MzMwO' 'TY4IiwicHdkX3VybCI6Imh0dHBzOi8vcG9ydGFsLm1pY3Jvc29mdG9ubG' 'luZS5jb20vQ2hhbmdlUGFzc3dvcmQuYXNweCJ9.3V50dHXTZOHj9UWtkn' '2g7BjX5JxNe8skYlK4PdhiLz4', 'expires_in': 3600, 'expires_on': 1423650396, 'not_before': 1423646496 }) refresh_token_body = json.dumps({ 'access_token': 'foobar-new-token', 'token_type': 'bearer', 'expires_in': 3600, 'refresh_token': 'foobar-new-refresh-token', 'scope': 'identity' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'foobar-new-token') python-social-auth-0.2.13/social/tests/backends/test_behance.py000066400000000000000000000032441260133235600245150ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class BehanceOAuth2Test(OAuth2Test): backend_path = 'social.backends.behance.BehanceOAuth2' access_token_body = json.dumps({ 'access_token': 'foobar', 'valid': 1, 'user': { 'username': 'foobar', 'city': 'Foo City', 'first_name': 'Foo', 'last_name': 'Bar', 'display_name': 'Foo Bar', 'url': 'http://www.behance.net/foobar', 'country': 'Fooland', 'company': '', 'created_on': 1355152329, 'state': '', 'fields': [ 'Programming', 'Web Design', 'Web Development' ], 'images': { '32': 'https://www.behance.net/assets/img/profile/' 'no-image-32.jpg', '50': 'https://www.behance.net/assets/img/profile/' 'no-image-50.jpg', '115': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '129': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '138': 'https://www.behance.net/assets/img/profile/' 'no-image-138.jpg', '78': 'https://www.behance.net/assets/img/profile/' 'no-image-78.jpg' }, 'id': 1010101, 'occupation': 'Software Developer' } }) expected_username = 'foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_bitbucket.py000066400000000000000000000131211260133235600250770ustar00rootroot00000000000000import json from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthForbidden from social.tests.backends.oauth import OAuth1Test, OAuth2Test class BitbucketOAuthMixin(object): user_data_url = 'https://api.bitbucket.org/2.0/user' expected_username = 'foobar' bb_api_user_emails = 'https://api.bitbucket.org/2.0/user/emails' user_data_body = json.dumps({ u'created_on': u'2012-03-29T18:07:38+00:00', u'display_name': u'Foo Bar', u'links': { u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'}, u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'}, u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'}, u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'}, u'html': {u'href': u'https://bitbucket.org/foobar'}, u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'}, u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}}, u'location': u'Fooville, Bar', u'type': u'user', u'username': u'foobar', u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}', u'website': u'http://foobar.com' }) emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 2, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': True, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' }, { u'email': u'not@confirme.com', u'is_confirmed': False, u'is_primary': False, u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/not@confirmed.com'}}, u'type': u'email' } ] }) class BitbucketOAuth1Test(BitbucketOAuthMixin, OAuth1Test): backend_path = 'social.backends.bitbucket.BitbucketOAuth' request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) def test_login(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_login() def test_partial_pipeline(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_partial_pipeline() class BitbucketOAuth1FailTest(BitbucketOAuth1Test): emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 1, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': False, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' } ] }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth1FailTest, self).test_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth1FailTest, self).test_partial_pipeline() class BitbucketOAuth2Test(BitbucketOAuthMixin, OAuth2Test): backend_path = 'social.backends.bitbucket.BitbucketOAuth2' access_token_body = json.dumps({ 'access_token': 'foobar_access', 'scopes': 'foo_scope', 'expires_in': 3600, 'refresh_token': 'foobar_refresh', 'token_type': 'bearer' }) def test_login(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_login() def test_partial_pipeline(self): HTTPretty.register_uri(HTTPretty.GET, self.bb_api_user_emails, status=200, body=self.emails_body) self.do_partial_pipeline() class BitbucketOAuth2FailTest(BitbucketOAuth2Test): emails_body = json.dumps({ u'page': 1, u'pagelen': 10, u'size': 1, u'values': [ { u'email': u'foo@bar.com', u'is_confirmed': False, u'is_primary': True, u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}}, u'type': u'email' } ] }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth2FailTest, self).test_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True }) with self.assertRaises(AuthForbidden): super(BitbucketOAuth2FailTest, self).test_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_box.py000066400000000000000000000043661260133235600237260ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class BoxOAuth2Test(OAuth2Test): backend_path = 'social.backends.box.BoxOAuth2' user_data_url = 'https://api.box.com/2.0/users/me' expected_username = 'sean+awesome@box.com' access_token_body = json.dumps({ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl', 'expires_in': 3600, 'restricted_to': [], 'token_type': 'bearer', 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9bU' 'nsUfGMinKBDLZWP9BgR' }) user_data_body = json.dumps({ 'type': 'user', 'id': '181216415', 'name': 'sean rose', 'login': 'sean+awesome@box.com', 'created_at': '2012-05-03T21:39:11-07:00', 'modified_at': '2012-11-14T11:21:32-08:00', 'role': 'admin', 'language': 'en', 'space_amount': 11345156112, 'space_used': 1237009912, 'max_upload_size': 2147483648, 'tracking_codes': [], 'can_see_managed_users': True, 'is_sync_enabled': True, 'status': 'active', 'job_title': '', 'phone': '6509241374', 'address': '', 'avatar_url': 'https://www.box.com/api/avatar/large/181216415', 'is_exempt_from_device_limits': False, 'is_exempt_from_login_verification': False, 'enterprise': { 'type': 'enterprise', 'id': '17077211', 'name': 'seanrose enterprise' } }) refresh_token_body = json.dumps({ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl', 'expires_in': 3600, 'restricted_to': [], 'token_type': 'bearer', 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9b' 'UnsUfGMinKBDLZWP9BgR' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def refresh_token_arguments(self): uri = self.strategy.build_absolute_uri('/complete/box/') return {'redirect_uri': uri} def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl') python-social-auth-0.2.13/social/tests/backends/test_broken.py000066400000000000000000000020601260133235600244030ustar00rootroot00000000000000import unittest2 as unittest from social.backends.base import BaseAuth class BrokenBackendAuth(BaseAuth): name = 'broken' class BrokenBackendTest(unittest.TestCase): def setUp(self): self.backend = BrokenBackendAuth() def tearDown(self): self.backend = None def test_auth_url(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_url() def test_auth_html(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_html() def test_auth_complete(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.auth_complete() def test_get_user_details(self): with self.assertRaisesRegexp(NotImplementedError, 'Implement in subclass'): self.backend.get_user_details(None) python-social-auth-0.2.13/social/tests/backends/test_clef.py000066400000000000000000000012021260133235600240310ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class ClefOAuth2Test(OAuth2Test): backend_path = 'social.backends.clef.ClefOAuth2' user_data_url = 'https://clef.io/api/v1/info' expected_username = 'test' access_token_body = json.dumps({ 'access_token': 'foobar' }) user_data_body = json.dumps({ 'info': { 'id': '123456789', 'first_name': 'Test', 'last_name': 'User', 'email': 'test@example.com' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_coinbase.py000066400000000000000000000026631260133235600247170ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class CoinbaseOAuth2Test(OAuth2Test): backend_path = 'social.backends.coinbase.CoinbaseOAuth2' user_data_url = 'https://coinbase.com/api/v1/users' expected_username = 'SatoshiNakamoto' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'users': [ { 'user': { 'id': "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa", 'name': "Satoshi Nakamoto", 'email': "satoshi@nakamoto.com", 'pin': None, 'time_zone': "Eastern Time (US & Canada)", 'native_currency': "USD", 'buy_level': 2, 'sell_level': 2, 'balance': { 'amount': "1000000", 'currency': "BTC" }, 'buy_limit': { 'amount': "50.00000000", 'currency': "BTC" }, 'sell_limit': { 'amount': "50.00000000", 'currency': "BTC" } } } ] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_coursera.py000066400000000000000000000021431260133235600247500ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class CourseraOAuth2Test(OAuth2Test): backend_path = 'social.backends.coursera.CourseraOAuth2' user_data_url = \ 'https://api.coursera.org/api/externalBasicProfiles.v1?q=me' expected_username = '560e7ed2076e0d589e88bd74b6aad4b7' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'Bearer', 'expires_in': 1795 }) request_token_body = json.dumps({ 'code': 'foobar-code', 'client_id': 'foobar-client-id', 'client_secret': 'foobar-client-secret', 'redirect_uri': 'http://localhost:8000/accounts/coursera/', 'grant_type': 'authorization_code' }) user_data_body = json.dumps({ 'token_type': 'Bearer', 'paging': None, 'elements': [{ 'id': '560e7ed2076e0d589e88bd74b6aad4b7' }], 'access_token': 'foobar', 'expires_in': 1800, 'linked': None }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_dailymotion.py000066400000000000000000000011161260133235600254540ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class DailymotionOAuth2Test(OAuth2Test): backend_path = 'social.backends.dailymotion.DailymotionOAuth2' user_data_url = 'https://api.dailymotion.com/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'screenname': 'foobar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_digitalocean.py000066400000000000000000000017411260133235600255530ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class DigitalOceanOAuthTest(OAuth2Test): backend_path = 'social.backends.digitalocean.DigitalOceanOAuth' user_data_url = 'https://api.digitalocean.com/v2/account' expected_username = 'sammy@digitalocean.com' access_token_body = json.dumps({ 'access_token': '547cac21118ae7', 'token_type': 'bearer', 'expires_in': 2592000, 'refresh_token': '00a3aae641658d', 'scope': 'read write', 'info': { 'name': 'Sammy Shark', 'email': 'sammy@digitalocean.com' } }) user_data_body = json.dumps({ "account": { 'droplet_limit': 25, 'email': 'sammy@digitalocean.com', 'uuid': 'b6fr89dbf6d9156cace5f3c78dc9851d957381ef', 'email_verified': True } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_disqus.py000066400000000000000000000041661260133235600244440ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class DisqusOAuth2Test(OAuth2Test): backend_path = 'social.backends.disqus.DisqusOAuth2' user_data_url = 'https://disqus.com/api/3.0/users/details.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'code': 0, 'response': { 'username': 'foobar', 'numFollowers': 0, 'isFollowing': False, 'numFollowing': 0, 'name': 'Foo Bar', 'numPosts': 0, 'url': '', 'isAnonymous': False, 'rep': 1.231755, 'about': '', 'isFollowedBy': False, 'connections': {}, 'emailHash': '5280f14cedf530b544aecc31fcfe0240', 'reputation': 1.231755, 'avatar': { 'small': { 'permalink': 'https://disqus.com/api/users/avatars/' 'foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/' 'users/453/4556/avatar32.jpg?1285535379' }, 'isCustom': False, 'permalink': 'https://disqus.com/api/users/avatars/foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/users/453/' '4556/avatar92.jpg?1285535379', 'large': { 'permalink': 'https://disqus.com/api/users/avatars/' 'foobar.jpg', 'cache': 'https://securecdn.disqus.com/uploads/users/' '453/4556/avatar92.jpg?1285535379' } }, 'profileUrl': 'http://disqus.com/foobar/', 'numLikesReceived': 0, 'isPrimary': True, 'joinedAt': '2010-09-26T21:09:39', 'id': '1010101', 'location': '' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_dribbble.py000066400000000000000000000011411260133235600246670ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class DribbbleOAuth2Test(OAuth2Test): backend_path = 'social.backends.dribbble.DribbbleOAuth2' user_data_url = 'https://api.dribbble.com/v1/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'username': 'foobar', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_dropbox.py000066400000000000000000000020221260133235600245760ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class DropboxOAuth1Test(OAuth1Test): backend_path = 'social.backends.dropbox.DropboxOAuth' user_data_url = 'https://api.dropbox.com/1/account/info' expected_username = '10101010' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'referral_link': 'https://www.dropbox.com/referrals/foobar', 'display_name': 'Foo Bar', 'uid': 10101010, 'country': 'US', 'quota_info': { 'shared': 138573, 'quota': 2952790016, 'normal': 157327 }, 'email': 'foo@bar.com' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_dummy.py000066400000000000000000000075341260133235600242710ustar00rootroot00000000000000import json import datetime import time from httpretty import HTTPretty from social.actions import do_disconnect from social.backends.oauth import BaseOAuth2 from social.exceptions import AuthForbidden from social.tests.models import User from social.tests.backends.oauth import OAuth2Test class DummyOAuth2(BaseOAuth2): name = 'dummy' AUTHORIZATION_URL = 'http://dummy.com/oauth/authorize' ACCESS_TOKEN_URL = 'http://dummy.com/oauth/access_token' REVOKE_TOKEN_URL = 'https://dummy.com/oauth/revoke' REVOKE_TOKEN_METHOD = 'GET' EXTRA_DATA = [ ('id', 'id'), ('expires', 'expires'), ('empty', 'empty', True), 'url' ] def get_user_details(self, response): """Return user details from Github account""" return {'username': response.get('username'), 'email': response.get('email', ''), 'first_name': response.get('first_name', ''), 'last_name': response.get('last_name', '')} def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" return self.get_json('http://dummy.com/user', params={ 'access_token': access_token }) class DummyOAuth2Test(OAuth2Test): backend_path = 'social.tests.backends.test_dummy.DummyOAuth2' user_data_url = 'http://dummy.com/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 1, 'username': 'foobar', 'url': 'http://dummy.com/user/foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'email': 'foo@bar.com' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_tokens(self): user = self.do_login() self.assertEqual(user.social[0].access_token, 'foobar') def test_revoke_token(self): self.strategy.set_settings({ 'SOCIAL_AUTH_REVOKE_TOKENS_ON_DISCONNECT': True }) self.do_login() user = User.get(self.expected_username) user.password = 'password' HTTPretty.register_uri(self._method(self.backend.REVOKE_TOKEN_METHOD), self.backend.REVOKE_TOKEN_URL, status=200) do_disconnect(self.backend, user) class WhitelistEmailsTest(DummyOAuth2Test): def test_valid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo@bar.com'] }) self.do_login() def test_invalid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo2@bar.com'] }) with self.assertRaises(AuthForbidden): self.do_login() class WhitelistDomainsTest(DummyOAuth2Test): def test_valid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_DOMAINS': ['bar.com'] }) self.do_login() def test_invalid_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['bar2.com'] }) with self.assertRaises(AuthForbidden): self.do_login() DELTA = datetime.timedelta(days=1) class ExpirationTimeTest(DummyOAuth2Test): user_data_body = json.dumps({ 'id': 1, 'username': 'foobar', 'url': 'http://dummy.com/user/foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'email': 'foo@bar.com', 'expires': time.mktime((datetime.datetime.utcnow() + DELTA).timetuple()) }) def test_expires_time(self): user = self.do_login() social = user.social[0] expiration = social.expiration_datetime() self.assertEqual(expiration <= DELTA, True) python-social-auth-0.2.13/social/tests/backends/test_email.py000066400000000000000000000007441260133235600242210ustar00rootroot00000000000000from social.tests.backends.legacy import BaseLegacyTest class EmailTest(BaseLegacyTest): backend_path = 'social.backends.email.EmailAuth' expected_username = 'foo' response_body = 'email=foo@bar.com' form = """
    """ def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_evernote.py000066400000000000000000000027651260133235600247660ustar00rootroot00000000000000from requests import HTTPError from social.p3 import urlencode from social.exceptions import AuthCanceled from social.tests.backends.oauth import OAuth1Test class EvernoteOAuth1Test(OAuth1Test): backend_path = 'social.backends.evernote.EvernoteOAuth' expected_username = '101010' access_token_body = urlencode({ 'edam_webApiUrlPrefix': 'https://sandbox.evernote.com/shard/s1/', 'edam_shard': 's1', 'oauth_token': 'foobar', 'edam_expires': '1395118279645', 'edam_userId': '101010', 'edam_noteStoreUrl': 'https://sandbox.evernote.com/shard/s1/notestore' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class EvernoteOAuth1CanceledTest(EvernoteOAuth1Test): access_token_status = 401 def test_login(self): with self.assertRaises(AuthCanceled): self.do_login() def test_partial_pipeline(self): with self.assertRaises(AuthCanceled): self.do_partial_pipeline() class EvernoteOAuth1ErrorTest(EvernoteOAuth1Test): access_token_status = 500 def test_login(self): with self.assertRaises(HTTPError): self.do_login() def test_partial_pipeline(self): with self.assertRaises(HTTPError): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_facebook.py000066400000000000000000000022561260133235600247030ustar00rootroot00000000000000import json from social.exceptions import AuthUnknownError from social.tests.backends.oauth import OAuth2Test class FacebookOAuth2Test(OAuth2Test): backend_path = 'social.backends.facebook.FacebookOAuth2' user_data_url = 'https://graph.facebook.com/v2.3/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'verified': True, 'name': 'Foo Bar', 'gender': 'male', 'updated_time': '2013-02-13T14:59:42+0000', 'link': 'http://www.facebook.com/foobar', 'id': '110011001100010' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class FacebookOAuth2WrongUserDataTest(FacebookOAuth2Test): user_data_body = 'null' def test_login(self): with self.assertRaises(AuthUnknownError): self.do_login() def test_partial_pipeline(self): with self.assertRaises(AuthUnknownError): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_fitbit.py000066400000000000000000000030241260133235600244050ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class FitbitOAuth1Test(OAuth1Test): backend_path = 'social.backends.fitbit.FitbitOAuth' expected_username = 'foobar' access_token_body = urlencode({ 'oauth_token_secret': 'a-secret', 'encoded_user_id': '101010', 'oauth_token': 'foobar' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_url = 'https://api.fitbit.com/1/user/-/profile.json' user_data_body = json.dumps({ 'user': { 'weightUnit': 'en_US', 'strideLengthWalking': 0, 'displayName': 'foobar', 'weight': 62.6, 'foodsLocale': 'en_US', 'heightUnit': 'en_US', 'locale': 'en_US', 'gender': 'NA', 'memberSince': '2011-12-26', 'offsetFromUTCMillis': -25200000, 'height': 0, 'timezone': 'America/Los_Angeles', 'dateOfBirth': '', 'encodedId': '101010', 'avatar': 'http://www.fitbit.com/images/profile/' 'defaultProfile_100_male.gif', 'waterUnit': 'en_US', 'distanceUnit': 'en_US', 'glucoseUnit': 'en_US', 'strideLengthRunning': 0 } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_flickr.py000066400000000000000000000012551260133235600244020ustar00rootroot00000000000000from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class FlickrOAuth1Test(OAuth1Test): backend_path = 'social.backends.flickr.FlickrOAuth' expected_username = 'foobar' access_token_body = urlencode({ 'oauth_token_secret': 'a-secret', 'username': 'foobar', 'oauth_token': 'foobar', 'user_nsid': '10101010@N01' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_foursquare.py000066400000000000000000000100141260133235600253150ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class FoursquareOAuth2Test(OAuth2Test): backend_path = 'social.backends.foursquare.FoursquareOAuth2' user_data_url = 'https://api.foursquare.com/v2/users/self' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'notifications': [{ 'item': { 'unreadCount': 0 }, 'type': 'notificationTray' }], 'meta': { 'errorType': 'deprecated', 'code': 200, 'errorDetail': 'Please provide an API version to avoid future ' 'errors.See http://bit.ly/vywCav' }, 'response': { 'user': { 'photo': 'https://is0.4sqi.net/userpix_thumbs/' 'BYKIT01VN4T4BISN.jpg', 'pings': False, 'homeCity': 'Foo, Bar', 'id': '1010101', 'badges': { 'count': 0, 'items': [] }, 'friends': { 'count': 1, 'groups': [{ 'count': 0, 'items': [], 'type': 'friends', 'name': 'Mutual friends' }, { 'count': 1, 'items': [{ 'bio': '', 'gender': 'male', 'firstName': 'Baz', 'relationship': 'friend', 'photo': 'https://is0.4sqi.net/userpix_thumbs/' 'BYKIT01VN4T4BISN.jpg', 'lists': { 'groups': [{ 'count': 1, 'items': [], 'type': 'created' }] }, 'homeCity': 'Baz, Qux', 'lastName': 'Qux', 'tips': { 'count': 0 }, 'id': '10101010' }], 'type': 'others', 'name': 'Other friends' }] }, 'referralId': 'u-1010101', 'tips': { 'count': 0 }, 'type': 'user', 'todos': { 'count': 0 }, 'bio': '', 'relationship': 'self', 'lists': { 'groups': [{ 'count': 1, 'items': [], 'type': 'created' }] }, 'photos': { 'count': 0, 'items': [] }, 'checkinPings': 'off', 'scores': { 'max': 0, 'checkinsCount': 0, 'goal': 50, 'recent': 0 }, 'checkins': { 'count': 0 }, 'firstName': 'Foo', 'gender': 'male', 'contact': { 'email': 'foo@bar.com' }, 'lastName': 'Bar', 'following': { 'count': 0 }, 'requests': { 'count': 0 }, 'mayorships': { 'count': 0, 'items': [] } } } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_github.py000066400000000000000000000144521260133235600244150ustar00rootroot00000000000000import json from httpretty import HTTPretty from social.exceptions import AuthFailed from social.tests.backends.oauth import OAuth2Test class GithubOAuth2Test(OAuth2Test): backend_path = 'social.backends.github.GithubOAuth2' user_data_url = 'https://api.github.com/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GithubOAuth2NoEmailTest(GithubOAuth2Test): user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': '', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): url = 'https://api.github.com/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps(['foo@bar.com']), content_type='application/json') self.do_login() def test_login_next_format(self): url = 'https://api.github.com/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps([{'email': 'foo@bar.com'}]), content_type='application/json') self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GithubOrganizationOAuth2Test(GithubOAuth2Test): backend_path = 'social.backends.github.GithubOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubOrganizationOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) self.do_partial_pipeline() class GithubOrganizationOAuth2FailTest(GithubOAuth2Test): backend_path = 'social.backends.github.GithubOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubOrganizationOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() class GithubTeamOAuth2Test(GithubOAuth2Test): backend_path = 'social.backends.github.GithubTeamOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubTeamOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) self.do_partial_pipeline() class GithubTeamOAuth2FailTest(GithubOAuth2Test): backend_path = 'social.backends.github.GithubTeamOAuth2' def auth_handlers(self, start_url): url = 'https://api.github.com/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubTeamOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_github_enterprise.py000066400000000000000000000235221260133235600266530ustar00rootroot00000000000000import json from httpretty import HTTPretty from social.exceptions import AuthFailed from social.tests.backends.oauth import OAuth2Test class GithubEnterpriseOAuth2Test(OAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOAuth2' user_data_url = 'https://www.example.com/api/v3/user' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://www.example.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://www.example.com/api/v3/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://www.example.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://www.example.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_partial_pipeline() class GithubEnterpriseOAuth2NoEmailTest(GithubEnterpriseOAuth2Test): user_data_body = json.dumps({ 'login': 'foobar', 'id': 1, 'avatar_url': 'https://www.example.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://www.example.com/api/v3/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://www.example.com/blog', 'location': 'San Francisco', 'email': '', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://www.example.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) url = 'https://www.example.com/api/v3/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps(['foo@bar.com']), content_type='application/json') self.do_login() def test_login_next_format(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) url = 'https://www.example.com/api/v3/user/emails' HTTPretty.register_uri(HTTPretty.GET, url, status=200, body=json.dumps([{'email': 'foo@bar.com'}]), content_type='application/json') self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL': 'https://www.example.com/api/v3'}) self.do_partial_pipeline() class GithubEnterpriseOrganizationOAuth2Test(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubEnterpriseOrganizationOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) self.do_partial_pipeline() class GithubEnterpriseOrganizationOAuth2FailTest(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseOrganizationOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/orgs/foobar/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubEnterpriseOrganizationOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME': 'foobar'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() class GithubEnterpriseTeamOAuth2Test(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseTeamOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=204, body='') return super(GithubEnterpriseTeamOAuth2Test, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) self.do_partial_pipeline() class GithubEnterpriseTeamOAuth2FailTest(GithubEnterpriseOAuth2Test): backend_path = 'social.backends.github_enterprise.GithubEnterpriseTeamOAuth2' def auth_handlers(self, start_url): url = 'https://www.example.com/api/v3/teams/123/members/foobar' HTTPretty.register_uri(HTTPretty.GET, url, status=404, body='{"message": "Not Found"}', content_type='application/json') return super(GithubEnterpriseTeamOAuth2FailTest, self).auth_handlers( start_url ) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL': 'https://www.example.com'}) self.strategy.set_settings({ 'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL': 'https://www.example.com/api/v3'}) self.strategy.set_settings({'SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID': '123'}) with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_google.py000066400000000000000000000242351260133235600244070ustar00rootroot00000000000000import datetime import json from httpretty import HTTPretty from social.p3 import urlencode from social.actions import do_disconnect from social.tests.models import User from social.tests.backends.oauth import OAuth1Test, OAuth2Test from social.tests.backends.open_id import OpenIdTest, OpenIdConnectTestMixin class GoogleOAuth2Test(OAuth2Test): backend_path = 'social.backends.google.GoogleOAuth2' user_data_url = 'https://www.googleapis.com/plus/v1/people/me' expected_username = 'foo' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'aboutMe': 'About me text', 'cover': { 'coverInfo': { 'leftImageOffset': 0, 'topImageOffset': 0 }, 'coverPhoto': { 'height': 629, 'url': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', 'width': 940 }, 'layout': 'banner' }, 'displayName': 'Foo Bar', 'emails': [{ 'type': 'account', 'value': 'foo@bar.com' }], 'etag': '"e-tag string"', 'gender': 'male', 'id': '101010101010101010101', 'image': { 'url': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', }, 'isPlusUser': True, 'kind': 'plus#person', 'language': 'en', 'name': { 'familyName': 'Bar', 'givenName': 'Foo' }, 'objectType': 'person', 'occupation': 'Software developer', 'organizations': [{ 'name': 'Org name', 'primary': True, 'type': 'school' }], 'placesLived': [{ 'primary': True, 'value': 'Anyplace' }], 'url': 'https://plus.google.com/101010101010101010101', 'urls': [{ 'label': 'http://foobar.com', 'type': 'otherProfile', 'value': 'http://foobar.com', }], 'verified': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID': True, }) self.do_login() class GoogleOAuth2DeprecatedAPITest(GoogleOAuth2Test): user_data_url = 'https://www.googleapis.com/oauth2/v1/userinfo' user_data_body = json.dumps({ 'family_name': 'Bar', 'name': 'Foo Bar', 'picture': 'https://lh5.googleusercontent.com/-ui-GqpNh5Ms/' 'AAAAAAAAAAI/AAAAAAAAAZw/a7puhHMO_fg/photo.jpg', 'locale': 'en', 'gender': 'male', 'email': 'foo@bar.com', 'birthday': '0000-01-22', 'link': 'https://plus.google.com/101010101010101010101', 'given_name': 'Foo', 'id': '101010101010101010101', 'verified_email': True }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_UNIQUE_USER_ID': True, 'SOCIAL_AUTH_GOOGLE_OAUTH2_USE_DEPRECATED_API': True }) self.do_login() class GoogleOAuth1Test(OAuth1Test): backend_path = 'social.backends.google.GoogleOAuth' user_data_url = 'https://www.googleapis.com/userinfo/email' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = urlencode({ 'email': 'foobar@gmail.com', 'isVerified': 'true', 'id': '101010101010101010101' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_with_unique_user_id(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH_USE_UNIQUE_USER_ID': True }) self.do_login() def test_with_anonymous_key_and_secret(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH_KEY': None, 'SOCIAL_AUTH_GOOGLE_OAUTH_SECRET': None }) self.do_login() JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class GoogleOpenIdTest(OpenIdTest): backend_path = 'social.backends.google.GoogleOpenId' expected_username = 'FooBar' discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=mail', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=gmail.com', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', '', 'https://www.google.com/accounts/o8/ud?source=googlemail.com', '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://openid.net/srv/ax/1.0', 'http://specs.openid.net/extensions/ui/1.0/mode/popup', 'http://specs.openid.net/extensions/ui/1.0/icon', 'http://specs.openid.net/extensions/pape/1.0', 'https://www.google.com/accounts/o8/ud?source=profiles', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.assoc_handle': 'assoc-handle', 'openid.claimed_id': 'https://www.google.com/accounts/o8/id?' 'id=some-google-id', 'openid.ext1.mode': 'fetch_response', 'openid.ext1.type.email': 'http://axschema.org/contact/email', 'openid.ext1.type.first_name': 'http://axschema.org/namePerson/first', 'openid.ext1.type.last_name': 'http://axschema.org/namePerson/last', 'openid.ext1.type.old_email': 'http://schema.openid.net/contact/email', 'openid.ext1.value.email': 'foo@bar.com', 'openid.ext1.value.first_name': 'Foo', 'openid.ext1.value.last_name': 'Bar', 'openid.ext1.value.old_email': 'foo@bar.com', 'openid.identity': 'https://www.google.com/accounts/o8/id?' 'id=some-google-id', 'openid.mode': 'id_res', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.ns.ext1': 'http://openid.net/srv/ax/1.0', 'openid.op_endpoint': 'https://www.google.com/accounts/o8/ud', 'openid.response_nonce': JANRAIN_NONCE + 'by95cT34vX7p9g', 'openid.return_to': 'http://myapp.com/complete/google/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.sig': 'brT2kmu3eCzb1gQ1pbaXdnWioVM=', 'openid.signed': 'op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle,ns.ext1,ext1.mode,' 'ext1.type.old_email,ext1.value.old_email,' 'ext1.type.first_name,ext1.value.first_name,' 'ext1.type.last_name,ext1.value.last_name,' 'ext1.type.email,ext1.value.email' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class GoogleRevokeTokenTest(GoogleOAuth2Test): def test_revoke_token(self): self.strategy.set_settings({ 'SOCIAL_AUTH_GOOGLE_OAUTH2_REVOKE_TOKENS_ON_DISCONNECT': True }) self.do_login() user = User.get(self.expected_username) user.password = 'password' HTTPretty.register_uri(self._method(self.backend.REVOKE_TOKEN_METHOD), self.backend.REVOKE_TOKEN_URL, status=200) do_disconnect(self.backend, user) class GoogleOpenIdConnectTest(OpenIdConnectTestMixin, GoogleOAuth2Test): backend_path = 'social.backends.google.GoogleOpenIdConnect' user_data_url = \ 'https://www.googleapis.com/plus/v1/people/me/openIdConnect' issuer = "accounts.google.com" python-social-auth-0.2.13/social/tests/backends/test_instagram.py000066400000000000000000000033731260133235600251200ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class InstagramOAuth2Test(OAuth2Test): backend_path = 'social.backends.instagram.InstagramOAuth2' user_data_url = 'https://api.instagram.com/v1/users/self' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'meta': { 'code': 200 }, 'data': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'counts': { 'media': 0, 'followed_by': 2, 'follows': 0 }, 'id': '101010101' }, 'user': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'id': '101010101' } }) user_data_body = json.dumps({ 'meta': { 'code': 200 }, 'data': { 'username': 'foobar', 'bio': '', 'website': '', 'profile_picture': 'http://images.instagram.com/profiles/' 'anonymousUser.jpg', 'full_name': '', 'counts': { 'media': 0, 'followed_by': 2, 'follows': 0 }, 'id': '101010101' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_kakao.py000066400000000000000000000015371260133235600242210ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class KakaoOAuth2Test(OAuth2Test): backend_path = 'social.backends.kakao.KakaoOAuth2' user_data_url = 'https://kapi.kakao.com/v1/user/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar' }) user_data_body = json.dumps({ 'id': '101010101', 'properties': { 'nickname': 'foobar', 'thumbnail_image': 'http://mud-kage.kakao.co.kr/14/dn/btqbh1AKmRf/' 'ujlHpQhxtMSbhKrBisrxe1/o.jpg', 'profile_image': 'http://mud-kage.kakao.co.kr/14/dn/btqbjCnl06Q/' 'wbMJSVAUZB7lzSImgGdsoK/o.jpg' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_khanacademy.py000066400000000000000000000015251260133235600253750ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class KhanAcademyOAuth1Test(OAuth1Test): backend_path = 'social.backends.khanacademy.KhanAcademyOAuth1' user_data_url = 'https://www.khanacademy.org/api/v1/user' expected_username = 'foo@bar.com' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ "key_email": "foo@bar.com", "user_id": "http://googleid.khanacademy.org/11111111111111", }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_linkedin.py000066400000000000000000000020321260133235600247170ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test, OAuth2Test class BaseLinkedinTest(object): user_data_url = 'https://api.linkedin.com/v1/people/~:' \ '(first-name,id,last-name)' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'lastName': 'Bar', 'id': '1010101010', 'firstName': 'Foo' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class LinkedinOAuth1Test(BaseLinkedinTest, OAuth1Test): backend_path = 'social.backends.linkedin.LinkedinOAuth' request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) class LinkedinOAuth2Test(BaseLinkedinTest, OAuth2Test): backend_path = 'social.backends.linkedin.LinkedinOAuth2' python-social-auth-0.2.13/social/tests/backends/test_live.py000066400000000000000000000016711260133235600240710ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class LiveOAuth2Test(OAuth2Test): backend_path = 'social.backends.live.LiveOAuth2' user_data_url = 'https://apis.live.net/v5.0/me' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'first_name': 'Foo', 'last_name': 'Bar', 'name': 'Foo Bar', 'locale': 'en_US', 'gender': None, 'emails': { 'personal': None, 'account': 'foobar@live.com', 'business': None, 'preferred': 'foobar@live.com' }, 'link': 'https://profile.live.com/', 'updated_time': '2013-03-17T05:51:30+0000', 'id': '1010101010101010' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_livejournal.py000066400000000000000000000071711260133235600254650ustar00rootroot00000000000000import datetime from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthMissingParameter from social.tests.backends.open_id import OpenIdTest JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class LiveJournalOpenIdTest(OpenIdTest): backend_path = 'social.backends.livejournal.LiveJournalOpenId' expected_username = 'foobar' discovery_body = ''.join([ '', '', '', 'http://specs.openid.net/auth/2.0/signon', 'http://www.livejournal.com/openid/server.bml', 'http://foobar.livejournal.com/', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.mode': 'id_res', 'openid.claimed_id': 'http://foobar.livejournal.com/', 'openid.identity': 'http://foobar.livejournal.com/', 'openid.op_endpoint': 'http://www.livejournal.com/openid/server.bml', 'openid.return_to': 'http://myapp.com/complete/livejournal/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'wGp2rj', 'openid.assoc_handle': '1364932966:ZTiur8sem3r2jzZougMZ:4d1cc3b44e', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.signed': 'mode,claimed_id,identity,op_endpoint,return_to,' 'response_nonce,assoc_handle', 'openid.sig': 'Z8MOozVPTOBhHG5ZS1NeGofxs1Q=', }) server_bml_body = '\n'.join([ 'assoc_handle:1364935340:ZhruPQ7DJ9eGgUkeUA9A:27f8c32464', 'assoc_type:HMAC-SHA1', 'dh_server_public:WzsRyLomvAV3vwvGUrfzXDgfqnTF+m1l3JWb55fyHO7visPT4tmQ' 'iTjqFFnSVAtAOvQzoViMiZQisxNwnqSK4lYexoez1z6pP5ry3pqxJAEYj60vFGvRztict' 'Eo0brjhmO1SNfjK1ppjOymdykqLpZeaL5fsuLtMCwTnR/JQZVA=', 'enc_mac_key:LiOEVlLJSVUqfNvb5zPd76nEfvc=', 'expires_in:1207060', 'ns:http://specs.openid.net/auth/2.0', 'session_type:DH-SHA1', '' ]) def openid_url(self): return super(LiveJournalOpenIdTest, self).openid_url() + '/data/yadis' def post_start(self): self.strategy.remove_from_request_data('openid_lj_user') def _setup_handlers(self): HTTPretty.register_uri( HTTPretty.POST, 'http://www.livejournal.com/openid/server.bml', headers={'Accept-Encoding': 'identity', 'Content-Type': 'application/x-www-form-urlencoded'}, status=200, body=self.server_bml_body ) HTTPretty.register_uri( HTTPretty.GET, 'http://foobar.livejournal.com/', headers={ 'Accept-Encoding': 'identity', 'Accept': 'text/html; q=0.3,' 'application/xhtml+xml; q=0.5,' 'application/xrds+xml' }, status=200, body=self.discovery_body ) def test_login(self): self.strategy.set_request_data({'openid_lj_user': 'foobar'}, self.backend) self._setup_handlers() self.do_login() def test_partial_pipeline(self): self.strategy.set_request_data({'openid_lj_user': 'foobar'}, self.backend) self._setup_handlers() self.do_partial_pipeline() def test_failed_login(self): self._setup_handlers() with self.assertRaises(AuthMissingParameter): self.do_login() python-social-auth-0.2.13/social/tests/backends/test_mapmyfitness.py000066400000000000000000000114531260133235600256500ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class MapMyFitnessOAuth2Test(OAuth2Test): backend_path = 'social.backends.mapmyfitness.MapMyFitnessOAuth2' user_data_url = 'https://oauth2-api.mapmyapi.com/v7.0/user/self/' expected_username = 'FredFlinstone' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'Bearer', 'expires_in': 4000000, 'refresh_token': 'bambaz', 'scope': 'read' }) user_data_body = json.dumps({ 'last_name': 'Flinstone', 'weight': 91.17206637, 'communication': { 'promotions': True, 'newsletter': True, 'system_messages': True }, 'height': 1.778, 'token_type': 'Bearer', 'id': 112233, 'date_joined': '2011-08-26T06:06:19+00:00', 'first_name': 'Fred', 'display_name': 'Fred Flinstone', 'display_measurement_system': 'imperial', 'expires_in': 4000000, '_links': { 'stats': [ { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=month', 'id': '112233', 'name': 'month' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=year', 'id': '112233', 'name': 'year' }, { 'href': '/v7.0/user_stats/112233/?aggregate_by_period=day', 'id': '112233', 'name': 'day' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=week', 'id': '112233', 'name': 'week' }, { 'href': '/v7.0/user_stats/112233/?' 'aggregate_by_period=lifetime', 'id': '112233', 'name': 'lifetime' } ], 'friendships': [ { 'href': '/v7.0/friendship/?from_user=112233' } ], 'privacy': [ { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'profile' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'workout' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'activity_feed' }, { 'href': '/v7.0/privacy_option/1/', 'id': '1', 'name': 'food_log' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'email_search' }, { 'href': '/v7.0/privacy_option/3/', 'id': '3', 'name': 'route' } ], 'image': [ { 'href': '/v7.0/user_profile_photo/112233/', 'id': '112233', 'name': 'user_profile_photo' } ], 'documentation': [ { 'href': 'https://www.mapmyapi.com/docs/User' } ], 'workouts': [ { 'href': '/v7.0/workout/?user=112233&' 'order_by=-start_datetime' } ], 'deactivation': [ { 'href': '/v7.0/user_deactivation/' } ], 'self': [ { 'href': '/v7.0/user/112233/', 'id': '112233' } ] }, 'location': { 'country': 'US', 'region': 'NC', 'locality': 'Bedrock', 'address': '150 Dinosaur Ln' }, 'last_login': '2014-02-23T22:36:52+00:00', 'email': 'fredflinstone@gmail.com', 'username': 'FredFlinstone', 'sharing': { 'twitter': False, 'facebook': False }, 'scope': 'read', 'refresh_token': 'bambaz', 'last_initial': 'S.', 'access_token': 'foobar', 'gender': 'M', 'time_zone': 'America/Denver', 'birthdate': '1983-04-15' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_mineid.py000066400000000000000000000011161260133235600243710ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class MineIDOAuth2Test(OAuth2Test): backend_path = 'social.backends.mineid.MineIDOAuth2' user_data_url = 'https://www.mineid.org/api/user' expected_username = 'foo@bar.com' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'email': 'foo@bar.com', 'primary_profile': None, }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_mixcloud.py000066400000000000000000000046021260133235600247530ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class MixcloudOAuth2Test(OAuth2Test): backend_path = 'social.backends.mixcloud.MixcloudOAuth2' user_data_url = 'https://api.mixcloud.com/me/' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'username': 'foobar', 'cloudcast_count': 0, 'following_count': 0, 'url': 'http://www.mixcloud.com/foobar/', 'pictures': { 'medium': 'http://images-mix.netdna-ssl.com/w/100/h/100/q/85/' 'images/graphics/33_Profile/default_user_600x600-v4.png', '320wx320h': 'http://images-mix.netdna-ssl.com/w/320/h/320/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'extra_large': 'http://images-mix.netdna-ssl.com/w/600/h/600/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'large': 'http://images-mix.netdna-ssl.com/w/300/h/300/q/85/' 'images/graphics/33_Profile/default_user_600x600-v4.png', '640wx640h': 'http://images-mix.netdna-ssl.com/w/640/h/640/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'medium_mobile': 'http://images-mix.netdna-ssl.com/w/80/h/80/q/75/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png', 'small': 'http://images-mix.netdna-ssl.com/w/25/h/25/q/85/images/' 'graphics/33_Profile/default_user_600x600-v4.png', 'thumbnail': 'http://images-mix.netdna-ssl.com/w/50/h/50/q/85/' 'images/graphics/33_Profile/' 'default_user_600x600-v4.png' }, 'is_current_user': True, 'listen_count': 0, 'updated_time': '2013-03-17T06:26:31Z', 'following': False, 'follower': False, 'key': '/foobar/', 'created_time': '2013-03-17T06:26:31Z', 'follower_count': 0, 'favorite_count': 0, 'name': 'foobar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_nationbuilder.py000066400000000000000000000204511260133235600257660ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class NationBuilderOAuth2Test(OAuth2Test): backend_path = 'social.backends.nationbuilder.NationBuilderOAuth2' user_data_url = 'https://foobar.nationbuilder.com/api/v1/people/me' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'created_at': 1422937981, 'expires_in': 2592000 }) user_data_body = json.dumps({ 'person': { 'twitter_followers_count': None, 'last_name': 'Bar', 'rule_violations_count': 0, 'linkedin_id': None, 'recruiter_id': None, 'membership_expires_at': None, 'donations_raised_count': 0, 'last_contacted_at': None, 'prefix': None, 'profile_content_html': None, 'email4': None, 'email2': None, 'availability': None, 'occupation': None, 'user_submitted_address': None, 'could_vote_status': None, 'state_upper_district': None, 'salesforce_id': None, 'van_id': None, 'phone_time': None, 'profile_content': None, 'auto_import_id': None, 'parent_id': None, 'email4_is_bad': False, 'twitter_updated_at': None, 'email3_is_bad': False, 'bio': None, 'party_member': None, 'unsubscribed_at': None, 'fax_number': None, 'last_contacted_by': None, 'active_customer_expires_at': None, 'federal_donotcall': False, 'warnings_count': 0, 'first_supporter_at': '2015-02-02T19:30:28-08:00', 'previous_party': None, 'donations_raised_amount_this_cycle_in_cents': 0, 'call_status_name': None, 'marital_status': None, 'facebook_updated_at': None, 'donations_count': 0, 'note_updated_at': None, 'closed_invoices_count': None, 'profile_headline': None, 'fire_district': None, 'mobile_normalized': None, 'import_id': None, 'last_call_id': None, 'donations_raised_amount_in_cents': 0, 'facebook_address': None, 'is_profile_private': False, 'last_rule_violation_at': None, 'sex': None, 'full_name': 'Foo Bar', 'last_donated_at': None, 'donations_pledged_amount_in_cents': 0, 'primary_email_id': 1, 'media_market_name': None, 'capital_amount_in_cents': 500, 'datatrust_id': None, 'precinct_code': None, 'email3': None, 'religion': None, 'first_prospect_at': None, 'judicial_district': None, 'donations_count_this_cycle': 0, 'work_address': None, 'is_twitter_follower': False, 'email1': 'foobar@gmail.com', 'email': 'foobar@gmail.com', 'contact_status_name': None, 'mobile_opt_in': True, 'twitter_description': None, 'parent': None, 'tags': [], 'first_volunteer_at': None, 'inferred_support_level': None, 'banned_at': None, 'first_invoice_at': None, 'donations_raised_count_this_cycle': 0, 'is_donor': False, 'twitter_location': None, 'email1_is_bad': False, 'legal_name': None, 'language': None, 'registered_at': None, 'call_status_id': None, 'last_invoice_at': None, 'school_sub_district': None, 'village_district': None, 'twitter_name': None, 'membership_started_at': None, 'subnations': [], 'meetup_address': None, 'author_id': None, 'registered_address': None, 'external_id': None, 'twitter_login': None, 'inferred_party': None, 'spent_capital_amount_in_cents': 0, 'suffix': None, 'mailing_address': None, 'is_leaderboardable': True, 'twitter_website': None, 'nbec_guid': None, 'city_district': None, 'church': None, 'is_profile_searchable': True, 'employer': None, 'is_fundraiser': False, 'email_opt_in': True, 'recruits_count': 0, 'email2_is_bad': False, 'county_district': None, 'recruiter': None, 'twitter_friends_count': None, 'facebook_username': None, 'active_customer_started_at': None, 'pf_strat_id': None, 'locale': None, 'twitter_address': None, 'is_supporter': True, 'do_not_call': False, 'profile_image_url_ssl': 'https://d3n8a8pro7vhmx.cloudfront.net' '/assets/icons/buddy.png', 'invoices_amount_in_cents': None, 'username': None, 'donations_amount_in_cents': 0, 'is_volunteer': False, 'civicrm_id': None, 'supranational_district': None, 'precinct_name': None, 'invoice_payments_amount_in_cents': None, 'work_phone_number': None, 'phone': '213.394.4623', 'received_capital_amount_in_cents': 500, 'primary_address': None, 'is_possible_duplicate': False, 'invoice_payments_referred_amount_in_cents': None, 'donations_amount_this_cycle_in_cents': 0, 'priority_level': None, 'first_fundraised_at': None, 'phone_normalized': '2133944623', 'rnc_regid': None, 'twitter_id': None, 'birthdate': None, 'mobile': None, 'federal_district': None, 'donations_to_raise_amount_in_cents': 0, 'support_probability_score': None, 'invoices_count': None, 'nbec_precinct_code': None, 'website': None, 'closed_invoices_amount_in_cents': None, 'home_address': None, 'school_district': None, 'support_level': None, 'demo': None, 'children_count': 0, 'updated_at': '2015-02-02T19:30:28-08:00', 'membership_level_name': None, 'billing_address': None, 'is_ignore_donation_limits': False, 'signup_type': 0, 'precinct_id': None, 'rnc_id': None, 'id': 2, 'ethnicity': None, 'is_survey_question_private': False, 'middle_name': None, 'author': None, 'last_fundraised_at': None, 'state_file_id': None, 'note': None, 'submitted_address': None, 'support_level_changed_at': None, 'party': None, 'contact_status_id': None, 'outstanding_invoices_amount_in_cents': None, 'page_slug': None, 'outstanding_invoices_count': None, 'first_recruited_at': None, 'county_file_id': None, 'first_name': 'Foo', 'facebook_profile_url': None, 'city_sub_district': None, 'has_facebook': False, 'is_deceased': False, 'labour_region': None, 'state_lower_district': None, 'dw_id': None, 'created_at': '2015-02-02T19:30:28-08:00', 'is_prospect': False, 'priority_level_changed_at': None, 'is_mobile_bad': False, 'overdue_invoices_count': None, 'ngp_id': None, 'do_not_contact': False, 'first_donated_at': None, 'turnout_probability_score': None }, 'precinct': None }) def test_login(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NATIONBUILDER_SLUG': 'foobar' }) self.do_login() def test_partial_pipeline(self): self.strategy.set_settings({ 'SOCIAL_AUTH_NATIONBUILDER_SLUG': 'foobar' }) self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_orbi.py000066400000000000000000000014221260133235600240570ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class OrbiOAuth2Test(OAuth2Test): backend_path = 'social.backends.orbi.OrbiOAuth2' user_data_url = 'https://login.orbi.kr/oauth/user/get' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'name': 'Foo Bar', 'imin': '100000', 'nick': 'foobar', 'photo': 'http://s3.orbi.kr/data/member/wi/wizetdev_132894975780.jpeg', 'sex': 'M', 'birth': '1973-08-03', }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_podio.py000066400000000000000000000032311260133235600242360ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class PodioOAuth2Test(OAuth2Test): backend_path = 'social.backends.podio.PodioOAuth2' user_data_url = 'https://api.podio.com/user/status' expected_username = 'user_1010101010' access_token_body = json.dumps({ 'token_type': 'bearer', 'access_token': '11309ea9016a4ad99f1a3bcb9bc7a9d1', 'refresh_token': '52d01df8b9ac46a4a6be1333d9f81ef2', 'expires_in': 28800, 'ref': { 'type': 'user', 'id': 1010101010, } }) user_data_body = json.dumps({ 'user': { 'user_id': 1010101010, 'activated_on': '2012-11-22 09:37:21', 'created_on': '2012-11-21 12:23:47', 'locale': 'en_GB', 'timezone': 'Europe/Copenhagen', 'mail': 'foo@bar.com', 'mails': [ { 'disabled': False, 'mail': 'foobar@example.com', 'primary': False, 'verified': True }, { 'disabled': False, 'mail': 'foo@bar.com', 'primary': True, 'verified': True } ], # more properties ... }, 'profile': { 'last_seen_on': '2013-05-16 12:21:13', 'link': 'https://podio.com/users/1010101010', 'name': 'Foo Bar', # more properties ... } # more properties ... }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_qiita.py000066400000000000000000000010761260133235600242400ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class QiitaOAuth2Test(OAuth2Test): backend_path = 'social.backends.qiita.QiitaOAuth2' user_data_url = 'https://qiita.com/api/v2/authenticated_user' expected_username = 'foobar' access_token_body = json.dumps({ 'token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'id': 'foobar', 'name': 'Foo Bar' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_readability.py000066400000000000000000000024421260133235600254200ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ReadabilityOAuth1Test(OAuth1Test): backend_path = 'social.backends.readability.ReadabilityOAuth' user_data_url = 'https://www.readability.com/api/rest/v1/users/_current' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'username': 'foobar', 'first_name': 'Foo', 'last_name': 'Bar', 'has_active_subscription': False, 'tags': [], 'is_publisher': False, 'email_into_address': 'foobar+sharp@inbox.readability.com', 'kindle_email_address': None, 'avatar_url': 'https://secure.gravatar.com/avatar/' '5280f15cedf540b544eecc30fcf3027c?d=' 'https://www.readability.com/media/images/' 'avatar.png&s=36', 'date_joined': '2013-03-18 02:51:02' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_reddit.py000066400000000000000000000033321260133235600244010ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class RedditOAuth2Test(OAuth2Test): backend_path = 'social.backends.reddit.RedditOAuth2' user_data_url = 'https://oauth.reddit.com/api/v1/me.json' expected_username = 'foobar' access_token_body = json.dumps({ 'name': 'foobar', 'created': 1203420772.0, 'access_token': 'foobar-token', 'created_utc': 1203420772.0, 'expires_in': 3600.0, 'link_karma': 34, 'token_type': 'bearer', 'comment_karma': 167, 'over_18': True, 'is_gold': False, 'is_mod': True, 'scope': 'identity', 'has_verified_email': False, 'id': '33bma', 'refresh_token': 'foobar-refresh-token' }) user_data_body = json.dumps({ 'name': 'foobar', 'created': 1203420772.0, 'created_utc': 1203420772.0, 'link_karma': 34, 'comment_karma': 167, 'over_18': True, 'is_gold': False, 'is_mod': True, 'has_verified_email': False, 'id': '33bma' }) refresh_token_body = json.dumps({ 'access_token': 'foobar-new-token', 'token_type': 'bearer', 'expires_in': 3600.0, 'refresh_token': 'foobar-new-refresh-token', 'scope': 'identity' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def refresh_token_arguments(self): uri = self.strategy.build_absolute_uri('/complete/reddit/') return {'redirect_uri': uri} def test_refresh_token(self): user, social = self.do_refresh_token() self.assertEqual(social.extra_data['access_token'], 'foobar-new-token') python-social-auth-0.2.13/social/tests/backends/test_saml.py000066400000000000000000000107311260133235600240630ustar00rootroot00000000000000import re import json import sys import unittest2 import requests from os import path from mock import patch from httpretty import HTTPretty try: from onelogin.saml2.utils import OneLogin_Saml2_Utils except ImportError: # Only available for python 2.7 at the moment, so don't worry if this fails pass from social.tests.backends.base import BaseBackendTest from social.p3 import urlparse, urlunparse, urlencode, parse_qs DATA_DIR = path.join(path.dirname(__file__), 'data') @unittest2.skipUnless( sys.version_info[:2] == (2, 7), 'python-saml currently depends on 2.7; 3+ support coming soon') @unittest2.skipIf('__pypy__' in sys.builtin_module_names, 'dm.xmlsec not compatible with pypy') class SAMLTest(BaseBackendTest): backend_path = 'social.backends.saml.SAMLAuth' expected_username = 'myself' def extra_settings(self): name = path.join(DATA_DIR, 'saml_config.json') with open(name, 'r') as config_file: config_str = config_file.read() return json.loads(config_str) def setUp(self): """Patch the time so that we can replay canned request/response pairs""" super(SAMLTest, self).setUp() @staticmethod def fixed_time(): return OneLogin_Saml2_Utils.parse_SAML_to_time( '2015-05-09T03:57:22Z' ) now_patch = patch.object(OneLogin_Saml2_Utils, 'now', fixed_time) now_patch.start() self.addCleanup(now_patch.stop) def install_http_intercepts(self, start_url, return_url): # When we request start_url # (https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO...) # we will eventually get a redirect back, with SAML assertion # data in the query string. A pre-recorded correct response # is kept in this .txt file: name = path.join(DATA_DIR, 'saml_response.txt') with open(name, 'r') as response_file: response_url = response_file.read() HTTPretty.register_uri(HTTPretty.GET, start_url, status=301, location=response_url) HTTPretty.register_uri(HTTPretty.GET, return_url, status=200, body='foobar') def do_start(self): # pretend we've started with a URL like /login/saml/?idp=testshib: self.strategy.set_request_data({'idp': 'testshib'}, self.backend) start_url = self.backend.start().url # Modify the start URL to make the SAML request consistent # from test to test: start_url = self.modify_start_url(start_url) # If the SAML Identity Provider recognizes the user, we will # be redirected back to: return_url = self.backend.redirect_uri self.install_http_intercepts(start_url, return_url) response = requests.get(start_url) self.assertTrue(response.url.startswith(return_url)) self.assertEqual(response.text, 'foobar') query_values = dict((k, v[0]) for k, v in parse_qs(urlparse(response.url).query).items()) self.assertNotIn(' ', query_values['SAMLResponse']) self.strategy.set_request_data(query_values, self.backend) return self.backend.complete() def test_metadata_generation(self): """Test that we can generate the metadata without error""" xml, errors = self.backend.generate_metadata_xml() self.assertEqual(len(errors), 0) self.assertEqual(xml[0], '<') def test_login(self): """Test that we can authenticate with a SAML IdP (TestShib)""" self.do_login() def modify_start_url(self, start_url): """ Given a SAML redirect URL, parse it and change the ID to a consistent value, so the request is always identical. """ # Parse the SAML Request URL to get the XML being sent to TestShib url_parts = urlparse(start_url) query = dict((k, v[0]) for (k, v) in parse_qs(url_parts.query).iteritems()) xml = OneLogin_Saml2_Utils.decode_base64_and_inflate( query['SAMLRequest'] ) # Modify the XML: xml, changed = re.subn(r'ID="[^"]+"', 'ID="TEST_ID"', xml) self.assertEqual(changed, 1) # Update the URL to use the modified query string: query['SAMLRequest'] = OneLogin_Saml2_Utils.deflate_and_base64_encode( xml ) url_parts = list(url_parts) url_parts[4] = urlencode(query) return urlunparse(url_parts) python-social-auth-0.2.13/social/tests/backends/test_skyrock.py000066400000000000000000000024771260133235600246240ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class SkyrockOAuth1Test(OAuth1Test): backend_path = 'social.backends.skyrock.SkyrockOAuth' user_data_url = 'https://api.skyrock.com/v2/user/get.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', }) user_data_body = json.dumps({ 'locale': 'en_US', 'city': '', 'has_blog': False, 'web_messager_enabled': True, 'email': 'foo@bar.com', 'username': 'foobar', 'firstname': 'Foo', 'user_url': '', 'address1': '', 'address2': '', 'has_profile': False, 'allow_messages_from': 'everybody', 'is_online': False, 'postalcode': '', 'lang': 'en', 'id_user': 10101010, 'name': 'Bar', 'gender': 0, 'avatar_url': 'http://www.skyrock.com/img/avatars/default-0.jpg', 'nb_friends': 0, 'country': 'US', 'birth_date': '1980-06-10' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_soundcloud.py000066400000000000000000000031371260133235600253100ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class SoundcloudOAuth2Test(OAuth2Test): backend_path = 'social.backends.soundcloud.SoundcloudOAuth2' user_data_url = 'https://api.soundcloud.com/me.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'website': None, 'myspace_name': None, 'public_favorites_count': 0, 'followings_count': 0, 'full_name': 'Foo Bar', 'id': 10101010, 'city': None, 'track_count': 0, 'playlist_count': 0, 'discogs_name': None, 'private_tracks_count': 0, 'followers_count': 0, 'online': True, 'username': 'foobar', 'description': None, 'subscriptions': [], 'kind': 'user', 'quota': { 'unlimited_upload_quota': False, 'upload_seconds_left': 7200, 'upload_seconds_used': 0 }, 'website_title': None, 'primary_email_confirmed': False, 'permalink_url': 'http://soundcloud.com/foobar', 'private_playlists_count': 0, 'permalink': 'foobar', 'upload_seconds_left': 7200, 'country': None, 'uri': 'https://api.soundcloud.com/users/10101010', 'avatar_url': 'https://a1.sndcdn.com/images/' 'default_avatar_large.png?ca77017', 'plan': 'Free' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_stackoverflow.py000066400000000000000000000032151260133235600260170ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth2Test class StackoverflowOAuth2Test(OAuth2Test): backend_path = 'social.backends.stackoverflow.StackoverflowOAuth2' user_data_url = 'https://api.stackexchange.com/2.1/me' expected_username = 'foobar' access_token_body = urlencode({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'items': [{ 'user_id': 101010, 'user_type': 'registered', 'creation_date': 1278525551, 'display_name': 'foobar', 'profile_image': 'http: //www.gravatar.com/avatar/' '5280f15cedf540b544eecc30fcf3027c?' 'd=identicon&r=PG', 'reputation': 547, 'reputation_change_day': 0, 'reputation_change_week': 0, 'reputation_change_month': 0, 'reputation_change_quarter': 65, 'reputation_change_year': 65, 'age': 22, 'last_access_date': 1363544705, 'last_modified_date': 1354035327, 'is_employee': False, 'link': 'http: //stackoverflow.com/users/101010/foobar', 'location': 'Fooland', 'account_id': 101010, 'badge_counts': { 'gold': 0, 'silver': 3, 'bronze': 6 } }], 'quota_remaining': 9997, 'quota_max': 10000, 'has_more': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_steam.py000066400000000000000000000124441260133235600242430ustar00rootroot00000000000000import json import datetime from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthFailed from social.tests.backends.open_id import OpenIdTest INFO_URL = 'http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?' JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') class SteamOpenIdTest(OpenIdTest): backend_path = 'social.backends.steam.SteamOpenId' expected_username = 'foobar' discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/server', 'https://steamcommunity.com/openid/login', '', '', '' ]) user_discovery_body = ''.join([ '', '', '', '', 'http://specs.openid.net/auth/2.0/signon ', 'https://steamcommunity.com/openid/login', '', '', '' ]) server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.mode': 'id_res', 'openid.op_endpoint': 'https://steamcommunity.com/openid/login', 'openid.claimed_id': 'https://steamcommunity.com/openid/id/123', 'openid.identity': 'https://steamcommunity.com/openid/id/123', 'openid.return_to': 'http://myapp.com/complete/steam/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'oD4UZ3w9chOAiQXk0AqDipqFYRA=', 'openid.assoc_handle': '1234567890', 'openid.signed': 'signed,op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle', 'openid.sig': '1az53vj9SVdiBwhk8%2BFQ68R2plo=', }) player_details = json.dumps({ 'response': { 'players': [{ 'steamid': '123', 'primaryclanid': '1234', 'timecreated': 1360768416, 'personaname': 'foobar', 'personastate': 0, 'communityvisibilitystate': 3, 'profileurl': 'http://steamcommunity.com/profiles/123/', 'avatar': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb.jpg', 'avatarfull': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb_full.jpg', 'avatarmedium': 'http://media.steampowered.com/steamcommunity/' 'public/images/avatars/fe/fef49e7fa7e1997310d7' '05b2a6158ff8dc1cdfeb_medium.jpg', 'lastlogoff': 1360790014 }] } }) def _login_setup(self, user_url=None): self.strategy.set_settings({ 'SOCIAL_AUTH_STEAM_API_KEY': '123abc' }) HTTPretty.register_uri(HTTPretty.POST, 'https://steamcommunity.com/openid/login', status=200, body=self.server_response) HTTPretty.register_uri( HTTPretty.GET, user_url or 'https://steamcommunity.com/openid/id/123', status=200, body=self.user_discovery_body ) HTTPretty.register_uri(HTTPretty.GET, INFO_URL, status=200, body=self.player_details) def test_login(self): self._login_setup() self.do_login() def test_partial_pipeline(self): self._login_setup() self.do_partial_pipeline() class SteamOpenIdMissingSteamIdTest(SteamOpenIdTest): server_response = urlencode({ 'janrain_nonce': JANRAIN_NONCE, 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.mode': 'id_res', 'openid.op_endpoint': 'https://steamcommunity.com/openid/login', 'openid.claimed_id': 'https://steamcommunity.com/openid/BROKEN', 'openid.identity': 'https://steamcommunity.com/openid/BROKEN', 'openid.return_to': 'http://myapp.com/complete/steam/?' 'janrain_nonce=' + JANRAIN_NONCE, 'openid.response_nonce': JANRAIN_NONCE + 'oD4UZ3w9chOAiQXk0AqDipqFYRA=', 'openid.assoc_handle': '1234567890', 'openid.signed': 'signed,op_endpoint,claimed_id,identity,return_to,' 'response_nonce,assoc_handle', 'openid.sig': '1az53vj9SVdiBwhk8%2BFQ68R2plo=', }) def test_login(self): self._login_setup(user_url='https://steamcommunity.com/openid/BROKEN') with self.assertRaises(AuthFailed): self.do_login() def test_partial_pipeline(self): self._login_setup(user_url='https://steamcommunity.com/openid/BROKEN') with self.assertRaises(AuthFailed): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_stocktwits.py000066400000000000000000000031721260133235600253460ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class StocktwitsOAuth2Test(OAuth2Test): backend_path = 'social.backends.stocktwits.StocktwitsOAuth2' user_data_url = 'https://api.stocktwits.com/api/2/account/verify.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'response': { 'status': 200 }, 'user': { 'username': 'foobar', 'name': 'Foo Bar', 'classification': [], 'avatar_url': 'http://avatars.stocktwits.net/images/' 'default_avatar_thumb.jpg', 'avatar_url_ssl': 'https://s3.amazonaws.com/st-avatars/images/' 'default_avatar_thumb.jpg', 'id': 101010, 'identity': 'User' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class StocktwitsOAuth2UsernameAlternativeTest(StocktwitsOAuth2Test): user_data_body = json.dumps({ 'response': { 'status': 200 }, 'user': { 'username': 'foobar', 'name': 'Foobar', 'classification': [], 'avatar_url': 'http://avatars.stocktwits.net/images/' 'default_avatar_thumb.jpg', 'avatar_url_ssl': 'https://s3.amazonaws.com/st-avatars/images/' 'default_avatar_thumb.jpg', 'id': 101010, 'identity': 'User' } }) python-social-auth-0.2.13/social/tests/backends/test_strava.py000066400000000000000000000035321260133235600244300ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class StravaOAuthTest(OAuth2Test): backend_path = 'social.backends.strava.StravaOAuth' user_data_url = 'https://www.strava.com/api/v3/athlete' expected_username = '227615' access_token_body = json.dumps({ "access_token": "83ebeabdec09f6670863766f792ead24d61fe3f9", "athlete": { "id": 227615, "resource_state": 3, "firstname": "John", "lastname": "Applestrava", "profile_medium": "http://pics.com/227615/medium.jpg", "profile": "http://pics.com/227615/large.jpg", "city": "San Francisco", "state": "California", "country": "United States", "sex": "M", "friend": "null", "follower": "null", "premium": "true", "created_at": "2008-01-01T17:44:00Z", "updated_at": "2013-09-04T20:00:50Z", "follower_count": 273, "friend_count": 19, "mutual_friend_count": 0, "date_preference": "%m/%d/%Y", "measurement_preference": "feet", "email": "john@applestrava.com", "clubs": [], "bikes": [], "shoes": [] } }) user_data_body = json.dumps({ "id": 227615, "resource_state": 2, "firstname": "John", "lastname": "Applestrava", "profile_medium": "http://pics.com/227615/medium.jpg", "profile": "http://pics.com/227615/large.jpg", "city": "San Francisco", "state": "CA", "country": "United States", "sex": "M", "friend": "null", "follower": "accepted", "premium": "true", "created_at": "2011-03-19T21:59:57Z", "updated_at": "2013-09-05T16:46:54Z", "approve_followers": "false" }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_stripe.py000066400000000000000000000011521260133235600244320ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class StripeOAuth2Test(OAuth2Test): backend_path = 'social.backends.stripe.StripeOAuth2' access_token_body = json.dumps({ 'stripe_publishable_key': 'pk_test_foobar', 'access_token': 'foobar', 'livemode': False, 'token_type': 'bearer', 'scope': 'read_only', 'refresh_token': 'rt_foobar', 'stripe_user_id': 'acct_foobar' }) expected_username = 'acct_foobar' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_taobao.py000066400000000000000000000013741260133235600243770ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class TaobaoOAuth2Test(OAuth2Test): backend_path = 'social.backends.taobao.TAOBAOAuth' user_data_url = 'https://eco.taobao.com/router/rest' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'w2_expires_in': 0, 'taobao_user_id': '1', 'taobao_user_nick': 'foobar', 'w1_expires_in': 1800, 're_expires_in': 0, 'r2_expires_in': 0, 'expires_in': 86400, 'r1_expires_in': 1800 }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_thisismyjam.py000066400000000000000000000015361260133235600254730ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ThisIsMyJameOAuth1Test(OAuth1Test): backend_path = 'social.backends.thisismyjam.ThisIsMyJamOAuth1' user_data_url = 'http://api.thisismyjam.com/1/verify.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'id': 10101010, 'person': { 'name': 'foobar', 'fullname': 'Foo Bar' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_tripit.py000066400000000000000000000101721260133235600244410ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TripitOAuth1Test(OAuth1Test): backend_path = 'social.backends.tripit.TripItOAuth' user_data_url = 'https://api.tripit.com/v1/get/profile' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_content_type = 'text/xml' user_data_body = \ '' \ '1363590451' \ '1040' \ '' \ '' \ '' \ '
    foobar@gmail.com
    ' \ 'false' \ 'true' \ 'true' \ '' \ 'true' \ '' \ '
    ' \ '
    ' \ 'true' \ 'false' \ 'foobar' \ 'Foo Bar' \ 'people/foobar' \ 'Foo, Barland' \ '' \ 'https://www.tripit.com/feed/activities/private/' \ 'ignore-this/activities.atom' \ '' \ '' \ 'https://www.tripit.com/feed/alerts/private/' \ 'ignore-this/alerts.atom' \ '' \ '' \ 'webcal://www.tripit.com/feed/ical/private/' \ 'ignore-this/tripit.ics' \ '' \ '
    ' \ '
    ' def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() class TripitOAuth1UsernameAlternativesTest(TripitOAuth1Test): user_data_body = \ '' \ '1363590451' \ '1040' \ '' \ '' \ '' \ '
    foobar@gmail.com
    ' \ 'false' \ 'true' \ 'true' \ '' \ 'true' \ '' \ '
    ' \ '
    ' \ 'true' \ 'false' \ 'foobar' \ 'Foobar' \ 'people/foobar' \ 'Foo, Barland' \ '' \ 'https://www.tripit.com/feed/activities/private/' \ 'ignore-this/activities.atom' \ '' \ '' \ 'https://www.tripit.com/feed/alerts/private/' \ 'ignore-this/alerts.atom' \ '' \ '' \ 'webcal://www.tripit.com/feed/ical/private/' \ 'ignore-this/tripit.ics' \ '' \ '
    ' \ '
    ' python-social-auth-0.2.13/social/tests/backends/test_tumblr.py000066400000000000000000000033171260133235600244360ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TumblrOAuth1Test(OAuth1Test): backend_path = 'social.backends.tumblr.TumblrOAuth' user_data_url = 'http://api.tumblr.com/v2/user/info' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'meta': { 'status': 200, 'msg': 'OK' }, 'response': { 'user': { 'following': 1, 'blogs': [{ 'updated': 0, 'description': '', 'drafts': 0, 'title': 'Untitled', 'url': 'http://foobar.tumblr.com/', 'messages': 0, 'tweet': 'N', 'share_likes': True, 'posts': 0, 'primary': True, 'queue': 0, 'admin': True, 'followers': 0, 'ask': False, 'facebook': 'N', 'type': 'public', 'facebook_opengraph_enabled': 'N', 'name': 'foobar' }], 'default_post_format': 'html', 'name': 'foobar', 'likes': 0 } } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_twitch.py000066400000000000000000000020321260133235600244240ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class TwitchOAuth2Test(OAuth2Test): backend_path = 'social.backends.twitch.TwitchOAuth2' user_data_url = 'https://api.twitch.tv/kraken/user/' expected_username = 'test_user1' access_token_body = json.dumps({ 'access_token': 'foobar', }) user_data_body = json.dumps({ 'type': 'user', 'name': 'test_user1', 'created_at': '2011-06-03T17:49:19Z', 'updated_at': '2012-06-18T17:19:57Z', '_links': { 'self': 'https://api.twitch.tv/kraken/users/test_user1' }, 'logo': 'http://static-cdn.jtvnw.net/jtv_user_pictures/' 'test_user1-profile_image-62e8318af864d6d7-300x300.jpeg', '_id': 22761313, 'display_name': 'test_user1', 'email': 'asdf@asdf.com', 'partnered': True, 'bio': 'test bio woo I\'m a test user' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_twitter.py000066400000000000000000000111071260133235600246270ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class TwitterOAuth1Test(OAuth1Test): backend_path = 'social.backends.twitter.TwitterOAuth' user_data_url = 'https://api.twitter.com/1.1/account/' \ 'verify_credentials.json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'follow_request_sent': False, 'profile_use_background_image': True, 'id': 10101010, 'description': 'Foo bar baz qux', 'verified': False, 'entities': { 'description': { 'urls': [] } }, 'profile_image_url_https': 'https://twimg0-a.akamaihd.net/' 'profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'profile_sidebar_fill_color': '252429', 'profile_text_color': '666666', 'followers_count': 77, 'profile_sidebar_border_color': '181A1E', 'location': 'Fooland', 'default_profile_image': False, 'listed_count': 4, 'status': { 'favorited': False, 'contributors': None, 'retweeted_status': { 'favorited': False, 'contributors': None, 'truncated': False, 'source': 'web', 'text': '"Foo foo foo foo', 'created_at': 'Fri Dec 21 18:12:00 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'truncated': False, 'source': 'web', 'text': 'RT @foo: "Foo foo foo foo', 'created_at': 'Fri Dec 21 18:24:10 +0000 2012', 'retweeted': True, 'in_reply_to_status_id': None, 'coordinates': None, 'id': 101010101010101010, 'entities': { 'user_mentions': [{ 'indices': [3, 10], 'id': 10101010, 'screen_name': 'foo', 'id_str': '10101010', 'name': 'Foo' }], 'hashtags': [], 'urls': [] }, 'in_reply_to_status_id_str': None, 'place': None, 'id_str': '101010101010101010', 'in_reply_to_screen_name': None, 'retweet_count': 8, 'geo': None, 'in_reply_to_user_id_str': None, 'in_reply_to_user_id': None }, 'utc_offset': -10800, 'statuses_count': 191, 'profile_background_color': '1A1B1F', 'friends_count': 151, 'profile_background_image_url_https': 'https://twimg0-a.akamaihd.net/' 'images/themes/theme9/bg.gif', 'profile_link_color': '2FC2EF', 'profile_image_url': 'http://a0.twimg.com/profile_images/532018826/' 'n587119531_1939735_9305_normal.jpg', 'is_translator': False, 'geo_enabled': False, 'id_str': '74313638', 'profile_background_image_url': 'http://a0.twimg.com/images/themes/' 'theme9/bg.gif', 'screen_name': 'foobar', 'lang': 'en', 'profile_background_tile': False, 'favourites_count': 2, 'name': 'Foo', 'notifications': False, 'url': None, 'created_at': 'Tue Sep 15 00:26:17 +0000 2009', 'contributors_enabled': False, 'time_zone': 'Buenos Aires', 'protected': False, 'default_profile': False, 'following': False }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_uber.py000066400000000000000000000017531260133235600240700ustar00rootroot00000000000000import json from httpretty import HTTPretty from social.p3 import urlencode from social.exceptions import AuthForbidden from social.tests.backends.oauth import OAuth1Test, OAuth2Test class UberOAuth2Test(OAuth2Test): user_data_url = 'https://api.uber.com/v1/me' backend_path = 'social.backends.uber.UberOAuth2' expected_username = 'foo@bar.com' user_data_body = json.dumps({ "first_name": "Foo", "last_name": "Bar", "email": "foo@bar.com", "picture": "https://", "promo_code": "barfoo", "uuid": "91d81273-45c2-4b57-8124-d0165f8240c0" }) access_token_body = json.dumps({ "access_token": "EE1IDxytP04tJ767GbjH7ED9PpGmYvL", "token_type": "Bearer", "expires_in": 2592000, "refresh_token": "Zx8fJ8qdSRRseIVlsGgtgQ4wnZBehr", "scope": "profile history request" }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_username.py000066400000000000000000000007611260133235600247500ustar00rootroot00000000000000from social.tests.backends.legacy import BaseLegacyTest class UsernameTest(BaseLegacyTest): backend_path = 'social.backends.username.UsernameAuth' expected_username = 'foobar' response_body = 'username=foobar' form = """
    """ def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_utils.py000066400000000000000000000033601260133235600242670ustar00rootroot00000000000000import unittest2 as unittest from social.tests.models import TestStorage from social.tests.strategy import TestStrategy from social.backends.utils import load_backends, get_backend from social.backends.github import GithubOAuth2 from social.exceptions import MissingBackend class BaseBackendUtilsTest(unittest.TestCase): def setUp(self): self.strategy = TestStrategy(storage=TestStorage) def tearDown(self): self.strategy = None class LoadBackendsTest(BaseBackendUtilsTest): def test_load_backends(self): loaded_backends = load_backends(( 'social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth' ), force_load=True) keys = list(loaded_backends.keys()) keys.sort() self.assertEqual(keys, ['facebook', 'flickr', 'github']) backends = () loaded_backends = load_backends(backends, force_load=True) self.assertEqual(len(list(loaded_backends.keys())), 0) class GetBackendTest(BaseBackendUtilsTest): def test_get_backend(self): backend = get_backend(( 'social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth' ), 'github') self.assertEqual(backend, GithubOAuth2) def test_get_missing_backend(self): with self.assertRaisesRegexp(MissingBackend, 'Missing backend "foobar" entry'): get_backend(('social.backends.github.GithubOAuth2', 'social.backends.facebook.FacebookOAuth2', 'social.backends.flickr.FlickrOAuth'), 'foobar') python-social-auth-0.2.13/social/tests/backends/test_vk.py000066400000000000000000000015171260133235600235510ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals import json from social.tests.backends.oauth import OAuth2Test class VKOAuth2Test(OAuth2Test): backend_path = 'social.backends.vk.VKOAuth2' user_data_url = 'https://api.vk.com/method/users.get' expected_username = 'durov' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'response': [{ 'uid': '1', 'first_name': 'Павел', 'last_name': 'Дуров', 'screen_name': 'durov', 'nickname': '', 'photo': "http:\/\/cs7003.vk.me\/v7003815\/22a1\/xgG9fb-IJ3Y.jpg" }] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_wunderlist.py000066400000000000000000000013651260133235600253320ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class WunderlistOAuth2Test(OAuth2Test): backend_path = 'social.backends.wunderlist.WunderlistOAuth2' user_data_url = 'https://a.wunderlist.com/api/v1/user' expected_username = '12345' access_token_body = json.dumps({ 'access_token': 'foobar-token', 'token_type': 'foobar'}) user_data_body = json.dumps({ 'created_at': '2015-01-21T00:56:51.442Z', 'email': 'foo@bar.com', 'id': 12345, 'name': 'foobar', 'revision': 1, 'type': 'user', 'updated_at': '2015-01-21T00:56:51.442Z'}) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_xing.py000066400000000000000000000140671260133235600241020ustar00rootroot00000000000000import json from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class XingOAuth1Test(OAuth1Test): backend_path = 'social.backends.xing.XingOAuth' user_data_url = 'https://api.xing.com/v1/users/me.json' expected_username = 'FooBar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer', 'user_id': '123456_abcdef' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) user_data_body = json.dumps({ 'users': [{ 'id': '123456_abcdef', 'first_name': 'Foo', 'last_name': 'Bar', 'display_name': 'Foo Bar', 'page_name': 'Foo_Bar', 'permalink': 'https://www.xing.com/profile/Foo_Bar', 'gender': 'm', 'birth_date': { 'day': 12, 'month': 8, 'year': 1963 }, 'active_email': 'foo@bar.com', 'time_zone': { 'name': 'Europe/Copenhagen', 'utc_offset': 2.0 }, 'premium_services': ['SEARCH', 'PRIVATEMESSAGES'], 'badges': ['PREMIUM', 'MODERATOR'], 'wants': 'Nothing', 'haves': 'Skills', 'interests': 'Foo Foo', 'organisation_member': 'ACM, GI', 'languages': { 'de': 'NATIVE', 'en': 'FLUENT', 'fr': None, 'zh': 'BASIC' }, 'private_address': { 'city': 'Foo', 'country': 'DE', 'zip_code': '20357', 'street': 'Bar', 'phone': '12|34|1234560', 'fax': '||', 'province': 'Foo', 'email': 'foo@bar.com', 'mobile_phone': '12|3456|1234567' }, 'business_address': { 'city': 'Foo', 'country': 'DE', 'zip_code': '20357', 'street': 'Bar', 'phone': '12|34|1234569', 'fax': '12|34|1234561', 'province': 'Foo', 'email': 'foo@bar.com', 'mobile_phone': '12|345|12345678' }, 'web_profiles': { 'qype': ['http://qype.de/users/foo'], 'google_plus': ['http://plus.google.com/foo'], 'blog': ['http://blog.example.org'], 'homepage': ['http://example.org', 'http://other-example.org'] }, 'instant_messaging_accounts': { 'skype': 'foobar', 'googletalk': 'foobar' }, 'professional_experience': { 'primary_company': { 'name': 'XING AG', 'title': 'Softwareentwickler', 'company_size': '201-500', 'tag': None, 'url': 'http://www.xing.com', 'career_level': 'PROFESSIONAL_EXPERIENCED', 'begin_date': '2010-01', 'description': None, 'end_date': None, 'industry': 'AEROSPACE' }, 'non_primary_companies': [{ 'name': 'Ninja Ltd.', 'title': 'DevOps', 'company_size': None, 'tag': 'NINJA', 'url': 'http://www.ninja-ltd.co.uk', 'career_level': None, 'begin_date': '2009-04', 'description': None, 'end_date': '2010-07', 'industry': 'ALTERNATIVE_MEDICINE' }, { 'name': None, 'title': 'Wiss. Mitarbeiter', 'company_size': None, 'tag': 'OFFIS', 'url': 'http://www.uni.de', 'career_level': None, 'begin_date': '2007', 'description': None, 'end_date': '2008', 'industry': 'APPAREL_AND_FASHION' }, { 'name': None, 'title': 'TEST NINJA', 'company_size': '201-500', 'tag': 'TESTCOMPANY', 'url': None, 'career_level': 'ENTRY_LEVEL', 'begin_date': '1998-12', 'description': None, 'end_date': '1999-05', 'industry': 'ARTS_AND_CRAFTS' }], 'awards': [{ 'name': 'Awesome Dude Of The Year', 'date_awarded': 2007, 'url': None }] }, 'educational_background': { 'schools': [{ 'name': 'Foo University', 'degree': 'MSc CE/CS', 'notes': None, 'subject': None, 'begin_date': '1998-08', 'end_date': '2005-02' }], 'qualifications': ['TOEFLS', 'PADI AOWD'] }, 'photo_urls': { 'large': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.140x185.jpg', 'mini_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.18x24.jpg', 'thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.30x40.jpg', 'medium_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.57x75.jpg', 'maxi_thumb': 'http://www.xing.com/img/users/e/3/d/' 'f94ef165a.123456,1.70x93.jpg' } }] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_yahoo.py000066400000000000000000000047471260133235600242600ustar00rootroot00000000000000import json import requests from httpretty import HTTPretty from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class YahooOAuth1Test(OAuth1Test): backend_path = 'social.backends.yahoo.YahooOAuth' user_data_url = 'https://social.yahooapis.com/v1/user/a-guid/profile?' \ 'format=json' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) guid_body = json.dumps({ 'guid': { 'uri': 'https://social.yahooapis.com/v1/me/guid', 'value': 'a-guid' } }) user_data_body = json.dumps({ 'profile': { 'bdRestricted': True, 'memberSince': '2007-12-11T14:40:30Z', 'image': { 'width': 192, 'imageUrl': 'http://l.yimg.com/dh/ap/social/profile/' 'profile_b192.png', 'size': '192x192', 'height': 192 }, 'created': '2013-03-18T04:15:08Z', 'uri': 'https://social.yahooapis.com/v1/user/a-guid/profile', 'isConnected': False, 'profileUrl': 'http://profile.yahoo.com/a-guid', 'guid': 'a-guid', 'nickname': 'foobar', 'emails': [{ 'handle': 'foobar@yahoo.com', 'id': 1, 'primary': True, 'type': 'HOME', }, { 'handle': 'foobar@email.com', 'id': 2, 'type': 'HOME', }], } }) def test_login(self): HTTPretty.register_uri( HTTPretty.GET, 'https://social.yahooapis.com/v1/me/guid?format=json', status=200, body=self.guid_body ) self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() def test_get_user_details(self): HTTPretty.register_uri( HTTPretty.GET, self.user_data_url, status=200, body=self.user_data_body ) response = requests.get(self.user_data_url) user_details = self.backend.get_user_details( response.json()['profile'] ) self.assertEqual(user_details['email'], 'foobar@yahoo.com') python-social-auth-0.2.13/social/tests/backends/test_yammer.py000066400000000000000000000076461260133235600244340ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class YammerOAuth2Test(OAuth2Test): backend_path = 'social.backends.yammer.YammerOAuth2' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': { 'user_id': 1010101010, 'view_groups': True, 'modify_messages': True, 'network_id': 101010, 'created_at': '2013/03/17 16:39:56 +0000', 'view_members': True, 'authorized_at': '2013/03/17 16:39:56 +0000', 'view_subscriptions': True, 'view_messages': True, 'modify_subscriptions': True, 'token': 'foobar', 'expires_at': None, 'network_permalink': 'foobar.com', 'view_tags': True, 'network_name': 'foobar.com' }, 'user': { 'last_name': 'Bar', 'web_url': 'https://www.yammer.com/foobar/users/foobar', 'expertise': None, 'full_name': 'Foo Bar', 'timezone': 'Pacific Time (US & Canada)', 'mugshot_url': 'https://mug0.assets-yammer.com/mugshot/images/' '48x48/no_photo.png', 'guid': None, 'network_name': 'foobar', 'id': 1010101010, 'previous_companies': [], 'first_name': 'Foo', 'stats': { 'following': 0, 'followers': 0, 'updates': 1 }, 'hire_date': None, 'state': 'active', 'location': None, 'department': 'Software Development', 'type': 'user', 'show_ask_for_photo': True, 'job_title': 'Software Developer', 'interests': None, 'kids_names': None, 'activated_at': '2013/03/17 16:27:50 +0000', 'verified_admin': 'false', 'can_broadcast': 'false', 'schools': [], 'admin': 'false', 'network_domains': ['foobar.com'], 'name': 'foobar', 'external_urls': [], 'url': 'https://www.yammer.com/api/v1/users/1010101010', 'settings': { 'xdr_proxy': 'https://xdrproxy.yammer.com' }, 'summary': None, 'network_id': 101010, 'contact': { 'phone_numbers': [], 'im': { 'username': '', 'provider': '' }, 'email_addresses': [{ 'type': 'primary', 'address': 'foo@bar.com' }], 'has_fake_email': False }, 'birth_date': '', 'mugshot_url_template': 'https://mug0.assets-yammer.com/mugshot/' 'images/{width}x{height}/no_photo.png', 'significant_other': None }, 'network': { 'show_upgrade_banner': False, 'header_text_color': '#FFFFFF', 'is_org_chart_enabled': True, 'name': 'foobar.com', 'is_group_enabled': True, 'header_background_color': '#396B9A', 'created_at': '2012/12/26 16:52:35 +0000', 'profile_fields_config': { 'enable_work_phone': True, 'enable_mobile_phone': True, 'enable_job_title': True }, 'permalink': 'foobar.com', 'paid': False, 'id': 101010, 'is_chat_enabled': True, 'web_url': 'https://www.yammer.com/foobar.com', 'moderated': False, 'community': False, 'type': 'network', 'navigation_background_color': '#38699F', 'navigation_text_color': '#FFFFFF' } }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_yandex.py000066400000000000000000000013151260133235600244150ustar00rootroot00000000000000import json from social.tests.backends.oauth import OAuth2Test class YandexOAuth2Test(OAuth2Test): backend_path = 'social.backends.yandex.YandexOAuth2' user_data_url = 'https://login.yandex.ru/info' expected_username = 'foobar' access_token_body = json.dumps({ 'access_token': 'foobar', 'token_type': 'bearer' }) user_data_body = json.dumps({ 'display_name': 'foobar', 'real_name': 'Foo Bar', 'sex': None, 'id': '101010101', 'default_email': 'foobar@yandex.com', 'emails': ['foobar@yandex.com'] }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/backends/test_zotero.py000066400000000000000000000012731260133235600244520ustar00rootroot00000000000000from social.p3 import urlencode from social.tests.backends.oauth import OAuth1Test class ZoteroOAuth1Test(OAuth1Test): backend_path = 'social.backends.zotero.ZoteroOAuth' expected_username = 'FooBar' access_token_body = urlencode({ 'oauth_token': 'foobar', 'oauth_token_secret': 'rodgsNDK4hLJU1504Atk131G', 'userID': '123456_abcdef', 'username': 'FooBar' }) request_token_body = urlencode({ 'oauth_token_secret': 'foobar-secret', 'oauth_token': 'foobar', 'oauth_callback_confirmed': 'true' }) def test_login(self): self.do_login() def test_partial_pipeline(self): self.do_partial_pipeline() python-social-auth-0.2.13/social/tests/models.py000066400000000000000000000125501260133235600216020ustar00rootroot00000000000000import base64 from social.storage.base import UserMixin, NonceMixin, AssociationMixin, \ CodeMixin, BaseStorage class BaseModel(object): @classmethod def next_id(cls): cls.NEXT_ID += 1 return cls.NEXT_ID - 1 @classmethod def get(cls, key): return cls.cache.get(key) @classmethod def reset_cache(cls): cls.cache = {} class User(BaseModel): NEXT_ID = 1 cache = {} _is_active = True def __init__(self, username, email=None): self.id = User.next_id() self.username = username self.email = email self.password = None self.slug = None self.social = [] self.extra_data = {} self.save() def is_active(self): return self._is_active @classmethod def set_active(cls, is_active=True): cls._is_active = is_active def set_password(self, password): self.password = password def save(self): User.cache[self.username] = self class TestUserSocialAuth(UserMixin, BaseModel): NEXT_ID = 1 cache = {} cache_by_uid = {} def __init__(self, user, provider, uid, extra_data=None): self.id = TestUserSocialAuth.next_id() self.user = user self.provider = provider self.uid = uid self.extra_data = extra_data or {} self.user.social.append(self) TestUserSocialAuth.cache_by_uid[uid] = self def save(self): pass @classmethod def reset_cache(cls): cls.cache = {} cls.cache_by_uid = {} @classmethod def changed(cls, user): pass @classmethod def get_username(cls, user): return user.username @classmethod def user_model(cls): return User @classmethod def username_max_length(cls): return 1024 @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): return user.password or len(user.social) > 1 @classmethod def disconnect(cls, entry): cls.cache.pop(entry.id, None) entry.user.social = [s for s in entry.user.social if entry != s] @classmethod def user_exists(cls, username): return User.cache.get(username) is not None @classmethod def create_user(cls, username, email=None): return User(username=username, email=email) @classmethod def get_user(cls, pk): for username, user in User.cache.items(): if user.id == pk: return user @classmethod def get_social_auth(cls, provider, uid): social_user = cls.cache_by_uid.get(uid) if social_user and social_user.provider == provider: return social_user @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): return [usa for usa in user.social if provider in (None, usa.provider) and id in (None, usa.id)] @classmethod def create_social_auth(cls, user, uid, provider): return cls(user=user, provider=provider, uid=uid) @classmethod def get_users_by_email(cls, email): return [user for user in User.cache.values() if user.email == email] class TestNonce(NonceMixin, BaseModel): NEXT_ID = 1 cache = {} def __init__(self, server_url, timestamp, salt): self.id = TestNonce.next_id() self.server_url = server_url self.timestamp = timestamp self.salt = salt @classmethod def use(cls, server_url, timestamp, salt): nonce = TestNonce(server_url, timestamp, salt) TestNonce.cache[server_url] = nonce return nonce class TestAssociation(AssociationMixin, BaseModel): NEXT_ID = 1 cache = {} def __init__(self, server_url, handle): self.id = TestAssociation.next_id() self.server_url = server_url self.handle = handle def save(self): TestAssociation.cache[(self.server_url, self.handle)] = self @classmethod def store(cls, server_url, association): assoc = TestAssociation.cache.get((server_url, association.handle)) if assoc is None: assoc = TestAssociation(server_url=server_url, handle=association.handle) assoc.secret = base64.encodestring(association.secret) assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, server_url=None, handle=None): result = [] for assoc in TestAssociation.cache.values(): if server_url and assoc.server_url != server_url: continue if handle and assoc.handle != handle: continue result.append(assoc) return result @classmethod def remove(cls, ids_to_delete): assoc = filter(lambda a: a.id in ids_to_delete, TestAssociation.cache.values()) for a in list(assoc): TestAssociation.cache.pop((a.server_url, a.handle), None) class TestCode(CodeMixin, BaseModel): NEXT_ID = 1 cache = {} @classmethod def get_code(cls, code): for c in cls.cache.values(): if c.code == code: return c class TestStorage(BaseStorage): user = TestUserSocialAuth nonce = TestNonce association = TestAssociation code = TestCode python-social-auth-0.2.13/social/tests/pipeline.py000066400000000000000000000022521260133235600221220ustar00rootroot00000000000000from social.pipeline.partial import partial def ask_for_password(strategy, *args, **kwargs): if strategy.session_get('password'): return {'password': strategy.session_get('password')} else: return strategy.redirect(strategy.build_absolute_uri('/password')) @partial def ask_for_slug(strategy, *args, **kwargs): if strategy.session_get('slug'): return {'slug': strategy.session_get('slug')} else: return strategy.redirect(strategy.build_absolute_uri('/slug')) def set_password(strategy, user, *args, **kwargs): user.set_password(kwargs['password']) def set_slug(strategy, user, *args, **kwargs): user.slug = kwargs['slug'] def remove_user(strategy, user, *args, **kwargs): return {'user': None} @partial def set_user_from_kwargs(strategy, *args, **kwargs): if strategy.session_get('attribute'): kwargs['user'].id else: return strategy.redirect(strategy.build_absolute_uri('/attribute')) @partial def set_user_from_args(strategy, user, *args, **kwargs): if strategy.session_get('attribute'): user.id else: return strategy.redirect(strategy.build_absolute_uri('/attribute')) python-social-auth-0.2.13/social/tests/requirements-python3.txt000066400000000000000000000001771260133235600246350ustar00rootroot00000000000000httpretty==0.6.5 coverage>=3.6 mock==1.0.1 nose>=1.2.1 rednose>=0.4.1 requests>=1.1.0 PyJWT>=1.0.0,<2.0.0 unittest2py3k==0.5.1 python-social-auth-0.2.13/social/tests/requirements.txt000066400000000000000000000002161260133235600232250ustar00rootroot00000000000000httpretty==0.6.5 coverage>=3.6 mock==1.0.1 nose>=1.2.1 rednose>=0.4.1 requests>=1.1.0 PyJWT>=1.0.0,<2.0.0 unittest2==0.5.1 python-saml==2.1.3 python-social-auth-0.2.13/social/tests/run_tests.sh000077500000000000000000000001031260133235600223210ustar00rootroot00000000000000#!/bin/sh nosetests --with-coverage --cover-package=social --stop python-social-auth-0.2.13/social/tests/strategy.py000066400000000000000000000070711260133235600221630ustar00rootroot00000000000000from social.strategies.base import BaseStrategy, BaseTemplateStrategy TEST_URI = 'http://myapp.com' TEST_HOST = 'myapp.com' class Redirect(object): def __init__(self, url): self.url = url class TestTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): return tpl def render_string(self, html, context): return html class TestStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = TestTemplateStrategy def __init__(self, storage, tpl=None): self._request_data = {} self._settings = {} self._session = {} super(TestStrategy, self).__init__(storage, tpl) def redirect(self, url): return Redirect(url) def get_setting(self, name): """Return value for given setting name""" return self._settings[name] def html(self, content): """Return HTTP response with given content""" return content def render_html(self, tpl=None, html=None, context=None): """Render given template or raw html with given context""" return tpl or html def request_data(self, merge=True): """Return current request data (POST or GET)""" return self._request_data def request_host(self): """Return current host value""" return TEST_HOST def request_is_secure(self): """ Is the request using HTTPS? """ return False def request_path(self): """ path of the current request """ return '' def request_port(self): """ Port in use for this request """ return 80 def request_get(self): """ Request GET data """ return self._request_data.copy() def request_post(self): """ Request POST data """ return self._request_data.copy() def session_get(self, name, default=None): """Return session value for given key""" return self._session.get(name, default) def session_set(self, name, value): """Set session value for given key""" self._session[name] = value def session_pop(self, name): """Pop session value for given key""" return self._session.pop(name, None) def build_absolute_uri(self, path=None): """Build absolute URI with given (optional) path""" path = path or '' if path.startswith('http://') or path.startswith('https://'): return path return TEST_URI + path def set_settings(self, values): self._settings.update(values) def set_request_data(self, values, backend): self._request_data.update(values) backend.data = self._request_data def remove_from_request_data(self, name): self._request_data.pop(name, None) def authenticate(self, *args, **kwargs): user = super(TestStrategy, self).authenticate(*args, **kwargs) if isinstance(user, self.storage.user.user_model()): self.session_set('username', user.username) return user def get_pipeline(self): return self.setting('PIPELINE', ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.auth_allowed', 'social.pipeline.social_auth.social_user', 'social.pipeline.user.get_username', 'social.pipeline.social_auth.associate_by_email', 'social.pipeline.user.create_user', 'social.pipeline.social_auth.associate_user', 'social.pipeline.social_auth.load_extra_data', 'social.pipeline.user.user_details')) python-social-auth-0.2.13/social/tests/test_exceptions.py000066400000000000000000000055301260133235600235370ustar00rootroot00000000000000import unittest2 as unittest from social.exceptions import SocialAuthBaseException, WrongBackend, \ AuthFailed, AuthTokenError, \ AuthMissingParameter, AuthStateMissing, \ NotAllowedToDisconnect, AuthException, \ AuthCanceled, AuthUnknownError, \ AuthStateForbidden, AuthAlreadyAssociated, \ AuthTokenRevoked class BaseExceptionTestCase(unittest.TestCase): exception = None expected_message = '' def test_exception_message(self): if self.exception is None and self.expected_message == '': return try: raise self.exception except SocialAuthBaseException as err: self.assertEqual(str(err), self.expected_message) class WrongBackendTest(BaseExceptionTestCase): exception = WrongBackend('foobar') expected_message = 'Incorrect authentication service "foobar"' class AuthFailedTest(BaseExceptionTestCase): exception = AuthFailed('foobar', 'wrong_user') expected_message = 'Authentication failed: wrong_user' class AuthFailedDeniedTest(BaseExceptionTestCase): exception = AuthFailed('foobar', 'access_denied') expected_message = 'Authentication process was canceled' class AuthTokenErrorTest(BaseExceptionTestCase): exception = AuthTokenError('foobar', 'Incorrect tokens') expected_message = 'Token error: Incorrect tokens' class AuthMissingParameterTest(BaseExceptionTestCase): exception = AuthMissingParameter('foobar', 'username') expected_message = 'Missing needed parameter username' class AuthStateMissingTest(BaseExceptionTestCase): exception = AuthStateMissing('foobar') expected_message = 'Session value state missing.' class NotAllowedToDisconnectTest(BaseExceptionTestCase): exception = NotAllowedToDisconnect() expected_message = '' class AuthExceptionTest(BaseExceptionTestCase): exception = AuthException('foobar', 'message') expected_message = 'message' class AuthCanceledTest(BaseExceptionTestCase): exception = AuthCanceled('foobar') expected_message = 'Authentication process canceled' class AuthUnknownErrorTest(BaseExceptionTestCase): exception = AuthUnknownError('foobar', 'some error') expected_message = 'An unknown error happened while ' \ 'authenticating some error' class AuthStateForbiddenTest(BaseExceptionTestCase): exception = AuthStateForbidden('foobar') expected_message = 'Wrong state parameter given.' class AuthAlreadyAssociatedTest(BaseExceptionTestCase): exception = AuthAlreadyAssociated('foobar') expected_message = '' class AuthTokenRevokedTest(BaseExceptionTestCase): exception = AuthTokenRevoked('foobar') expected_message = 'User revoke access to the token' python-social-auth-0.2.13/social/tests/test_pipeline.py000066400000000000000000000165511260133235600231700ustar00rootroot00000000000000import json from social.exceptions import AuthException from social.tests.models import TestUserSocialAuth, TestStorage, User from social.tests.strategy import TestStrategy from social.tests.actions.actions import BaseActionTest class IntegrityError(Exception): pass class UnknownError(Exception): pass class IntegrityErrorUserSocialAuth(TestUserSocialAuth): @classmethod def create_social_auth(cls, user, uid, provider): raise IntegrityError() @classmethod def get_social_auth(cls, provider, uid): if not hasattr(cls, '_called_times'): cls._called_times = 0 cls._called_times += 1 if cls._called_times == 2: user = list(User.cache.values())[0] return IntegrityErrorUserSocialAuth(user, provider, uid) else: return super(IntegrityErrorUserSocialAuth, cls).get_social_auth( provider, uid ) class IntegrityErrorStorage(TestStorage): user = IntegrityErrorUserSocialAuth @classmethod def is_integrity_error(cls, exception): """Check if given exception flags an integrity error in the DB""" return isinstance(exception, IntegrityError) class UnknownErrorUserSocialAuth(TestUserSocialAuth): @classmethod def create_social_auth(cls, user, uid, provider): raise UnknownError() class UnknownErrorStorage(IntegrityErrorStorage): user = UnknownErrorUserSocialAuth class IntegrityErrorOnLoginTest(BaseActionTest): def setUp(self): self.strategy = TestStrategy(IntegrityErrorStorage) super(IntegrityErrorOnLoginTest, self).setUp() def test_integrity_error(self): self.do_login() class UnknownErrorOnLoginTest(BaseActionTest): def setUp(self): self.strategy = TestStrategy(UnknownErrorStorage) super(UnknownErrorOnLoginTest, self).setUp() def test_unknown_error(self): with self.assertRaises(UnknownError): self.do_login() class EmailAsUsernameTest(BaseActionTest): expected_username = 'foo@bar.com' def test_email_as_username(self): self.strategy.set_settings({ 'SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL': True }) self.do_login() class RandomUsernameTest(BaseActionTest): user_data_body = json.dumps({ 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_random_username(self): self.do_login(after_complete_checks=False) class SluggedUsernameTest(BaseActionTest): expected_username = 'foo-bar' user_data_body = json.dumps({ 'login': 'Foo Bar', 'id': 1, 'avatar_url': 'https://github.com/images/error/foobar_happy.gif', 'gravatar_id': 'somehexcode', 'url': 'https://api.github.com/users/foobar', 'name': 'monalisa foobar', 'company': 'GitHub', 'blog': 'https://github.com/blog', 'location': 'San Francisco', 'email': 'foo@bar.com', 'hireable': False, 'bio': 'There once was...', 'public_repos': 2, 'public_gists': 1, 'followers': 20, 'following': 0, 'html_url': 'https://github.com/foobar', 'created_at': '2008-01-14T04:33:35Z', 'type': 'User', 'total_private_repos': 100, 'owned_private_repos': 100, 'private_gists': 81, 'disk_usage': 10000, 'collaborators': 8, 'plan': { 'name': 'Medium', 'space': 400, 'collaborators': 10, 'private_repos': 20 } }) def test_random_username(self): self.strategy.set_settings({ 'SOCIAL_AUTH_CLEAN_USERNAMES': False, 'SOCIAL_AUTH_SLUGIFY_USERNAMES': True }) self.do_login() class RepeatedUsernameTest(BaseActionTest): def test_random_username(self): User(username='foobar') self.do_login(after_complete_checks=False) self.assertTrue(self.strategy.session_get('username') .startswith('foobar')) class AssociateByEmailTest(BaseActionTest): def test_multiple_accounts_with_same_email(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.do_login(after_complete_checks=False) self.assertTrue(self.strategy.session_get('username') .startswith('foobar')) class MultipleAccountsWithSameEmailTest(BaseActionTest): def test_multiple_accounts_with_same_email(self): user1 = User(username='foobar1') user2 = User(username='foobar2') user1.email = 'foo@bar.com' user2.email = 'foo@bar.com' with self.assertRaises(AuthException): self.do_login(after_complete_checks=False) class UserPersistsInPartialPipeline(BaseActionTest): def test_user_persists_in_partial_pipeline_kwargs(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.associate_by_email', 'social.tests.pipeline.set_user_from_kwargs' ) }) self.do_login(after_complete_checks=False) # Handle the partial pipeline self.strategy.session_set('attribute', 'testing') data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) def test_user_persists_in_partial_pipeline(self): user = User(username='foobar1') user.email = 'foo@bar.com' self.strategy.set_settings({ 'SOCIAL_AUTH_PIPELINE': ( 'social.pipeline.social_auth.social_details', 'social.pipeline.social_auth.social_uid', 'social.pipeline.social_auth.associate_by_email', 'social.tests.pipeline.set_user_from_args' ) }) self.do_login(after_complete_checks=False) # Handle the partial pipeline self.strategy.session_set('attribute', 'testing') data = self.strategy.session_pop('partial_pipeline') idx, backend, xargs, xkwargs = self.strategy.partial_from_session(data) self.backend.continue_pipeline(pipeline_index=idx, *xargs, **xkwargs) python-social-auth-0.2.13/social/tests/test_storage.py000066400000000000000000000144411260133235600230230ustar00rootroot00000000000000import six import random import unittest2 as unittest from social.strategies.base import BaseStrategy from social.storage.base import UserMixin, NonceMixin, AssociationMixin, \ CodeMixin, BaseStorage from social.tests.models import User NOT_IMPLEMENTED_MSG = 'Implement in subclass' class BrokenUser(UserMixin): pass class BrokenAssociation(AssociationMixin): pass class BrokenNonce(NonceMixin): pass class BrokenCode(CodeMixin): pass class BrokenStrategy(BaseStrategy): pass class BrokenStrategyWithSettings(BrokenStrategy): def get_setting(self, name): raise AttributeError() class BrokenStorage(BaseStorage): pass class BrokenUserTests(unittest.TestCase): def setUp(self): self.user = BrokenUser def tearDown(self): self.user = None def test_get_username(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_username(User('foobar')) def test_user_model(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.user_model() def test_username_max_length(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.username_max_length() def test_get_user(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_user(1) def test_get_social_auth(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_social_auth('foo', 1) def test_get_social_auth_for_user(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.get_social_auth_for_user(User('foobar')) def test_create_social_auth(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.create_social_auth(User('foobar'), 1, 'foo') def test_disconnect(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.user.disconnect(BrokenUser()) class BrokenAssociationTests(unittest.TestCase): def setUp(self): self.association = BrokenAssociation def tearDown(self): self.association = None def test_store(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.store('http://foobar.com', BrokenAssociation()) def test_get(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.get() def test_remove(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.association.remove([1, 2, 3]) class BrokenNonceTests(unittest.TestCase): def setUp(self): self.nonce = BrokenNonce def tearDown(self): self.nonce = None def test_use(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.nonce.use('http://foobar.com', 1364951922, 'foobar123') class BrokenCodeTest(unittest.TestCase): def setUp(self): self.code = BrokenCode def tearDown(self): self.code = None def test_get_code(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.code.get_code('foobar') class BrokenStrategyTests(unittest.TestCase): def setUp(self): self.strategy = BrokenStrategy(storage=BrokenStorage) def tearDown(self): self.strategy = None def test_redirect(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.redirect('http://foobar.com') def test_get_setting(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.get_setting('foobar') def test_html(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.html('

    foobar

    ') def test_request_data(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.request_data() def test_request_host(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.request_host() def test_session_get(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_get('foobar') def test_session_set(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_set('foobar', 123) def test_session_pop(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.session_pop('foobar') def test_build_absolute_uri(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.build_absolute_uri('/foobar') def test_render_html_with_tpl(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.render_html('foobar.html', context={}) def test_render_html_with_html(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.render_html(html='

    foobar

    ', context={}) def test_render_html_with_none(self): with self.assertRaisesRegexp(ValueError, 'Missing template or html parameters'): self.strategy.render_html() def test_is_integrity_error(self): with self.assertRaisesRegexp(NotImplementedError, NOT_IMPLEMENTED_MSG): self.strategy.storage.is_integrity_error(None) def test_random_string(self): self.assertTrue(isinstance(self.strategy.random_string(), six.string_types)) def test_random_string_without_systemrandom(self): def SystemRandom(): raise NotImplementedError() orig_random = getattr(random, 'SystemRandom', None) random.SystemRandom = SystemRandom strategy = BrokenStrategyWithSettings(storage=BrokenStorage) self.assertTrue(isinstance(strategy.random_string(), six.string_types)) random.SystemRandom = orig_random python-social-auth-0.2.13/social/tests/test_utils.py000066400000000000000000000116321260133235600225160ustar00rootroot00000000000000import sys import unittest2 as unittest from mock import Mock from social.utils import sanitize_redirect, user_is_authenticated, \ user_is_active, slugify, build_absolute_uri, \ partial_pipeline_data PY3 = sys.version_info[0] == 3 class SanitizeRedirectTest(unittest.TestCase): def test_none_redirect(self): self.assertEqual(sanitize_redirect('myapp.com', None), None) def test_empty_redirect(self): self.assertEqual(sanitize_redirect('myapp.com', ''), None) def test_dict_redirect(self): self.assertEqual(sanitize_redirect('myapp.com', {}), None) def test_invalid_redirect(self): self.assertEqual(sanitize_redirect('myapp.com', {'foo': 'bar'}), None) def test_wrong_path_redirect(self): self.assertEqual( sanitize_redirect('myapp.com', 'http://notmyapp.com/path/'), None ) def test_valid_absolute_redirect(self): self.assertEqual( sanitize_redirect('myapp.com', 'http://myapp.com/path/'), 'http://myapp.com/path/' ) def test_valid_relative_redirect(self): self.assertEqual(sanitize_redirect('myapp.com', '/path/'), '/path/') class UserIsAuthenticatedTest(unittest.TestCase): def test_user_is_none(self): self.assertEqual(user_is_authenticated(None), False) def test_user_is_not_none(self): self.assertEqual(user_is_authenticated(object()), True) def test_user_has_is_authenticated(self): class User(object): is_authenticated = True self.assertEqual(user_is_authenticated(User()), True) def test_user_has_is_authenticated_callable(self): class User(object): def is_authenticated(self): return True self.assertEqual(user_is_authenticated(User()), True) class UserIsActiveTest(unittest.TestCase): def test_user_is_none(self): self.assertEqual(user_is_active(None), False) def test_user_is_not_none(self): self.assertEqual(user_is_active(object()), True) def test_user_has_is_active(self): class User(object): is_active = True self.assertEqual(user_is_active(User()), True) def test_user_has_is_active_callable(self): class User(object): def is_active(self): return True self.assertEqual(user_is_active(User()), True) class SlugifyTest(unittest.TestCase): def test_slugify_formats(self): if PY3: self.assertEqual(slugify('FooBar'), 'foobar') self.assertEqual(slugify('Foo Bar'), 'foo-bar') self.assertEqual(slugify('Foo (Bar)'), 'foo-bar') else: self.assertEqual(slugify('FooBar'.decode('utf-8')), 'foobar') self.assertEqual(slugify('Foo Bar'.decode('utf-8')), 'foo-bar') self.assertEqual(slugify('Foo (Bar)'.decode('utf-8')), 'foo-bar') class BuildAbsoluteURITest(unittest.TestCase): def setUp(self): self.host = 'http://foobar.com' def tearDown(self): self.host = None def test_path_none(self): self.assertEqual(build_absolute_uri(self.host), self.host) def test_path_empty(self): self.assertEqual(build_absolute_uri(self.host, ''), self.host) def test_path_http(self): self.assertEqual(build_absolute_uri(self.host, 'http://barfoo.com'), 'http://barfoo.com') def test_path_https(self): self.assertEqual(build_absolute_uri(self.host, 'https://barfoo.com'), 'https://barfoo.com') def test_host_ends_with_slash_and_path_starts_with_slash(self): self.assertEqual(build_absolute_uri(self.host + '/', '/foo/bar'), 'http://foobar.com/foo/bar') def test_absolute_uri(self): self.assertEqual(build_absolute_uri(self.host, '/foo/bar'), 'http://foobar.com/foo/bar') class PartialPipelineData(unittest.TestCase): def test_kwargs_included_in_result(self): backend = self._backend() key, val = ('foo', 'bar') _, xkwargs = partial_pipeline_data(backend, None, *(), **dict([(key, val)])) self.assertTrue(key in xkwargs) self.assertEqual(xkwargs[key], val) def test_update_user(self): user = object() backend = self._backend(session_kwargs={'user': None}) _, xkwargs = partial_pipeline_data(backend, user) self.assertTrue('user' in xkwargs) self.assertEqual(xkwargs['user'], user) def _backend(self, session_kwargs=None): strategy = Mock() strategy.request = None strategy.session_get.return_value = object() strategy.partial_from_session.return_value = \ (0, 'mock-backend', [], session_kwargs or {}) backend = Mock() backend.name = 'mock-backend' backend.strategy = strategy return backend python-social-auth-0.2.13/social/utils.py000066400000000000000000000161061260133235600203160ustar00rootroot00000000000000import re import sys import unicodedata import collections import functools import logging import six import requests import social from requests.adapters import HTTPAdapter from requests.packages.urllib3.poolmanager import PoolManager from social.exceptions import AuthCanceled, AuthUnreachableProvider from social.p3 import urlparse, urlunparse, urlencode, \ parse_qs as battery_parse_qs SETTING_PREFIX = 'SOCIAL_AUTH' social_logger = logging.Logger('social') class SSLHttpAdapter(HTTPAdapter): """" Transport adapter that allows to use any SSL protocol. Based on: http://requests.rtfd.org/latest/user/advanced/#example-specific-ssl-version """ def __init__(self, ssl_protocol): self.ssl_protocol = ssl_protocol super(SSLHttpAdapter, self).__init__() def init_poolmanager(self, connections, maxsize, block=False): self.poolmanager = PoolManager( num_pools=connections, maxsize=maxsize, block=block, ssl_version=self.ssl_protocol ) @classmethod def ssl_adapter_session(cls, ssl_protocol): session = requests.Session() session.mount('https://', SSLHttpAdapter(ssl_protocol)) return session def import_module(name): __import__(name) return sys.modules[name] def module_member(name): mod, member = name.rsplit('.', 1) module = import_module(mod) return getattr(module, member) def user_agent(): """Builds a simple User-Agent string to send in requests""" return 'python-social-auth-' + social.__version__ def url_add_parameters(url, params): """Adds parameters to URL, parameter will be repeated if already present""" if params: fragments = list(urlparse(url)) value = parse_qs(fragments[4]) value.update(params) fragments[4] = urlencode(value) url = urlunparse(fragments) return url def to_setting_name(*names): return '_'.join([name.upper().replace('-', '_') for name in names if name]) def setting_name(*names): return to_setting_name(*((SETTING_PREFIX,) + names)) def sanitize_redirect(host, redirect_to): """ Given the hostname and an untrusted URL to redirect to, this method tests it to make sure it isn't garbage/harmful and returns it, else returns None, similar as how's it done on django.contrib.auth.views. """ if redirect_to: try: # Don't redirect to a different host netloc = urlparse(redirect_to)[1] or host except (TypeError, AttributeError): pass else: if netloc == host: return redirect_to def user_is_authenticated(user): if user and hasattr(user, 'is_authenticated'): if isinstance(user.is_authenticated, collections.Callable): authenticated = user.is_authenticated() else: authenticated = user.is_authenticated elif user: authenticated = True else: authenticated = False return authenticated def user_is_active(user): if user and hasattr(user, 'is_active'): if isinstance(user.is_active, collections.Callable): is_active = user.is_active() else: is_active = user.is_active elif user: is_active = True else: is_active = False return is_active # This slugify version was borrowed from django revision a61dbd6 def slugify(value): """Converts to lowercase, removes non-word characters (alphanumerics and underscores) and converts spaces to hyphens. Also strips leading and trailing whitespace.""" value = unicodedata.normalize('NFKD', value) \ .encode('ascii', 'ignore') \ .decode('ascii') value = re.sub('[^\w\s-]', '', value).strip().lower() return re.sub('[-\s]+', '-', value) def first(func, items): """Return the first item in the list for what func returns True""" for item in items: if func(item): return item def parse_qs(value): """Like urlparse.parse_qs but transform list values to single items""" return drop_lists(battery_parse_qs(value)) def drop_lists(value): out = {} for key, val in value.items(): val = val[0] if isinstance(key, six.binary_type): key = six.text_type(key, 'utf-8') if isinstance(val, six.binary_type): val = six.text_type(val, 'utf-8') out[key] = val return out def partial_pipeline_data(backend, user=None, *args, **kwargs): partial = backend.strategy.session_get('partial_pipeline', None) if partial: idx, backend_name, xargs, xkwargs = \ backend.strategy.partial_from_session(partial) if backend_name == backend.name: kwargs.setdefault('pipeline_index', idx) if user: # don't update user if it's None kwargs.setdefault('user', user) kwargs.setdefault('request', backend.strategy.request_data()) xkwargs.update(kwargs) return xargs, xkwargs else: backend.strategy.clean_partial_pipeline() def build_absolute_uri(host_url, path=None): """Build absolute URI with given (optional) path""" path = path or '' if path.startswith('http://') or path.startswith('https://'): return path if host_url.endswith('/') and path.startswith('/'): path = path[1:] return host_url + path def constant_time_compare(val1, val2): """ Returns True if the two strings are equal, False otherwise. The time taken is independent of the number of characters that match. This code was borrowed from Django 1.5.4-final """ if len(val1) != len(val2): return False result = 0 if six.PY3 and isinstance(val1, bytes) and isinstance(val2, bytes): for x, y in zip(val1, val2): result |= x ^ y else: for x, y in zip(val1, val2): result |= ord(x) ^ ord(y) return result == 0 def is_url(value): return value and \ (value.startswith('http://') or value.startswith('https://') or value.startswith('/')) def setting_url(backend, *names): for name in names: if is_url(name): return name else: value = backend.setting(name) if is_url(value): return value def handle_http_errors(func): @functools.wraps(func) def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except requests.HTTPError as err: if err.response.status_code == 400: raise AuthCanceled(args[0]) elif err.response.status_code == 503: raise AuthUnreachableProvider(args[0]) else: raise return wrapper def append_slash(url): """Make sure we append a slash at the end of the URL otherwise we have issues with urljoin Example: >>> urlparse.urljoin('http://www.example.com/api/v3', 'user/1/') 'http://www.example.com/api/user/1/' """ if url and not url.endswith('/'): url = '{0}/'.format(url) return url python-social-auth-0.2.13/test_requirements.txt000066400000000000000000000000671260133235600216540ustar00rootroot00000000000000mock==1.0.1 freeze==0.8.0 sure==1.2.7 httpretty==0.8.3 python-social-auth-0.2.13/tox.ini000066400000000000000000000012431260133235600166410ustar00rootroot00000000000000# Tox (http://tox.testrun.org/) is a tool for running tests # in multiple virtualenvs. This configuration file will run the # test suite on all supported python versions. To use it, "pip install tox" # and then run "tox" from this directory. [tox] envlist = py26, py27, py33, py34, pypy, doc [testenv] commands = nosetests --where=social/tests --stop deps = -r{toxinidir}/social/tests/requirements.txt [testenv:py33] deps = -r{toxinidir}/social/tests/requirements-python3.txt [testenv:py34] deps = -r{toxinidir}/social/tests/requirements-python3.txt [testenv:doc] changedir = docs deps = sphinx commands = sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html