From 97d35e633ce84051c19e89e2ec4c469817bc0c21 Mon Sep 17 00:00:00 2001 From: Karthik Pullela Date: Sun, 21 Jan 2018 10:46:18 -0800 Subject: [PATCH] Integrated my history into data --- flex/bin/pyjwt | 11 + flex/flex/db.sqlite3 | Bin 131072 -> 192512 bytes .../flex/__pycache__/settings.cpython-36.pyc | Bin 2317 -> 2862 bytes .../flex/flex/__pycache__/urls.cpython-36.pyc | Bin 1144 -> 1210 bytes flex/flex/flex/settings.py | 14 + flex/flex/flex/urls.py | 3 +- .../flex/home/__pycache__/urls.cpython-36.pyc | Bin 386 -> 420 bytes .../home/__pycache__/views.cpython-36.pyc | Bin 2253 -> 2582 bytes flex/flex/home/static/home/main.css | 11 + flex/flex/home/templates/home/dashboard.html | 30 +- flex/flex/home/templates/home/home.html | 7 +- flex/flex/home/urls.py | 1 + flex/flex/home/views.py | 9 + .../PyJWT-1.5.3.dist-info/DESCRIPTION.rst | 76 + .../PyJWT-1.5.3.dist-info/INSTALLER | 1 + .../PyJWT-1.5.3.dist-info/METADATA | 110 + .../PyJWT-1.5.3.dist-info/RECORD | 33 + .../site-packages/PyJWT-1.5.3.dist-info/WHEEL | 6 + .../PyJWT-1.5.3.dist-info/entry_points.txt | 3 + .../PyJWT-1.5.3.dist-info/metadata.json | 1 + .../PyJWT-1.5.3.dist-info/top_level.txt | 1 + .../__pycache__/six.cpython-36.pyc | Bin 0 -> 25081 bytes .../DESCRIPTION.rst | 851 ++++++++ .../defusedxml-0.5.0.dist-info/INSTALLER | 1 + .../defusedxml-0.5.0.dist-info/METADATA | 875 ++++++++ .../defusedxml-0.5.0.dist-info/RECORD | 29 + .../defusedxml-0.5.0.dist-info/WHEEL | 6 + .../defusedxml-0.5.0.dist-info/metadata.json | 1 + .../defusedxml-0.5.0.dist-info/top_level.txt | 1 + .../site-packages/defusedxml/ElementTree.py | 112 + .../site-packages/defusedxml/__init__.py | 45 + .../__pycache__/ElementTree.cpython-36.pyc | Bin 0 -> 3085 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1248 bytes .../__pycache__/cElementTree.cpython-36.pyc | Bin 0 -> 806 bytes .../__pycache__/common.cpython-36.pyc | Bin 0 -> 4376 bytes .../__pycache__/expatbuilder.cpython-36.pyc | Bin 0 -> 3677 bytes .../__pycache__/expatreader.cpython-36.pyc | Bin 0 -> 2423 bytes .../__pycache__/lxml.cpython-36.pyc | Bin 0 -> 4913 bytes .../__pycache__/minidom.cpython-36.pyc | Bin 0 -> 1158 bytes .../__pycache__/pulldom.cpython-36.pyc | Bin 0 -> 933 bytes .../defusedxml/__pycache__/sax.cpython-36.pyc | Bin 0 -> 1295 bytes .../__pycache__/xmlrpc.cpython-36.pyc | Bin 0 -> 4649 bytes .../site-packages/defusedxml/cElementTree.py | 30 + .../site-packages/defusedxml/common.py | 120 + .../site-packages/defusedxml/expatbuilder.py | 110 + .../site-packages/defusedxml/expatreader.py | 59 + .../site-packages/defusedxml/lxml.py | 153 ++ .../site-packages/defusedxml/minidom.py | 42 + .../site-packages/defusedxml/pulldom.py | 34 + .../python3.6/site-packages/defusedxml/sax.py | 49 + .../site-packages/defusedxml/xmlrpc.py | 157 ++ .../urls/__pycache__/__init__.cpython-36.pyc | Bin 630 -> 625 bytes .../__pycache__/http.cpython-36.pyc | Bin 1725 -> 1720 bytes .../views/__pycache__/defaults.cpython-36.pyc | Bin 2986 -> 2981 bytes .../__pycache__/http.cpython-36.pyc | Bin 4435 -> 4430 bytes .../python3.6/site-packages/jwt/__init__.py | 29 + .../python3.6/site-packages/jwt/__main__.py | 162 ++ .../jwt/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1075 bytes .../jwt/__pycache__/__main__.cpython-36.pyc | Bin 0 -> 3516 bytes .../jwt/__pycache__/algorithms.cpython-36.pyc | Bin 0 -> 11163 bytes .../jwt/__pycache__/api_jws.cpython-36.pyc | Bin 0 -> 6163 bytes .../jwt/__pycache__/api_jwt.cpython-36.pyc | Bin 0 -> 5631 bytes .../jwt/__pycache__/compat.cpython-36.pyc | Bin 0 -> 1726 bytes .../jwt/__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 2024 bytes .../jwt/__pycache__/utils.cpython-36.pyc | Bin 0 -> 3184 bytes .../python3.6/site-packages/jwt/algorithms.py | 403 ++++ .../python3.6/site-packages/jwt/api_jws.py | 226 ++ .../python3.6/site-packages/jwt/api_jwt.py | 199 ++ .../lib/python3.6/site-packages/jwt/compat.py | 76 + .../site-packages/jwt/contrib/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 188 bytes .../jwt/contrib/algorithms/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 199 bytes .../__pycache__/py_ecdsa.cpython-36.pyc | Bin 0 -> 1820 bytes .../__pycache__/pycrypto.cpython-36.pyc | Bin 0 -> 1762 bytes .../jwt/contrib/algorithms/py_ecdsa.py | 60 + .../jwt/contrib/algorithms/pycrypto.py | 47 + .../python3.6/site-packages/jwt/exceptions.py | 48 + flex/lib/python3.6/site-packages/jwt/utils.py | 113 + .../oauthlib-2.0.6.dist-info/DESCRIPTION.rst | 103 + .../oauthlib-2.0.6.dist-info/INSTALLER | 1 + .../oauthlib-2.0.6.dist-info/METADATA | 144 ++ .../oauthlib-2.0.6.dist-info/RECORD | 103 + .../oauthlib-2.0.6.dist-info/WHEEL | 6 + .../oauthlib-2.0.6.dist-info/metadata.json | 1 + .../oauthlib-2.0.6.dist-info/top_level.txt | 1 + .../site-packages/oauthlib/__init__.py | 25 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 905 bytes .../__pycache__/common.cpython-36.pyc | Bin 0 -> 13480 bytes .../__pycache__/signals.cpython-36.pyc | Bin 0 -> 1926 bytes .../__pycache__/uri_validate.cpython-36.pyc | Bin 0 -> 4663 bytes .../site-packages/oauthlib/common.py | 460 ++++ .../site-packages/oauthlib/oauth1/__init__.py | 19 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1162 bytes .../oauthlib/oauth1/rfc5849/__init__.py | 328 +++ .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 10058 bytes .../rfc5849/__pycache__/errors.cpython-36.pyc | Bin 0 -> 3324 bytes .../__pycache__/parameters.cpython-36.pyc | Bin 0 -> 3437 bytes .../request_validator.cpython-36.pyc | Bin 0 -> 32995 bytes .../__pycache__/signature.cpython-36.pyc | Bin 0 -> 16245 bytes .../rfc5849/__pycache__/utils.cpython-36.pyc | Bin 0 -> 2972 bytes .../oauth1/rfc5849/endpoints/__init__.py | 9 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 650 bytes .../__pycache__/access_token.cpython-36.pyc | Bin 0 -> 6309 bytes .../__pycache__/authorization.cpython-36.pyc | Bin 0 -> 6280 bytes .../endpoints/__pycache__/base.cpython-36.pyc | Bin 0 -> 5653 bytes .../__pycache__/pre_configured.cpython-36.pyc | Bin 0 -> 829 bytes .../__pycache__/request_token.cpython-36.pyc | Bin 0 -> 6134 bytes .../__pycache__/resource.cpython-36.pyc | Bin 0 -> 4130 bytes .../__pycache__/signature_only.cpython-36.pyc | Bin 0 -> 2365 bytes .../oauth1/rfc5849/endpoints/access_token.py | 215 ++ .../oauth1/rfc5849/endpoints/authorization.py | 162 ++ .../oauthlib/oauth1/rfc5849/endpoints/base.py | 213 ++ .../rfc5849/endpoints/pre_configured.py | 14 + .../oauth1/rfc5849/endpoints/request_token.py | 209 ++ .../oauth1/rfc5849/endpoints/resource.py | 165 ++ .../rfc5849/endpoints/signature_only.py | 84 + .../oauthlib/oauth1/rfc5849/errors.py | 79 + .../oauthlib/oauth1/rfc5849/parameters.py | 139 ++ .../oauth1/rfc5849/request_validator.py | 833 +++++++ .../oauthlib/oauth1/rfc5849/signature.py | 627 ++++++ .../oauthlib/oauth1/rfc5849/utils.py | 90 + .../site-packages/oauthlib/oauth2/__init__.py | 34 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2553 bytes .../oauthlib/oauth2/rfc6749/__init__.py | 66 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 2195 bytes .../rfc6749/__pycache__/errors.cpython-36.pyc | Bin 0 -> 16131 bytes .../__pycache__/parameters.cpython-36.pyc | Bin 0 -> 14508 bytes .../request_validator.cpython-36.pyc | Bin 0 -> 26382 bytes .../rfc6749/__pycache__/tokens.cpython-36.pyc | Bin 0 -> 9003 bytes .../rfc6749/__pycache__/utils.cpython-36.pyc | Bin 0 -> 3081 bytes .../oauth2/rfc6749/clients/__init__.py | 16 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 870 bytes .../backend_application.cpython-36.pyc | Bin 0 -> 2907 bytes .../clients/__pycache__/base.cpython-36.pyc | Bin 0 -> 18407 bytes .../legacy_application.cpython-36.pyc | Bin 0 -> 3807 bytes .../mobile_application.cpython-36.pyc | Bin 0 -> 9169 bytes .../service_application.cpython-36.pyc | Bin 0 -> 6526 bytes .../web_application.cpython-36.pyc | Bin 0 -> 9570 bytes .../rfc6749/clients/backend_application.py | 60 + .../oauthlib/oauth2/rfc6749/clients/base.py | 488 +++++ .../rfc6749/clients/legacy_application.py | 72 + .../rfc6749/clients/mobile_application.py | 172 ++ .../rfc6749/clients/service_application.py | 175 ++ .../oauth2/rfc6749/clients/web_application.py | 176 ++ .../oauth2/rfc6749/endpoints/__init__.py | 19 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 897 bytes .../__pycache__/authorization.cpython-36.pyc | Bin 0 -> 4741 bytes .../endpoints/__pycache__/base.cpython-36.pyc | Bin 0 -> 2189 bytes .../__pycache__/pre_configured.cpython-36.pyc | Bin 0 -> 8581 bytes .../__pycache__/resource.cpython-36.pyc | Bin 0 -> 3913 bytes .../__pycache__/revocation.cpython-36.pyc | Bin 0 -> 5350 bytes .../__pycache__/token.cpython-36.pyc | Bin 0 -> 4163 bytes .../oauth2/rfc6749/endpoints/authorization.py | 117 + .../oauthlib/oauth2/rfc6749/endpoints/base.py | 65 + .../rfc6749/endpoints/pre_configured.py | 223 ++ .../oauth2/rfc6749/endpoints/resource.py | 87 + .../oauth2/rfc6749/endpoints/revocation.py | 134 ++ .../oauth2/rfc6749/endpoints/token.py | 117 + .../oauthlib/oauth2/rfc6749/errors.py | 415 ++++ .../oauth2/rfc6749/grant_types/__init__.py | 18 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 983 bytes .../authorization_code.cpython-36.pyc | Bin 0 -> 15322 bytes .../__pycache__/base.cpython-36.pyc | Bin 0 -> 7214 bytes .../client_credentials.cpython-36.pyc | Bin 0 -> 4628 bytes .../__pycache__/implicit.cpython-36.pyc | Bin 0 -> 12803 bytes .../__pycache__/openid_connect.cpython-36.pyc | Bin 0 -> 16654 bytes .../__pycache__/refresh_token.cpython-36.pyc | Bin 0 -> 4358 bytes ..._owner_password_credentials.cpython-36.pyc | Bin 0 -> 7625 bytes .../rfc6749/grant_types/authorization_code.py | 430 ++++ .../oauth2/rfc6749/grant_types/base.py | 185 ++ .../rfc6749/grant_types/client_credentials.py | 120 + .../oauth2/rfc6749/grant_types/implicit.py | 382 ++++ .../rfc6749/grant_types/openid_connect.py | 390 ++++ .../rfc6749/grant_types/refresh_token.py | 131 ++ .../resource_owner_password_credentials.py | 197 ++ .../oauthlib/oauth2/rfc6749/parameters.py | 409 ++++ .../oauth2/rfc6749/request_validator.py | 573 +++++ .../oauthlib/oauth2/rfc6749/tokens.py | 305 +++ .../oauthlib/oauth2/rfc6749/utils.py | 94 + .../site-packages/oauthlib/signals.py | 41 + .../site-packages/oauthlib/uri_validate.py | 216 ++ .../site-packages/openid/__init__.py | 48 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1639 bytes .../__pycache__/association.cpython-36.pyc | Bin 0 -> 16973 bytes .../__pycache__/codecutil.cpython-36.pyc | Bin 0 -> 2270 bytes .../__pycache__/cryptutil.cpython-36.pyc | Bin 0 -> 4104 bytes .../openid/__pycache__/dh.cpython-36.pyc | Bin 0 -> 2213 bytes .../__pycache__/extension.cpython-36.pyc | Bin 0 -> 1917 bytes .../__pycache__/fetchers.cpython-36.pyc | Bin 0 -> 13075 bytes .../openid/__pycache__/kvform.cpython-36.pyc | Bin 0 -> 3137 bytes .../openid/__pycache__/message.cpython-36.pyc | Bin 0 -> 17837 bytes .../openid/__pycache__/oidutil.cpython-36.pyc | Bin 0 -> 6633 bytes .../openid/__pycache__/sreg.cpython-36.pyc | Bin 0 -> 389 bytes .../openid/__pycache__/urinorm.cpython-36.pyc | Bin 0 -> 3112 bytes .../site-packages/openid/association.py | 562 +++++ .../site-packages/openid/codecutil.py | 91 + .../site-packages/openid/consumer/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 351 bytes .../__pycache__/consumer.cpython-36.pyc | Bin 0 -> 61329 bytes .../__pycache__/discover.cpython-36.pyc | Bin 0 -> 12627 bytes .../__pycache__/html_parse.cpython-36.pyc | Bin 0 -> 7065 bytes .../site-packages/openid/consumer/consumer.py | 1924 +++++++++++++++++ .../site-packages/openid/consumer/discover.py | 465 ++++ .../openid/consumer/html_parse.py | 278 +++ .../site-packages/openid/cryptutil.py | 154 ++ flex/lib/python3.6/site-packages/openid/dh.py | 47 + .../site-packages/openid/extension.py | 51 + .../openid/extensions/__init__.py | 5 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 340 bytes .../extensions/__pycache__/ax.cpython-36.pyc | Bin 0 -> 22920 bytes .../__pycache__/sreg.cpython-36.pyc | Bin 0 -> 16438 bytes .../site-packages/openid/extensions/ax.py | 781 +++++++ .../openid/extensions/draft/__init__.py | 0 .../draft/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 209 bytes .../draft/__pycache__/pape2.cpython-36.pyc | Bin 0 -> 8114 bytes .../draft/__pycache__/pape5.cpython-36.pyc | Bin 0 -> 13489 bytes .../openid/extensions/draft/pape2.py | 285 +++ .../openid/extensions/draft/pape5.py | 481 +++++ .../site-packages/openid/extensions/sreg.py | 527 +++++ .../site-packages/openid/fetchers.py | 493 +++++ .../python3.6/site-packages/openid/kvform.py | 132 ++ .../python3.6/site-packages/openid/message.py | 678 ++++++ .../python3.6/site-packages/openid/oidutil.py | 236 ++ .../site-packages/openid/server/__init__.py | 6 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 376 bytes .../server/__pycache__/server.cpython-36.pyc | Bin 0 -> 52860 bytes .../__pycache__/trustroot.cpython-36.pyc | Bin 0 -> 11347 bytes .../site-packages/openid/server/server.py | 1822 ++++++++++++++++ .../site-packages/openid/server/trustroot.py | 454 ++++ .../python3.6/site-packages/openid/sreg.py | 7 + .../site-packages/openid/store/__init__.py | 8 + .../store/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 421 bytes .../__pycache__/filestore.cpython-36.pyc | Bin 0 -> 9933 bytes .../__pycache__/interface.cpython-36.pyc | Bin 0 -> 7824 bytes .../store/__pycache__/memstore.cpython-36.pyc | Bin 0 -> 4120 bytes .../store/__pycache__/nonce.cpython-36.pyc | Bin 0 -> 2593 bytes .../store/__pycache__/sqlstore.cpython-36.pyc | Bin 0 -> 15005 bytes .../site-packages/openid/store/filestore.py | 397 ++++ .../site-packages/openid/store/interface.py | 198 ++ .../site-packages/openid/store/memstore.py | 126 ++ .../site-packages/openid/store/nonce.py | 101 + .../site-packages/openid/store/sqlstore.py | 509 +++++ .../python3.6/site-packages/openid/urinorm.py | 161 ++ .../site-packages/openid/yadis/__init__.py | 17 + .../yadis/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 522 bytes .../yadis/__pycache__/accept.cpython-36.pyc | Bin 0 -> 3648 bytes .../__pycache__/constants.cpython-36.pyc | Bin 0 -> 519 bytes .../yadis/__pycache__/discover.cpython-36.pyc | Bin 0 -> 3944 bytes .../yadis/__pycache__/etxrd.cpython-36.pyc | Bin 0 -> 8150 bytes .../yadis/__pycache__/filters.cpython-36.pyc | Bin 0 -> 6683 bytes .../yadis/__pycache__/manager.cpython-36.pyc | Bin 0 -> 6286 bytes .../__pycache__/parsehtml.cpython-36.pyc | Bin 0 -> 5192 bytes .../yadis/__pycache__/services.cpython-36.pyc | Bin 0 -> 1961 bytes .../yadis/__pycache__/xri.cpython-36.pyc | Bin 0 -> 3356 bytes .../yadis/__pycache__/xrires.cpython-36.pyc | Bin 0 -> 3027 bytes .../site-packages/openid/yadis/accept.py | 137 ++ .../site-packages/openid/yadis/constants.py | 12 + .../site-packages/openid/yadis/discover.py | 169 ++ .../site-packages/openid/yadis/etxrd.py | 318 +++ .../site-packages/openid/yadis/filters.py | 210 ++ .../site-packages/openid/yadis/manager.py | 195 ++ .../site-packages/openid/yadis/parsehtml.py | 207 ++ .../site-packages/openid/yadis/services.py | 56 + .../site-packages/openid/yadis/xri.py | 122 ++ .../site-packages/openid/yadis/xrires.py | 123 ++ .../DESCRIPTION.rst | 6 + .../python3_openid-3.1.0.dist-info/INSTALLER | 1 + .../python3_openid-3.1.0.dist-info/METADATA | 29 + .../python3_openid-3.1.0.dist-info/RECORD | 91 + .../python3_openid-3.1.0.dist-info/WHEEL | 5 + .../metadata.json | 1 + .../top_level.txt | 1 + .../DESCRIPTION.rst | 161 ++ .../INSTALLER | 1 + .../METADATA | 186 ++ .../requests_oauthlib-0.8.0.dist-info/RECORD | 33 + .../requests_oauthlib-0.8.0.dist-info/WHEEL | 6 + .../metadata.json | 1 + .../top_level.txt | 1 + .../requests_oauthlib/__init__.py | 22 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 1069 bytes .../__pycache__/oauth1_auth.cpython-36.pyc | Bin 0 -> 2537 bytes .../__pycache__/oauth1_session.cpython-36.pyc | Bin 0 -> 16009 bytes .../__pycache__/oauth2_auth.cpython-36.pyc | Bin 0 -> 1891 bytes .../__pycache__/oauth2_session.cpython-36.pyc | Bin 0 -> 14163 bytes .../compliance_fixes/__init__.py | 8 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 600 bytes .../__pycache__/douban.cpython-36.pyc | Bin 0 -> 822 bytes .../__pycache__/facebook.cpython-36.pyc | Bin 0 -> 1099 bytes .../__pycache__/fitbit.cpython-36.pyc | Bin 0 -> 1183 bytes .../__pycache__/linkedin.cpython-36.pyc | Bin 0 -> 1162 bytes .../__pycache__/mailchimp.cpython-36.pyc | Bin 0 -> 1072 bytes .../__pycache__/slack.cpython-36.pyc | Bin 0 -> 1005 bytes .../__pycache__/weibo.cpython-36.pyc | Bin 0 -> 820 bytes .../compliance_fixes/douban.py | 18 + .../compliance_fixes/facebook.py | 33 + .../compliance_fixes/fitbit.py | 26 + .../compliance_fixes/linkedin.py | 24 + .../compliance_fixes/mailchimp.py | 22 + .../compliance_fixes/slack.py | 37 + .../compliance_fixes/weibo.py | 17 + .../requests_oauthlib/oauth1_auth.py | 95 + .../requests_oauthlib/oauth1_session.py | 378 ++++ .../requests_oauthlib/oauth2_auth.py | 36 + .../requests_oauthlib/oauth2_session.py | 376 ++++ .../six-1.11.0.dist-info/DESCRIPTION.rst | 27 + .../six-1.11.0.dist-info/INSTALLER | 1 + .../six-1.11.0.dist-info/METADATA | 43 + .../site-packages/six-1.11.0.dist-info/RECORD | 9 + .../site-packages/six-1.11.0.dist-info/WHEEL | 6 + .../six-1.11.0.dist-info/metadata.json | 1 + .../six-1.11.0.dist-info/top_level.txt | 1 + flex/lib/python3.6/site-packages/six.py | 891 ++++++++ .../DESCRIPTION.rst | 53 + .../INSTALLER | 1 + .../METADATA | 75 + .../RECORD | 53 + .../WHEEL | 5 + .../metadata.json | 1 + .../top_level.txt | 1 + .../DESCRIPTION.rst | 45 + .../INSTALLER | 1 + .../social_auth_core-1.6.0.dist-info/METADATA | 82 + .../social_auth_core-1.6.0.dist-info/RECORD | 550 +++++ .../social_auth_core-1.6.0.dist-info/WHEEL | 5 + .../metadata.json | 1 + .../top_level.txt | 1 + .../site-packages/social_core/__init__.py | 1 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 220 bytes .../__pycache__/actions.cpython-36.pyc | Bin 0 -> 2715 bytes .../__pycache__/exceptions.cpython-36.pyc | Bin 0 -> 6411 bytes .../__pycache__/storage.cpython-36.pyc | Bin 0 -> 12004 bytes .../__pycache__/store.cpython-36.pyc | Bin 0 -> 3121 bytes .../__pycache__/strategy.cpython-36.pyc | Bin 0 -> 9685 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 9110 bytes .../site-packages/social_core/actions.py | 127 ++ .../social_core/backends/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 208 bytes .../__pycache__/amazon.cpython-36.pyc | Bin 0 -> 1672 bytes .../backends/__pycache__/angel.cpython-36.pyc | Bin 0 -> 1367 bytes .../backends/__pycache__/aol.cpython-36.pyc | Bin 0 -> 569 bytes .../__pycache__/appsfuel.cpython-36.pyc | Bin 0 -> 1817 bytes .../__pycache__/arcgis.cpython-36.pyc | Bin 0 -> 1328 bytes .../backends/__pycache__/asana.cpython-36.pyc | Bin 0 -> 1746 bytes .../__pycache__/azuread.cpython-36.pyc | Bin 0 -> 3490 bytes .../__pycache__/azuread_tenant.cpython-36.pyc | Bin 0 -> 3074 bytes .../backends/__pycache__/base.cpython-36.pyc | Bin 0 -> 8982 bytes .../__pycache__/battlenet.cpython-36.pyc | Bin 0 -> 1813 bytes .../backends/__pycache__/beats.cpython-36.pyc | Bin 0 -> 2391 bytes .../__pycache__/behance.cpython-36.pyc | Bin 0 -> 1683 bytes .../__pycache__/belgiumeid.cpython-36.pyc | Bin 0 -> 695 bytes .../__pycache__/bitbucket.cpython-36.pyc | Bin 0 -> 4200 bytes .../backends/__pycache__/box.cpython-36.pyc | Bin 0 -> 2239 bytes .../__pycache__/bungie.cpython-36.pyc | Bin 0 -> 3440 bytes .../__pycache__/changetip.cpython-36.pyc | Bin 0 -> 1240 bytes .../__pycache__/chatwork.cpython-36.pyc | Bin 0 -> 2535 bytes .../__pycache__/classlink.cpython-36.pyc | Bin 0 -> 1771 bytes .../backends/__pycache__/clef.cpython-36.pyc | Bin 0 -> 2026 bytes .../__pycache__/coding.cpython-36.pyc | Bin 0 -> 1827 bytes .../__pycache__/coinbase.cpython-36.pyc | Bin 0 -> 1700 bytes .../__pycache__/coursera.cpython-36.pyc | Bin 0 -> 1997 bytes .../__pycache__/dailymotion.cpython-36.pyc | Bin 0 -> 1312 bytes .../__pycache__/deezer.cpython-36.pyc | Bin 0 -> 2020 bytes .../__pycache__/digitalocean.cpython-36.pyc | Bin 0 -> 1761 bytes .../__pycache__/disqus.cpython-36.pyc | Bin 0 -> 2127 bytes .../__pycache__/docker.cpython-36.pyc | Bin 0 -> 1719 bytes .../__pycache__/douban.cpython-36.pyc | Bin 0 -> 2564 bytes .../__pycache__/dribbble.cpython-36.pyc | Bin 0 -> 2226 bytes .../backends/__pycache__/drip.cpython-36.pyc | Bin 0 -> 1327 bytes .../__pycache__/dropbox.cpython-36.pyc | Bin 0 -> 4272 bytes .../__pycache__/echosign.cpython-36.pyc | Bin 0 -> 1377 bytes .../__pycache__/edmodo.cpython-36.pyc | Bin 0 -> 1363 bytes .../backends/__pycache__/email.cpython-36.pyc | Bin 0 -> 610 bytes .../__pycache__/eventbrite.cpython-36.pyc | Bin 0 -> 1526 bytes .../__pycache__/eveonline.cpython-36.pyc | Bin 0 -> 1661 bytes .../__pycache__/evernote.cpython-36.pyc | Bin 0 -> 2878 bytes .../__pycache__/exacttarget.cpython-36.pyc | Bin 0 -> 3848 bytes .../__pycache__/facebook.cpython-36.pyc | Bin 0 -> 7606 bytes .../__pycache__/fedora.cpython-36.pyc | Bin 0 -> 621 bytes .../__pycache__/fitbit.cpython-36.pyc | Bin 0 -> 2779 bytes .../five_hundred_px.cpython-36.pyc | Bin 0 -> 1475 bytes .../__pycache__/flickr.cpython-36.pyc | Bin 0 -> 1753 bytes .../__pycache__/foursquare.cpython-36.pyc | Bin 0 -> 1673 bytes .../backends/__pycache__/gae.cpython-36.pyc | Bin 0 -> 1784 bytes .../__pycache__/github.cpython-36.pyc | Bin 0 -> 4270 bytes .../github_enterprise.cpython-36.pyc | Bin 0 -> 2243 bytes .../__pycache__/gitlab.cpython-36.pyc | Bin 0 -> 2486 bytes .../__pycache__/goclio.cpython-36.pyc | Bin 0 -> 1465 bytes .../__pycache__/goclioeu.cpython-36.pyc | Bin 0 -> 821 bytes .../__pycache__/google.cpython-36.pyc | Bin 0 -> 6547 bytes .../google_openidconnect.cpython-36.pyc | Bin 0 -> 1034 bytes .../__pycache__/instagram.cpython-36.pyc | Bin 0 -> 2074 bytes .../__pycache__/itembase.cpython-36.pyc | Bin 0 -> 3441 bytes .../__pycache__/jawbone.cpython-36.pyc | Bin 0 -> 2853 bytes .../__pycache__/justgiving.cpython-36.pyc | Bin 0 -> 2229 bytes .../backends/__pycache__/kakao.cpython-36.pyc | Bin 0 -> 1746 bytes .../__pycache__/khanacademy.cpython-36.pyc | Bin 0 -> 4985 bytes .../__pycache__/lastfm.cpython-36.pyc | Bin 0 -> 2194 bytes .../__pycache__/launchpad.cpython-36.pyc | Bin 0 -> 545 bytes .../__pycache__/legacy.cpython-36.pyc | Bin 0 -> 1844 bytes .../backends/__pycache__/line.cpython-36.pyc | Bin 0 -> 3064 bytes .../__pycache__/linkedin.cpython-36.pyc | Bin 0 -> 3757 bytes .../backends/__pycache__/live.cpython-36.pyc | Bin 0 -> 1703 bytes .../__pycache__/livejournal.cpython-36.pyc | Bin 0 -> 1384 bytes .../__pycache__/loginradius.cpython-36.pyc | Bin 0 -> 3053 bytes .../backends/__pycache__/lyft.cpython-36.pyc | Bin 0 -> 2224 bytes .../__pycache__/mailchimp.cpython-36.pyc | Bin 0 -> 1455 bytes .../__pycache__/mailru.cpython-36.pyc | Bin 0 -> 2026 bytes .../__pycache__/mapmyfitness.cpython-36.pyc | Bin 0 -> 1929 bytes .../__pycache__/mediawiki.cpython-36.pyc | Bin 0 -> 4726 bytes .../__pycache__/meetup.cpython-36.pyc | Bin 0 -> 1477 bytes .../__pycache__/mendeley.cpython-36.pyc | Bin 0 -> 2851 bytes .../__pycache__/microsoft.cpython-36.pyc | Bin 0 -> 2865 bytes .../__pycache__/mineid.cpython-36.pyc | Bin 0 -> 1835 bytes .../__pycache__/mixcloud.cpython-36.pyc | Bin 0 -> 1266 bytes .../backends/__pycache__/monzo.cpython-36.pyc | Bin 0 -> 1232 bytes .../backends/__pycache__/moves.cpython-36.pyc | Bin 0 -> 1407 bytes .../__pycache__/nationbuilder.cpython-36.pyc | Bin 0 -> 2146 bytes .../backends/__pycache__/naver.cpython-36.pyc | Bin 0 -> 2209 bytes .../__pycache__/ngpvan.cpython-36.pyc | Bin 0 -> 2261 bytes .../backends/__pycache__/nk.cpython-36.pyc | Bin 0 -> 2611 bytes .../backends/__pycache__/oauth.cpython-36.pyc | Bin 0 -> 14284 bytes .../__pycache__/odnoklassniki.cpython-36.pyc | Bin 0 -> 6718 bytes .../__pycache__/open_id.cpython-36.pyc | Bin 0 -> 8878 bytes .../open_id_connect.cpython-36.pyc | Bin 0 -> 6866 bytes .../__pycache__/openshift.cpython-36.pyc | Bin 0 -> 1761 bytes .../__pycache__/openstreetmap.cpython-36.pyc | Bin 0 -> 2067 bytes .../backends/__pycache__/orbi.cpython-36.pyc | Bin 0 -> 1530 bytes .../backends/__pycache__/orcid.cpython-36.pyc | Bin 0 -> 2587 bytes .../__pycache__/patreon.cpython-36.pyc | Bin 0 -> 1763 bytes .../__pycache__/persona.cpython-36.pyc | Bin 0 -> 1968 bytes .../__pycache__/pinterest.cpython-36.pyc | Bin 0 -> 1584 bytes .../__pycache__/pixelpin.cpython-36.pyc | Bin 0 -> 1411 bytes .../__pycache__/pocket.cpython-36.pyc | Bin 0 -> 2125 bytes .../backends/__pycache__/podio.cpython-36.pyc | Bin 0 -> 1608 bytes .../__pycache__/professionali.cpython-36.pyc | Bin 0 -> 2234 bytes .../__pycache__/pushbullet.cpython-36.pyc | Bin 0 -> 1273 bytes .../backends/__pycache__/qiita.cpython-36.pyc | Bin 0 -> 2545 bytes .../backends/__pycache__/qq.cpython-36.pyc | Bin 0 -> 2291 bytes .../__pycache__/quizlet.cpython-36.pyc | Bin 0 -> 1076 bytes .../backends/__pycache__/rdio.cpython-36.pyc | Bin 0 -> 2702 bytes .../__pycache__/readability.cpython-36.pyc | Bin 0 -> 1547 bytes .../__pycache__/reddit.cpython-36.pyc | Bin 0 -> 2338 bytes .../__pycache__/runkeeper.cpython-36.pyc | Bin 0 -> 1935 bytes .../__pycache__/salesforce.cpython-36.pyc | Bin 0 -> 2131 bytes .../backends/__pycache__/saml.cpython-36.pyc | Bin 0 -> 11756 bytes .../__pycache__/shimmering.cpython-36.pyc | Bin 0 -> 1422 bytes .../__pycache__/shopify.cpython-36.pyc | Bin 0 -> 3217 bytes .../__pycache__/sketchfab.cpython-36.pyc | Bin 0 -> 1599 bytes .../__pycache__/skyrock.cpython-36.pyc | Bin 0 -> 1479 bytes .../backends/__pycache__/slack.cpython-36.pyc | Bin 0 -> 2082 bytes .../__pycache__/soundcloud.cpython-36.pyc | Bin 0 -> 2051 bytes .../__pycache__/spotify.cpython-36.pyc | Bin 0 -> 1866 bytes .../__pycache__/stackoverflow.cpython-36.pyc | Bin 0 -> 1812 bytes .../backends/__pycache__/steam.cpython-36.pyc | Bin 0 -> 1777 bytes .../__pycache__/stocktwits.cpython-36.pyc | Bin 0 -> 1701 bytes .../__pycache__/strava.cpython-36.pyc | Bin 0 -> 1886 bytes .../__pycache__/stripe.cpython-36.pyc | Bin 0 -> 1991 bytes .../backends/__pycache__/suse.cpython-36.pyc | Bin 0 -> 872 bytes .../__pycache__/taobao.cpython-36.pyc | Bin 0 -> 1230 bytes .../__pycache__/thisismyjam.cpython-36.pyc | Bin 0 -> 1532 bytes .../__pycache__/trello.cpython-36.pyc | Bin 0 -> 1751 bytes .../__pycache__/tripit.cpython-36.pyc | Bin 0 -> 1757 bytes .../__pycache__/tumblr.cpython-36.pyc | Bin 0 -> 1645 bytes .../__pycache__/twilio.cpython-36.pyc | Bin 0 -> 1702 bytes .../__pycache__/twitch.cpython-36.pyc | Bin 0 -> 1307 bytes .../__pycache__/twitter.cpython-36.pyc | Bin 0 -> 1883 bytes .../backends/__pycache__/uber.cpython-36.pyc | Bin 0 -> 1611 bytes .../__pycache__/ubuntu.cpython-36.pyc | Bin 0 -> 780 bytes .../backends/__pycache__/udata.cpython-36.pyc | Bin 0 -> 1591 bytes .../__pycache__/untappd.cpython-36.pyc | Bin 0 -> 3243 bytes .../__pycache__/upwork.cpython-36.pyc | Bin 0 -> 1612 bytes .../__pycache__/username.cpython-36.pyc | Bin 0 -> 591 bytes .../backends/__pycache__/utils.cpython-36.pyc | Bin 0 -> 3066 bytes .../backends/__pycache__/vend.cpython-36.pyc | Bin 0 -> 1558 bytes .../backends/__pycache__/vimeo.cpython-36.pyc | Bin 0 -> 3158 bytes .../backends/__pycache__/vk.cpython-36.pyc | Bin 0 -> 6561 bytes .../backends/__pycache__/weibo.cpython-36.pyc | Bin 0 -> 2105 bytes .../__pycache__/weixin.cpython-36.pyc | Bin 0 -> 5066 bytes .../__pycache__/withings.cpython-36.pyc | Bin 0 -> 931 bytes .../__pycache__/wunderlist.cpython-36.pyc | Bin 0 -> 1366 bytes .../backends/__pycache__/xing.cpython-36.pyc | Bin 0 -> 2401 bytes .../backends/__pycache__/yahoo.cpython-36.pyc | Bin 0 -> 6110 bytes .../__pycache__/yammer.cpython-36.pyc | Bin 0 -> 1794 bytes .../__pycache__/yandex.cpython-36.pyc | Bin 0 -> 3160 bytes .../__pycache__/zotero.cpython-36.pyc | Bin 0 -> 1330 bytes .../social_core/backends/amazon.py | 45 + .../social_core/backends/angel.py | 30 + .../site-packages/social_core/backends/aol.py | 10 + .../social_core/backends/appsfuel.py | 42 + .../social_core/backends/arcgis.py | 33 + .../social_core/backends/asana.py | 42 + .../social_core/backends/azuread.py | 122 ++ .../social_core/backends/azuread_tenant.py | 115 + .../social_core/backends/base.py | 247 +++ .../social_core/backends/battlenet.py | 50 + .../social_core/backends/beats.py | 65 + .../social_core/backends/behance.py | 40 + .../social_core/backends/belgiumeid.py | 11 + .../social_core/backends/bitbucket.py | 101 + .../site-packages/social_core/backends/box.py | 55 + .../social_core/backends/bungie.py | 90 + .../social_core/backends/changetip.py | 27 + .../social_core/backends/chatwork.py | 67 + .../social_core/backends/classlink.py | 44 + .../social_core/backends/clef.py | 54 + .../social_core/backends/coding.py | 48 + .../social_core/backends/coinbase.py | 37 + .../social_core/backends/coursera.py | 43 + .../social_core/backends/dailymotion.py | 24 + .../social_core/backends/deezer.py | 49 + .../social_core/backends/digitalocean.py | 41 + .../social_core/backends/disqus.py | 52 + .../social_core/backends/docker.py | 46 + .../social_core/backends/douban.py | 59 + .../social_core/backends/dribbble.py | 62 + .../social_core/backends/drip.py | 25 + .../social_core/backends/dropbox.py | 113 + .../social_core/backends/echosign.py | 24 + .../social_core/backends/edmodo.py | 34 + .../social_core/backends/email.py | 12 + .../social_core/backends/eventbrite.py | 29 + .../social_core/backends/eveonline.py | 42 + .../social_core/backends/evernote.py | 75 + .../social_core/backends/exacttarget.py | 104 + .../social_core/backends/facebook.py | 233 ++ .../social_core/backends/fedora.py | 11 + .../social_core/backends/fitbit.py | 67 + .../social_core/backends/five_hundred_px.py | 34 + .../social_core/backends/flickr.py | 43 + .../social_core/backends/foursquare.py | 36 + .../site-packages/social_core/backends/gae.py | 40 + .../social_core/backends/github.py | 119 + .../social_core/backends/github_enterprise.py | 42 + .../social_core/backends/gitlab.py | 58 + .../social_core/backends/goclio.py | 35 + .../social_core/backends/goclioeu.py | 14 + .../social_core/backends/google.py | 186 ++ .../backends/google_openidconnect.py | 21 + .../social_core/backends/instagram.py | 53 + .../social_core/backends/itembase.py | 86 + .../social_core/backends/jawbone.py | 77 + .../social_core/backends/justgiving.py | 56 + .../social_core/backends/kakao.py | 40 + .../social_core/backends/khanacademy.py | 126 ++ .../social_core/backends/lastfm.py | 59 + .../social_core/backends/launchpad.py | 11 + .../social_core/backends/legacy.py | 44 + .../social_core/backends/line.py | 94 + .../social_core/backends/linkedin.py | 98 + .../social_core/backends/live.py | 44 + .../social_core/backends/livejournal.py | 27 + .../social_core/backends/loginradius.py | 69 + .../social_core/backends/lyft.py | 58 + .../social_core/backends/mailchimp.py | 32 + .../social_core/backends/mailru.py | 46 + .../social_core/backends/mapmyfitness.py | 49 + .../social_core/backends/mediawiki.py | 182 ++ .../social_core/backends/meetup.py | 34 + .../social_core/backends/mendeley.py | 67 + .../social_core/backends/microsoft.py | 78 + .../social_core/backends/mineid.py | 38 + .../social_core/backends/mixcloud.py | 26 + .../social_core/backends/monzo.py | 32 + .../social_core/backends/moves.py | 30 + .../social_core/backends/nationbuilder.py | 48 + .../social_core/backends/naver.py | 60 + .../social_core/backends/ngpvan.py | 66 + .../site-packages/social_core/backends/nk.py | 76 + .../social_core/backends/oauth.py | 436 ++++ .../social_core/backends/odnoklassniki.py | 172 ++ .../social_core/backends/open_id.py | 263 +++ .../social_core/backends/open_id_connect.py | 197 ++ .../social_core/backends/openshift.py | 39 + .../social_core/backends/openstreetmap.py | 57 + .../social_core/backends/orbi.py | 42 + .../social_core/backends/orcid.py | 75 + .../social_core/backends/patreon.py | 41 + .../social_core/backends/persona.py | 50 + .../social_core/backends/pinterest.py | 46 + .../social_core/backends/pixelpin.py | 33 + .../social_core/backends/pocket.py | 45 + .../social_core/backends/podio.py | 38 + .../social_core/backends/professionali.py | 55 + .../social_core/backends/pushbullet.py | 23 + .../social_core/backends/qiita.py | 66 + .../site-packages/social_core/backends/qq.py | 71 + .../social_core/backends/quizlet.py | 23 + .../social_core/backends/rdio.py | 72 + .../social_core/backends/readability.py | 35 + .../social_core/backends/reddit.py | 56 + .../social_core/backends/runkeeper.py | 47 + .../social_core/backends/salesforce.py | 49 + .../social_core/backends/saml.py | 335 +++ .../social_core/backends/shimmering.py | 36 + .../social_core/backends/shopify.py | 92 + .../social_core/backends/sketchfab.py | 39 + .../social_core/backends/skyrock.py | 32 + .../social_core/backends/slack.py | 59 + .../social_core/backends/soundcloud.py | 56 + .../social_core/backends/spotify.py | 47 + .../social_core/backends/stackoverflow.py | 43 + .../social_core/backends/steam.py | 47 + .../social_core/backends/stocktwits.py | 37 + .../social_core/backends/strava.py | 46 + .../social_core/backends/stripe.py | 46 + .../social_core/backends/suse.py | 17 + .../social_core/backends/taobao.py | 26 + .../social_core/backends/thisismyjam.py | 33 + .../social_core/backends/trello.py | 47 + .../social_core/backends/tripit.py | 43 + .../social_core/backends/tumblr.py | 31 + .../social_core/backends/twilio.py | 40 + .../social_core/backends/twitch.py | 30 + .../social_core/backends/twitter.py | 41 + .../social_core/backends/uber.py | 39 + .../social_core/backends/ubuntu.py | 16 + .../social_core/backends/udata.py | 37 + .../social_core/backends/untappd.py | 111 + .../social_core/backends/upwork.py | 40 + .../social_core/backends/username.py | 11 + .../social_core/backends/utils.py | 82 + .../social_core/backends/vend.py | 39 + .../social_core/backends/vimeo.py | 79 + .../site-packages/social_core/backends/vk.py | 201 ++ .../social_core/backends/weibo.py | 61 + .../social_core/backends/weixin.py | 178 ++ .../social_core/backends/withings.py | 14 + .../social_core/backends/wunderlist.py | 29 + .../social_core/backends/xing.py | 70 + .../social_core/backends/yahoo.py | 159 ++ .../social_core/backends/yammer.py | 44 + .../social_core/backends/yandex.py | 79 + .../social_core/backends/zotero.py | 29 + .../site-packages/social_core/exceptions.py | 116 + .../social_core/pipeline/__init__.py | 59 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 954 bytes .../pipeline/__pycache__/debug.cpython-36.pyc | Bin 0 -> 485 bytes .../__pycache__/disconnect.cpython-36.pyc | Bin 0 -> 1141 bytes .../pipeline/__pycache__/mail.cpython-36.pyc | Bin 0 -> 989 bytes .../__pycache__/partial.cpython-36.pyc | Bin 0 -> 1688 bytes .../__pycache__/social_auth.cpython-36.pyc | Bin 0 -> 2679 bytes .../pipeline/__pycache__/user.cpython-36.pyc | Bin 0 -> 2485 bytes .../pipeline/__pycache__/utils.cpython-36.pyc | Bin 0 -> 2419 bytes .../social_core/pipeline/debug.py | 13 + .../social_core/pipeline/disconnect.py | 31 + .../social_core/pipeline/mail.py | 27 + .../social_core/pipeline/partial.py | 44 + .../social_core/pipeline/social_auth.py | 88 + .../social_core/pipeline/user.py | 105 + .../social_core/pipeline/utils.py | 73 + .../site-packages/social_core/storage.py | 339 +++ .../site-packages/social_core/store.py | 84 + .../site-packages/social_core/strategy.py | 226 ++ .../social_core/tests/__init__.py | 0 .../tests/__pycache__/__init__.cpython-36.pyc | Bin 0 -> 205 bytes .../tests/__pycache__/models.cpython-36.pyc | Bin 0 -> 8896 bytes .../tests/__pycache__/pipeline.cpython-36.pyc | Bin 0 -> 1496 bytes .../tests/__pycache__/strategy.cpython-36.pyc | Bin 0 -> 5647 bytes .../test_exceptions.cpython-36.pyc | Bin 0 -> 5134 bytes .../__pycache__/test_partial.cpython-36.pyc | Bin 0 -> 3783 bytes .../__pycache__/test_pipeline.cpython-36.pyc | Bin 0 -> 8862 bytes .../__pycache__/test_storage.cpython-36.pyc | Bin 0 -> 10333 bytes .../__pycache__/test_utils.cpython-36.pyc | Bin 0 -> 10076 bytes .../social_core/tests/actions/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 213 bytes .../__pycache__/actions.cpython-36.pyc | Bin 0 -> 6187 bytes .../__pycache__/test_associate.cpython-36.pyc | Bin 0 -> 3668 bytes .../test_disconnect.cpython-36.pyc | Bin 0 -> 2801 bytes .../__pycache__/test_login.cpython-36.pyc | Bin 0 -> 3377 bytes .../social_core/tests/actions/actions.py | 211 ++ .../tests/actions/test_associate.py | 87 + .../tests/actions/test_disconnect.py | 67 + .../social_core/tests/actions/test_login.py | 65 + .../social_core/tests/backends/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 214 bytes .../backends/__pycache__/base.cpython-36.pyc | Bin 0 -> 5258 bytes .../__pycache__/legacy.cpython-36.pyc | Bin 0 -> 1676 bytes .../backends/__pycache__/oauth.cpython-36.pyc | Bin 0 -> 4114 bytes .../__pycache__/open_id.cpython-36.pyc | Bin 0 -> 3409 bytes .../open_id_connect.cpython-36.pyc | Bin 0 -> 5926 bytes .../__pycache__/test_amazon.cpython-36.pyc | Bin 0 -> 1934 bytes .../__pycache__/test_angel.cpython-36.pyc | Bin 0 -> 1423 bytes .../__pycache__/test_arcgis.cpython-36.pyc | Bin 0 -> 1271 bytes .../__pycache__/test_asana.cpython-36.pyc | Bin 0 -> 1177 bytes .../__pycache__/test_azuread.cpython-36.pyc | Bin 0 -> 3380 bytes .../__pycache__/test_behance.cpython-36.pyc | Bin 0 -> 1579 bytes .../__pycache__/test_bitbucket.cpython-36.pyc | Bin 0 -> 5359 bytes .../__pycache__/test_box.cpython-36.pyc | Bin 0 -> 2334 bytes .../__pycache__/test_broken.cpython-36.pyc | Bin 0 -> 1951 bytes .../__pycache__/test_chatwork.cpython-36.pyc | Bin 0 -> 1606 bytes .../__pycache__/test_clef.cpython-36.pyc | Bin 0 -> 1092 bytes .../__pycache__/test_coinbase.cpython-36.pyc | Bin 0 -> 1314 bytes .../__pycache__/test_coursera.cpython-36.pyc | Bin 0 -> 1428 bytes .../test_dailymotion.cpython-36.pyc | Bin 0 -> 1081 bytes .../__pycache__/test_deezer.cpython-36.pyc | Bin 0 -> 1611 bytes .../test_digitalocean.cpython-36.pyc | Bin 0 -> 1343 bytes .../__pycache__/test_disqus.cpython-36.pyc | Bin 0 -> 1782 bytes .../__pycache__/test_dribbble.cpython-36.pyc | Bin 0 -> 1079 bytes .../__pycache__/test_drip.cpython-36.pyc | Bin 0 -> 1093 bytes .../__pycache__/test_dropbox.cpython-36.pyc | Bin 0 -> 1451 bytes .../__pycache__/test_dummy.cpython-36.pyc | Bin 0 -> 5726 bytes .../__pycache__/test_edmodo.cpython-36.pyc | Bin 0 -> 1602 bytes .../__pycache__/test_email.cpython-36.pyc | Bin 0 -> 987 bytes .../__pycache__/test_evernote.cpython-36.pyc | Bin 0 -> 2543 bytes .../__pycache__/test_facebook.cpython-36.pyc | Bin 0 -> 2817 bytes .../__pycache__/test_fitbit.cpython-36.pyc | Bin 0 -> 1700 bytes .../test_five_hundred_px.cpython-36.pyc | Bin 0 -> 2397 bytes .../__pycache__/test_flickr.cpython-36.pyc | Bin 0 -> 1124 bytes .../test_foursquare.cpython-36.pyc | Bin 0 -> 2238 bytes .../__pycache__/test_github.cpython-36.pyc | Bin 0 -> 7216 bytes .../test_github_enterprise.cpython-36.pyc | Bin 0 -> 9337 bytes .../__pycache__/test_gitlab.cpython-36.pyc | Bin 0 -> 3150 bytes .../__pycache__/test_google.cpython-36.pyc | Bin 0 -> 8911 bytes .../__pycache__/test_instagram.cpython-36.pyc | Bin 0 -> 1430 bytes .../__pycache__/test_itembase.cpython-36.pyc | Bin 0 -> 1732 bytes .../__pycache__/test_kakao.cpython-36.pyc | Bin 0 -> 1238 bytes .../test_khanacademy.cpython-36.pyc | Bin 0 -> 1335 bytes .../__pycache__/test_linkedin.cpython-36.pyc | Bin 0 -> 1690 bytes .../__pycache__/test_live.cpython-36.pyc | Bin 0 -> 1304 bytes .../test_livejournal.cpython-36.pyc | Bin 0 -> 3848 bytes .../__pycache__/test_lyft.cpython-36.pyc | Bin 0 -> 1149 bytes .../test_mapmyfitness.cpython-36.pyc | Bin 0 -> 2861 bytes .../__pycache__/test_mineid.cpython-36.pyc | Bin 0 -> 1070 bytes .../__pycache__/test_mixcloud.cpython-36.pyc | Bin 0 -> 2307 bytes .../test_nationbuilder.cpython-36.pyc | Bin 0 -> 5676 bytes .../__pycache__/test_naver.cpython-36.pyc | Bin 0 -> 1514 bytes .../__pycache__/test_ngpvan.cpython-36.pyc | Bin 0 -> 7177 bytes .../__pycache__/test_orbi.cpython-36.pyc | Bin 0 -> 1201 bytes .../__pycache__/test_patreon.cpython-36.pyc | Bin 0 -> 1808 bytes .../__pycache__/test_pinterest.cpython-36.pyc | Bin 0 -> 1896 bytes .../__pycache__/test_podio.cpython-36.pyc | Bin 0 -> 1579 bytes .../__pycache__/test_qiita.cpython-36.pyc | Bin 0 -> 1051 bytes .../__pycache__/test_quizlet.cpython-36.pyc | Bin 0 -> 1030 bytes .../test_readability.cpython-36.pyc | Bin 0 -> 1645 bytes .../__pycache__/test_reddit.cpython-36.pyc | Bin 0 -> 2010 bytes .../__pycache__/test_saml.cpython-36.pyc | Bin 0 -> 4825 bytes .../__pycache__/test_sketchfab.cpython-36.pyc | Bin 0 -> 1141 bytes .../__pycache__/test_skyrock.cpython-36.pyc | Bin 0 -> 1638 bytes .../__pycache__/test_slack.cpython-36.pyc | Bin 0 -> 1180 bytes .../test_soundcloud.cpython-36.pyc | Bin 0 -> 1773 bytes .../__pycache__/test_spotify.cpython-36.pyc | Bin 0 -> 1276 bytes .../test_stackoverflow.cpython-36.pyc | Bin 0 -> 1817 bytes .../__pycache__/test_steam.cpython-36.pyc | Bin 0 -> 4679 bytes .../test_stocktwits.cpython-36.pyc | Bin 0 -> 1811 bytes .../__pycache__/test_strava.cpython-36.pyc | Bin 0 -> 1900 bytes .../__pycache__/test_stripe.cpython-36.pyc | Bin 0 -> 1083 bytes .../__pycache__/test_taobao.cpython-36.pyc | Bin 0 -> 1197 bytes .../test_thisismyjam.cpython-36.pyc | Bin 0 -> 1314 bytes .../__pycache__/test_tripit.cpython-36.pyc | Bin 0 -> 3225 bytes .../__pycache__/test_tumblr.cpython-36.pyc | Bin 0 -> 1667 bytes .../__pycache__/test_twitch.cpython-36.pyc | Bin 0 -> 1409 bytes .../__pycache__/test_twitter.cpython-36.pyc | Bin 0 -> 5565 bytes .../__pycache__/test_uber.cpython-36.pyc | Bin 0 -> 1474 bytes .../__pycache__/test_udata.cpython-36.pyc | Bin 0 -> 1272 bytes .../__pycache__/test_upwork.cpython-36.pyc | Bin 0 -> 1704 bytes .../__pycache__/test_username.cpython-36.pyc | Bin 0 -> 1009 bytes .../__pycache__/test_utils.cpython-36.pyc | Bin 0 -> 2510 bytes .../__pycache__/test_vk.cpython-36.pyc | Bin 0 -> 1254 bytes .../test_wunderlist.cpython-36.pyc | Bin 0 -> 1207 bytes .../__pycache__/test_xing.cpython-36.pyc | Bin 0 -> 3815 bytes .../__pycache__/test_yahoo.cpython-36.pyc | Bin 0 -> 2462 bytes .../__pycache__/test_yammer.cpython-36.pyc | Bin 0 -> 2945 bytes .../__pycache__/test_yandex.cpython-36.pyc | Bin 0 -> 1144 bytes .../__pycache__/test_zotero.cpython-36.pyc | Bin 0 -> 1146 bytes .../social_core/tests/backends/base.py | 152 ++ .../tests/backends/data/saml_response.txt | 1 + .../social_core/tests/backends/legacy.py | 47 + .../social_core/tests/backends/oauth.py | 120 + .../social_core/tests/backends/open_id.py | 103 + .../tests/backends/open_id_connect.py | 168 ++ .../social_core/tests/backends/test_amazon.py | 48 + .../social_core/tests/backends/test_angel.py | 39 + .../social_core/tests/backends/test_arcgis.py | 29 + .../social_core/tests/backends/test_asana.py | 34 + .../tests/backends/test_azuread.py | 73 + .../tests/backends/test_behance.py | 51 + .../tests/backends/test_bitbucket.py | 167 ++ .../social_core/tests/backends/test_box.py | 68 + .../social_core/tests/backends/test_broken.py | 37 + .../tests/backends/test_chatwork.py | 44 + .../social_core/tests/backends/test_clef.py | 26 + .../tests/backends/test_coinbase.py | 32 + .../tests/backends/test_coursera.py | 38 + .../tests/backends/test_dailymotion.py | 23 + .../social_core/tests/backends/test_deezer.py | 36 + .../tests/backends/test_digitalocean.py | 34 + .../social_core/tests/backends/test_disqus.py | 62 + .../tests/backends/test_dribbble.py | 26 + .../social_core/tests/backends/test_drip.py | 29 + .../tests/backends/test_dropbox.py | 38 + .../social_core/tests/backends/test_dummy.py | 159 ++ .../social_core/tests/backends/test_edmodo.py | 44 + .../social_core/tests/backends/test_email.py | 19 + .../tests/backends/test_evernote.py | 56 + .../tests/backends/test_facebook.py | 69 + .../social_core/tests/backends/test_fitbit.py | 51 + .../tests/backends/test_five_hundred_px.py | 108 + .../social_core/tests/backends/test_flickr.py | 25 + .../tests/backends/test_foursquare.py | 127 ++ .../social_core/tests/backends/test_github.py | 191 ++ .../tests/backends/test_github_enterprise.py | 243 +++ .../social_core/tests/backends/test_gitlab.py | 107 + .../social_core/tests/backends/test_google.py | 294 +++ .../tests/backends/test_instagram.py | 64 + .../tests/backends/test_itembase.py | 45 + .../social_core/tests/backends/test_kakao.py | 28 + .../tests/backends/test_khanacademy.py | 31 + .../tests/backends/test_linkedin.py | 40 + .../social_core/tests/backends/test_live.py | 35 + .../tests/backends/test_livejournal.py | 100 + .../social_core/tests/backends/test_lyft.py | 26 + .../tests/backends/test_mapmyfitness.py | 158 ++ .../social_core/tests/backends/test_mineid.py | 23 + .../tests/backends/test_mixcloud.py | 58 + .../tests/backends/test_nationbuilder.py | 232 ++ .../social_core/tests/backends/test_naver.py | 41 + .../social_core/tests/backends/test_ngpvan.py | 197 ++ .../social_core/tests/backends/test_orbi.py | 30 + .../tests/backends/test_patreon.py | 65 + .../tests/backends/test_pinterest.py | 49 + .../social_core/tests/backends/test_podio.py | 56 + .../social_core/tests/backends/test_qiita.py | 25 + .../tests/backends/test_quizlet.py | 22 + .../tests/backends/test_readability.py | 41 + .../social_core/tests/backends/test_reddit.py | 59 + .../social_core/tests/backends/test_saml.py | 131 ++ .../tests/backends/test_sketchfab.py | 26 + .../tests/backends/test_skyrock.py | 49 + .../social_core/tests/backends/test_slack.py | 31 + .../tests/backends/test_soundcloud.py | 54 + .../tests/backends/test_spotify.py | 34 + .../tests/backends/test_stackoverflow.py | 53 + .../social_core/tests/backends/test_steam.py | 136 ++ .../tests/backends/test_stocktwits.py | 54 + .../social_core/tests/backends/test_strava.py | 62 + .../social_core/tests/backends/test_stripe.py | 23 + .../social_core/tests/backends/test_taobao.py | 29 + .../tests/backends/test_thisismyjam.py | 33 + .../social_core/tests/backends/test_tripit.py | 102 + .../social_core/tests/backends/test_tumblr.py | 60 + .../social_core/tests/backends/test_twitch.py | 33 + .../tests/backends/test_twitter.py | 261 +++ .../social_core/tests/backends/test_uber.py | 37 + .../social_core/tests/backends/test_udata.py | 29 + .../social_core/tests/backends/test_upwork.py | 54 + .../tests/backends/test_username.py | 19 + .../social_core/tests/backends/test_utils.py | 48 + .../social_core/tests/backends/test_vk.py | 32 + .../tests/backends/test_wunderlist.py | 26 + .../social_core/tests/backends/test_xing.py | 170 ++ .../social_core/tests/backends/test_yahoo.py | 83 + .../social_core/tests/backends/test_yammer.py | 115 + .../social_core/tests/backends/test_yandex.py | 27 + .../social_core/tests/backends/test_zotero.py | 25 + .../site-packages/social_core/tests/models.py | 230 ++ .../social_core/tests/pipeline.py | 45 + .../social_core/tests/requirements-base.txt | 4 + .../social_core/tests/requirements-pypy.txt | 3 + .../tests/requirements-python2.txt | 3 + .../tests/requirements-python3.txt | 2 + .../social_core/tests/requirements.txt | 1 + .../social_core/tests/strategy.py | 124 ++ .../social_core/tests/test_exceptions.py | 115 + .../social_core/tests/test_partial.py | 101 + .../social_core/tests/test_pipeline.py | 233 ++ .../social_core/tests/test_storage.py | 202 ++ .../social_core/tests/test_utils.py | 194 ++ .../social_core/tests/testkey.pem | 27 + .../site-packages/social_core/utils.py | 312 +++ .../site-packages/social_django/__init__.py | 23 + .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 821 bytes .../__pycache__/admin.cpython-36.pyc | Bin 0 -> 2748 bytes .../__pycache__/compat.cpython-36.pyc | Bin 0 -> 1115 bytes .../__pycache__/config.cpython-36.pyc | Bin 0 -> 495 bytes .../context_processors.cpython-36.pyc | Bin 0 -> 1920 bytes .../__pycache__/fields.cpython-36.pyc | Bin 0 -> 3305 bytes .../__pycache__/managers.cpython-36.pyc | Bin 0 -> 931 bytes .../__pycache__/middleware.cpython-36.pyc | Bin 0 -> 2591 bytes .../__pycache__/models.cpython-36.pyc | Bin 0 -> 5581 bytes .../__pycache__/storage.cpython-36.pyc | Bin 0 -> 6616 bytes .../__pycache__/strategy.cpython-36.pyc | Bin 0 -> 6994 bytes .../__pycache__/urls.cpython-36.pyc | Bin 0 -> 857 bytes .../__pycache__/utils.cpython-36.pyc | Bin 0 -> 1823 bytes .../__pycache__/views.cpython-36.pyc | Bin 0 -> 2582 bytes .../site-packages/social_django/admin.py | 62 + .../site-packages/social_django/compat.py | 34 + .../site-packages/social_django/config.py | 10 + .../social_django/context_processors.py | 52 + .../site-packages/social_django/fields.py | 94 + .../site-packages/social_django/managers.py | 15 + .../site-packages/social_django/middleware.py | 60 + .../social_django/migrations/0001_initial.py | 122 ++ .../migrations/0002_add_related_name.py | 32 + .../migrations/0003_alter_email_max_length.py | 28 + .../migrations/0004_auto_20160423_0400.py | 25 + .../migrations/0005_auto_20160727_2333.py | 22 + .../social_django/migrations/0006_partial.py | 31 + .../migrations/0007_code_timestamp.py | 24 + .../migrations/0008_partial_timestamp.py | 24 + .../social_django/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-36.pyc | Bin 0 -> 2496 bytes .../0002_add_related_name.cpython-36.pyc | Bin 0 -> 1023 bytes ...0003_alter_email_max_length.cpython-36.pyc | Bin 0 -> 960 bytes .../0004_auto_20160423_0400.cpython-36.pyc | Bin 0 -> 849 bytes .../0005_auto_20160727_2333.cpython-36.pyc | Bin 0 -> 765 bytes .../__pycache__/0006_partial.cpython-36.pyc | Bin 0 -> 1132 bytes .../0007_code_timestamp.cpython-36.pyc | Bin 0 -> 807 bytes .../0008_partial_timestamp.cpython-36.pyc | Bin 0 -> 820 bytes .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 218 bytes .../site-packages/social_django/models.py | 139 ++ .../site-packages/social_django/storage.py | 198 ++ .../site-packages/social_django/strategy.py | 159 ++ .../site-packages/social_django/urls.py | 24 + .../site-packages/social_django/utils.py | 51 + .../site-packages/social_django/views.py | 129 ++ 917 files changed, 50890 insertions(+), 11 deletions(-) create mode 100755 flex/bin/pyjwt create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/entry_points.txt create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/defusedxml/ElementTree.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__init__.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/ElementTree.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/cElementTree.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/common.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatbuilder.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatreader.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/lxml.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/minidom.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/pulldom.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/sax.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/__pycache__/xmlrpc.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/defusedxml/cElementTree.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/common.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/expatbuilder.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/expatreader.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/lxml.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/minidom.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/pulldom.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/sax.py create mode 100644 flex/lib/python3.6/site-packages/defusedxml/xmlrpc.py create mode 100644 flex/lib/python3.6/site-packages/jwt/__init__.py create mode 100644 flex/lib/python3.6/site-packages/jwt/__main__.py create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/__main__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/algorithms.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/api_jws.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/api_jwt.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/compat.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/exceptions.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/algorithms.py create mode 100644 flex/lib/python3.6/site-packages/jwt/api_jws.py create mode 100644 flex/lib/python3.6/site-packages/jwt/api_jwt.py create mode 100644 flex/lib/python3.6/site-packages/jwt/compat.py create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/__init__.py create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__init__.py create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/py_ecdsa.py create mode 100644 flex/lib/python3.6/site-packages/jwt/contrib/algorithms/pycrypto.py create mode 100644 flex/lib/python3.6/site-packages/jwt/exceptions.py create mode 100644 flex/lib/python3.6/site-packages/jwt/utils.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/oauthlib/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/__pycache__/common.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/__pycache__/signals.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/__pycache__/uri_validate.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/common.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/errors.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/parameters.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/request_validator.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/signature.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/access_token.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/authorization.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/pre_configured.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/request_token.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/resource.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/signature_only.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/errors.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/parameters.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/request_validator.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/signature.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/utils.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/errors.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/parameters.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/request_validator.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/tokens.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/backend_application.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/legacy_application.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/mobile_application.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/service_application.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/web_application.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/authorization.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/pre_configured.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/resource.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/revocation.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/token.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/errors.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/authorization_code.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/client_credentials.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/implicit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/openid_connect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/refresh_token.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/resource_owner_password_credentials.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/request_validator.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/utils.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/signals.py create mode 100644 flex/lib/python3.6/site-packages/oauthlib/uri_validate.py create mode 100644 flex/lib/python3.6/site-packages/openid/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/association.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/codecutil.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/cryptutil.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/dh.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/extension.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/fetchers.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/kvform.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/message.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/oidutil.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/sreg.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/__pycache__/urinorm.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/association.py create mode 100644 flex/lib/python3.6/site-packages/openid/codecutil.py create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/__pycache__/consumer.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/__pycache__/discover.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/__pycache__/html_parse.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/consumer.py create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/discover.py create mode 100644 flex/lib/python3.6/site-packages/openid/consumer/html_parse.py create mode 100644 flex/lib/python3.6/site-packages/openid/cryptutil.py create mode 100644 flex/lib/python3.6/site-packages/openid/dh.py create mode 100644 flex/lib/python3.6/site-packages/openid/extension.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/__pycache__/ax.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/__pycache__/sreg.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/ax.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape2.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape5.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/pape2.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/draft/pape5.py create mode 100644 flex/lib/python3.6/site-packages/openid/extensions/sreg.py create mode 100644 flex/lib/python3.6/site-packages/openid/fetchers.py create mode 100644 flex/lib/python3.6/site-packages/openid/kvform.py create mode 100644 flex/lib/python3.6/site-packages/openid/message.py create mode 100644 flex/lib/python3.6/site-packages/openid/oidutil.py create mode 100644 flex/lib/python3.6/site-packages/openid/server/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/server/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/server/__pycache__/server.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/server/__pycache__/trustroot.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/server/server.py create mode 100644 flex/lib/python3.6/site-packages/openid/server/trustroot.py create mode 100644 flex/lib/python3.6/site-packages/openid/sreg.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/filestore.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/interface.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/memstore.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/nonce.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/__pycache__/sqlstore.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/store/filestore.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/interface.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/memstore.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/nonce.py create mode 100644 flex/lib/python3.6/site-packages/openid/store/sqlstore.py create mode 100644 flex/lib/python3.6/site-packages/openid/urinorm.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__init__.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/accept.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/constants.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/discover.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/etxrd.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/filters.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/manager.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/parsehtml.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/services.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xri.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xrires.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/accept.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/constants.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/discover.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/etxrd.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/filters.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/manager.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/parsehtml.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/services.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/xri.py create mode 100644 flex/lib/python3.6/site-packages/openid/yadis/xrires.py create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__init__.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_auth.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_session.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_auth.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_session.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__init__.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/douban.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/facebook.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/fitbit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/linkedin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/mailchimp.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/slack.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/weibo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/douban.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/facebook.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/fitbit.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/linkedin.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/mailchimp.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/slack.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/weibo.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_auth.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_session.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_auth.py create mode 100644 flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/six-1.11.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/six.py create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/DESCRIPTION.rst create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/INSTALLER create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/METADATA create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/RECORD create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/WHEEL create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/metadata.json create mode 100644 flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/top_level.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/actions.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/exceptions.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/storage.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/store.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/strategy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/actions.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/amazon.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/angel.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/aol.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/appsfuel.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/arcgis.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/asana.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread_tenant.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/battlenet.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/beats.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/behance.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/belgiumeid.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bitbucket.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/box.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bungie.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/changetip.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/chatwork.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/classlink.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/clef.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coding.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coinbase.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coursera.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dailymotion.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/deezer.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/digitalocean.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/disqus.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/docker.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/douban.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dribbble.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/drip.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dropbox.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/echosign.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/edmodo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/email.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eventbrite.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eveonline.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/evernote.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/exacttarget.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/facebook.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fedora.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fitbit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/five_hundred_px.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/flickr.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/foursquare.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gae.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github_enterprise.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gitlab.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclio.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclioeu.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google_openidconnect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/instagram.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/itembase.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/jawbone.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/justgiving.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/kakao.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/khanacademy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lastfm.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/launchpad.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/legacy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/line.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/linkedin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/live.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/livejournal.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/loginradius.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lyft.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailchimp.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailru.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mapmyfitness.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mediawiki.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/meetup.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mendeley.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/microsoft.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mineid.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mixcloud.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/monzo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/moves.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nationbuilder.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/naver.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ngpvan.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nk.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/oauth.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/odnoklassniki.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id_connect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openshift.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openstreetmap.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orbi.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orcid.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/patreon.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/persona.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pinterest.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pixelpin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pocket.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/podio.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/professionali.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pushbullet.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qiita.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qq.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/quizlet.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/rdio.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/readability.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/reddit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/runkeeper.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/salesforce.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/saml.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shimmering.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shopify.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/sketchfab.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/skyrock.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/slack.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/soundcloud.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/spotify.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stackoverflow.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/steam.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stocktwits.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/strava.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stripe.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/suse.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/taobao.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/thisismyjam.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/trello.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tripit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tumblr.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twilio.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitch.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitter.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/uber.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ubuntu.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/udata.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/untappd.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/upwork.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/username.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vend.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vimeo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vk.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weibo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weixin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/withings.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/wunderlist.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/xing.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yahoo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yammer.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yandex.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/__pycache__/zotero.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/amazon.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/angel.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/aol.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/appsfuel.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/arcgis.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/asana.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/azuread.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/azuread_tenant.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/base.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/battlenet.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/beats.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/behance.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/belgiumeid.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/bitbucket.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/box.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/bungie.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/changetip.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/chatwork.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/classlink.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/clef.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/coding.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/coinbase.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/coursera.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/dailymotion.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/deezer.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/digitalocean.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/disqus.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/docker.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/douban.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/dribbble.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/drip.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/dropbox.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/echosign.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/edmodo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/email.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/eventbrite.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/eveonline.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/evernote.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/exacttarget.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/facebook.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/fedora.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/fitbit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/five_hundred_px.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/flickr.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/foursquare.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/gae.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/github.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/github_enterprise.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/gitlab.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/goclio.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/goclioeu.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/google.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/google_openidconnect.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/instagram.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/itembase.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/jawbone.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/justgiving.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/kakao.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/khanacademy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/lastfm.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/launchpad.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/legacy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/line.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/linkedin.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/live.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/livejournal.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/loginradius.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/lyft.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mailchimp.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mailru.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mapmyfitness.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mediawiki.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/meetup.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mendeley.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/microsoft.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mineid.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/mixcloud.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/monzo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/moves.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/nationbuilder.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/naver.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/ngpvan.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/nk.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/oauth.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/odnoklassniki.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/open_id.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/open_id_connect.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/openshift.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/openstreetmap.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/orbi.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/orcid.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/patreon.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/persona.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/pinterest.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/pixelpin.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/pocket.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/podio.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/professionali.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/pushbullet.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/qiita.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/qq.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/quizlet.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/rdio.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/readability.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/reddit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/runkeeper.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/salesforce.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/saml.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/shimmering.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/shopify.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/sketchfab.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/skyrock.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/slack.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/soundcloud.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/spotify.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/stackoverflow.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/steam.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/stocktwits.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/strava.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/stripe.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/suse.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/taobao.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/thisismyjam.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/trello.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/tripit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/tumblr.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/twilio.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/twitch.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/twitter.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/uber.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/ubuntu.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/udata.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/untappd.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/upwork.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/username.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/utils.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/vend.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/vimeo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/vk.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/weibo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/weixin.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/withings.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/wunderlist.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/xing.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/yahoo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/yammer.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/yandex.py create mode 100644 flex/lib/python3.6/site-packages/social_core/backends/zotero.py create mode 100644 flex/lib/python3.6/site-packages/social_core/exceptions.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/debug.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/disconnect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/mail.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/partial.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/social_auth.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/user.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/debug.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/disconnect.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/mail.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/partial.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/social_auth.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/user.py create mode 100644 flex/lib/python3.6/site-packages/social_core/pipeline/utils.py create mode 100644 flex/lib/python3.6/site-packages/social_core/storage.py create mode 100644 flex/lib/python3.6/site-packages/social_core/store.py create mode 100644 flex/lib/python3.6/site-packages/social_core/strategy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/models.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/pipeline.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/strategy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_exceptions.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_partial.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_pipeline.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_storage.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/actions.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_associate.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_disconnect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_login.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/actions.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/test_associate.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/test_disconnect.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/actions/test_login.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/base.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/legacy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/oauth.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id_connect.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_amazon.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_angel.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_arcgis.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_asana.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_azuread.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_behance.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_bitbucket.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_box.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_broken.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_chatwork.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_clef.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coinbase.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coursera.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dailymotion.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_deezer.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_digitalocean.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_disqus.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dribbble.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_drip.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dropbox.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dummy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_edmodo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_email.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_evernote.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_facebook.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_fitbit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_five_hundred_px.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_flickr.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_foursquare.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github_enterprise.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_gitlab.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_google.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_instagram.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_itembase.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_kakao.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_khanacademy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_linkedin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_live.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_livejournal.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_lyft.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mapmyfitness.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mineid.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mixcloud.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_nationbuilder.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_naver.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_ngpvan.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_orbi.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_patreon.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_pinterest.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_podio.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_qiita.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_quizlet.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_readability.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_reddit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_saml.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_sketchfab.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_skyrock.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_slack.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_soundcloud.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_spotify.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stackoverflow.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_steam.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stocktwits.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_strava.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stripe.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_taobao.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_thisismyjam.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tripit.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tumblr.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitch.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitter.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_uber.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_udata.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_upwork.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_username.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_vk.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_wunderlist.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_xing.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yahoo.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yammer.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yandex.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_zotero.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/base.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/data/saml_response.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/legacy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/oauth.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/open_id.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/open_id_connect.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_amazon.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_angel.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_arcgis.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_asana.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_azuread.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_behance.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_bitbucket.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_box.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_broken.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_chatwork.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_clef.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_coinbase.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_coursera.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_dailymotion.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_deezer.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_digitalocean.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_disqus.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_dribbble.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_drip.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_dropbox.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_dummy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_edmodo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_email.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_evernote.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_facebook.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_fitbit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_five_hundred_px.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_flickr.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_foursquare.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_github.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_github_enterprise.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_gitlab.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_google.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_instagram.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_itembase.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_kakao.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_khanacademy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_linkedin.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_live.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_livejournal.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_lyft.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_mapmyfitness.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_mineid.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_mixcloud.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_nationbuilder.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_naver.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_ngpvan.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_orbi.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_patreon.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_pinterest.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_podio.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_qiita.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_quizlet.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_readability.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_reddit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_saml.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_sketchfab.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_skyrock.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_slack.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_soundcloud.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_spotify.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_stackoverflow.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_steam.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_stocktwits.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_strava.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_stripe.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_taobao.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_thisismyjam.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_tripit.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_tumblr.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitch.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitter.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_uber.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_udata.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_upwork.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_username.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_utils.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_vk.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_wunderlist.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_xing.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_yahoo.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_yammer.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_yandex.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/backends/test_zotero.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/models.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/pipeline.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/requirements-base.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/requirements-pypy.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/requirements-python2.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/requirements-python3.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/requirements.txt create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/strategy.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/test_exceptions.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/test_partial.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/test_pipeline.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/test_storage.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/test_utils.py create mode 100644 flex/lib/python3.6/site-packages/social_core/tests/testkey.pem create mode 100644 flex/lib/python3.6/site-packages/social_core/utils.py create mode 100644 flex/lib/python3.6/site-packages/social_django/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/admin.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/compat.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/config.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/context_processors.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/fields.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/managers.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/middleware.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/models.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/storage.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/strategy.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/urls.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/utils.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/__pycache__/views.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/admin.py create mode 100644 flex/lib/python3.6/site-packages/social_django/compat.py create mode 100644 flex/lib/python3.6/site-packages/social_django/config.py create mode 100644 flex/lib/python3.6/site-packages/social_django/context_processors.py create mode 100644 flex/lib/python3.6/site-packages/social_django/fields.py create mode 100644 flex/lib/python3.6/site-packages/social_django/managers.py create mode 100644 flex/lib/python3.6/site-packages/social_django/middleware.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0001_initial.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0002_add_related_name.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0003_alter_email_max_length.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0004_auto_20160423_0400.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0005_auto_20160727_2333.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0006_partial.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0007_code_timestamp.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/0008_partial_timestamp.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__init__.py create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0001_initial.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0002_add_related_name.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0003_alter_email_max_length.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0004_auto_20160423_0400.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0005_auto_20160727_2333.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0006_partial.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0007_code_timestamp.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0008_partial_timestamp.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/__init__.cpython-36.pyc create mode 100644 flex/lib/python3.6/site-packages/social_django/models.py create mode 100644 flex/lib/python3.6/site-packages/social_django/storage.py create mode 100644 flex/lib/python3.6/site-packages/social_django/strategy.py create mode 100644 flex/lib/python3.6/site-packages/social_django/urls.py create mode 100644 flex/lib/python3.6/site-packages/social_django/utils.py create mode 100644 flex/lib/python3.6/site-packages/social_django/views.py diff --git a/flex/bin/pyjwt b/flex/bin/pyjwt new file mode 100755 index 0000000..a7fa659 --- /dev/null +++ b/flex/bin/pyjwt @@ -0,0 +1,11 @@ +#!/Users/karthikpullela/Desktop/hackdavis/Flex/flex/bin/python3 + +# -*- coding: utf-8 -*- +import re +import sys + +from jwt.__main__ import main + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/flex/flex/db.sqlite3 b/flex/flex/db.sqlite3 index 703415a694c339bf2fada440877ea28dd4b13a1b..45508de2520a0b052e9aebe9dbed66c34a4b0c25 100644 GIT binary patch literal 192512 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 delta 1067 zcmah{YiJx*7`@-j&Z9fK^Xo-Ad9tHoKe6HVGvSF*VktNud&?((RUA)1A&X zkKJsnf+j^uNeVSwbfVCtloq6lA_ERhTc{u+6+w$oZ3?Y|KSWI|2ns>so%k#wy>n;a zJKw!?&bjwY+)*YbOY3fkAIj~XTCqxa+V#3izOWN`-u|Hr_VV`eCm{*0b#IVvx*|Ph*Y1LSASyqR?R_=$17*{L&V^+% zLq;wW|N8>hYQ?_T1ABm0D<8YORiusntfb8yPr&zeEIpUQH8pU&zMMU1e ziV&n%1;N~Znrk(ubx2#MWeM!-yCLR}$v23PRETt2ii!iS5&k1S$qm9Bsdzv4Zk2Dj zPk8D)-zu-NL^z{!rXwiE9~Hx0nxozH=zyTvokh69+1)$foFKR_5g}}E)ZuL~zbime z^*&5)I(yq1#PA=McmA(sO+C+TU2|4*UuXDVXJPZ1LtvVX1-K}-CH|yaZ~6=fQ#kK+ipr%!AWz_Yqx(2*8==4 lv{~-n!f2H5h5JR#TwI1YTW+IyZWVn0O1=|1^Xpa6eg}#9J#PR2 diff --git a/flex/flex/flex/__pycache__/settings.cpython-36.pyc b/flex/flex/flex/__pycache__/settings.cpython-36.pyc index 018fd4965e49cd4624cb609f758355aee69bffc4..94fff7cfcce51a7073ca324431edb99366bbd6f2 100644 GIT binary patch delta 872 zcma)4y>HV%6ptM@aYK`~=|}pJBBYi!hSmvATH1{?wNk5TOw$ypCnU@DGmewgPR|ai zvP6iPq4HX>F|Z(^j*N(nzo07%D;vthIYE3#Ah^?e_wK#lJ^g;C_agpgdNCV~Mm~H5 z^ePqzdz%gb_wJU=oeM6igcwFpz*5n1wl* zha@Z*A(MiemyAK7DCAHK7SSjy!EK$Y)Acbu1k2B9Sh=K43XMYwID$^WoxZ=@_p2sl zFenadXaYDMO#*|ah)okqKpM?J2F=1fGzS8jhb&40eHh@@-}YE~6h$3dGu5U7b=5NL zD>`^iKkjh9C3(%ZT>actTG-Zf$FZ@)pQu_xx4`M7{|Rl{hG{8S2NUa>>ve{fy1kX} zuQVI$KYHrugZz=RRZXmX=3!5jrO42`B z?AxF>w+H&XmqW+Y$ko@OWvY7}BuYAwbj?21)1W%F&hADgD+t2o{ai*6)^ewgqv6(B z!_ac}=9#r+8Cjfhbyo$gxv}N)YOCe&1L|!@$NgWmT*BfM&6JHtstNdD6BpOp`?dYO zClBSPTL%)y;1C%t91f7F!>k_;6UQxhtKo}8B7aof70XqrkgrN|S=r7P_Qdjz_b&Xb zJ6w{BQdv2wl&<50up;hA6|qqDb7UF$VoR;?AFXitKDpRHoFtAlOjX2!DN^TGBgvSDepbI8lx$iiiHD| PznP%-g-wPSiqxrZ|A7b> delta 289 zcmZ1{)+?lJ%*)HAT$vK(%frC%m;niJ0ND;eTzqMwvTJ=hdlXj+PYQ1eUkZPUK#E|B z5Kx3WML3ElMI=QuoiT+aMJz=;MIuEqMJk;olQBhl4of;?6mN=56km#Lid-sV7E=~~ zDqD(tGgFGf9HvahD1j8k6eXZa#S~>QsRAZdGa1uaq6AaaqJ&b^ql8mfqC|jLSQJQz zrD#Nnr)Wk=q%Z|DXliX1VB}!htj=7)H2F8H%H-p0k&GOh71`Ao`Kq||GxBp&^-~gy zGc+Y9U*VWIxsJ15M##tC-P12V$koL&$kjO{J~YT@B}0)S(2qrmlT*2(8N((Y(<`ljZ{uF^;re>ye z)+nA7!C(eWp^bfim<*~|6H7}n^s9u5^OG|ZbK+C767$mY^-7C!if^$7UiapooF prWO|@CZ|sBVyiNeVVZo3 fxt@&=C|Sfe*`Fm`2*hMz6kz0Gt`xRj=5*F5 zt`zQI22GxcEhbXjMX4pFMS1Zli6x1*m~-;eZ?U8#7H9Zra!)+!AYH@-G~^a{X;Dr= jVo6DAQC{&%h9W+oSP|c3X~u9N5R-?Ahlz!Wj}Za^TY(~F delta 118 zcmZ3&+{CPG%*)HAT$vKZ%E-X*m;niJ0ND;eTpTk|Ih-q&bf7%2b% diff --git a/flex/flex/home/__pycache__/views.cpython-36.pyc b/flex/flex/home/__pycache__/views.cpython-36.pyc index aeebb6bc8a1789ec6fcbbee4997fe85ef604d8e7..3ea779f3b1a08c42e772989a8bd6793a74b805ce 100644 GIT binary patch delta 450 zcmY+AKT88K7{-&_oeh<~JUk@%%)RD|RohO}FIFF<5&A)Y27&q%mQ>Hhu5GiG z2UO28vxZ8U_m?eeYJg_KI@cZ#Fh(8J!J@+O1S`@p#(0J&M45~%6l=ky7GcH{sUhf_ zGh9OqL85gz^4dxbr02TB;jj`!QPAc7Ae5D6)N@;*>T@aG`$IRDTu8Ua-6`{xUyEYV z}l)+N`}~kEiqzo6ed`RqE;5z*8OF{RV?}qeYX>6NcdEQFvTWL;{p-Y zk2mX>;IsL6%EP(&I=ym)3l6cWQP9}Y*wrX&ROgShT*yP2#V`qlFlx{ZYEhe}e*p`$ BZD{}i delta 130 zcmbOxa#m2;n3tDpe{D*XJtqUhV+JI^1Y|n^adFN> + + + + @@ -63,7 +67,8 @@


- +
+
{% endif %} diff --git a/flex/flex/home/urls.py b/flex/flex/home/urls.py index ba6d125..6954f43 100644 --- a/flex/flex/home/urls.py +++ b/flex/flex/home/urls.py @@ -6,6 +6,7 @@ appname = 'home' urlpatterns = [ path('', views.home, name='home'), path('signin', views.user_login, name='signin'), + path('return_data', views.return_data, name='return_data'), path('log', views.log, name='log'), path('dash', views.dash, name='dash'), diff --git a/flex/flex/home/views.py b/flex/flex/home/views.py index d0a436a..245dfa9 100644 --- a/flex/flex/home/views.py +++ b/flex/flex/home/views.py @@ -53,6 +53,15 @@ def user_login(request): return HttpResponse("It's not supposed to come till here.") +def return_data(request): + if request.user.is_authenticated: + uri = 'https://www.googleapis.com/fitness/v1/users/me/dataSources' + r = requests.get(uri) + return HttpResponse("Works, I guess.", auth=(request.user.username, request.user.password)) + else: + return HttpResponse("User not authenticated") + + def dash(request): diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..4824ff3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/DESCRIPTION.rst @@ -0,0 +1,76 @@ +PyJWT +===== + +.. image:: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master + :target: http://travis-ci.org/jpadilla/pyjwt?branch=master + +.. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true + :target: https://ci.appveyor.com/project/jpadilla/pyjwt + +.. image:: https://img.shields.io/pypi/v/pyjwt.svg + :target: https://pypi.python.org/pypi/pyjwt + +.. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master + :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master + +.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest + :target: https://pyjwt.readthedocs.io + +A Python implementation of `RFC +7519 `_. Original implementation +was written by `@progrium `_. + +Installing +---------- + +Install with **pip**: + +.. code-block:: sh + + $ pip install PyJWT + + +Usage +----- + +.. code:: python + + >>> import jwt + >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256') + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg' + + >>> jwt.decode(encoded, 'secret', algorithms=['HS256']) + {'some': 'payload'} + + +Command line +------------ + +Usage:: + + pyjwt [options] INPUT + +Decoding examples:: + + pyjwt --key=secret TOKEN + pyjwt --no-verify TOKEN + +See more options executing ``pyjwt --help``. + + +Documentation +------------- + +View the full docs online at https://pyjwt.readthedocs.io/en/latest/ + + +Tests +----- + +You can run tests from the project root after cloning with: + +.. code-block:: sh + + $ python setup.py test + + diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/METADATA b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/METADATA new file mode 100644 index 0000000..c119adc --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/METADATA @@ -0,0 +1,110 @@ +Metadata-Version: 2.0 +Name: PyJWT +Version: 1.5.3 +Summary: JSON Web Token implementation in Python +Home-page: http://github.com/jpadilla/pyjwt +Author: Jose Padilla +Author-email: hello@jpadilla.com +License: MIT +Description-Content-Type: UNKNOWN +Keywords: jwt json web token security signing +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Utilities +Provides-Extra: crypto +Requires-Dist: cryptography (>=1.4); extra == 'crypto' +Provides-Extra: flake8 +Requires-Dist: flake8; extra == 'flake8' +Requires-Dist: flake8-import-order; extra == 'flake8' +Requires-Dist: pep8-naming; extra == 'flake8' +Provides-Extra: test +Requires-Dist: pytest (<4,>3); extra == 'test' +Requires-Dist: pytest-cov; extra == 'test' +Requires-Dist: pytest-runner; extra == 'test' + +PyJWT +===== + +.. image:: https://secure.travis-ci.org/jpadilla/pyjwt.svg?branch=master + :target: http://travis-ci.org/jpadilla/pyjwt?branch=master + +.. image:: https://ci.appveyor.com/api/projects/status/h8nt70aqtwhht39t?svg=true + :target: https://ci.appveyor.com/project/jpadilla/pyjwt + +.. image:: https://img.shields.io/pypi/v/pyjwt.svg + :target: https://pypi.python.org/pypi/pyjwt + +.. image:: https://coveralls.io/repos/jpadilla/pyjwt/badge.svg?branch=master + :target: https://coveralls.io/r/jpadilla/pyjwt?branch=master + +.. image:: https://readthedocs.org/projects/pyjwt/badge/?version=latest + :target: https://pyjwt.readthedocs.io + +A Python implementation of `RFC +7519 `_. Original implementation +was written by `@progrium `_. + +Installing +---------- + +Install with **pip**: + +.. code-block:: sh + + $ pip install PyJWT + + +Usage +----- + +.. code:: python + + >>> import jwt + >>> encoded = jwt.encode({'some': 'payload'}, 'secret', algorithm='HS256') + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg' + + >>> jwt.decode(encoded, 'secret', algorithms=['HS256']) + {'some': 'payload'} + + +Command line +------------ + +Usage:: + + pyjwt [options] INPUT + +Decoding examples:: + + pyjwt --key=secret TOKEN + pyjwt --no-verify TOKEN + +See more options executing ``pyjwt --help``. + + +Documentation +------------- + +View the full docs online at https://pyjwt.readthedocs.io/en/latest/ + + +Tests +----- + +You can run tests from the project root after cloning with: + +.. code-block:: sh + + $ python setup.py test + + diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/RECORD b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/RECORD new file mode 100644 index 0000000..16a8a23 --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/RECORD @@ -0,0 +1,33 @@ +PyJWT-1.5.3.dist-info/DESCRIPTION.rst,sha256=NTi807oEiMl9QPTbA_NX8OLmLAtL4WCN9lj6zmw04bA,1639 +PyJWT-1.5.3.dist-info/METADATA,sha256=YQMYS9ks5luILyBhUJWEzRxiaowncPD0NfrJDhNgpwE,2911 +PyJWT-1.5.3.dist-info/RECORD,, +PyJWT-1.5.3.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +PyJWT-1.5.3.dist-info/entry_points.txt,sha256=Xl_tLkGbTgywYa7PwaEY2xSiCtVtM2PdHTL4CW_n9dM,45 +PyJWT-1.5.3.dist-info/metadata.json,sha256=qfgrSQx6qIBvcFkAaFz34q76NkBadsJioyH2k63e9NU,1501 +PyJWT-1.5.3.dist-info/top_level.txt,sha256=RP5DHNyJbMq2ka0FmfTgoSaQzh7e3r5XuCWCO8a00k8,4 +jwt/__init__.py,sha256=Au0HJMdNUk9rn1oKHq3qlX4etWu_7UZoGpZJgsb738c,761 +jwt/__main__.py,sha256=E2cfCobbAPoNorrICDSD4LJTemWTtKT04wsHHp8g2Kk,4151 +jwt/algorithms.py,sha256=kL1ARjxNL8JeuxEpWS8On14qJWomMX_A_ncIrnZhBrA,13336 +jwt/api_jws.py,sha256=YYGC3eKhyxeeKZaIZkQdyOVZxz0DwZBK0Ee4iIqBE1M,7555 +jwt/api_jwt.py,sha256=xWMYu2xSCOAhIpPhO_t-HJ7IDBPFW7LkqJVKOl7cCsw,7068 +jwt/compat.py,sha256=5cYHQWJuAxcpUQo0e0Fm8-Hn7acYdZQde6M8bnZ6rBg,1784 +jwt/exceptions.py,sha256=63QgVtqVgRHdVAi0NqRO0s13opKdKZmwUGzW_CyPw4c,841 +jwt/utils.py,sha256=RraFiloy_xsB8NA1CrlHxS9lR73If8amInQ3P1mKXeM,2629 +jwt/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jwt/contrib/algorithms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +jwt/contrib/algorithms/py_ecdsa.py,sha256=tSTUrwx-u14DJcqAChRzJG-wf7bEY2Gv2hI5xSZZNjk,1771 +jwt/contrib/algorithms/pycrypto.py,sha256=M3nH1Rrk6yb6aPGo6zT4EI_MvPUM4vhO1EwC-uX9JAo,1250 +../../../bin/pyjwt,sha256=z_4TrA61Dek8m352cf7FY32R6fdqljxzg42Wyzv0HyM,260 +PyJWT-1.5.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +jwt/__pycache__/exceptions.cpython-36.pyc,, +jwt/__pycache__/algorithms.cpython-36.pyc,, +jwt/__pycache__/__main__.cpython-36.pyc,, +jwt/__pycache__/compat.cpython-36.pyc,, +jwt/__pycache__/utils.cpython-36.pyc,, +jwt/__pycache__/__init__.cpython-36.pyc,, +jwt/__pycache__/api_jwt.cpython-36.pyc,, +jwt/__pycache__/api_jws.cpython-36.pyc,, +jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc,, +jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc,, +jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc,, +jwt/contrib/__pycache__/__init__.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/WHEEL b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/WHEEL new file mode 100644 index 0000000..8b6dd1b --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/entry_points.txt b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/entry_points.txt new file mode 100644 index 0000000..78717b2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +pyjwt = jwt.__main__:main + diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/metadata.json b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/metadata.json new file mode 100644 index 0000000..6742116 --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Utilities"], "description_content_type": "UNKNOWN", "extensions": {"python.commands": {"wrap_console": {"pyjwt": "jwt.__main__:main"}}, "python.details": {"contacts": [{"email": "hello@jpadilla.com", "name": "Jose Padilla", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/jpadilla/pyjwt"}}, "python.exports": {"console_scripts": {"pyjwt": "jwt.__main__:main"}}}, "extras": ["crypto", "flake8", "test"], "generator": "bdist_wheel (0.29.0)", "keywords": ["jwt", "json", "web", "token", "security", "signing"], "license": "MIT", "metadata_version": "2.0", "name": "PyJWT", "run_requires": [{"extra": "crypto", "requires": ["cryptography (>=1.4)"]}, {"extra": "flake8", "requires": ["flake8-import-order", "flake8", "pep8-naming"]}, {"extra": "test", "requires": ["pytest (<4,>3)", "pytest-cov", "pytest-runner"]}], "summary": "JSON Web Token implementation in Python", "test_requires": [{"requires": ["pytest (<4,>3)", "pytest-cov", "pytest-runner"]}], "version": "1.5.3"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/top_level.txt new file mode 100644 index 0000000..27ccc9b --- /dev/null +++ b/flex/lib/python3.6/site-packages/PyJWT-1.5.3.dist-info/top_level.txt @@ -0,0 +1 @@ +jwt diff --git a/flex/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc b/flex/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9de6f605257fdda3ecbcd325106f861995a8b321 GIT binary patch literal 25081 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 Y1L{C_9Vs7*Cgb=*QUZJvNse3p7nE + +Synopsis +======== + +The results of an attack on a vulnerable XML library can be fairly dramatic. +With just a few hundred **Bytes** of XML data an attacker can occupy several +**Gigabytes** of memory within **seconds**. An attacker can also keep +CPUs busy for a long time with a small to medium size request. Under some +circumstances it is even possible to access local files on your +server, to circumvent a firewall, or to abuse services to rebound attacks to +third parties. + +The attacks use and abuse less common features of XML and its parsers. The +majority of developers are unacquainted with features such as processing +instructions and entity expansions that XML inherited from SGML. At best +they know about ```` from experience with HTML but they are not +aware that a document type definition (DTD) can generate an HTTP request +or load a file from the file system. + +None of the issues is new. They have been known for a long time. Billion +laughs was first reported in 2003. Nevertheless some XML libraries and +applications are still vulnerable and even heavy users of XML are +surprised by these features. It's hard to say whom to blame for the +situation. It's too short sighted to shift all blame on XML parsers and +XML libraries for using insecure default settings. After all they +properly implement XML specifications. Application developers must not rely +that a library is always configured for security and potential harmful data +by default. + + +.. contents:: Table of Contents + :depth: 2 + + +Attack vectors +============== + +billion laughs / exponential entity expansion +--------------------------------------------- + +The `Billion Laughs`_ attack -- also known as exponential entity expansion -- +uses multiple levels of nested entities. The original example uses 9 levels +of 10 expansions in each level to expand the string ``lol`` to a string of +3 * 10 :sup:`9` bytes, hence the name "billion laughs". The resulting string +occupies 3 GB (2.79 GiB) of memory; intermediate strings require additional +memory. Because most parsers don't cache the intermediate step for every +expansion it is repeated over and over again. It increases the CPU load even +more. + +An XML document of just a few hundred bytes can disrupt all services on a +machine within seconds. + +Example XML:: + + + + + + ]> + &d; + + +quadratic blowup entity expansion +--------------------------------- + +A quadratic blowup attack is similar to a `Billion Laughs`_ attack; it abuses +entity expansion, too. Instead of nested entities it repeats one large entity +with a couple of thousand chars over and over again. The attack isn't as +efficient as the exponential case but it avoids triggering countermeasures of +parsers against heavily nested entities. Some parsers limit the depth and +breadth of a single entity but not the total amount of expanded text +throughout an entire XML document. + +A medium-sized XML document with a couple of hundred kilobytes can require a +couple of hundred MB to several GB of memory. When the attack is combined +with some level of nested expansion an attacker is able to achieve a higher +ratio of success. + +:: + + + ]> + &a;&a;&a;... repeat + + +external entity expansion (remote) +---------------------------------- + +Entity declarations can contain more than just text for replacement. They can +also point to external resources by public identifiers or system identifiers. +System identifiers are standard URIs. When the URI is a URL (e.g. a +``http://`` locator) some parsers download the resource from the remote +location and embed them into the XML document verbatim. + +Simple example of a parsed external entity:: + + + ]> + + +The case of parsed external entities works only for valid XML content. The +XML standard also supports unparsed external entities with a +``NData declaration``. + +External entity expansion opens the door to plenty of exploits. An attacker +can abuse a vulnerable XML library and application to rebound and forward +network requests with the IP address of the server. It highly depends +on the parser and the application what kind of exploit is possible. For +example: + +* An attacker can circumvent firewalls and gain access to restricted + resources as all the requests are made from an internal and trustworthy + IP address, not from the outside. +* An attacker can abuse a service to attack, spy on or DoS your servers but + also third party services. The attack is disguised with the IP address of + the server and the attacker is able to utilize the high bandwidth of a big + machine. +* An attacker can exhaust additional resources on the machine, e.g. with + requests to a service that doesn't respond or responds with very large + files. +* An attacker may gain knowledge, when, how often and from which IP address + a XML document is accessed. +* An attacker could send mail from inside your network if the URL handler + supports ``smtp://`` URIs. + + +external entity expansion (local file) +-------------------------------------- + +External entities with references to local files are a sub-case of external +entity expansion. It's listed as an extra attack because it deserves extra +attention. Some XML libraries such as lxml disable network access by default +but still allow entity expansion with local file access by default. Local +files are either referenced with a ``file://`` URL or by a file path (either +relative or absolute). + +An attacker may be able to access and download all files that can be read by +the application process. This may include critical configuration files, too. + +:: + + + ]> + + + +DTD retrieval +------------- + +This case is similar to external entity expansion, too. Some XML libraries +like Python's xml.dom.pulldom retrieve document type definitions from remote +or local locations. Several attack scenarios from the external entity case +apply to this issue as well. + +:: + + + + + + text + + + +Python XML Libraries +==================== + +.. csv-table:: vulnerabilities and features + :header: "kind", "sax", "etree", "minidom", "pulldom", "xmlrpc", "lxml", "genshi" + :widths: 24, 7, 8, 8, 7, 8, 8, 8 + :stub-columns: 0 + + "billion laughs", "**True**", "**True**", "**True**", "**True**", "**True**", "False (1)", "False (5)" + "quadratic blowup", "**True**", "**True**", "**True**", "**True**", "**True**", "**True**", "False (5)" + "external entity expansion (remote)", "**True**", "False (3)", "False (4)", "**True**", "false", "False (1)", "False (5)" + "external entity expansion (local file)", "**True**", "False (3)", "False (4)", "**True**", "false", "**True**", "False (5)" + "DTD retrieval", "**True**", "False", "False", "**True**", "false", "False (1)", "False" + "gzip bomb", "False", "False", "False", "False", "**True**", "**partly** (2)", "False" + "xpath support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xsl(t) support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xinclude support (7)", "False", "**True** (6)", "False", "False", "False", "**True** (6)", "**True**" + "C library", "expat", "expat", "expat", "expat", "expat", "libxml2", "expat" + +1. Lxml is protected against billion laughs attacks and doesn't do network + lookups by default. +2. libxml2 and lxml are not directly vulnerable to gzip decompression bombs + but they don't protect you against them either. +3. xml.etree doesn't expand entities and raises a ParserError when an entity + occurs. +4. minidom doesn't expand entities and simply returns the unexpanded entity + verbatim. +5. genshi.input of genshi 0.6 doesn't support entity expansion and raises a + ParserError when an entity occurs. +6. Library has (limited) XInclude support but requires an additional step to + process inclusion. +7. These are features but they may introduce exploitable holes, see + `Other things to consider`_ + + +Settings in standard library +---------------------------- + + +xml.sax.handler Features +........................ + +feature_external_ges (http://xml.org/sax/features/external-general-entities) + disables external entity expansion + +feature_external_pes (http://xml.org/sax/features/external-parameter-entities) + the option is ignored and doesn't modify any functionality + +DOM xml.dom.xmlbuilder.Options +.............................. + +external_parameter_entities + ignored + +external_general_entities + ignored + +external_dtd_subset + ignored + +entities + unsure + + +defusedxml +========== + +The `defusedxml package`_ (`defusedxml on PyPI`_) +contains several Python-only workarounds and fixes +for denial of service and other vulnerabilities in Python's XML libraries. +In order to benefit from the protection you just have to import and use the +listed functions / classes from the right defusedxml module instead of the +original module. Merely `defusedxml.xmlrpc`_ is implemented as monkey patch. + +Instead of:: + + >>> from xml.etree.ElementTree import parse + >>> et = parse(xmlfile) + +alter code to:: + + >>> from defusedxml.ElementTree import parse + >>> et = parse(xmlfile) + +Additionally the package has an **untested** function to monkey patch +all stdlib modules with ``defusedxml.defuse_stdlib()``. + +All functions and parser classes accept three additional keyword arguments. +They return either the same objects as the original functions or compatible +subclasses. + +forbid_dtd (default: False) + disallow XML with a ```` processing instruction and raise a + *DTDForbidden* exception when a DTD processing instruction is found. + +forbid_entities (default: True) + disallow XML with ```` declarations inside the DTD and raise an + *EntitiesForbidden* exception when an entity is declared. + +forbid_external (default: True) + disallow any access to remote or local resources in external entities + or DTD and raising an *ExternalReferenceForbidden* exception when a DTD + or entity references an external resource. + + +defusedxml (package) +-------------------- + +DefusedXmlException, DTDForbidden, EntitiesForbidden, +ExternalReferenceForbidden, NotSupportedError + +defuse_stdlib() (*experimental*) + + +defusedxml.cElementTree +----------------------- + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.ElementTree +----------------------- + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.expatreader +---------------------- + +create_parser(), DefusedExpatParser + + +defusedxml.sax +-------------- + +parse(), parseString(), create_parser() + + +defusedxml.expatbuilder +----------------------- + +parse(), parseString(), DefusedExpatBuilder, DefusedExpatBuilderNS + + +defusedxml.minidom +------------------ + +parse(), parseString() + + +defusedxml.pulldom +------------------ + +parse(), parseString() + + +defusedxml.xmlrpc +----------------- + +The fix is implemented as monkey patch for the stdlib's xmlrpc package (3.x) +or xmlrpclib module (2.x). The function `monkey_patch()` enables the fixes, +`unmonkey_patch()` removes the patch and puts the code in its former state. + +The monkey patch protects against XML related attacks as well as +decompression bombs and excessively large requests or responses. The default +setting is 30 MB for requests, responses and gzip decompression. You can +modify the default by changing the module variable `MAX_DATA`. A value of +`-1` disables the limit. + + +defusedxml.lxml +--------------- + +The module acts as an *example* how you could protect code that uses +lxml.etree. It implements a custom Element class that filters out +Entity instances, a custom parser factory and a thread local storage for +parser instances. It also has a check_docinfo() function which inspects +a tree for internal or external DTDs and entity declarations. In order to +check for entities lxml > 3.0 is required. + +parse(), fromstring() +RestrictedElement, GlobalParserTLS, getDefaultParser(), check_docinfo() + + +defusedexpat +============ + +The `defusedexpat package`_ (`defusedexpat on PyPI`_) +comes with binary extensions and a +`modified expat`_ libary instead of the standard `expat parser`_. It's +basically a stand-alone version of the patches for Python's standard +library C extensions. + +Modifications in expat +---------------------- + +new definitions:: + + XML_BOMB_PROTECTION + XML_DEFAULT_MAX_ENTITY_INDIRECTIONS + XML_DEFAULT_MAX_ENTITY_EXPANSIONS + XML_DEFAULT_RESET_DTD + +new XML_FeatureEnum members:: + + XML_FEATURE_MAX_ENTITY_INDIRECTIONS + XML_FEATURE_MAX_ENTITY_EXPANSIONS + XML_FEATURE_IGNORE_DTD + +new XML_Error members:: + + XML_ERROR_ENTITY_INDIRECTIONS + XML_ERROR_ENTITY_EXPANSION + +new API functions:: + + int XML_GetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long *value); + int XML_SetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long value); + int XML_GetFeatureDefault(enum XML_FeatureEnum feature, + long *value); + int XML_SetFeatureDefault(enum XML_FeatureEnum feature, + long value); + +XML_FEATURE_MAX_ENTITY_INDIRECTIONS + Limit the amount of indirections that are allowed to occur during the + expansion of a nested entity. A counter starts when an entity reference + is encountered. It resets after the entity is fully expanded. The limit + protects the parser against exponential entity expansion attacks (aka + billion laughs attack). When the limit is exceeded the parser stops and + fails with `XML_ERROR_ENTITY_INDIRECTIONS`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 40 + +XML_FEATURE_MAX_ENTITY_EXPANSIONS + Limit the total length of all entity expansions throughout the entire + document. The lengths of all entities are accumulated in a parser variable. + The setting protects against quadratic blowup attacks (lots of expansions + of a large entity declaration). When the sum of all entities exceeds + the limit, the parser stops and fails with `XML_ERROR_ENTITY_EXPANSION`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 8 MiB + +XML_FEATURE_RESET_DTD + Reset all DTD information after the block has been parsed. When + the flag is set (default: false) all DTD information after the + endDoctypeDeclHandler has been called. The flag can be set inside the + endDoctypeDeclHandler. Without DTD information any entity reference in + the document body leads to `XML_ERROR_UNDEFINED_ENTITY`. + + Supported range + 0, 1 + Default + 0 + + +How to avoid XML vulnerabilities +================================ + +Best practices +-------------- + +* Don't allow DTDs +* Don't expand entities +* Don't resolve externals +* Limit parse depth +* Limit total input size +* Limit parse time +* Favor a SAX or iterparse-like parser for potential large data +* Validate and properly quote arguments to XSL transformations and + XPath queries +* Don't use XPath expression from untrusted sources +* Don't apply XSL transformations that come untrusted sources + +(based on Brad Hill's `Attacking XML Security`_) + + +Other things to consider +======================== + +XML, XML parsers and processing libraries have more features and possible +issue that could lead to DoS vulnerabilities or security exploits in +applications. I have compiled an incomplete list of theoretical issues that +need further research and more attention. The list is deliberately pessimistic +and a bit paranoid, too. It contains things that might go wrong under daffy +circumstances. + + +attribute blowup / hash collision attack +---------------------------------------- + +XML parsers may use an algorithm with quadratic runtime O(n :sup:`2`) to +handle attributes and namespaces. If it uses hash tables (dictionaries) to +store attributes and namespaces the implementation may be vulnerable to +hash collision attacks, thus reducing the performance to O(n :sup:`2`) again. +In either case an attacker is able to forge a denial of service attack with +an XML document that contains thousands upon thousands of attributes in +a single node. + +I haven't researched yet if expat, pyexpat or libxml2 are vulnerable. + + +decompression bomb +------------------ + +The issue of decompression bombs (aka `ZIP bomb`_) apply to all XML libraries +that can parse compressed XML stream like gzipped HTTP streams or LZMA-ed +files. For an attacker it can reduce the amount of transmitted data by three +magnitudes or more. Gzip is able to compress 1 GiB zeros to roughly 1 MB, +lzma is even better:: + + $ dd if=/dev/zero bs=1M count=1024 | gzip > zeros.gz + $ dd if=/dev/zero bs=1M count=1024 | lzma -z > zeros.xy + $ ls -sh zeros.* + 1020K zeros.gz + 148K zeros.xy + +None of Python's standard XML libraries decompress streams except for +``xmlrpclib``. The module is vulnerable +to decompression bombs. + +lxml can load and process compressed data through libxml2 transparently. +libxml2 can handle even very large blobs of compressed data efficiently +without using too much memory. But it doesn't protect applications from +decompression bombs. A carefully written SAX or iterparse-like approach can +be safe. + + +Processing Instruction +---------------------- + +`PI`_'s like:: + + + +may impose more threats for XML processing. It depends if and how a +processor handles processing instructions. The issue of URL retrieval with +network or local file access apply to processing instructions, too. + + +Other DTD features +------------------ + +`DTD`_ has more features like ````. I haven't researched how +these features may be a security threat. + + +XPath +----- + +XPath statements may introduce DoS vulnerabilities. Code should never execute +queries from untrusted sources. An attacker may also be able to create a XML +document that makes certain XPath queries costly or resource hungry. + + +XPath injection attacks +----------------------- + +XPath injeciton attacks pretty much work like SQL injection attacks. +Arguments to XPath queries must be quoted and validated properly, especially +when they are taken from the user. The page `Avoid the dangers of XPath injection`_ +list some ramifications of XPath injections. + +Python's standard library doesn't have XPath support. Lxml supports +parameterized XPath queries which does proper quoting. You just have to use +its xpath() method correctly:: + + # DON'T + >>> tree.xpath("/tag[@id='%s']" % value) + + # instead do + >>> tree.xpath("/tag[@id=$tagid]", tagid=name) + + +XInclude +-------- + +`XML Inclusion`_ is another way to load and include external files:: + + + + + +This feature should be disabled when XML files from an untrusted source are +processed. Some Python XML libraries and libxml2 support XInclude but don't +have an option to sandbox inclusion and limit it to allowed directories. + + +XMLSchema location +------------------ + +A validating XML parser may download schema files from the information in a +``xsi:schemaLocation`` attribute. + +:: + + + + + +XSL Transformation +------------------ + +You should keep in mind that XSLT is a Turing complete language. Never +process XSLT code from unknown or untrusted source! XSLT processors may +allow you to interact with external resources in ways you can't even imagine. +Some processors even support extensions that allow read/write access to file +system, access to JRE objects or scripting with Jython. + +Example from `Attacking XML Security`_ for Xalan-J:: + + + + + + + + + + + +Related CVEs +============ + +CVE-2013-1664 + Unrestricted entity expansion induces DoS vulnerabilities in Python XML + libraries (XML bomb) + +CVE-2013-1665 + External entity expansion in Python XML libraries inflicts potential + security flaws and DoS vulnerabilities + + +Other languages / frameworks +============================= + +Several other programming languages and frameworks are vulnerable as well. A +couple of them are affected by the fact that libxml2 up to 2.9.0 has no +protection against quadratic blowup attacks. Most of them have potential +dangerous default settings for entity expansion and external entities, too. + +Perl +---- + +Perl's XML::Simple is vulnerable to quadratic entity expansion and external +entity expansion (both local and remote). + + +Ruby +---- + +Ruby's REXML document parser is vulnerable to entity expansion attacks +(both quadratic and exponential) but it doesn't do external entity +expansion by default. In order to counteract entity expansion you have to +disable the feature:: + + REXML::Document.entity_expansion_limit = 0 + +libxml-ruby and hpricot don't expand entities in their default configuration. + + +PHP +--- + +PHP's SimpleXML API is vulnerable to quadratic entity expansion and loads +entites from local and remote resources. The option ``LIBXML_NONET`` disables +network access but still allows local file access. ``LIBXML_NOENT`` seems to +have no effect on entity expansion in PHP 5.4.6. + + +C# / .NET / Mono +---------------- + +Information in `XML DoS and Defenses (MSDN)`_ suggest that .NET is +vulnerable with its default settings. The article contains code snippets +how to create a secure XML reader:: + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.ProhibitDtd = false; + settings.MaxCharactersFromEntities = 1024; + settings.XmlResolver = null; + XmlReader reader = XmlReader.Create(stream, settings); + + +Java +---- + +Untested. The documentation of Xerces and its `Xerces SecurityMananger`_ +sounds like Xerces is also vulnerable to billion laugh attacks with its +default settings. It also does entity resolving when an +``org.xml.sax.EntityResolver`` is configured. I'm not yet sure about the +default setting here. + +Java specialists suggest to have a custom builder factory:: + + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setXIncludeAware(False); + builderFactory.setExpandEntityReferences(False); + builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, True); + # either + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", True); + # or if you need DTDs + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", False); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", False); + + +TODO +==== + +* DOM: Use xml.dom.xmlbuilder options for entity handling +* SAX: take feature_external_ges and feature_external_pes (?) into account +* test experimental monkey patching of stdlib modules +* improve documentation + + +License +======= + +Copyright (c) 2013-2017 by Christian Heimes + +Licensed to PSF under a Contributor Agreement. + +See http://www.python.org/psf/license for licensing details. + + +Acknowledgements +================ + +Brett Cannon (Python Core developer) + review and code cleanup + +Antoine Pitrou (Python Core developer) + code review + +Aaron Patterson, Ben Murphy and Michael Koziarski (Ruby community) + Many thanks to Aaron, Ben and Michael from the Ruby community for their + report and assistance. + +Thierry Carrez (OpenStack) + Many thanks to Thierry for his report to the Python Security Response + Team on behalf of the OpenStack security team. + +Carl Meyer (Django) + Many thanks to Carl for his report to PSRT on behalf of the Django security + team. + +Daniel Veillard (libxml2) + Many thanks to Daniel for his insight and assistance with libxml2. + +semantics GmbH (http://www.semantics.de/) + Many thanks to my employer semantics for letting me work on the issue + during working hours as part of semantics's open source initiative. + + +References +========== + +* `XML DoS and Defenses (MSDN)`_ +* `Billion Laughs`_ on Wikipedia +* `ZIP bomb`_ on Wikipedia +* `Configure SAX parsers for secure processing`_ +* `Testing for XML Injection`_ + +.. _defusedxml package: https://bitbucket.org/tiran/defusedxml +.. _defusedxml on PyPI: https://pypi.python.org/pypi/defusedxml +.. _defusedexpat package: https://bitbucket.org/tiran/defusedexpat +.. _defusedexpat on PyPI: https://pypi.python.org/pypi/defusedexpat +.. _modified expat: https://bitbucket.org/tiran/expat +.. _expat parser: http://expat.sourceforge.net/ +.. _Attacking XML Security: https://www.isecpartners.com/media/12976/iSEC-HILL-Attacking-XML-Security-bh07.pdf +.. _Billion Laughs: http://en.wikipedia.org/wiki/Billion_laughs +.. _XML DoS and Defenses (MSDN): http://msdn.microsoft.com/en-us/magazine/ee335713.aspx +.. _ZIP bomb: http://en.wikipedia.org/wiki/Zip_bomb +.. _DTD: http://en.wikipedia.org/wiki/Document_Type_Definition +.. _PI: https://en.wikipedia.org/wiki/Processing_Instruction +.. _Avoid the dangers of XPath injection: http://www.ibm.com/developerworks/xml/library/x-xpathinjection/index.html +.. _Configure SAX parsers for secure processing: http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html +.. _Testing for XML Injection: https://www.owasp.org/index.php/Testing_for_XML_Injection_(OWASP-DV-008) +.. _Xerces SecurityMananger: http://xerces.apache.org/xerces2-j/javadocs/xerces2/org/apache/xerces/util/SecurityManager.html +.. _XML Inclusion: http://www.w3.org/TR/xinclude/#include_element + +Changelog +========= + +defusedxml 0.5.0 +---------------- + +*Release date: 07-Feb-2017* + +- No changes + +defusedxml 0.5.0.rc1 +-------------------- + +*Release date: 28-Jan-2017* + +- Add compatibility with Python 3.6 +- Drop support for Python 2.6, 3.1, 3.2, 3.3 +- Fix lxml tests (XMLSyntaxError: Detected an entity reference loop) + + +defusedxml 0.4.1 +---------------- + +*Release date: 28-Mar-2013* + +- Add more demo exploits, e.g. python_external.py and Xalan XSLT demos. +- Improved documentation. + + +defusedxml 0.4 +-------------- + +*Release date: 25-Feb-2013* + +- As per http://seclists.org/oss-sec/2013/q1/340 please REJECT + CVE-2013-0278, CVE-2013-0279 and CVE-2013-0280 and use CVE-2013-1664, + CVE-2013-1665 for OpenStack/etc. +- Add missing parser_list argument to sax.make_parser(). The argument is + ignored, though. (thanks to Florian Apolloner) +- Add demo exploit for external entity attack on Python's SAX parser, XML-RPC + and WebDAV. + + +defusedxml 0.3 +-------------- + +*Release date: 19-Feb-2013* + +- Improve documentation + + +defusedxml 0.2 +-------------- + +*Release date: 15-Feb-2013* + +- Rename ExternalEntitiesForbidden to ExternalReferenceForbidden +- Rename defusedxml.lxml.check_dtd() to check_docinfo() +- Unify argument names in callbacks +- Add arguments and formatted representation to exceptions +- Add forbid_external argument to all functions and classs +- More tests +- LOTS of documentation +- Add example code for other languages (Ruby, Perl, PHP) and parsers (Genshi) +- Add protection against XML and gzip attacks to xmlrpclib + +defusedxml 0.1 +-------------- + +*Release date: 08-Feb-2013* + +- Initial and internal release for PSRT review + + diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/METADATA new file mode 100644 index 0000000..323d083 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/METADATA @@ -0,0 +1,875 @@ +Metadata-Version: 2.0 +Name: defusedxml +Version: 0.5.0 +Summary: XML bomb protection for Python stdlib modules +Home-page: https://github.com/tiran/defusedxml +Author: Christian Heimes +Author-email: christian@python.org +License: PSFL +Download-URL: https://pypi.python.org/pypi/defusedxml +Keywords: xml bomb DoS +Platform: all +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Python Software Foundation License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Topic :: Text Processing :: Markup :: XML + +=================================================== +defusedxml -- defusing XML bombs and other exploits +=================================================== + + "It's just XML, what could probably go wrong?" + +Christian Heimes + +Synopsis +======== + +The results of an attack on a vulnerable XML library can be fairly dramatic. +With just a few hundred **Bytes** of XML data an attacker can occupy several +**Gigabytes** of memory within **seconds**. An attacker can also keep +CPUs busy for a long time with a small to medium size request. Under some +circumstances it is even possible to access local files on your +server, to circumvent a firewall, or to abuse services to rebound attacks to +third parties. + +The attacks use and abuse less common features of XML and its parsers. The +majority of developers are unacquainted with features such as processing +instructions and entity expansions that XML inherited from SGML. At best +they know about ```` from experience with HTML but they are not +aware that a document type definition (DTD) can generate an HTTP request +or load a file from the file system. + +None of the issues is new. They have been known for a long time. Billion +laughs was first reported in 2003. Nevertheless some XML libraries and +applications are still vulnerable and even heavy users of XML are +surprised by these features. It's hard to say whom to blame for the +situation. It's too short sighted to shift all blame on XML parsers and +XML libraries for using insecure default settings. After all they +properly implement XML specifications. Application developers must not rely +that a library is always configured for security and potential harmful data +by default. + + +.. contents:: Table of Contents + :depth: 2 + + +Attack vectors +============== + +billion laughs / exponential entity expansion +--------------------------------------------- + +The `Billion Laughs`_ attack -- also known as exponential entity expansion -- +uses multiple levels of nested entities. The original example uses 9 levels +of 10 expansions in each level to expand the string ``lol`` to a string of +3 * 10 :sup:`9` bytes, hence the name "billion laughs". The resulting string +occupies 3 GB (2.79 GiB) of memory; intermediate strings require additional +memory. Because most parsers don't cache the intermediate step for every +expansion it is repeated over and over again. It increases the CPU load even +more. + +An XML document of just a few hundred bytes can disrupt all services on a +machine within seconds. + +Example XML:: + + + + + + ]> + &d; + + +quadratic blowup entity expansion +--------------------------------- + +A quadratic blowup attack is similar to a `Billion Laughs`_ attack; it abuses +entity expansion, too. Instead of nested entities it repeats one large entity +with a couple of thousand chars over and over again. The attack isn't as +efficient as the exponential case but it avoids triggering countermeasures of +parsers against heavily nested entities. Some parsers limit the depth and +breadth of a single entity but not the total amount of expanded text +throughout an entire XML document. + +A medium-sized XML document with a couple of hundred kilobytes can require a +couple of hundred MB to several GB of memory. When the attack is combined +with some level of nested expansion an attacker is able to achieve a higher +ratio of success. + +:: + + + ]> + &a;&a;&a;... repeat + + +external entity expansion (remote) +---------------------------------- + +Entity declarations can contain more than just text for replacement. They can +also point to external resources by public identifiers or system identifiers. +System identifiers are standard URIs. When the URI is a URL (e.g. a +``http://`` locator) some parsers download the resource from the remote +location and embed them into the XML document verbatim. + +Simple example of a parsed external entity:: + + + ]> + + +The case of parsed external entities works only for valid XML content. The +XML standard also supports unparsed external entities with a +``NData declaration``. + +External entity expansion opens the door to plenty of exploits. An attacker +can abuse a vulnerable XML library and application to rebound and forward +network requests with the IP address of the server. It highly depends +on the parser and the application what kind of exploit is possible. For +example: + +* An attacker can circumvent firewalls and gain access to restricted + resources as all the requests are made from an internal and trustworthy + IP address, not from the outside. +* An attacker can abuse a service to attack, spy on or DoS your servers but + also third party services. The attack is disguised with the IP address of + the server and the attacker is able to utilize the high bandwidth of a big + machine. +* An attacker can exhaust additional resources on the machine, e.g. with + requests to a service that doesn't respond or responds with very large + files. +* An attacker may gain knowledge, when, how often and from which IP address + a XML document is accessed. +* An attacker could send mail from inside your network if the URL handler + supports ``smtp://`` URIs. + + +external entity expansion (local file) +-------------------------------------- + +External entities with references to local files are a sub-case of external +entity expansion. It's listed as an extra attack because it deserves extra +attention. Some XML libraries such as lxml disable network access by default +but still allow entity expansion with local file access by default. Local +files are either referenced with a ``file://`` URL or by a file path (either +relative or absolute). + +An attacker may be able to access and download all files that can be read by +the application process. This may include critical configuration files, too. + +:: + + + ]> + + + +DTD retrieval +------------- + +This case is similar to external entity expansion, too. Some XML libraries +like Python's xml.dom.pulldom retrieve document type definitions from remote +or local locations. Several attack scenarios from the external entity case +apply to this issue as well. + +:: + + + + + + text + + + +Python XML Libraries +==================== + +.. csv-table:: vulnerabilities and features + :header: "kind", "sax", "etree", "minidom", "pulldom", "xmlrpc", "lxml", "genshi" + :widths: 24, 7, 8, 8, 7, 8, 8, 8 + :stub-columns: 0 + + "billion laughs", "**True**", "**True**", "**True**", "**True**", "**True**", "False (1)", "False (5)" + "quadratic blowup", "**True**", "**True**", "**True**", "**True**", "**True**", "**True**", "False (5)" + "external entity expansion (remote)", "**True**", "False (3)", "False (4)", "**True**", "false", "False (1)", "False (5)" + "external entity expansion (local file)", "**True**", "False (3)", "False (4)", "**True**", "false", "**True**", "False (5)" + "DTD retrieval", "**True**", "False", "False", "**True**", "false", "False (1)", "False" + "gzip bomb", "False", "False", "False", "False", "**True**", "**partly** (2)", "False" + "xpath support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xsl(t) support (7)", "False", "False", "False", "False", "False", "**True**", "False" + "xinclude support (7)", "False", "**True** (6)", "False", "False", "False", "**True** (6)", "**True**" + "C library", "expat", "expat", "expat", "expat", "expat", "libxml2", "expat" + +1. Lxml is protected against billion laughs attacks and doesn't do network + lookups by default. +2. libxml2 and lxml are not directly vulnerable to gzip decompression bombs + but they don't protect you against them either. +3. xml.etree doesn't expand entities and raises a ParserError when an entity + occurs. +4. minidom doesn't expand entities and simply returns the unexpanded entity + verbatim. +5. genshi.input of genshi 0.6 doesn't support entity expansion and raises a + ParserError when an entity occurs. +6. Library has (limited) XInclude support but requires an additional step to + process inclusion. +7. These are features but they may introduce exploitable holes, see + `Other things to consider`_ + + +Settings in standard library +---------------------------- + + +xml.sax.handler Features +........................ + +feature_external_ges (http://xml.org/sax/features/external-general-entities) + disables external entity expansion + +feature_external_pes (http://xml.org/sax/features/external-parameter-entities) + the option is ignored and doesn't modify any functionality + +DOM xml.dom.xmlbuilder.Options +.............................. + +external_parameter_entities + ignored + +external_general_entities + ignored + +external_dtd_subset + ignored + +entities + unsure + + +defusedxml +========== + +The `defusedxml package`_ (`defusedxml on PyPI`_) +contains several Python-only workarounds and fixes +for denial of service and other vulnerabilities in Python's XML libraries. +In order to benefit from the protection you just have to import and use the +listed functions / classes from the right defusedxml module instead of the +original module. Merely `defusedxml.xmlrpc`_ is implemented as monkey patch. + +Instead of:: + + >>> from xml.etree.ElementTree import parse + >>> et = parse(xmlfile) + +alter code to:: + + >>> from defusedxml.ElementTree import parse + >>> et = parse(xmlfile) + +Additionally the package has an **untested** function to monkey patch +all stdlib modules with ``defusedxml.defuse_stdlib()``. + +All functions and parser classes accept three additional keyword arguments. +They return either the same objects as the original functions or compatible +subclasses. + +forbid_dtd (default: False) + disallow XML with a ```` processing instruction and raise a + *DTDForbidden* exception when a DTD processing instruction is found. + +forbid_entities (default: True) + disallow XML with ```` declarations inside the DTD and raise an + *EntitiesForbidden* exception when an entity is declared. + +forbid_external (default: True) + disallow any access to remote or local resources in external entities + or DTD and raising an *ExternalReferenceForbidden* exception when a DTD + or entity references an external resource. + + +defusedxml (package) +-------------------- + +DefusedXmlException, DTDForbidden, EntitiesForbidden, +ExternalReferenceForbidden, NotSupportedError + +defuse_stdlib() (*experimental*) + + +defusedxml.cElementTree +----------------------- + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.ElementTree +----------------------- + +parse(), iterparse(), fromstring(), XMLParser + + +defusedxml.expatreader +---------------------- + +create_parser(), DefusedExpatParser + + +defusedxml.sax +-------------- + +parse(), parseString(), create_parser() + + +defusedxml.expatbuilder +----------------------- + +parse(), parseString(), DefusedExpatBuilder, DefusedExpatBuilderNS + + +defusedxml.minidom +------------------ + +parse(), parseString() + + +defusedxml.pulldom +------------------ + +parse(), parseString() + + +defusedxml.xmlrpc +----------------- + +The fix is implemented as monkey patch for the stdlib's xmlrpc package (3.x) +or xmlrpclib module (2.x). The function `monkey_patch()` enables the fixes, +`unmonkey_patch()` removes the patch and puts the code in its former state. + +The monkey patch protects against XML related attacks as well as +decompression bombs and excessively large requests or responses. The default +setting is 30 MB for requests, responses and gzip decompression. You can +modify the default by changing the module variable `MAX_DATA`. A value of +`-1` disables the limit. + + +defusedxml.lxml +--------------- + +The module acts as an *example* how you could protect code that uses +lxml.etree. It implements a custom Element class that filters out +Entity instances, a custom parser factory and a thread local storage for +parser instances. It also has a check_docinfo() function which inspects +a tree for internal or external DTDs and entity declarations. In order to +check for entities lxml > 3.0 is required. + +parse(), fromstring() +RestrictedElement, GlobalParserTLS, getDefaultParser(), check_docinfo() + + +defusedexpat +============ + +The `defusedexpat package`_ (`defusedexpat on PyPI`_) +comes with binary extensions and a +`modified expat`_ libary instead of the standard `expat parser`_. It's +basically a stand-alone version of the patches for Python's standard +library C extensions. + +Modifications in expat +---------------------- + +new definitions:: + + XML_BOMB_PROTECTION + XML_DEFAULT_MAX_ENTITY_INDIRECTIONS + XML_DEFAULT_MAX_ENTITY_EXPANSIONS + XML_DEFAULT_RESET_DTD + +new XML_FeatureEnum members:: + + XML_FEATURE_MAX_ENTITY_INDIRECTIONS + XML_FEATURE_MAX_ENTITY_EXPANSIONS + XML_FEATURE_IGNORE_DTD + +new XML_Error members:: + + XML_ERROR_ENTITY_INDIRECTIONS + XML_ERROR_ENTITY_EXPANSION + +new API functions:: + + int XML_GetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long *value); + int XML_SetFeature(XML_Parser parser, + enum XML_FeatureEnum feature, + long value); + int XML_GetFeatureDefault(enum XML_FeatureEnum feature, + long *value); + int XML_SetFeatureDefault(enum XML_FeatureEnum feature, + long value); + +XML_FEATURE_MAX_ENTITY_INDIRECTIONS + Limit the amount of indirections that are allowed to occur during the + expansion of a nested entity. A counter starts when an entity reference + is encountered. It resets after the entity is fully expanded. The limit + protects the parser against exponential entity expansion attacks (aka + billion laughs attack). When the limit is exceeded the parser stops and + fails with `XML_ERROR_ENTITY_INDIRECTIONS`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 40 + +XML_FEATURE_MAX_ENTITY_EXPANSIONS + Limit the total length of all entity expansions throughout the entire + document. The lengths of all entities are accumulated in a parser variable. + The setting protects against quadratic blowup attacks (lots of expansions + of a large entity declaration). When the sum of all entities exceeds + the limit, the parser stops and fails with `XML_ERROR_ENTITY_EXPANSION`. + A value of 0 disables the protection. + + Supported range + 0 .. UINT_MAX + Default + 8 MiB + +XML_FEATURE_RESET_DTD + Reset all DTD information after the block has been parsed. When + the flag is set (default: false) all DTD information after the + endDoctypeDeclHandler has been called. The flag can be set inside the + endDoctypeDeclHandler. Without DTD information any entity reference in + the document body leads to `XML_ERROR_UNDEFINED_ENTITY`. + + Supported range + 0, 1 + Default + 0 + + +How to avoid XML vulnerabilities +================================ + +Best practices +-------------- + +* Don't allow DTDs +* Don't expand entities +* Don't resolve externals +* Limit parse depth +* Limit total input size +* Limit parse time +* Favor a SAX or iterparse-like parser for potential large data +* Validate and properly quote arguments to XSL transformations and + XPath queries +* Don't use XPath expression from untrusted sources +* Don't apply XSL transformations that come untrusted sources + +(based on Brad Hill's `Attacking XML Security`_) + + +Other things to consider +======================== + +XML, XML parsers and processing libraries have more features and possible +issue that could lead to DoS vulnerabilities or security exploits in +applications. I have compiled an incomplete list of theoretical issues that +need further research and more attention. The list is deliberately pessimistic +and a bit paranoid, too. It contains things that might go wrong under daffy +circumstances. + + +attribute blowup / hash collision attack +---------------------------------------- + +XML parsers may use an algorithm with quadratic runtime O(n :sup:`2`) to +handle attributes and namespaces. If it uses hash tables (dictionaries) to +store attributes and namespaces the implementation may be vulnerable to +hash collision attacks, thus reducing the performance to O(n :sup:`2`) again. +In either case an attacker is able to forge a denial of service attack with +an XML document that contains thousands upon thousands of attributes in +a single node. + +I haven't researched yet if expat, pyexpat or libxml2 are vulnerable. + + +decompression bomb +------------------ + +The issue of decompression bombs (aka `ZIP bomb`_) apply to all XML libraries +that can parse compressed XML stream like gzipped HTTP streams or LZMA-ed +files. For an attacker it can reduce the amount of transmitted data by three +magnitudes or more. Gzip is able to compress 1 GiB zeros to roughly 1 MB, +lzma is even better:: + + $ dd if=/dev/zero bs=1M count=1024 | gzip > zeros.gz + $ dd if=/dev/zero bs=1M count=1024 | lzma -z > zeros.xy + $ ls -sh zeros.* + 1020K zeros.gz + 148K zeros.xy + +None of Python's standard XML libraries decompress streams except for +``xmlrpclib``. The module is vulnerable +to decompression bombs. + +lxml can load and process compressed data through libxml2 transparently. +libxml2 can handle even very large blobs of compressed data efficiently +without using too much memory. But it doesn't protect applications from +decompression bombs. A carefully written SAX or iterparse-like approach can +be safe. + + +Processing Instruction +---------------------- + +`PI`_'s like:: + + + +may impose more threats for XML processing. It depends if and how a +processor handles processing instructions. The issue of URL retrieval with +network or local file access apply to processing instructions, too. + + +Other DTD features +------------------ + +`DTD`_ has more features like ````. I haven't researched how +these features may be a security threat. + + +XPath +----- + +XPath statements may introduce DoS vulnerabilities. Code should never execute +queries from untrusted sources. An attacker may also be able to create a XML +document that makes certain XPath queries costly or resource hungry. + + +XPath injection attacks +----------------------- + +XPath injeciton attacks pretty much work like SQL injection attacks. +Arguments to XPath queries must be quoted and validated properly, especially +when they are taken from the user. The page `Avoid the dangers of XPath injection`_ +list some ramifications of XPath injections. + +Python's standard library doesn't have XPath support. Lxml supports +parameterized XPath queries which does proper quoting. You just have to use +its xpath() method correctly:: + + # DON'T + >>> tree.xpath("/tag[@id='%s']" % value) + + # instead do + >>> tree.xpath("/tag[@id=$tagid]", tagid=name) + + +XInclude +-------- + +`XML Inclusion`_ is another way to load and include external files:: + + + + + +This feature should be disabled when XML files from an untrusted source are +processed. Some Python XML libraries and libxml2 support XInclude but don't +have an option to sandbox inclusion and limit it to allowed directories. + + +XMLSchema location +------------------ + +A validating XML parser may download schema files from the information in a +``xsi:schemaLocation`` attribute. + +:: + + + + + +XSL Transformation +------------------ + +You should keep in mind that XSLT is a Turing complete language. Never +process XSLT code from unknown or untrusted source! XSLT processors may +allow you to interact with external resources in ways you can't even imagine. +Some processors even support extensions that allow read/write access to file +system, access to JRE objects or scripting with Jython. + +Example from `Attacking XML Security`_ for Xalan-J:: + + + + + + + + + + + +Related CVEs +============ + +CVE-2013-1664 + Unrestricted entity expansion induces DoS vulnerabilities in Python XML + libraries (XML bomb) + +CVE-2013-1665 + External entity expansion in Python XML libraries inflicts potential + security flaws and DoS vulnerabilities + + +Other languages / frameworks +============================= + +Several other programming languages and frameworks are vulnerable as well. A +couple of them are affected by the fact that libxml2 up to 2.9.0 has no +protection against quadratic blowup attacks. Most of them have potential +dangerous default settings for entity expansion and external entities, too. + +Perl +---- + +Perl's XML::Simple is vulnerable to quadratic entity expansion and external +entity expansion (both local and remote). + + +Ruby +---- + +Ruby's REXML document parser is vulnerable to entity expansion attacks +(both quadratic and exponential) but it doesn't do external entity +expansion by default. In order to counteract entity expansion you have to +disable the feature:: + + REXML::Document.entity_expansion_limit = 0 + +libxml-ruby and hpricot don't expand entities in their default configuration. + + +PHP +--- + +PHP's SimpleXML API is vulnerable to quadratic entity expansion and loads +entites from local and remote resources. The option ``LIBXML_NONET`` disables +network access but still allows local file access. ``LIBXML_NOENT`` seems to +have no effect on entity expansion in PHP 5.4.6. + + +C# / .NET / Mono +---------------- + +Information in `XML DoS and Defenses (MSDN)`_ suggest that .NET is +vulnerable with its default settings. The article contains code snippets +how to create a secure XML reader:: + + XmlReaderSettings settings = new XmlReaderSettings(); + settings.ProhibitDtd = false; + settings.MaxCharactersFromEntities = 1024; + settings.XmlResolver = null; + XmlReader reader = XmlReader.Create(stream, settings); + + +Java +---- + +Untested. The documentation of Xerces and its `Xerces SecurityMananger`_ +sounds like Xerces is also vulnerable to billion laugh attacks with its +default settings. It also does entity resolving when an +``org.xml.sax.EntityResolver`` is configured. I'm not yet sure about the +default setting here. + +Java specialists suggest to have a custom builder factory:: + + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + builderFactory.setXIncludeAware(False); + builderFactory.setExpandEntityReferences(False); + builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, True); + # either + builderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", True); + # or if you need DTDs + builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", False); + builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", False); + builderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", False); + + +TODO +==== + +* DOM: Use xml.dom.xmlbuilder options for entity handling +* SAX: take feature_external_ges and feature_external_pes (?) into account +* test experimental monkey patching of stdlib modules +* improve documentation + + +License +======= + +Copyright (c) 2013-2017 by Christian Heimes + +Licensed to PSF under a Contributor Agreement. + +See http://www.python.org/psf/license for licensing details. + + +Acknowledgements +================ + +Brett Cannon (Python Core developer) + review and code cleanup + +Antoine Pitrou (Python Core developer) + code review + +Aaron Patterson, Ben Murphy and Michael Koziarski (Ruby community) + Many thanks to Aaron, Ben and Michael from the Ruby community for their + report and assistance. + +Thierry Carrez (OpenStack) + Many thanks to Thierry for his report to the Python Security Response + Team on behalf of the OpenStack security team. + +Carl Meyer (Django) + Many thanks to Carl for his report to PSRT on behalf of the Django security + team. + +Daniel Veillard (libxml2) + Many thanks to Daniel for his insight and assistance with libxml2. + +semantics GmbH (http://www.semantics.de/) + Many thanks to my employer semantics for letting me work on the issue + during working hours as part of semantics's open source initiative. + + +References +========== + +* `XML DoS and Defenses (MSDN)`_ +* `Billion Laughs`_ on Wikipedia +* `ZIP bomb`_ on Wikipedia +* `Configure SAX parsers for secure processing`_ +* `Testing for XML Injection`_ + +.. _defusedxml package: https://bitbucket.org/tiran/defusedxml +.. _defusedxml on PyPI: https://pypi.python.org/pypi/defusedxml +.. _defusedexpat package: https://bitbucket.org/tiran/defusedexpat +.. _defusedexpat on PyPI: https://pypi.python.org/pypi/defusedexpat +.. _modified expat: https://bitbucket.org/tiran/expat +.. _expat parser: http://expat.sourceforge.net/ +.. _Attacking XML Security: https://www.isecpartners.com/media/12976/iSEC-HILL-Attacking-XML-Security-bh07.pdf +.. _Billion Laughs: http://en.wikipedia.org/wiki/Billion_laughs +.. _XML DoS and Defenses (MSDN): http://msdn.microsoft.com/en-us/magazine/ee335713.aspx +.. _ZIP bomb: http://en.wikipedia.org/wiki/Zip_bomb +.. _DTD: http://en.wikipedia.org/wiki/Document_Type_Definition +.. _PI: https://en.wikipedia.org/wiki/Processing_Instruction +.. _Avoid the dangers of XPath injection: http://www.ibm.com/developerworks/xml/library/x-xpathinjection/index.html +.. _Configure SAX parsers for secure processing: http://www.ibm.com/developerworks/xml/library/x-tipcfsx/index.html +.. _Testing for XML Injection: https://www.owasp.org/index.php/Testing_for_XML_Injection_(OWASP-DV-008) +.. _Xerces SecurityMananger: http://xerces.apache.org/xerces2-j/javadocs/xerces2/org/apache/xerces/util/SecurityManager.html +.. _XML Inclusion: http://www.w3.org/TR/xinclude/#include_element + +Changelog +========= + +defusedxml 0.5.0 +---------------- + +*Release date: 07-Feb-2017* + +- No changes + +defusedxml 0.5.0.rc1 +-------------------- + +*Release date: 28-Jan-2017* + +- Add compatibility with Python 3.6 +- Drop support for Python 2.6, 3.1, 3.2, 3.3 +- Fix lxml tests (XMLSyntaxError: Detected an entity reference loop) + + +defusedxml 0.4.1 +---------------- + +*Release date: 28-Mar-2013* + +- Add more demo exploits, e.g. python_external.py and Xalan XSLT demos. +- Improved documentation. + + +defusedxml 0.4 +-------------- + +*Release date: 25-Feb-2013* + +- As per http://seclists.org/oss-sec/2013/q1/340 please REJECT + CVE-2013-0278, CVE-2013-0279 and CVE-2013-0280 and use CVE-2013-1664, + CVE-2013-1665 for OpenStack/etc. +- Add missing parser_list argument to sax.make_parser(). The argument is + ignored, though. (thanks to Florian Apolloner) +- Add demo exploit for external entity attack on Python's SAX parser, XML-RPC + and WebDAV. + + +defusedxml 0.3 +-------------- + +*Release date: 19-Feb-2013* + +- Improve documentation + + +defusedxml 0.2 +-------------- + +*Release date: 15-Feb-2013* + +- Rename ExternalEntitiesForbidden to ExternalReferenceForbidden +- Rename defusedxml.lxml.check_dtd() to check_docinfo() +- Unify argument names in callbacks +- Add arguments and formatted representation to exceptions +- Add forbid_external argument to all functions and classs +- More tests +- LOTS of documentation +- Add example code for other languages (Ruby, Perl, PHP) and parsers (Genshi) +- Add protection against XML and gzip attacks to xmlrpclib + +defusedxml 0.1 +-------------- + +*Release date: 08-Feb-2013* + +- Initial and internal release for PSRT review + + diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/RECORD new file mode 100644 index 0000000..d9e38eb --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/RECORD @@ -0,0 +1,29 @@ +defusedxml/ElementTree.py,sha256=2U5ZI_-Hyaxa6ExNSos7PmXWWPKGDywM4TfdXfYp4Oc,3766 +defusedxml/__init__.py,sha256=C167JKHHezQZccLSaQWBvWLdLSLzgyGGYP6gk-6C6pI,1317 +defusedxml/cElementTree.py,sha256=UX_zROjHHbK_p0zgqrVYrMKJaS-S3JdV5Sb9flsjhrM,1039 +defusedxml/common.py,sha256=tDVeUaPPXwz1uGK6KywJXbFVIHOTwSRt4Ff8xCmzKog,4016 +defusedxml/expatbuilder.py,sha256=UuTvbviQ2V4BrjzK0B7IVO_3fFlzyM1vOCR8F2RZ-eY,3990 +defusedxml/expatreader.py,sha256=M2KdCKQug6i60qCr433epQo0bvqKHiO7l5CtN7pO74M,2307 +defusedxml/lxml.py,sha256=P8yxaANK5W7vtqrbQTU6CkF4A2-Lk8HbuG9hjbX62mQ,4976 +defusedxml/minidom.py,sha256=W2upditczs87W7MLTeW5k4rTmPEgTVipLxOvqpHCpKg,1865 +defusedxml/pulldom.py,sha256=L2e7wXKeo9QEJo2nRVT6DyNZzhGX6b49hH3Z3PfuQbw,1162 +defusedxml/sax.py,sha256=xEumsdgS5TZbpYz4qFuS2U26MR9yL6W7nKsX6EyrmCE,1464 +defusedxml/xmlrpc.py,sha256=68hIIn3edVOdYoO7URab8N7_HazFu1Ot__P6zCg2MEI,5417 +defusedxml-0.5.0.dist-info/DESCRIPTION.rst,sha256=4DYB6L167IcLWRB6x4HcSsklMhz5eXGwniejibFFA28,28590 +defusedxml-0.5.0.dist-info/METADATA,sha256=hMi15SBMg2B6FM_0VJMBk-dbCP1g390AOHsNUmLNIDg,29519 +defusedxml-0.5.0.dist-info/RECORD,, +defusedxml-0.5.0.dist-info/WHEEL,sha256=5wvfB7GvgZAbKBSE9uX9Zbi6LCL-_KgezgHblXhCRnM,113 +defusedxml-0.5.0.dist-info/metadata.json,sha256=haTsjz9D53YTHsd8eJyFvbFl-HXS-2KKTTOO7OlZpoM,1068 +defusedxml-0.5.0.dist-info/top_level.txt,sha256=QGHa90F50pVKhWSFlERI0jtSKtqDiGyfeZX7dQNZAAw,11 +defusedxml-0.5.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +defusedxml/__pycache__/expatbuilder.cpython-36.pyc,, +defusedxml/__pycache__/ElementTree.cpython-36.pyc,, +defusedxml/__pycache__/lxml.cpython-36.pyc,, +defusedxml/__pycache__/sax.cpython-36.pyc,, +defusedxml/__pycache__/cElementTree.cpython-36.pyc,, +defusedxml/__pycache__/common.cpython-36.pyc,, +defusedxml/__pycache__/xmlrpc.cpython-36.pyc,, +defusedxml/__pycache__/expatreader.cpython-36.pyc,, +defusedxml/__pycache__/pulldom.cpython-36.pyc,, +defusedxml/__pycache__/minidom.cpython-36.pyc,, +defusedxml/__pycache__/__init__.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/WHEEL new file mode 100644 index 0000000..7bf9daa --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0.a0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/metadata.json new file mode 100644 index 0000000..6e77612 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License", "Natural Language :: English", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Text Processing :: Markup :: XML"], "download_url": "https://pypi.python.org/pypi/defusedxml", "extensions": {"python.details": {"contacts": [{"email": "christian@python.org", "name": "Christian Heimes", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/tiran/defusedxml"}}}, "generator": "bdist_wheel (0.30.0.a0)", "keywords": ["xml", "bomb", "DoS"], "license": "PSFL", "metadata_version": "2.0", "name": "defusedxml", "platform": "all", "summary": "XML bomb protection for Python stdlib modules", "version": "0.5.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/top_level.txt new file mode 100644 index 0000000..36969f2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/top_level.txt @@ -0,0 +1 @@ +defusedxml diff --git a/flex/lib/python3.6/site-packages/defusedxml/ElementTree.py b/flex/lib/python3.6/site-packages/defusedxml/ElementTree.py new file mode 100644 index 0000000..41b2ea8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/ElementTree.py @@ -0,0 +1,112 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.etree.ElementTree facade +""" +from __future__ import print_function, absolute_import + +import sys +from xml.etree.ElementTree import TreeBuilder as _TreeBuilder +from xml.etree.ElementTree import parse as _parse +from xml.etree.ElementTree import tostring + +from .common import PY3 + + +if PY3: + import importlib +else: + from xml.etree.ElementTree import XMLParser as _XMLParser + from xml.etree.ElementTree import iterparse as _iterparse + from xml.etree.ElementTree import ParseError + + +from .common import (DTDForbidden, EntitiesForbidden, + ExternalReferenceForbidden, _generate_etree_functions) + +__origin__ = "xml.etree.ElementTree" + + +def _get_py3_cls(): + """Python 3.3 hides the pure Python code but defusedxml requires it. + + The code is based on test.support.import_fresh_module(). + """ + pymodname = "xml.etree.ElementTree" + cmodname = "_elementtree" + + pymod = sys.modules.pop(pymodname, None) + cmod = sys.modules.pop(cmodname, None) + + sys.modules[cmodname] = None + pure_pymod = importlib.import_module(pymodname) + if cmod is not None: + sys.modules[cmodname] = cmod + else: + sys.modules.pop(cmodname) + sys.modules[pymodname] = pymod + + _XMLParser = pure_pymod.XMLParser + _iterparse = pure_pymod.iterparse + ParseError = pure_pymod.ParseError + + return _XMLParser, _iterparse, ParseError + + +if PY3: + _XMLParser, _iterparse, ParseError = _get_py3_cls() + + +class DefusedXMLParser(_XMLParser): + + def __init__(self, html=0, target=None, encoding=None, + forbid_dtd=False, forbid_entities=True, + forbid_external=True): + # Python 2.x old style class + _XMLParser.__init__(self, html, target, encoding) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + if PY3: + parser = self.parser + else: + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + def defused_start_doctype_decl(self, name, sysid, pubid, + has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, + notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def defused_external_entity_ref_handler(self, context, base, sysid, + pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + +# aliases +XMLTreeBuilder = XMLParse = DefusedXMLParser + +parse, iterparse, fromstring = _generate_etree_functions(DefusedXMLParser, + _TreeBuilder, _parse, + _iterparse) +XML = fromstring + + +__all__ = ['XML', 'XMLParse', 'XMLTreeBuilder', 'fromstring', 'iterparse', + 'parse', 'tostring'] diff --git a/flex/lib/python3.6/site-packages/defusedxml/__init__.py b/flex/lib/python3.6/site-packages/defusedxml/__init__.py new file mode 100644 index 0000000..590a5a9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/__init__.py @@ -0,0 +1,45 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defuse XML bomb denial of service vulnerabilities +""" +from __future__ import print_function, absolute_import + +from .common import (DefusedXmlException, DTDForbidden, EntitiesForbidden, + ExternalReferenceForbidden, NotSupportedError, + _apply_defusing) + + +def defuse_stdlib(): + """Monkey patch and defuse all stdlib packages + + :warning: The monkey patch is an EXPERIMETNAL feature. + """ + defused = {} + + from . import cElementTree + from . import ElementTree + from . import minidom + from . import pulldom + from . import sax + from . import expatbuilder + from . import expatreader + from . import xmlrpc + + xmlrpc.monkey_patch() + defused[xmlrpc] = None + + for defused_mod in [cElementTree, ElementTree, minidom, pulldom, sax, + expatbuilder, expatreader]: + stdlib_mod = _apply_defusing(defused_mod) + defused[defused_mod] = stdlib_mod + + return defused + + +__version__ = "0.5.0" + +__all__ = ['DefusedXmlException', 'DTDForbidden', 'EntitiesForbidden', + 'ExternalReferenceForbidden', 'NotSupportedError'] diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/ElementTree.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/ElementTree.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e9ef31462e35a12a57cecbd8d926bad46ca973fe GIT binary patch literal 3085 zcmaJ@TW{M&7UoS9MeAleacnnjg*Gi#+eS)Zx7z?g5plZVn^(Wx@d^axr4JF z>$2P8HhY(E@oj#C-{d>|7T-Osv-i0Dz0u#BeRL$o(_HZ0%OoC%LJBc>6pKWp#WDEa zI2?ss_`kwQM&DBHiHy=B7*Eqt5oM|BgeQ3xPYV%5$t07-Lf4rm~;>cz~ht^dH|op)+*45fwr%C49a7NXkt19o0NKKKdq;ClTi& zRhy5}A}S)0UoGuFdI{k)jK33OAw@b8S9^QGsYrzkp{X`Z|@xUmZ^V($%)V*{;>zDPiR=Q<`C!_8c7_Eih=L<(2gOcPeys~BqR*rrG?0gK|Y;OGzV2s zgE7R;f+XY9Sp2C!(86zVn`#E4f)7FIao&$?tgK35A8G^9uFtcf7dK!eEzTyiQ3Xm}8e;=BhP^4lOr%`qLTZMH4n^i9ufT20fpToaEa z_p!f1ARTtmAEM`9fCz(`C|MTo@EUhtnarjFgfijq2KO=6_y%ucba{)nG1j>SLxVr9F3mQlL4ofJY;+fk`89ikcjh2C!OB zG1q7{&Mo;hc(1Gn4UD#Y3hHCFfw76Ph0&2?&^BfrZa_ctalb2f@J;0fL6k;C5Ga4F zGd|!2SDgiq+N3l8YGIN0lC)Q@E+10Wg9Wp{h*FRjp)3NPjf#tj2)G!te6`hOqbGMi0rm6lM57q_tepsyPYV`-f50`>sq5V=&ig9pO zX~A}vC9RT2VO!{j#By>GkCk&)B(ZW~1wf`eRK1L&#>#i$3i%$9_lfj~5dJH&M;xK> zA$kQzr%TYoctuKL2>uUqvy423yb*)?2?|WKn`DgHKJIV=N&>xyE0;g&(!gwl_L$y?X zf@AVi5YJ{%q~wq`kxdZw=D!?CDa)F{+NUJFS-FeiNncRzC`*zo)#X** zz)E#bQ`S_<+#-%{Y@Oed(nXS?vID~4!c0b|Q3|c-V$%joy2#|`ME*kL5QGc~;~2-T zLcDUJ@*Y$_311Q5^LrqU*|l(S(qlDk2iK?O?Oofky?w`Q+TFfq;hMz>$M((t0YTO% A2LJ#7 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4facc5a9ac298723bb2ee5b6a95582d27428912c GIT binary patch literal 1248 zcma)6O>f&a7$#+X%TAiD+hMy119tF$#_pO9O%V(m77rNE1Vd_Ifq;RKMcU;;QVJz` zjv<}8!+yvv`z!kiy6)7!u+v^j1)3fPj0PV(B;OCd&rA8qWDp>IMPjA=_Q`@6JG{NAj2etbBDUWp(LVSGNwKa zXh%v3F-jmwENFE=&?e7h31KJ0xjQh&w_}Ib3Zr zb4fEIm>$gUV3vOXaMTVmB8giJ5@9+LNLsDca~t@mHPT}00JmGa=Pq!kb$adrcU!mT zK5(z~dL97xTOXsw|0Q^hU@vTdpP}6s4Q{Mv)Yy&FxQ(~*&e6{g+n@@Y;5BZ;#@GdC z==S%YP=lZ`(Oz8S>W1CLMOtOA;#5)SOy8+gh`6k%;43)GZqg03B#dD^|25SLN`D?N zU$I!;cjqNUh!@vCF0OvKSS&BUeG#u&TGg5z_paIoVWzknXA8k3Q`J&42J7hkr6;-K zG?#GV6}1op9hK?M__7_`byaggnKr)tTC;Rmx;rU!kzG!s4r$XAJvJ~gaA4rjzykwE z20j2t#zPgP7eR-xHH{K)5Q6^iDA8L7B6FIAgLa1hiS$DV`H!5zOWvjxn{87)Tj!>L za<>jvmM70{PEO_NS0{5A8#P83Q24w#N9qpXFn}-}CX<(jSA(-kBdiJ)Udx$DmyU=51@= zKmc$#J?K0_Xr2+$g#Ga&F^umo`+>SfQJ>qxVMIMl#}czg6L$R0n2z`NU+_&^bN1k)6f@oc=d<-o z!6h%6OPqwaXxx~ZDqUUGrDz*Yb6J(pU?|c*Ja2QsIN;Q+R#0;!K~vTZf zi)Oyd*x^p#cKWn`FJ{#z6+Dq8YXyI*Z+U$W!7*biHu15#ujSZ*<(L>X9V;|{0uXTH A`2YX_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/common.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/common.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89471de9dd931130abd11ea2c857dac469eedc55 GIT binary patch literal 4376 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatreader.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatreader.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6146aee4231b7791fc0cb4ef09396503a129345 GIT binary patch literal 2423 zcmaJ@TW=dh6yCenj-5*rXnLVCSJgn`poXeJ2$ePoJOxC9g!W~$*%>FB?8P~=8xm`I z!F}d`@K^W=`^r-!egQ9V&aBsU+B9R$IkP*LJ>R)}<42uN>w~|zXErtMZ*8VoIDU#< z?xWzEr*WMdksceK5u2W=)3X^_vF$lHwzwTNV%Kxyrq_&HUJKtj-rRQ@Z*cde#$Dlz zO|Q+Hy!BG^I(+Vh)@zUMZ;L@L1@E56(Yo}{*TwnJ&p`NGKeh6Wbu|b{$ zS(ql}f`1^>D9;27<6#O}PcP?oXy1=MNCB5RZ8OXE_Wt&63I`$QA}N=4k}S+ZAz!_^ zyK|liNc`w0F%TecJdeaW5b#Tb#3G3m6ubL@gNhnyis^Ig@*@iakgM1*vk$?rX3&?s{Lh+{t1mfReA5-AW&qV*shyEaqFg;{{0R2ZN zUnFq4xwXMZe6%GupPX!N#aoXzH?SQh{r&zh9IhSYVZ_(IOage4ZJhRbm2w#1*`e}f zm3ZsJ3z)~xzJp!5C^U0kcknm5_hyLJYi9(M5!6Ue{Y+3Y<>SH}o4;e0R6f;#!qKzg z7OK)BkCe8&kRdPaVU9lamXCaiey%c<$^1ZyOnIXcWL$Z7f`!S99r0O6&#?o-f}3}&<6yD z5SYq}i_$*xqg<5MfsciH8<)WYc?zt!#c|vL5k^gp1ls?HS0gDt;eN`9&77C4A>D>tbk9aAdJ!D z8$jG}L_lmsjgpkQ!m^8likHSZa*}pz{DvHLY{CPSR-rRwreg;-Q8w_&McqW*LOlo1 zP`2@@Q&-tMbnv{=27$#>HYgu4?<$hHgEQXk7gRXgRo!f3*(gTBC5nQmhP>+HxR{-M zK=1GTJPGa&5#E(~qAuvtqSg9}sTScyGxm2pDU1iAuiW0|M^daom>n=^}GA~y%rHS)fW*rW7VaB<2GZbxgSkt8jNU!F~xvD zP6BcR5N3r***25$%5q{Oz&{Wg*{h6pb<03Y^p@jV__JM0%?CKI8b#d1{sVT|LV+2K zJo$%S^CO*xm%ru1p)8$~UrEn=R`FjaK*q-Z0hJTKqN5b$>F^nj_j>d25H+D4hLN#g`*9H)umX&|})swEH4N+CQ96FiGdR9~90YwC{D{kn4TH?&!{ QQCRw-(Kc30%Upc;KjcMervLx| literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/lxml.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/lxml.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c6ebd92f42ef13a00d54f1969dcbe2a360947df6 GIT binary patch literal 4913 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/pulldom.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/pulldom.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab71987f946304714e7cc1545e48d4209e5a26e1 GIT binary patch literal 933 zcmZWny>8nu5GJV~JF*iyoeFdbx~XWQ6mg3fD2f6tI(msGEre{+hG6~WNCmE;PU+M) z>8tb+xOU1bbm~zuP{XBgA3yQPI78MG{W}t#@1B2_ zt6E8MvCXq9QRY`won<%&Pk1K@nK`Qms}+|#f}2P>ho(1RYHGQp~ZMJl+^!i@G@7CP0bRR0#XT0)Uz#_v)K{dlZ2 z$Ry__aAa74@w==|Z&9yQx|7hy_;6w&K=?`E45vR?gc3a@T1F2EM5{6r5>&K;=w|b# zfcwexTI|GbswTIa$uyt7n_S~q7tuVb(&}H8hOICU-4=(Y$GIq%oST4S=jj?G=fG?nTT^`8Ms4XZ zuznj2#t6ny$+qo?7=x6MuG0d8*cUqho15vs&r4Bf@`LRk^$fwKV>YDtG1nn9I3e^e D^yA=} literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/sax.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/sax.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..412321f236c8fea25644411626a942ed36dbb371 GIT binary patch literal 1295 zcmZWp&2H2%5VoCclFep!TmFSWLfrPyZV?5w2qDCu;8G6dl#7+Fy=}JnW9&txN>Alw zcmtk+NAQ&sufU11cWD=JG?UL`$DWyQCJ+1l`2Kg1e(MnOgRBf6@pBZVQ3;X~K?N&l z$x>E&sYmVHEBvyP1`hj(!!$zN5kb){<1|Jbrac**6A_87=uUkRi{2NSCV$xy(MMbV zkW8r{i`#@p zi-1icSHg!Lh60IIeN-k`IP{gkRh<4h8e?`pWm=9uWiX!B1!hN$r!ana{Gfu5+q+xh zRGjXr?VaQ8-E#Nw_7;xAYW!i`dh6p@EEvSxu0rH;v-u1IyzUB$ zvVgFF#`py8acdVubaB~3aYP&;aK%VUdWOZ_b?y->?6Fh`SbS&P+Q255(UTX9*ov1l@*=gZ<{V7tNeC+IHr z*`j1@4=oTca&+C+&!sOeA6sZrwuuOJ^S#l~gq(|d!nuh#_OPCVNEQTKI^mGZO7yg>#k`QuY!K=ODxa>ggeELu NzK5xZed^O>{WswJGb{iA literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/xmlrpc.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/xmlrpc.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b536ebe0e88d6ff2563e2a174667948bb82dc90 GIT binary patch literal 4649 zcma)9&2QVt6(4?zqGdV$i0wGujT&cnt3@5>!%5?|*d`m>Nm|rdB< z+^b(??B8tUserx@$gXOPapp0uaXr@Bx~I1d&(P@Gh)vJrCbwd%ZF_dRY^tkQ0m{*F&+7+*&);RHayXs9Sx(xb)H>qeBbEdp$MUR1=@h)mCS>=_%4znja z81bw(!3}Q`JG=4h$g0_5)nP^PHs{U#jwSOZ3+MM;@lTD>i*<9~uta~s_=0zdU*;41 z!W+$7c*OW5pL)aiR5g;$tQxHERDM|^rLns6>{q>gwN_`URmE>-%Q0$#Ce$HlN*VQWE5tnT?-orG89P= zKMR|o2$M#5I%a(J&F$WJ!PRx}6(+K-PMP1T4tXeYe%|dEsN`Yog3fjN5G{VefU>GsWI@UU-!c6fSP*WxhQ zm3y`Dpb>^(ygrtjbdY>{E0s^$oj7cRpO9||k&4{;&4U;I`h)EUxgMo?>B~bIW>22x z4ykt&f6q-320Sl4d0NbOzYgMVSkP7@PN8)JKdf81Ndn5PIBG{S*P6Mp+iT{f!eIE{ znOFk*|J`4KmA?vPxbi9xE6p_Kp~zO6V&(S!%|yIhUAw`1ytkIE-rHYYYp>m1y@6*p zS=nCcM4jcGZWQz7J4r*_lQ&+j6u!h^bfe_JwmXOU^k9Nd#~+yPGPuaD0x`?fOw9qf zT2<3Qx%w2SDkw*r0f~U7YRVD^{aTYwtE_sN@4~;wT9rQI@b{L> zt?wv}FbK=u8Zr0FWZ>^HY;ggM*L5+8MobYsE~bf|A%q;p#1%r9fO412xbO_|5%pds zG)riX#+^(e{O(4nm{*^^A0?6WeZ*Tc3*%<)4AoF0tzbRZF{`DkilA;Kdq7o1g7P2h@*SACgw0VYLsrMmjrHDZgZCQQR z*Nzc7``DnR73%mY)0q!7wFnqg^J1u)KJpd?-{HFFg)+?lR~dDukjrP_F*n*#QmBF? zp%ic`^tvg?eR3_y%{XjIah@niXWs%c(kp|DR+MW_p@qxpy(=FOB5Aro5tANjDFZ11 zE`WgDX)T>{3~~W$87-Z%g0ehq3%n@{Qfx0`w)innT~i#NRX2_A0Wwk$17|(t0hRU7 z@p9x^#L7B;QOX_PPl9&n`?>4;2=v{U=!);Z>;~~*q~!ZNZTP-2szQQ_pC$b2EF+(y zZxfI?K#pPR?xa%@)Y|}@JO-%yfb1^NN%X1?B9}3UT;?EhS%_(_ieO{Bg0{rR;hYos z)ZmOG6~pFsgMtY?Hh})~5RiF5B<{izA#RA>*ZZ(Y-{`~beR6&42$tH?#VugFZ}}xs zig*m{;H!+*MLUMp6kh{Z@HIYES5=hp4<)1AZK|5c=aT0W0~(1=)kdcsgUTo``5jg5 zCoy@CsGepu%f>rw3u_^aE6i`ig*HyA#3Q zw|owNLt(zF;s&`4J=B%}%5W(yD2~ZYt-jqa9owUPla(XjQ5X|n0D03AccTll|}zMlBnA-(ykc z0z*aXVw{ih<@Jtu7{*7Hd} z#qekuuMQ#}(FRJ=r-)>pSG&pR0sex%vj?C;En)|R*uON2M?Vs`Xf1Mbvn#s-Ia!cG zgh7Zx0`A3W8~dubCCJkR*`}b_;b9pu86`;NKfGV79Oc=?y+n{4i<6HLrH(*a5x*j& zH1&k2Dj_OH@-bZ6aQ|0#uA;2{gvO{w$?f94P^g*gNhwCq;m#F|^NXV1_@ay%8RZ32 myP!MzysB0!C}QZX>LxJVjqs)G)T6m2({L{>es*EWasCInkZbn< literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/defusedxml/cElementTree.py b/flex/lib/python3.6/site-packages/defusedxml/cElementTree.py new file mode 100644 index 0000000..cc13689 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/cElementTree.py @@ -0,0 +1,30 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.etree.cElementTree +""" +from __future__ import absolute_import + +from xml.etree.cElementTree import TreeBuilder as _TreeBuilder +from xml.etree.cElementTree import parse as _parse +from xml.etree.cElementTree import tostring +# iterparse from ElementTree! +from xml.etree.ElementTree import iterparse as _iterparse + +from .ElementTree import DefusedXMLParser +from .common import _generate_etree_functions + +__origin__ = "xml.etree.cElementTree" + + +XMLTreeBuilder = XMLParse = DefusedXMLParser + +parse, iterparse, fromstring = _generate_etree_functions(DefusedXMLParser, + _TreeBuilder, _parse, + _iterparse) +XML = fromstring + +__all__ = ['XML', 'XMLParse', 'XMLTreeBuilder', 'fromstring', 'iterparse', + 'parse', 'tostring'] diff --git a/flex/lib/python3.6/site-packages/defusedxml/common.py b/flex/lib/python3.6/site-packages/defusedxml/common.py new file mode 100644 index 0000000..668b609 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/common.py @@ -0,0 +1,120 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Common constants, exceptions and helpe functions +""" +import sys + +PY3 = sys.version_info[0] == 3 + + +class DefusedXmlException(ValueError): + """Base exception + """ + + def __repr__(self): + return str(self) + + +class DTDForbidden(DefusedXmlException): + """Document type definition is forbidden + """ + + def __init__(self, name, sysid, pubid): + super(DTDForbidden, self).__init__() + self.name = name + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class EntitiesForbidden(DefusedXmlException): + """Entity definition is forbidden + """ + + def __init__(self, name, value, base, sysid, pubid, notation_name): + super(EntitiesForbidden, self).__init__() + self.name = name + self.value = value + self.base = base + self.sysid = sysid + self.pubid = pubid + self.notation_name = notation_name + + def __str__(self): + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class ExternalReferenceForbidden(DefusedXmlException): + """Resolving an external reference is forbidden + """ + + def __init__(self, context, base, sysid, pubid): + super(ExternalReferenceForbidden, self).__init__() + self.context = context + self.base = base + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" + return tpl.format(self.sysid, self.pubid) + + +class NotSupportedError(DefusedXmlException): + """The operation is not supported + """ + + +def _apply_defusing(defused_mod): + assert defused_mod is sys.modules[defused_mod.__name__] + stdlib_name = defused_mod.__origin__ + __import__(stdlib_name, {}, {}, ["*"]) + stdlib_mod = sys.modules[stdlib_name] + stdlib_names = set(dir(stdlib_mod)) + for name, obj in vars(defused_mod).items(): + if name.startswith("_") or name not in stdlib_names: + continue + setattr(stdlib_mod, name, obj) + return stdlib_mod + + +def _generate_etree_functions(DefusedXMLParser, _TreeBuilder, + _parse, _iterparse): + """Factory for functions needed by etree, dependent on whether + cElementTree or ElementTree is used.""" + + def parse(source, parser=None, forbid_dtd=False, forbid_entities=True, + forbid_external=True): + if parser is None: + parser = DefusedXMLParser(target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + return _parse(source, parser) + + def iterparse(source, events=None, parser=None, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + if parser is None: + parser = DefusedXMLParser(target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + return _iterparse(source, events, parser) + + def fromstring(text, forbid_dtd=False, forbid_entities=True, + forbid_external=True): + parser = DefusedXMLParser(target=_TreeBuilder(), + forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + parser.feed(text) + return parser.close() + + return parse, iterparse, fromstring diff --git a/flex/lib/python3.6/site-packages/defusedxml/expatbuilder.py b/flex/lib/python3.6/site-packages/defusedxml/expatbuilder.py new file mode 100644 index 0000000..0eb6b91 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/expatbuilder.py @@ -0,0 +1,110 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.dom.expatbuilder +""" +from __future__ import print_function, absolute_import + +from xml.dom.expatbuilder import ExpatBuilder as _ExpatBuilder +from xml.dom.expatbuilder import Namespaces as _Namespaces + +from .common import (DTDForbidden, EntitiesForbidden, + ExternalReferenceForbidden) + +__origin__ = "xml.dom.expatbuilder" + + +class DefusedExpatBuilder(_ExpatBuilder): + """Defused document builder""" + + def __init__(self, options=None, forbid_dtd=False, forbid_entities=True, + forbid_external=True): + _ExpatBuilder.__init__(self, options) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + + def defused_start_doctype_decl(self, name, sysid, pubid, + has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, + notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def defused_external_entity_ref_handler(self, context, base, sysid, + pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def install(self, parser): + _ExpatBuilder.install(self, parser) + + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + # if self._options.entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + +class DefusedExpatBuilderNS(_Namespaces, DefusedExpatBuilder): + """Defused document builder that supports namespaces.""" + + def install(self, parser): + DefusedExpatBuilder.install(self, parser) + if self._options.namespace_declarations: + parser.StartNamespaceDeclHandler = ( + self.start_namespace_decl_handler) + + def reset(self): + DefusedExpatBuilder.reset(self) + self._initNamespaces() + + +def parse(file, namespaces=True, forbid_dtd=False, forbid_entities=True, + forbid_external=True): + """Parse a document, returning the resulting Document node. + + 'file' may be either a file name or an open file object. + """ + if namespaces: + build_builder = DefusedExpatBuilderNS + else: + build_builder = DefusedExpatBuilder + builder = build_builder(forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + + if isinstance(file, str): + fp = open(file, 'rb') + try: + result = builder.parseFile(fp) + finally: + fp.close() + else: + result = builder.parseFile(file) + return result + + +def parseString(string, namespaces=True, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + """Parse a document from a string, returning the resulting + Document node. + """ + if namespaces: + build_builder = DefusedExpatBuilderNS + else: + build_builder = DefusedExpatBuilder + builder = build_builder(forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + return builder.parseString(string) diff --git a/flex/lib/python3.6/site-packages/defusedxml/expatreader.py b/flex/lib/python3.6/site-packages/defusedxml/expatreader.py new file mode 100644 index 0000000..ef6bc39 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/expatreader.py @@ -0,0 +1,59 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.sax.expatreader +""" +from __future__ import print_function, absolute_import + +from xml.sax.expatreader import ExpatParser as _ExpatParser + +from .common import (DTDForbidden, EntitiesForbidden, + ExternalReferenceForbidden) + +__origin__ = "xml.sax.expatreader" + + +class DefusedExpatParser(_ExpatParser): + """Defused SAX driver for the pyexpat C module.""" + + def __init__(self, namespaceHandling=0, bufsize=2 ** 16 - 20, + forbid_dtd=False, forbid_entities=True, + forbid_external=True): + _ExpatParser.__init__(self, namespaceHandling, bufsize) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + + def defused_start_doctype_decl(self, name, sysid, pubid, + has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, + notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def defused_external_entity_ref_handler(self, context, base, sysid, + pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def reset(self): + _ExpatParser.reset(self) + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + +def create_parser(*args, **kwargs): + return DefusedExpatParser(*args, **kwargs) diff --git a/flex/lib/python3.6/site-packages/defusedxml/lxml.py b/flex/lib/python3.6/site-packages/defusedxml/lxml.py new file mode 100644 index 0000000..7f3ee0b --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/lxml.py @@ -0,0 +1,153 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Example code for lxml.etree protection + +The code has NO protection against decompression bombs. +""" +from __future__ import print_function, absolute_import + +import threading +from lxml import etree as _etree + +from .common import DTDForbidden, EntitiesForbidden, NotSupportedError + +LXML3 = _etree.LXML_VERSION[0] >= 3 + +__origin__ = "lxml.etree" + +tostring = _etree.tostring + + +class RestrictedElement(_etree.ElementBase): + """A restricted Element class that filters out instances of some classes + """ + __slots__ = () + # blacklist = (etree._Entity, etree._ProcessingInstruction, etree._Comment) + blacklist = _etree._Entity + + def _filter(self, iterator): + blacklist = self.blacklist + for child in iterator: + if isinstance(child, blacklist): + continue + yield child + + def __iter__(self): + iterator = super(RestrictedElement, self).__iter__() + return self._filter(iterator) + + def iterchildren(self, tag=None, reversed=False): + iterator = super(RestrictedElement, self).iterchildren( + tag=tag, reversed=reversed) + return self._filter(iterator) + + def iter(self, tag=None, *tags): + iterator = super(RestrictedElement, self).iter(tag=tag, *tags) + return self._filter(iterator) + + def iterdescendants(self, tag=None, *tags): + iterator = super(RestrictedElement, + self).iterdescendants(tag=tag, *tags) + return self._filter(iterator) + + def itersiblings(self, tag=None, preceding=False): + iterator = super(RestrictedElement, self).itersiblings( + tag=tag, preceding=preceding) + return self._filter(iterator) + + def getchildren(self): + iterator = super(RestrictedElement, self).__iter__() + return list(self._filter(iterator)) + + def getiterator(self, tag=None): + iterator = super(RestrictedElement, self).getiterator(tag) + return self._filter(iterator) + + +class GlobalParserTLS(threading.local): + """Thread local context for custom parser instances + """ + parser_config = { + 'resolve_entities': False, + # 'remove_comments': True, + # 'remove_pis': True, + } + + element_class = RestrictedElement + + def createDefaultParser(self): + parser = _etree.XMLParser(**self.parser_config) + element_class = self.element_class + if self.element_class is not None: + lookup = _etree.ElementDefaultClassLookup(element=element_class) + parser.set_element_class_lookup(lookup) + return parser + + def setDefaultParser(self, parser): + self._default_parser = parser + + def getDefaultParser(self): + parser = getattr(self, "_default_parser", None) + if parser is None: + parser = self.createDefaultParser() + self.setDefaultParser(parser) + return parser + + +_parser_tls = GlobalParserTLS() +getDefaultParser = _parser_tls.getDefaultParser + + +def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True): + """Check docinfo of an element tree for DTD and entity declarations + + The check for entity declarations needs lxml 3 or newer. lxml 2.x does + not support dtd.iterentities(). + """ + docinfo = elementtree.docinfo + if docinfo.doctype: + if forbid_dtd: + raise DTDForbidden(docinfo.doctype, + docinfo.system_url, + docinfo.public_id) + if forbid_entities and not LXML3: + # lxml < 3 has no iterentities() + raise NotSupportedError("Unable to check for entity declarations " + "in lxml 2.x") + + if forbid_entities: + for dtd in docinfo.internalDTD, docinfo.externalDTD: + if dtd is None: + continue + for entity in dtd.iterentities(): + raise EntitiesForbidden(entity.name, entity.content, None, + None, None, None) + + +def parse(source, parser=None, base_url=None, forbid_dtd=False, + forbid_entities=True): + if parser is None: + parser = getDefaultParser() + elementtree = _etree.parse(source, parser, base_url=base_url) + check_docinfo(elementtree, forbid_dtd, forbid_entities) + return elementtree + + +def fromstring(text, parser=None, base_url=None, forbid_dtd=False, + forbid_entities=True): + if parser is None: + parser = getDefaultParser() + rootelement = _etree.fromstring(text, parser, base_url=base_url) + elementtree = rootelement.getroottree() + check_docinfo(elementtree, forbid_dtd, forbid_entities) + return rootelement + + +XML = fromstring + + +def iterparse(*args, **kwargs): + raise NotSupportedError("defused lxml.etree.iterparse not available") diff --git a/flex/lib/python3.6/site-packages/defusedxml/minidom.py b/flex/lib/python3.6/site-packages/defusedxml/minidom.py new file mode 100644 index 0000000..0fd8684 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/minidom.py @@ -0,0 +1,42 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.dom.minidom +""" +from __future__ import print_function, absolute_import + +from xml.dom.minidom import _do_pulldom_parse +from . import expatbuilder as _expatbuilder +from . import pulldom as _pulldom + +__origin__ = "xml.dom.minidom" + + +def parse(file, parser=None, bufsize=None, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + """Parse a file into a DOM by filename or file object.""" + if parser is None and not bufsize: + return _expatbuilder.parse(file, forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + else: + return _do_pulldom_parse(_pulldom.parse, (file,), + {'parser': parser, 'bufsize': bufsize, + 'forbid_dtd': forbid_dtd, 'forbid_entities': forbid_entities, + 'forbid_external': forbid_external}) + + +def parseString(string, parser=None, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + """Parse a file into a DOM from a string.""" + if parser is None: + return _expatbuilder.parseString(string, forbid_dtd=forbid_dtd, + forbid_entities=forbid_entities, + forbid_external=forbid_external) + else: + return _do_pulldom_parse(_pulldom.parseString, (string,), + {'parser': parser, 'forbid_dtd': forbid_dtd, + 'forbid_entities': forbid_entities, + 'forbid_external': forbid_external}) diff --git a/flex/lib/python3.6/site-packages/defusedxml/pulldom.py b/flex/lib/python3.6/site-packages/defusedxml/pulldom.py new file mode 100644 index 0000000..fc9e466 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/pulldom.py @@ -0,0 +1,34 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.dom.pulldom +""" +from __future__ import print_function, absolute_import + +from xml.dom.pulldom import parse as _parse +from xml.dom.pulldom import parseString as _parseString +from .sax import make_parser + +__origin__ = "xml.dom.pulldom" + + +def parse(stream_or_string, parser=None, bufsize=None, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + if parser is None: + parser = make_parser() + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + return _parse(stream_or_string, parser, bufsize) + + +def parseString(string, parser=None, forbid_dtd=False, + forbid_entities=True, forbid_external=True): + if parser is None: + parser = make_parser() + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + return _parseString(string, parser) diff --git a/flex/lib/python3.6/site-packages/defusedxml/sax.py b/flex/lib/python3.6/site-packages/defusedxml/sax.py new file mode 100644 index 0000000..534d0ca --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/sax.py @@ -0,0 +1,49 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xml.sax +""" +from __future__ import print_function, absolute_import + +from xml.sax import InputSource as _InputSource +from xml.sax import ErrorHandler as _ErrorHandler + +from . import expatreader + +__origin__ = "xml.sax" + + +def parse(source, handler, errorHandler=_ErrorHandler(), forbid_dtd=False, + forbid_entities=True, forbid_external=True): + parser = make_parser() + parser.setContentHandler(handler) + parser.setErrorHandler(errorHandler) + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + parser.parse(source) + + +def parseString(string, handler, errorHandler=_ErrorHandler(), + forbid_dtd=False, forbid_entities=True, + forbid_external=True): + from io import BytesIO + + if errorHandler is None: + errorHandler = _ErrorHandler() + parser = make_parser() + parser.setContentHandler(handler) + parser.setErrorHandler(errorHandler) + parser.forbid_dtd = forbid_dtd + parser.forbid_entities = forbid_entities + parser.forbid_external = forbid_external + + inpsrc = _InputSource() + inpsrc.setByteStream(BytesIO(string)) + parser.parse(inpsrc) + + +def make_parser(parser_list=[]): + return expatreader.create_parser() diff --git a/flex/lib/python3.6/site-packages/defusedxml/xmlrpc.py b/flex/lib/python3.6/site-packages/defusedxml/xmlrpc.py new file mode 100644 index 0000000..2a456e6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/defusedxml/xmlrpc.py @@ -0,0 +1,157 @@ +# defusedxml +# +# Copyright (c) 2013 by Christian Heimes +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/psf/license for licensing details. +"""Defused xmlrpclib + +Also defuses gzip bomb +""" +from __future__ import print_function, absolute_import + +import io + +from .common import ( + DTDForbidden, EntitiesForbidden, ExternalReferenceForbidden, PY3) + +if PY3: + __origin__ = "xmlrpc.client" + from xmlrpc.client import ExpatParser + from xmlrpc import client as xmlrpc_client + from xmlrpc import server as xmlrpc_server + from xmlrpc.client import gzip_decode as _orig_gzip_decode + from xmlrpc.client import GzipDecodedResponse as _OrigGzipDecodedResponse +else: + __origin__ = "xmlrpclib" + from xmlrpclib import ExpatParser + import xmlrpclib as xmlrpc_client + xmlrpc_server = None + from xmlrpclib import gzip_decode as _orig_gzip_decode + from xmlrpclib import GzipDecodedResponse as _OrigGzipDecodedResponse + +try: + import gzip +except ImportError: + gzip = None + + +# Limit maximum request size to prevent resource exhaustion DoS +# Also used to limit maximum amount of gzip decoded data in order to prevent +# decompression bombs +# A value of -1 or smaller disables the limit +MAX_DATA = 30 * 1024 * 1024 # 30 MB + + +def defused_gzip_decode(data, limit=None): + """gzip encoded data -> unencoded data + + Decode data using the gzip content encoding as described in RFC 1952 + """ + if not gzip: + raise NotImplementedError + if limit is None: + limit = MAX_DATA + f = io.BytesIO(data) + gzf = gzip.GzipFile(mode="rb", fileobj=f) + try: + if limit < 0: # no limit + decoded = gzf.read() + else: + decoded = gzf.read(limit + 1) + except IOError: + raise ValueError("invalid data") + f.close() + gzf.close() + if limit >= 0 and len(decoded) > limit: + raise ValueError("max gzipped payload length exceeded") + return decoded + + +class DefusedGzipDecodedResponse(gzip.GzipFile if gzip else object): + """a file-like object to decode a response encoded with the gzip + method, as described in RFC 1952. + """ + + def __init__(self, response, limit=None): + # response doesn't support tell() and read(), required by + # GzipFile + if not gzip: + raise NotImplementedError + self.limit = limit = limit if limit is not None else MAX_DATA + if limit < 0: # no limit + data = response.read() + self.readlength = None + else: + data = response.read(limit + 1) + self.readlength = 0 + if limit >= 0 and len(data) > limit: + raise ValueError("max payload length exceeded") + self.stringio = io.BytesIO(data) + gzip.GzipFile.__init__(self, mode="rb", fileobj=self.stringio) + + def read(self, n): + if self.limit >= 0: + left = self.limit - self.readlength + n = min(n, left + 1) + data = gzip.GzipFile.read(self, n) + self.readlength += len(data) + if self.readlength > self.limit: + raise ValueError("max payload length exceeded") + return data + else: + return gzip.GzipFile.read(self, n) + + def close(self): + gzip.GzipFile.close(self) + self.stringio.close() + + +class DefusedExpatParser(ExpatParser): + + def __init__(self, target, forbid_dtd=False, forbid_entities=True, + forbid_external=True): + ExpatParser.__init__(self, target) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + self.forbid_external = forbid_external + parser = self._parser + if self.forbid_dtd: + parser.StartDoctypeDeclHandler = self.defused_start_doctype_decl + if self.forbid_entities: + parser.EntityDeclHandler = self.defused_entity_decl + parser.UnparsedEntityDeclHandler = self.defused_unparsed_entity_decl + if self.forbid_external: + parser.ExternalEntityRefHandler = self.defused_external_entity_ref_handler + + def defused_start_doctype_decl(self, name, sysid, pubid, + has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def defused_entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def defused_unparsed_entity_decl(self, name, base, sysid, pubid, + notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def defused_external_entity_ref_handler(self, context, base, sysid, + pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + +def monkey_patch(): + xmlrpc_client.FastParser = DefusedExpatParser + xmlrpc_client.GzipDecodedResponse = DefusedGzipDecodedResponse + xmlrpc_client.gzip_decode = defused_gzip_decode + if xmlrpc_server: + xmlrpc_server.gzip_decode = defused_gzip_decode + + +def unmonkey_patch(): + xmlrpc_client.FastParser = None + xmlrpc_client.GzipDecodedResponse = _OrigGzipDecodedResponse + xmlrpc_client.gzip_decode = _orig_gzip_decode + if xmlrpc_server: + xmlrpc_server.gzip_decode = _orig_gzip_decode diff --git a/flex/lib/python3.6/site-packages/django/conf/urls/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/django/conf/urls/__pycache__/__init__.cpython-36.pyc index e00a254ee64cf560626b731166c7a134e5767afd..774984fbb876a013097ec45b68a144d7d47588bb 100644 GIT binary patch delta 127 zcmeyy@{xtjn3tE!ZPP|JZAQN={m|mnqGJ8*#G;an%X;Dsb5fcLgLlHBOxHb7a<8lBkq%27Q delta 132 zcmey!@{NVfn3tDp@9d3i+Kj>Z`UORqWr-!J`elhl`f2$&DXB%p`e{Y_#@U8>MTI7o z1}Rl3RhGpj7TG42xt3-o20)OWryrtUkXfLcRGOKSqFYv3l9^OwlBA!Km6(^FKe?XK dSR#rW!qF=&$|){lVqjn>Vg?dXlTR@&2LN`VD*XTe diff --git a/flex/lib/python3.6/site-packages/django/middleware/__pycache__/http.cpython-36.pyc b/flex/lib/python3.6/site-packages/django/middleware/__pycache__/http.cpython-36.pyc index ef1df9bfa815dd99b1a607d726cf6949f6f4172d..7b8c180043451f9eb21a5714ae48989c990352c3 100644 GIT binary patch delta 142 zcmdnXyMvd_n3tE!ebYuZU1q;@{m|mnqGJ8*#G;an%Bcf= delta 122 zcmdnNyO)>En3tDp|Ll!yy3D~j`UORqWr-!J`elhl`f2$&DXB%p`e{Y_#@U8>MTI7o z1}Rl3RhGpj7TG42xt3-o20)OWryrtUkXfLcRGOKSqFYv3l9^OwlBA!Km6(^Fzqyfl VH4|ghWL-8TAQ`|W2PE^^JOQQGCu0Br diff --git a/flex/lib/python3.6/site-packages/django/views/__pycache__/defaults.cpython-36.pyc b/flex/lib/python3.6/site-packages/django/views/__pycache__/defaults.cpython-36.pyc index 7203f75dc516614bdae3a91534318e49e64876e0..c86d3eb4a0d26598f03f92a08d93e7902ed22f67 100644 GIT binary patch delta 325 zcmZ1_zEqsen3tE!ebYv^!z_%qCZA%-4_07cVEC1$A6lGRRIHz!SX7dcnO#ttlard0 zsPB?moL!P%pr4VLoSl+bmRYRtmXlhcp9UgwGL!TRDoZl*^NjV(^ouh~QgsV}$`aF4 ziziQHjb&8Y{GL^YQA!GEe33MWkOL74a6)miD!V45%4UCdX-0XlDjtv=cBP#h8jNb2 zH*s__vVoP#PIlz7C#Y*amjQ{3kz13mb149Q{DVscNXl|+7^{P9Qw0&qAVLX5 WsDKDH5CPJL};mRORiUzS*;pO&AKl3G-( zpH`%AoNbs_RA^#pkW!UWWm#-uk!@m`YiVX;00ilI`XTxSnFYE@rI|S?x@DCmnMp+^ zN%|>SiFxVylY3ZW85K9bVwGW(k_6gOBn2X5K?KN32%#`pfnAePd9xS0G^0FN6%THu zEgTw*s+-qxbTYDmmC8)E;j$;FYZjM*raI72KTVM+Ziv(LGD=DcikLt)F@p#e5Wxy0 sq9%XlQUQjG1hMG&D3B2+;H$V4Q6fPJJextQAs08b-LJOBUy diff --git a/flex/lib/python3.6/site-packages/django/views/decorators/__pycache__/http.cpython-36.pyc b/flex/lib/python3.6/site-packages/django/views/decorators/__pycache__/http.cpython-36.pyc index 8cc7b06d7a02321d618496bc88976eef25b99901..ce503a299e63860fa4385367c61fccf09ca22ac9 100644 GIT binary patch delta 148 zcmcbtbWVxQn3tE!ebYv^EEd09{m|mnqGJ8*#G;an%MTI7o z1}Rl3RhGpj7TG42xt3-o20)OWryrtUkXfLcRGOKSqFYv3l9^OwlBA!Km6(^Fzj+@^ zJU3(1WH|vnAn7Y0As;0UkI%OG!=6FG?)QFDfo#XJBB6n%p7~091KZpb`Lp CsWh+v diff --git a/flex/lib/python3.6/site-packages/jwt/__init__.py b/flex/lib/python3.6/site-packages/jwt/__init__.py new file mode 100644 index 0000000..090b26a --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/__init__.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# flake8: noqa + +""" +JSON Web Token implementation + +Minimum implementation based on this spec: +http://self-issued.info/docs/draft-jones-json-web-token-01.html +""" + + +__title__ = 'pyjwt' +__version__ = '1.5.3' +__author__ = 'José Padilla' +__license__ = 'MIT' +__copyright__ = 'Copyright 2015 José Padilla' + + +from .api_jwt import ( + encode, decode, register_algorithm, unregister_algorithm, + get_unverified_header, PyJWT +) +from .api_jws import PyJWS +from .exceptions import ( + InvalidTokenError, DecodeError, InvalidAlgorithmError, + InvalidAudienceError, ExpiredSignatureError, ImmatureSignatureError, + InvalidIssuedAtError, InvalidIssuerError, ExpiredSignature, + InvalidAudience, InvalidIssuer, MissingRequiredClaimError +) diff --git a/flex/lib/python3.6/site-packages/jwt/__main__.py b/flex/lib/python3.6/site-packages/jwt/__main__.py new file mode 100644 index 0000000..52e7abf --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/__main__.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python + +from __future__ import absolute_import, print_function + +import argparse +import json +import sys +import time + +from . import DecodeError, __version__, decode, encode + + +def encode_payload(args): + # Try to encode + if args.key is None: + raise ValueError('Key is required when encoding. See --help for usage.') + + # Build payload object to encode + payload = {} + + for arg in args.payload: + k, v = arg.split('=', 1) + + # exp +offset special case? + if k == 'exp' and v[0] == '+' and len(v) > 1: + v = str(int(time.time()+int(v[1:]))) + + # Cast to integer? + if v.isdigit(): + v = int(v) + else: + # Cast to float? + try: + v = float(v) + except ValueError: + pass + + # Cast to true, false, or null? + constants = {'true': True, 'false': False, 'null': None} + + if v in constants: + v = constants[v] + + payload[k] = v + + token = encode( + payload, + key=args.key, + algorithm=args.algorithm + ) + + return token.decode('utf-8') + + +def decode_payload(args): + try: + if sys.stdin.isatty(): + token = sys.stdin.read() + else: + token = args.token + + token = token.encode('utf-8') + data = decode(token, key=args.key, verify=args.verify) + + return json.dumps(data) + + except DecodeError as e: + raise DecodeError('There was an error decoding the token: %s' % e) + + +def build_argparser(): + + usage = ''' + Encodes or decodes JSON Web Tokens based on input. + + %(prog)s [options] [options] input + + Decoding examples: + + %(prog)s --key=secret decode json.web.token + %(prog)s decode --no-verify json.web.token + + Encoding requires the key option and takes space separated key/value pairs + separated by equals (=) as input. Examples: + + %(prog)s --key=secret encode iss=me exp=1302049071 + %(prog)s --key=secret encode foo=bar exp=+10 + + The exp key is special and can take an offset to current Unix time. + ''' + + arg_parser = argparse.ArgumentParser( + prog='pyjwt', + usage=usage + ) + + arg_parser.add_argument( + '-v', '--version', + action='version', + version='%(prog)s ' + __version__ + ) + + arg_parser.add_argument( + '--key', + dest='key', + metavar='KEY', + default=None, + help='set the secret key to sign with' + ) + + arg_parser.add_argument( + '--alg', + dest='algorithm', + metavar='ALG', + default='HS256', + help='set crypto algorithm to sign with. default=HS256' + ) + + subparsers = arg_parser.add_subparsers( + title='PyJWT subcommands', + description='valid subcommands', + help='additional help' + ) + + # Encode subcommand + encode_parser = subparsers.add_parser('encode', help='use to encode a supplied payload') + + payload_help = """Payload to encode. Must be a space separated list of key/value + pairs separated by equals (=) sign.""" + + encode_parser.add_argument('payload', nargs='+', help=payload_help) + encode_parser.set_defaults(func=encode_payload) + + # Decode subcommand + decode_parser = subparsers.add_parser('decode', help='use to decode a supplied JSON web token') + decode_parser.add_argument('token', help='JSON web token to decode.') + + decode_parser.add_argument( + '-n', '--no-verify', + action='store_false', + dest='verify', + default=True, + help='ignore signature and claims verification on decode' + ) + + decode_parser.set_defaults(func=decode_payload) + + return arg_parser + + +def main(): + arg_parser = build_argparser() + + try: + arguments = arg_parser.parse_args(sys.argv[1:]) + + output = arguments.func(arguments) + + print(output) + except Exception as e: + print('There was an unforseen error: ', e) + arg_parser.print_help() diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fee578c50fcd41d23e47b2a008fb7b7699aaab38 GIT binary patch literal 1075 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/algorithms.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/algorithms.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..32e28dd97d99583b9e047b881827979be2eb3e28 GIT binary patch literal 11163 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jws.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jws.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d638a8e5949bbc4171c8acbb45eb07bbe5bd9dd GIT binary patch literal 6163 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 ZP;`urv5N!1ZGG3=_&4oi8$74}{|`L6HH!cM literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jwt.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jwt.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7dcc6cd4f4e9a70348d4577684ca6daad761d1ef GIT binary patch literal 5631 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 MFSHE{EJ*+V1v;9Em;e9( literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/compat.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/compat.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2929735e774aebc015bf21dbf33859f9a3881fbd GIT binary patch literal 1726 zcmZ8iO>Y}T7@m*y+Sxd!rGd60LL(6lMroXq5E4-Vg*FF-LIsi1u0Y%I&e+~~y}Oy2 zb>hf5nJYbVK!`hkg`Y52PW}go6Ys1;)Mi%m&iD9zp7)v8+bb)<2Y-q1A0MH=(QBm% z{V$>F-@phIB7p@-afqvygrsU|NF_b-1ip_19g?snYidvWVO=)DrK}+X==tH2tjnby z373T@YCof}ISC{_SqWRmC|mQ8d}|n}F-GnazNo{F)(e!8=Sbd?w?#vc8C9>uk_ces z?W`e|7d=6KK<(zmuffqkvX^l_D!6&cM!6_c$qJQ^6CpLzWl`kHuwJfMm&e01u7qZb z-AOk|6Ek6Fi5ajw6;cIbsdSQOn&mxKOw1t9HW<&u%>#-T1?*&88bL39VU^K-*0X>}GgS+OBz_dNPVsgXNNXt0Sgto26#;BxQ8u?VITv<=>iMI8t zby{bJgip%;-)}b+(5OGj+PNza=ij#lGIz8}96n z_IG!-p!BoOQKv|X2i-DB#e+|>SnZju)6TQWH^)bvlQYv|K+LWc9nz8{Rq` z+DQ*#f&<*5?~?{mZX$Nvx#IkEwc_ww=onyQhQZ<)9#XX`fD=J3$@xbHPu(nef__^2 zkzCSCZ%PcAQrJzV-V8rR-=Rx#x_*qNM3AAA6TSF7U{~dPX-Wl3I8vo#Nsk$jnK{du z20pXCi@jv>wB+dqJK(97EQcNHEYWf^xW2?`(}87%4`s#@&5}&WLW0SR6Pdd;D@R?a z+$bJ!#be{7Wd_`^5jXK*^ZK>}<)qZ6mZmbZ^dc!N&6Q}^LT}8|hn4-<`bz3DH5Q*+ zJgFkOFiv{*9gu}KJTsAVE2?6mWE++i#QV3fM?A8M?-6y!9cY+5>Q0Q**D`jRz7D}I z45lDOfr0Y1DYOuTb!f>KPM}24PaVktkPM=QG2{T@|3ZQ6`e6lU-heCS4w$9gq<_%H z*4IftlcJ5)Doi`P0M9NdIpRqMa2wYTc|Xz?;(8C}p1^;d1*`XU)ada}Mt9+|LpC0)9a0N1E8Hr8|LL#==VZL01= z1B8^l|8YZlx&gI9@f7x5kURcR;;>C!`p9OT|G|Qx{^^O5=efg5{`nu z6lCjtX*gscOCc1Mzpr6%$wpPGY&=@z?ay5p^g0+1P{2o96rzTG;$qjJjg=}dkk;)c H4ubyxgKfFL literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/exceptions.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/exceptions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af13e18e3f118fce9bd1d303109e6c33dbc05d20 GIT binary patch literal 2024 zcmbW2-)_?|6vpEwZPR~iyR8$GKy*Vx>cy6!6|_nSZ5k6QhzhYuNV%~}>~`7Gw6)W* zRl8#^!yV7TBlMQH;EHqHWlM)ZDX#3~7rXw>iR~{tg+lhp4`w%YP5Y@uiUId~P~~@M z-e^o?yTSI*`f&O7)3J5>C9wE~!v}%e_vO~$QeYZbWda(LD2|M`?@ZybdQ_2w+^StRS$dn_p$gyR^e3dNQ59SfhflwtPj2v5d zrP=GLrTwop#EhBpifAIo)~tm4%Io%p8=Um!QCNGf_!m1I0XXAc!X-Z`vD6K(X z7~I;wf>Wbgwd8&4Tg1tiW)+mPD-$e2#|BEEO`62i1wLuj7ABd)(4CbNE4vivR03T? z;7b>Ho&@e}fm==Bs;mForTg}1hg&+dExRydCz14TgPWKyr{AVm>a*|wW-l3k{4xeH F$ZrBWad7|u literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9155e1232b9620305b62a7383fd813ae8c7271e GIT binary patch literal 3184 zcmcguTW=f36`q;Ba7kS(*|cQ4XviB3Du0kT>KS z>NUA8Z=zn8w`3LdO?g{xpuQzH~kEva=&r@cHjW0lxylD|6Iq0Q-`+QauD zKD~{?uo*M_OsMLN9Sh0NAd(yNF!uV4q326+#=irU5Y{`K24mCR+MjMdJsl+-lgL_J z(?+FvuXYj-#z{S}l~kv>HgVob6dh*6LQ)k_r+fs>o+>p|b#4Q<=3h)L@rJID?m?(N{|<&C4pC>?FJ$LTR=|FFfCU&JeRGp+X&QpTu29DHV6HuwR*B5FPy`x4H{NsRTfds*!4PxC%({tKc5vP8j;=Ou!sg5Fe ztv5(=8@6MeJlwPEV>QrmH)*vW?iGvxi-R;Djg9qGl3iDHtyqUutz!CX*isWyoW;bl zi1?aNchO(ydxhXP@yD=ZPI42uL1f`btZw1Y5BR`nbsNeQqsNeyu!=_Sfp05fgn3vxr}V9MF2iW8kcK-+`A*)o>29DT0q zv~Gs2OVG!d_YM|cKpyc(L}Ho4OO9-hKVVULHwk(vhFz)1+Syn0~!HN06Ff+kc7|K2YOR z|NhSIsZ$cVi|w7JPikM&`p3$nL8?t>n2jEPL>toXA)CJC!tPZ#wjT|Kop_)hqw#Xh zIXPUte2w~!mNrCa9CwTj$N4Dk^cBf!JqqRi@i5K7z8a1AGB&aG+T-pu+3c;K5#l{8 zxpaEiq{$Dbh)B3a_k-@PwgN}Q1+iNopEP^CR&tU)c#WovCp^B%dMNGi+!7`S)zK0Yz;@W zskZ#OEp5fkXwk7BV=qlGFz$o2f=7InSNZRRdVsMjU=f8KSZ|_1abm`+U{sePqHqyJ zAbxm6%(*x7oJSJ5-ab7i%pFIOyCcfo6c}wO-fg#@C#Nn7!YRFcoY^vp!ta`uQ7k1C z!IWmDo%UKuF4H)_Zv7ir(N0v0+>&krM>tG}C>USEs}9gw_~Dh%M|vA%_G95fJN(Sq zOZ@@kU|k)RdKZN)wRd9@1Id`kiah>`5bkJ_p^&DDWiR90BeWMtl3+wVhvY6AGk)x- zCix~J-96B!h;(0xxjzpO?fe<88K?uQH;W6#c?m1eDJ>GB{bYj6%`{Q8xzo2EHcPk{ z(3Qsqov}JeY~+AVjaK`ZWx29kU)KV+C5NMmzrDOIeS_(mj$%CI%Sb4S0>IBJMA-k5 zLK38?vc9L@HbVO3F_cmfjJ$t{yYQg7PyLxE*+oAvK9mofRf0oB9<3fzv4>*1{z;BF zoyywcT2~L$U+6c*59OSMkXHm1;c=mqdY=YApn`xL|oS>0V?nvIT{jLfj7;?eWT_Vaj}#bz6~ zy38djy&daGmL=d>XWL!N3cHdXcIlIBG*qU@TqK7g61zC+(wECRF3>p#c4SZv1~Qrx y^Cx=JH9@)iQuHVr%JCq1Ob(*oM1f2O<*En?Rd1DtUR1`r9QpVv(MlAeRsJ7r?821* literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/algorithms.py b/flex/lib/python3.6/site-packages/jwt/algorithms.py new file mode 100644 index 0000000..1343688 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/algorithms.py @@ -0,0 +1,403 @@ +import hashlib +import hmac +import json + + +from .compat import constant_time_compare, string_types +from .exceptions import InvalidKeyError +from .utils import ( + base64url_decode, base64url_encode, der_to_raw_signature, + force_bytes, force_unicode, from_base64url_uint, raw_to_der_signature, + to_base64url_uint +) + +try: + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.serialization import ( + load_pem_private_key, load_pem_public_key, load_ssh_public_key + ) + from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKey, RSAPublicKey, RSAPrivateNumbers, RSAPublicNumbers, + rsa_recover_prime_factors, rsa_crt_dmp1, rsa_crt_dmq1, rsa_crt_iqmp + ) + from cryptography.hazmat.primitives.asymmetric.ec import ( + EllipticCurvePrivateKey, EllipticCurvePublicKey + ) + from cryptography.hazmat.primitives.asymmetric import ec, padding + from cryptography.hazmat.backends import default_backend + from cryptography.exceptions import InvalidSignature + + has_crypto = True +except ImportError: + has_crypto = False + +requires_cryptography = set(['RS256', 'RS384', 'RS512', 'ES256', 'ES384', + 'ES521', 'ES512', 'PS256', 'PS384', 'PS512']) + + +def get_default_algorithms(): + """ + Returns the algorithms that are implemented by the library. + """ + default_algorithms = { + 'none': NoneAlgorithm(), + 'HS256': HMACAlgorithm(HMACAlgorithm.SHA256), + 'HS384': HMACAlgorithm(HMACAlgorithm.SHA384), + 'HS512': HMACAlgorithm(HMACAlgorithm.SHA512) + } + + if has_crypto: + default_algorithms.update({ + 'RS256': RSAAlgorithm(RSAAlgorithm.SHA256), + 'RS384': RSAAlgorithm(RSAAlgorithm.SHA384), + 'RS512': RSAAlgorithm(RSAAlgorithm.SHA512), + 'ES256': ECAlgorithm(ECAlgorithm.SHA256), + 'ES384': ECAlgorithm(ECAlgorithm.SHA384), + 'ES521': ECAlgorithm(ECAlgorithm.SHA512), + 'ES512': ECAlgorithm(ECAlgorithm.SHA512), # Backward compat for #219 fix + 'PS256': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA256), + 'PS384': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA384), + 'PS512': RSAPSSAlgorithm(RSAPSSAlgorithm.SHA512) + }) + + return default_algorithms + + +class Algorithm(object): + """ + The interface for an algorithm used to sign and verify tokens. + """ + def prepare_key(self, key): + """ + Performs necessary validation and conversions on the key and returns + the key value in the proper format for sign() and verify(). + """ + raise NotImplementedError + + def sign(self, msg, key): + """ + Returns a digital signature for the specified message + using the specified key value. + """ + raise NotImplementedError + + def verify(self, msg, key, sig): + """ + Verifies that the specified digital signature is valid + for the specified message and key values. + """ + raise NotImplementedError + + @staticmethod + def to_jwk(key_obj): + """ + Serializes a given RSA key into a JWK + """ + raise NotImplementedError + + @staticmethod + def from_jwk(jwk): + """ + Deserializes a given RSA key from JWK back into a PublicKey or PrivateKey object + """ + raise NotImplementedError + + +class NoneAlgorithm(Algorithm): + """ + Placeholder for use when no signing or verification + operations are required. + """ + def prepare_key(self, key): + if key == '': + key = None + + if key is not None: + raise InvalidKeyError('When alg = "none", key value must be None.') + + return key + + def sign(self, msg, key): + return b'' + + def verify(self, msg, key, sig): + return False + + +class HMACAlgorithm(Algorithm): + """ + Performs signing and verification operations using HMAC + and the specified hash function. + """ + SHA256 = hashlib.sha256 + SHA384 = hashlib.sha384 + SHA512 = hashlib.sha512 + + def __init__(self, hash_alg): + self.hash_alg = hash_alg + + def prepare_key(self, key): + key = force_bytes(key) + + invalid_strings = [ + b'-----BEGIN PUBLIC KEY-----', + b'-----BEGIN CERTIFICATE-----', + b'-----BEGIN RSA PUBLIC KEY-----', + b'ssh-rsa' + ] + + if any([string_value in key for string_value in invalid_strings]): + raise InvalidKeyError( + 'The specified key is an asymmetric key or x509 certificate and' + ' should not be used as an HMAC secret.') + + return key + + @staticmethod + def to_jwk(key_obj): + return json.dumps({ + 'k': force_unicode(base64url_encode(force_bytes(key_obj))), + 'kty': 'oct' + }) + + @staticmethod + def from_jwk(jwk): + obj = json.loads(jwk) + + if obj.get('kty') != 'oct': + raise InvalidKeyError('Not an HMAC key') + + return base64url_decode(obj['k']) + + def sign(self, msg, key): + return hmac.new(key, msg, self.hash_alg).digest() + + def verify(self, msg, key, sig): + return constant_time_compare(sig, self.sign(msg, key)) + + +if has_crypto: + + class RSAAlgorithm(Algorithm): + """ + Performs signing and verification operations using + RSASSA-PKCS-v1_5 and the specified hash function. + """ + SHA256 = hashes.SHA256 + SHA384 = hashes.SHA384 + SHA512 = hashes.SHA512 + + def __init__(self, hash_alg): + self.hash_alg = hash_alg + + def prepare_key(self, key): + if isinstance(key, RSAPrivateKey) or \ + isinstance(key, RSAPublicKey): + return key + + if isinstance(key, string_types): + key = force_bytes(key) + + try: + if key.startswith(b'ssh-rsa'): + key = load_ssh_public_key(key, backend=default_backend()) + else: + key = load_pem_private_key(key, password=None, backend=default_backend()) + except ValueError: + key = load_pem_public_key(key, backend=default_backend()) + else: + raise TypeError('Expecting a PEM-formatted key.') + + return key + + @staticmethod + def to_jwk(key_obj): + obj = None + + if getattr(key_obj, 'private_numbers', None): + # Private key + numbers = key_obj.private_numbers() + + obj = { + 'kty': 'RSA', + 'key_ops': ['sign'], + 'n': force_unicode(to_base64url_uint(numbers.public_numbers.n)), + 'e': force_unicode(to_base64url_uint(numbers.public_numbers.e)), + 'd': force_unicode(to_base64url_uint(numbers.d)), + 'p': force_unicode(to_base64url_uint(numbers.p)), + 'q': force_unicode(to_base64url_uint(numbers.q)), + 'dp': force_unicode(to_base64url_uint(numbers.dmp1)), + 'dq': force_unicode(to_base64url_uint(numbers.dmq1)), + 'qi': force_unicode(to_base64url_uint(numbers.iqmp)) + } + + elif getattr(key_obj, 'verify', None): + # Public key + numbers = key_obj.public_numbers() + + obj = { + 'kty': 'RSA', + 'key_ops': ['verify'], + 'n': force_unicode(to_base64url_uint(numbers.n)), + 'e': force_unicode(to_base64url_uint(numbers.e)) + } + else: + raise InvalidKeyError('Not a public or private key') + + return json.dumps(obj) + + @staticmethod + def from_jwk(jwk): + try: + obj = json.loads(jwk) + except ValueError: + raise InvalidKeyError('Key is not valid JSON') + + if obj.get('kty') != 'RSA': + raise InvalidKeyError('Not an RSA key') + + if 'd' in obj and 'e' in obj and 'n' in obj: + # Private key + if 'oth' in obj: + raise InvalidKeyError('Unsupported RSA private key: > 2 primes not supported') + + other_props = ['p', 'q', 'dp', 'dq', 'qi'] + props_found = [prop in obj for prop in other_props] + any_props_found = any(props_found) + + if any_props_found and not all(props_found): + raise InvalidKeyError('RSA key must include all parameters if any are present besides d') + + public_numbers = RSAPublicNumbers( + from_base64url_uint(obj['e']), from_base64url_uint(obj['n']) + ) + + if any_props_found: + numbers = RSAPrivateNumbers( + d=from_base64url_uint(obj['d']), + p=from_base64url_uint(obj['p']), + q=from_base64url_uint(obj['q']), + dmp1=from_base64url_uint(obj['dp']), + dmq1=from_base64url_uint(obj['dq']), + iqmp=from_base64url_uint(obj['qi']), + public_numbers=public_numbers + ) + else: + d = from_base64url_uint(obj['d']) + p, q = rsa_recover_prime_factors( + public_numbers.n, d, public_numbers.e + ) + + numbers = RSAPrivateNumbers( + d=d, + p=p, + q=q, + dmp1=rsa_crt_dmp1(d, p), + dmq1=rsa_crt_dmq1(d, q), + iqmp=rsa_crt_iqmp(p, q), + public_numbers=public_numbers + ) + + return numbers.private_key(default_backend()) + elif 'n' in obj and 'e' in obj: + # Public key + numbers = RSAPublicNumbers( + from_base64url_uint(obj['e']), from_base64url_uint(obj['n']) + ) + + return numbers.public_key(default_backend()) + else: + raise InvalidKeyError('Not a public or private key') + + def sign(self, msg, key): + return key.sign(msg, padding.PKCS1v15(), self.hash_alg()) + + def verify(self, msg, key, sig): + try: + key.verify(sig, msg, padding.PKCS1v15(), self.hash_alg()) + return True + except InvalidSignature: + return False + + class ECAlgorithm(Algorithm): + """ + Performs signing and verification operations using + ECDSA and the specified hash function + """ + SHA256 = hashes.SHA256 + SHA384 = hashes.SHA384 + SHA512 = hashes.SHA512 + + def __init__(self, hash_alg): + self.hash_alg = hash_alg + + def prepare_key(self, key): + if isinstance(key, EllipticCurvePrivateKey) or \ + isinstance(key, EllipticCurvePublicKey): + return key + + if isinstance(key, string_types): + key = force_bytes(key) + + # Attempt to load key. We don't know if it's + # a Signing Key or a Verifying Key, so we try + # the Verifying Key first. + try: + if key.startswith(b'ecdsa-sha2-'): + key = load_ssh_public_key(key, backend=default_backend()) + else: + key = load_pem_public_key(key, backend=default_backend()) + except ValueError: + key = load_pem_private_key(key, password=None, backend=default_backend()) + + else: + raise TypeError('Expecting a PEM-formatted key.') + + return key + + def sign(self, msg, key): + der_sig = key.sign(msg, ec.ECDSA(self.hash_alg())) + + return der_to_raw_signature(der_sig, key.curve) + + def verify(self, msg, key, sig): + try: + der_sig = raw_to_der_signature(sig, key.curve) + except ValueError: + return False + + try: + key.verify(der_sig, msg, ec.ECDSA(self.hash_alg())) + return True + except InvalidSignature: + return False + + class RSAPSSAlgorithm(RSAAlgorithm): + """ + Performs a signature using RSASSA-PSS with MGF1 + """ + + def sign(self, msg, key): + return key.sign( + msg, + padding.PSS( + mgf=padding.MGF1(self.hash_alg()), + salt_length=self.hash_alg.digest_size + ), + self.hash_alg() + ) + + def verify(self, msg, key, sig): + try: + key.verify( + sig, + msg, + padding.PSS( + mgf=padding.MGF1(self.hash_alg()), + salt_length=self.hash_alg.digest_size + ), + self.hash_alg() + ) + return True + except InvalidSignature: + return False diff --git a/flex/lib/python3.6/site-packages/jwt/api_jws.py b/flex/lib/python3.6/site-packages/jwt/api_jws.py new file mode 100644 index 0000000..223b22b --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/api_jws.py @@ -0,0 +1,226 @@ +import binascii +import json +import warnings + +from collections import Mapping + +from .algorithms import ( + Algorithm, get_default_algorithms, has_crypto, requires_cryptography # NOQA +) +from .compat import binary_type, string_types, text_type +from .exceptions import DecodeError, InvalidAlgorithmError, InvalidTokenError +from .utils import base64url_decode, base64url_encode, force_bytes, merge_dict + + +class PyJWS(object): + header_typ = 'JWT' + + def __init__(self, algorithms=None, options=None): + self._algorithms = get_default_algorithms() + self._valid_algs = (set(algorithms) if algorithms is not None + else set(self._algorithms)) + + # Remove algorithms that aren't on the whitelist + for key in list(self._algorithms.keys()): + if key not in self._valid_algs: + del self._algorithms[key] + + if not options: + options = {} + + self.options = merge_dict(self._get_default_options(), options) + + @staticmethod + def _get_default_options(): + return { + 'verify_signature': True + } + + def register_algorithm(self, alg_id, alg_obj): + """ + Registers a new Algorithm for use when creating and verifying tokens. + """ + if alg_id in self._algorithms: + raise ValueError('Algorithm already has a handler.') + + if not isinstance(alg_obj, Algorithm): + raise TypeError('Object is not of type `Algorithm`') + + self._algorithms[alg_id] = alg_obj + self._valid_algs.add(alg_id) + + def unregister_algorithm(self, alg_id): + """ + Unregisters an Algorithm for use when creating and verifying tokens + Throws KeyError if algorithm is not registered. + """ + if alg_id not in self._algorithms: + raise KeyError('The specified algorithm could not be removed' + ' because it is not registered.') + + del self._algorithms[alg_id] + self._valid_algs.remove(alg_id) + + def get_algorithms(self): + """ + Returns a list of supported values for the 'alg' parameter. + """ + return list(self._valid_algs) + + def encode(self, payload, key, algorithm='HS256', headers=None, + json_encoder=None): + segments = [] + + if algorithm is None: + algorithm = 'none' + + if algorithm not in self._valid_algs: + pass + + # Header + header = {'typ': self.header_typ, 'alg': algorithm} + + if headers: + self._validate_headers(headers) + header.update(headers) + + json_header = force_bytes( + json.dumps( + header, + separators=(',', ':'), + cls=json_encoder + ) + ) + + segments.append(base64url_encode(json_header)) + segments.append(base64url_encode(payload)) + + # Segments + signing_input = b'.'.join(segments) + try: + alg_obj = self._algorithms[algorithm] + key = alg_obj.prepare_key(key) + signature = alg_obj.sign(signing_input, key) + + except KeyError: + if not has_crypto and algorithm in requires_cryptography: + raise NotImplementedError( + "Algorithm '%s' could not be found. Do you have cryptography " + "installed?" % algorithm + ) + else: + raise NotImplementedError('Algorithm not supported') + + segments.append(base64url_encode(signature)) + + return b'.'.join(segments) + + def decode(self, jws, key='', verify=True, algorithms=None, options=None, + **kwargs): + + merged_options = merge_dict(self.options, options) + verify_signature = merged_options['verify_signature'] + + if verify_signature and not algorithms: + warnings.warn( + 'It is strongly recommended that you pass in a ' + + 'value for the "algorithms" argument when calling decode(). ' + + 'This argument will be mandatory in a future version.', + DeprecationWarning + ) + + payload, signing_input, header, signature = self._load(jws) + + if not verify: + warnings.warn('The verify parameter is deprecated. ' + 'Please use verify_signature in options instead.', + DeprecationWarning, stacklevel=2) + elif verify_signature: + self._verify_signature(payload, signing_input, header, signature, + key, algorithms) + + return payload + + def get_unverified_header(self, jwt): + """Returns back the JWT header parameters as a dict() + + Note: The signature is not verified so the header parameters + should not be fully trusted until signature verification is complete + """ + headers = self._load(jwt)[2] + self._validate_headers(headers) + + return headers + + def _load(self, jwt): + if isinstance(jwt, text_type): + jwt = jwt.encode('utf-8') + + if not issubclass(type(jwt), binary_type): + raise DecodeError("Invalid token type. Token must be a {0}".format( + binary_type)) + + try: + signing_input, crypto_segment = jwt.rsplit(b'.', 1) + header_segment, payload_segment = signing_input.split(b'.', 1) + except ValueError: + raise DecodeError('Not enough segments') + + try: + header_data = base64url_decode(header_segment) + except (TypeError, binascii.Error): + raise DecodeError('Invalid header padding') + + try: + header = json.loads(header_data.decode('utf-8')) + except ValueError as e: + raise DecodeError('Invalid header string: %s' % e) + + if not isinstance(header, Mapping): + raise DecodeError('Invalid header string: must be a json object') + + try: + payload = base64url_decode(payload_segment) + except (TypeError, binascii.Error): + raise DecodeError('Invalid payload padding') + + try: + signature = base64url_decode(crypto_segment) + except (TypeError, binascii.Error): + raise DecodeError('Invalid crypto padding') + + return (payload, signing_input, header, signature) + + def _verify_signature(self, payload, signing_input, header, signature, + key='', algorithms=None): + + alg = header.get('alg') + + if algorithms is not None and alg not in algorithms: + raise InvalidAlgorithmError('The specified alg value is not allowed') + + try: + alg_obj = self._algorithms[alg] + key = alg_obj.prepare_key(key) + + if not alg_obj.verify(signing_input, key, signature): + raise DecodeError('Signature verification failed') + + except KeyError: + raise InvalidAlgorithmError('Algorithm not supported') + + def _validate_headers(self, headers): + if 'kid' in headers: + self._validate_kid(headers['kid']) + + def _validate_kid(self, kid): + if not isinstance(kid, string_types): + raise InvalidTokenError('Key ID header parameter must be a string') + + +_jws_global_obj = PyJWS() +encode = _jws_global_obj.encode +decode = _jws_global_obj.decode +register_algorithm = _jws_global_obj.register_algorithm +unregister_algorithm = _jws_global_obj.unregister_algorithm +get_unverified_header = _jws_global_obj.get_unverified_header diff --git a/flex/lib/python3.6/site-packages/jwt/api_jwt.py b/flex/lib/python3.6/site-packages/jwt/api_jwt.py new file mode 100644 index 0000000..9e20141 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/api_jwt.py @@ -0,0 +1,199 @@ +import json +import warnings + +from calendar import timegm +from collections import Mapping +from datetime import datetime, timedelta + +from .api_jws import PyJWS +from .algorithms import Algorithm, get_default_algorithms # NOQA +from .compat import string_types, timedelta_total_seconds +from .exceptions import ( + DecodeError, ExpiredSignatureError, ImmatureSignatureError, + InvalidAudienceError, InvalidIssuedAtError, + InvalidIssuerError, MissingRequiredClaimError +) +from .utils import merge_dict + + +class PyJWT(PyJWS): + header_type = 'JWT' + + @staticmethod + def _get_default_options(): + return { + 'verify_signature': True, + 'verify_exp': True, + 'verify_nbf': True, + 'verify_iat': True, + 'verify_aud': True, + 'verify_iss': True, + 'require_exp': False, + 'require_iat': False, + 'require_nbf': False + } + + def encode(self, payload, key, algorithm='HS256', headers=None, + json_encoder=None): + # Check that we get a mapping + if not isinstance(payload, Mapping): + raise TypeError('Expecting a mapping object, as JWT only supports ' + 'JSON objects as payloads.') + + # Payload + for time_claim in ['exp', 'iat', 'nbf']: + # Convert datetime to a intDate value in known time-format claims + if isinstance(payload.get(time_claim), datetime): + payload[time_claim] = timegm(payload[time_claim].utctimetuple()) + + json_payload = json.dumps( + payload, + separators=(',', ':'), + cls=json_encoder + ).encode('utf-8') + + return super(PyJWT, self).encode( + json_payload, key, algorithm, headers, json_encoder + ) + + def decode(self, jwt, key='', verify=True, algorithms=None, options=None, + **kwargs): + + if verify and not algorithms: + warnings.warn( + 'It is strongly recommended that you pass in a ' + + 'value for the "algorithms" argument when calling decode(). ' + + 'This argument will be mandatory in a future version.', + DeprecationWarning + ) + + payload, signing_input, header, signature = self._load(jwt) + + if options is None: + options = {'verify_signature': verify} + else: + options.setdefault('verify_signature', verify) + + decoded = super(PyJWT, self).decode( + jwt, key=key, algorithms=algorithms, options=options, **kwargs + ) + + try: + payload = json.loads(decoded.decode('utf-8')) + except ValueError as e: + raise DecodeError('Invalid payload string: %s' % e) + if not isinstance(payload, Mapping): + raise DecodeError('Invalid payload string: must be a json object') + + if verify: + merged_options = merge_dict(self.options, options) + self._validate_claims(payload, merged_options, **kwargs) + + return payload + + def _validate_claims(self, payload, options, audience=None, issuer=None, + leeway=0, **kwargs): + + if 'verify_expiration' in kwargs: + options['verify_exp'] = kwargs.get('verify_expiration', True) + warnings.warn('The verify_expiration parameter is deprecated. ' + 'Please use verify_exp in options instead.', + DeprecationWarning) + + if isinstance(leeway, timedelta): + leeway = timedelta_total_seconds(leeway) + + if not isinstance(audience, (string_types, type(None))): + raise TypeError('audience must be a string or None') + + self._validate_required_claims(payload, options) + + now = timegm(datetime.utcnow().utctimetuple()) + + if 'iat' in payload and options.get('verify_iat'): + self._validate_iat(payload, now, leeway) + + if 'nbf' in payload and options.get('verify_nbf'): + self._validate_nbf(payload, now, leeway) + + if 'exp' in payload and options.get('verify_exp'): + self._validate_exp(payload, now, leeway) + + if options.get('verify_iss'): + self._validate_iss(payload, issuer) + + if options.get('verify_aud'): + self._validate_aud(payload, audience) + + def _validate_required_claims(self, payload, options): + if options.get('require_exp') and payload.get('exp') is None: + raise MissingRequiredClaimError('exp') + + if options.get('require_iat') and payload.get('iat') is None: + raise MissingRequiredClaimError('iat') + + if options.get('require_nbf') and payload.get('nbf') is None: + raise MissingRequiredClaimError('nbf') + + def _validate_iat(self, payload, now, leeway): + try: + int(payload['iat']) + except ValueError: + raise InvalidIssuedAtError('Issued At claim (iat) must be an integer.') + + def _validate_nbf(self, payload, now, leeway): + try: + nbf = int(payload['nbf']) + except ValueError: + raise DecodeError('Not Before claim (nbf) must be an integer.') + + if nbf > (now + leeway): + raise ImmatureSignatureError('The token is not yet valid (nbf)') + + def _validate_exp(self, payload, now, leeway): + try: + exp = int(payload['exp']) + except ValueError: + raise DecodeError('Expiration Time claim (exp) must be an' + ' integer.') + + if exp < (now - leeway): + raise ExpiredSignatureError('Signature has expired') + + def _validate_aud(self, payload, audience): + if audience is None and 'aud' not in payload: + return + + if audience is not None and 'aud' not in payload: + # Application specified an audience, but it could not be + # verified since the token does not contain a claim. + raise MissingRequiredClaimError('aud') + + audience_claims = payload['aud'] + + if isinstance(audience_claims, string_types): + audience_claims = [audience_claims] + if not isinstance(audience_claims, list): + raise InvalidAudienceError('Invalid claim format in token') + if any(not isinstance(c, string_types) for c in audience_claims): + raise InvalidAudienceError('Invalid claim format in token') + if audience not in audience_claims: + raise InvalidAudienceError('Invalid audience') + + def _validate_iss(self, payload, issuer): + if issuer is None: + return + + if 'iss' not in payload: + raise MissingRequiredClaimError('iss') + + if payload['iss'] != issuer: + raise InvalidIssuerError('Invalid issuer') + + +_jwt_global_obj = PyJWT() +encode = _jwt_global_obj.encode +decode = _jwt_global_obj.decode +register_algorithm = _jwt_global_obj.register_algorithm +unregister_algorithm = _jwt_global_obj.unregister_algorithm +get_unverified_header = _jwt_global_obj.get_unverified_header diff --git a/flex/lib/python3.6/site-packages/jwt/compat.py b/flex/lib/python3.6/site-packages/jwt/compat.py new file mode 100644 index 0000000..b928c7d --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/compat.py @@ -0,0 +1,76 @@ +""" +The `compat` module provides support for backwards compatibility with older +versions of python, and compatibility wrappers around optional packages. +""" +# flake8: noqa +import hmac +import struct +import sys + + +PY3 = sys.version_info[0] == 3 + + +if PY3: + text_type = str + binary_type = bytes +else: + text_type = unicode + binary_type = str + +string_types = (text_type, binary_type) + + +def timedelta_total_seconds(delta): + try: + delta.total_seconds + except AttributeError: + # On Python 2.6, timedelta instances do not have + # a .total_seconds() method. + total_seconds = delta.days * 24 * 60 * 60 + delta.seconds + else: + total_seconds = delta.total_seconds() + + return total_seconds + + +try: + constant_time_compare = hmac.compare_digest +except AttributeError: + # Fallback for Python < 2.7 + 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. + """ + if len(val1) != len(val2): + return False + + result = 0 + + for x, y in zip(val1, val2): + result |= ord(x) ^ ord(y) + + return result == 0 + +# Use int.to_bytes if it exists (Python 3) +if getattr(int, 'to_bytes', None): + def bytes_from_int(val): + remaining = val + byte_length = 0 + + while remaining != 0: + remaining = remaining >> 8 + byte_length += 1 + + return val.to_bytes(byte_length, 'big', signed=False) +else: + def bytes_from_int(val): + buf = [] + while val: + val, remainder = divmod(val, 256) + buf.append(remainder) + + buf.reverse() + return struct.pack('%sB' % len(buf), *buf) diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/__init__.py b/flex/lib/python3.6/site-packages/jwt/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/contrib/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d08d6e430763d4e92adcf993e44e7d054f414e7 GIT binary patch literal 188 zcmXr!<>gX7l@i4O1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuT1@dqRg_yl2rY& z#3KE){G623qGJ8DB7Ng*!@QzG6H9}Xs+20rViSvO6U$snGZO&`m1M z%t_HT%}Xw_C^0D152*AC57EymFVRoV&nqd)Owx~!&&GXMZh1Teh- literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__init__.py b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00da80620a4ed28003037a9e66c8e83f3367b76a GIT binary patch literal 199 zcmXr!<>gX7l@i4O1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuM+)&qRg_yl2rY& z#3KE){G623qGJ8DB7Ng*!@QzG6H9}Xs+20rViSvO6U$snGZO&`m1M z%t_HT%}Xw_C^0D152*AC57EymFVRoV&nqd)Owv!xNzX6JEXl|%){l?R%*!l^kJl@x Vyv1RYo1apelWGTYO)(HN0067@H8B7H literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8d55880f3c4225752839cdab1ff97eb317bf6f2 GIT binary patch literal 1820 zcmZux&2A($5N`M1^mHcKAp|x^E7TIFk;rs5KTCoT+K?SUAfku~?BYwS)Ar2tr2k@j z!pa%xOSN#uuXY_B^F#a@_nho+( z=n@BJFp?RxnF%GJo0*wg)FQZMWp?gRCwHk!j4upkGv}4T9B$oMw8hBK=(&HvUyPF; zsoO`{xD=_HFRYa@d4K}rZ##>1eaI2%J@Y{|2c z08SXc8N!3<9~D+2y^0kNuVN97%8YR#!;uJgFCG`-a_?}PE!g5v?j2n09p;Bm_O_vo zi|{P0(rW8`mS${gzevP^+P(~Rkm2-46((f?6gUsz*oGA{tmcui;bo7_3sS+gL4I}HO`+wP=HjCsd;0{&0E>JF~y^6Ag0!B(1ey8kUqX9Q*{v-kllUKoX^fQUEb7lJ4BtdoT^Q=_ee z#ruPoKoF(b6`T&f*+Ry}Nf7lT6Yd)u)?~#ym_yK6b z0&D`WY3=eNDH*5UNR)Y0@tj&v9lH8m!S#upJsW&mx6j~~4TLB~&lD~6v*1`=-DYUx z+DavO6$>80-XmC$n=lw=hjfT-`UF15BweyjHZ0FvJX|itl4pX!VZ+&huB-7b2-oIR zGjB?i3Awcy`PG6fnJ2(H%|N_0v<=s=0o8MWjF19NM$v&k)b>oJ8STC{iJDiWXWgR8 z2C<6%1P9T4W5I&uqd%~I_45KNT=eE6{8 z12c!li{@5YfJwXsz8VhWSfkH-Ztd!KWqyxHyp2W(s4=<)RIeRL$ptP;7v; z23T9*@+Qi6VD}^F`i>jKgu->m8d+3R>V1vqS}w5ykxTs Wj;y^;8q}X+$t8y3nI7=HYyAt}2E>8@ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a696fe18b6ecbc6f51ac53acb6c75ea128687e89 GIT binary patch literal 1762 zcmZuxOK;mo5ayDUL{YTsBn^;04g?Am5CV~Y26j;tF$}jxgBnIOio232^X1)@ z6A9|2&Y$UD=}+LXJ>@U-)S0F2$Vryinc3Z$*>4`powYUho4*#b-HR^7vFQi@bv(ii+{mI^NYv-y#j zSE}@@IJt}`+y`;Q{j`uO&N9wcO~HB;OK@I}$1r`oG0m%t=e$r2+_V6XKmT^_Zw5QT zw!SsNgyKMs^9ZJNfh@_=h@kqA9)bmQ5Ofr06DXA}c{UafX#HgfRQ^wel}N8+#l!1Z zgyS+}T*z=N!kx>lLR{@0Y_bJg9LU}M%iV+g;KlAHj7brmhgDh)Mzb_ygS{dV`)czl z)F8v@YZWGC0ZlOq;o7E8WLV7;os*!N*G?3rMXI9c3q-9$A&aN0r3~&g9t3KEx(@V5 z5EO)}$keEkhnY9zPl8r`>vYkYsqtWc z@$K;&sI<}@GVn{`1E>LZQ>+x!=#tNazEis?bPd$9NVq`sv=f0|Ihu+VtVA0~ z-R4D7GETct%u=XVd|r3Y;fWjzQHs7Px~ODXFBHM*@JAYm?W4a9LVnz_6!!vn35#~J~fxd)B+jOaf2ygq^Lzlq2 z(OuHFZeiwfA`r$0Sj7|Adk*uihL0YgLO%)qxNso9hw~*YJ_Tn9^;-ob9)n`t){l4I zK(aL?@i`o5NVNsme2D8QoCYxOa6Nf|t8sVQU&BT$;+#iO?M6{vvRQ_*7e!aIIJ-UR zL=h_!*lstEn%XDuqU!eW9Jwk%X5r||39X>jNWfmPEsH@BC zI5u(cGabwM%(3)of1N&Vt_<+Tpt(?d1x6no0D_P901e%^b#OKtWoZIU*!1h&_7Ex* ztC`@s{B#}OZt(ui3alA*aWg6NDpv2?TLb)5T($FklgA$j2Dc8R3CtrN^zerDKT2J( AH2?qr literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/py_ecdsa.py b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/py_ecdsa.py new file mode 100644 index 0000000..bf0dea5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/py_ecdsa.py @@ -0,0 +1,60 @@ +# Note: This file is named py_ecdsa.py because import behavior in Python 2 +# would cause ecdsa.py to squash the ecdsa library that it depends upon. + +import hashlib + +import ecdsa + +from jwt.algorithms import Algorithm +from jwt.compat import string_types, text_type + + +class ECAlgorithm(Algorithm): + """ + Performs signing and verification operations using + ECDSA and the specified hash function + + This class requires the ecdsa package to be installed. + + This is based off of the implementation in PyJWT 0.3.2 + """ + SHA256 = hashlib.sha256 + SHA384 = hashlib.sha384 + SHA512 = hashlib.sha512 + + def __init__(self, hash_alg): + self.hash_alg = hash_alg + + def prepare_key(self, key): + + if isinstance(key, ecdsa.SigningKey) or \ + isinstance(key, ecdsa.VerifyingKey): + return key + + if isinstance(key, string_types): + if isinstance(key, text_type): + key = key.encode('utf-8') + + # Attempt to load key. We don't know if it's + # a Signing Key or a Verifying Key, so we try + # the Verifying Key first. + try: + key = ecdsa.VerifyingKey.from_pem(key) + except ecdsa.der.UnexpectedDER: + key = ecdsa.SigningKey.from_pem(key) + + else: + raise TypeError('Expecting a PEM-formatted key.') + + return key + + def sign(self, msg, key): + return key.sign(msg, hashfunc=self.hash_alg, + sigencode=ecdsa.util.sigencode_string) + + def verify(self, msg, key, sig): + try: + return key.verify(sig, msg, hashfunc=self.hash_alg, + sigdecode=ecdsa.util.sigdecode_string) + except AssertionError: + return False diff --git a/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/pycrypto.py b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/pycrypto.py new file mode 100644 index 0000000..e6afaa5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/pycrypto.py @@ -0,0 +1,47 @@ +import Crypto.Hash.SHA256 +import Crypto.Hash.SHA384 +import Crypto.Hash.SHA512 + +from Crypto.PublicKey import RSA +from Crypto.Signature import PKCS1_v1_5 + +from jwt.algorithms import Algorithm +from jwt.compat import string_types, text_type + + +class RSAAlgorithm(Algorithm): + """ + Performs signing and verification operations using + RSASSA-PKCS-v1_5 and the specified hash function. + + This class requires PyCrypto package to be installed. + + This is based off of the implementation in PyJWT 0.3.2 + """ + SHA256 = Crypto.Hash.SHA256 + SHA384 = Crypto.Hash.SHA384 + SHA512 = Crypto.Hash.SHA512 + + def __init__(self, hash_alg): + self.hash_alg = hash_alg + + def prepare_key(self, key): + + if isinstance(key, RSA._RSAobj): + return key + + if isinstance(key, string_types): + if isinstance(key, text_type): + key = key.encode('utf-8') + + key = RSA.importKey(key) + else: + raise TypeError('Expecting a PEM- or RSA-formatted key.') + + return key + + def sign(self, msg, key): + return PKCS1_v1_5.new(key).sign(self.hash_alg.new(msg)) + + def verify(self, msg, key, sig): + return PKCS1_v1_5.new(key).verify(self.hash_alg.new(msg), sig) diff --git a/flex/lib/python3.6/site-packages/jwt/exceptions.py b/flex/lib/python3.6/site-packages/jwt/exceptions.py new file mode 100644 index 0000000..31177a0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/exceptions.py @@ -0,0 +1,48 @@ +class InvalidTokenError(Exception): + pass + + +class DecodeError(InvalidTokenError): + pass + + +class ExpiredSignatureError(InvalidTokenError): + pass + + +class InvalidAudienceError(InvalidTokenError): + pass + + +class InvalidIssuerError(InvalidTokenError): + pass + + +class InvalidIssuedAtError(InvalidTokenError): + pass + + +class ImmatureSignatureError(InvalidTokenError): + pass + + +class InvalidKeyError(Exception): + pass + + +class InvalidAlgorithmError(InvalidTokenError): + pass + + +class MissingRequiredClaimError(InvalidTokenError): + def __init__(self, claim): + self.claim = claim + + def __str__(self): + return 'Token is missing the "%s" claim' % self.claim + + +# Compatibility aliases (deprecated) +ExpiredSignature = ExpiredSignatureError +InvalidAudience = InvalidAudienceError +InvalidIssuer = InvalidIssuerError diff --git a/flex/lib/python3.6/site-packages/jwt/utils.py b/flex/lib/python3.6/site-packages/jwt/utils.py new file mode 100644 index 0000000..b33c7a2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/jwt/utils.py @@ -0,0 +1,113 @@ +import base64 +import binascii +import struct + +from .compat import binary_type, bytes_from_int, text_type + +try: + from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature, encode_dss_signature + ) +except ImportError: + pass + + +def force_unicode(value): + if isinstance(value, binary_type): + return value.decode('utf-8') + elif isinstance(value, text_type): + return value + else: + raise TypeError('Expected a string value') + + +def force_bytes(value): + if isinstance(value, text_type): + return value.encode('utf-8') + elif isinstance(value, binary_type): + return value + else: + raise TypeError('Expected a string value') + + +def base64url_decode(input): + if isinstance(input, text_type): + input = input.encode('ascii') + + rem = len(input) % 4 + + if rem > 0: + input += b'=' * (4 - rem) + + return base64.urlsafe_b64decode(input) + + +def base64url_encode(input): + return base64.urlsafe_b64encode(input).replace(b'=', b'') + + +def to_base64url_uint(val): + if val < 0: + raise ValueError('Must be a positive integer') + + int_bytes = bytes_from_int(val) + + if len(int_bytes) == 0: + int_bytes = b'\x00' + + return base64url_encode(int_bytes) + + +def from_base64url_uint(val): + if isinstance(val, text_type): + val = val.encode('ascii') + + data = base64url_decode(val) + + buf = struct.unpack('%sB' % len(data), data) + return int(''.join(["%02x" % byte for byte in buf]), 16) + + +def merge_dict(original, updates): + if not updates: + return original + + try: + merged_options = original.copy() + merged_options.update(updates) + except (AttributeError, ValueError) as e: + raise TypeError('original and updates must be a dictionary: %s' % e) + + return merged_options + + +def number_to_bytes(num, num_bytes): + padded_hex = '%0*x' % (2 * num_bytes, num) + big_endian = binascii.a2b_hex(padded_hex.encode('ascii')) + return big_endian + + +def bytes_to_number(string): + return int(binascii.b2a_hex(string), 16) + + +def der_to_raw_signature(der_sig, curve): + num_bits = curve.key_size + num_bytes = (num_bits + 7) // 8 + + r, s = decode_dss_signature(der_sig) + + return number_to_bytes(r, num_bytes) + number_to_bytes(s, num_bytes) + + +def raw_to_der_signature(raw_sig, curve): + num_bits = curve.key_size + num_bytes = (num_bits + 7) // 8 + + if len(raw_sig) != 2 * num_bytes: + raise ValueError('Invalid signature') + + r = bytes_to_number(raw_sig[:num_bytes]) + s = bytes_to_number(raw_sig[num_bytes:]) + + return encode_dss_signature(r, s) diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..a12cf21 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/DESCRIPTION.rst @@ -0,0 +1,103 @@ +OAuthLib +======== + +*A generic, spec-compliant, thorough implementation of the OAuth request-signing +logic for python* + +.. image:: https://travis-ci.org/idan/oauthlib.svg?branch=master + :target: https://travis-ci.org/idan/oauthlib +.. image:: https://coveralls.io/repos/idan/oauthlib/badge.svg?branch=master + :target: https://coveralls.io/r/idan/oauthlib + + +OAuth often seems complicated and difficult-to-implement. There are several +prominent libraries for handling OAuth requests, but they all suffer from one or +both of the following: + +1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849. +2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749. +3. They assume the usage of a specific HTTP request library. + +.. _`OAuth 1.0 spec`: http://tools.ietf.org/html/rfc5849 +.. _`OAuth 2.0 spec`: http://tools.ietf.org/html/rfc6749 + +OAuthLib is a generic utility which implements the logic of OAuth without +assuming a specific HTTP request object or web framework. Use it to graft OAuth +client support onto your favorite HTTP library, or provide support onto your +favourite web framework. If you're a maintainer of such a library, write a thin +veneer on top of OAuthLib and get OAuth support for very little effort. + + +Documentation +-------------- + +Full documentation is available on `Read the Docs`_. All contributions are very +welcome! The documentation is still quite sparse, please open an issue for what +you'd like to know, or discuss it in our `G+ community`_, or even better, send a +pull request! + +.. _`G+ community`: https://plus.google.com/communities/101889017375384052571 +.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html + +Interested in making OAuth requests? +------------------------------------ + +Then you might be more interested in using `requests`_ which has OAuthLib +powered OAuth support provided by the `requests-oauthlib`_ library. + +.. _`requests`: https://github.com/kennethreitz/requests +.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib + +Which web frameworks are supported? +----------------------------------- + +The following packages provide OAuth support using OAuthLib. + +- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support. +- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_. +- For Pyramid there is `pyramid-oauthlib`_. + +If you have written an OAuthLib package that supports your favorite framework, +please open a Pull Request, updating the documentation. + +.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit +.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib +.. _`Django REST framework`: http://django-rest-framework.org +.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance +.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib + +Using OAuthLib? Please get in touch! +------------------------------------ +Patching OAuth support onto an http request framework? Creating an OAuth +provider extension for a web framework? Simply using OAuthLib to Get Things Done +or to learn? + +No matter which we'd love to hear from you in our `G+ community`_ or if you have +anything in particular you would like to have, change or comment on don't +hesitate for a second to send a pull request or open an issue. We might be quite +busy and therefore slow to reply but we love feedback! + +Chances are you have run into something annoying that you wish there was +documentation for, if you wish to gain eternal fame and glory, and a drink if we +have the pleasure to run into eachother, please send a docs pull request =) + +.. _`G+ community`: https://plus.google.com/communities/101889017375384052571 + +License +------- + +OAuthLib is yours to use and abuse according to the terms of the BSD license. +Check the LICENSE file for full details. + +Changelog +--------- + +*OAuthLib is in active development, with the core of both OAuth 1 and 2 +completed, for providers as well as clients.* See `supported features`_ for +details. + +.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html + +For a full changelog see ``CHANGELOG.rst``. + + diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/METADATA b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/METADATA new file mode 100644 index 0000000..aca7933 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/METADATA @@ -0,0 +1,144 @@ +Metadata-Version: 2.0 +Name: oauthlib +Version: 2.0.6 +Summary: A generic, spec-compliant, thorough implementation of the OAuth request-signing logic +Home-page: https://github.com/idan/oauthlib +Author: Ib Lundgren +Author-email: ib.lundgren@gmail.com +License: BSD +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: MacOS +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Provides-Extra: rsa +Requires-Dist: cryptography; extra == 'rsa' +Provides-Extra: signals +Requires-Dist: blinker; extra == 'signals' +Provides-Extra: signedtoken +Requires-Dist: cryptography; extra == 'signedtoken' +Requires-Dist: pyjwt (>=1.0.0); extra == 'signedtoken' +Provides-Extra: test +Requires-Dist: nose; extra == 'test' +Requires-Dist: cryptography; extra == 'test' +Requires-Dist: pyjwt (>=1.0.0); extra == 'test' +Requires-Dist: blinker; extra == 'test' + +OAuthLib +======== + +*A generic, spec-compliant, thorough implementation of the OAuth request-signing +logic for python* + +.. image:: https://travis-ci.org/idan/oauthlib.svg?branch=master + :target: https://travis-ci.org/idan/oauthlib +.. image:: https://coveralls.io/repos/idan/oauthlib/badge.svg?branch=master + :target: https://coveralls.io/r/idan/oauthlib + + +OAuth often seems complicated and difficult-to-implement. There are several +prominent libraries for handling OAuth requests, but they all suffer from one or +both of the following: + +1. They predate the `OAuth 1.0 spec`_, AKA RFC 5849. +2. They predate the `OAuth 2.0 spec`_, AKA RFC 6749. +3. They assume the usage of a specific HTTP request library. + +.. _`OAuth 1.0 spec`: http://tools.ietf.org/html/rfc5849 +.. _`OAuth 2.0 spec`: http://tools.ietf.org/html/rfc6749 + +OAuthLib is a generic utility which implements the logic of OAuth without +assuming a specific HTTP request object or web framework. Use it to graft OAuth +client support onto your favorite HTTP library, or provide support onto your +favourite web framework. If you're a maintainer of such a library, write a thin +veneer on top of OAuthLib and get OAuth support for very little effort. + + +Documentation +-------------- + +Full documentation is available on `Read the Docs`_. All contributions are very +welcome! The documentation is still quite sparse, please open an issue for what +you'd like to know, or discuss it in our `G+ community`_, or even better, send a +pull request! + +.. _`G+ community`: https://plus.google.com/communities/101889017375384052571 +.. _`Read the Docs`: https://oauthlib.readthedocs.io/en/latest/index.html + +Interested in making OAuth requests? +------------------------------------ + +Then you might be more interested in using `requests`_ which has OAuthLib +powered OAuth support provided by the `requests-oauthlib`_ library. + +.. _`requests`: https://github.com/kennethreitz/requests +.. _`requests-oauthlib`: https://github.com/requests/requests-oauthlib + +Which web frameworks are supported? +----------------------------------- + +The following packages provide OAuth support using OAuthLib. + +- For Django there is `django-oauth-toolkit`_, which includes `Django REST framework`_ support. +- For Flask there is `flask-oauthlib`_ and `Flask-Dance`_. +- For Pyramid there is `pyramid-oauthlib`_. + +If you have written an OAuthLib package that supports your favorite framework, +please open a Pull Request, updating the documentation. + +.. _`django-oauth-toolkit`: https://github.com/evonove/django-oauth-toolkit +.. _`flask-oauthlib`: https://github.com/lepture/flask-oauthlib +.. _`Django REST framework`: http://django-rest-framework.org +.. _`Flask-Dance`: https://github.com/singingwolfboy/flask-dance +.. _`pyramid-oauthlib`: https://github.com/tilgovi/pyramid-oauthlib + +Using OAuthLib? Please get in touch! +------------------------------------ +Patching OAuth support onto an http request framework? Creating an OAuth +provider extension for a web framework? Simply using OAuthLib to Get Things Done +or to learn? + +No matter which we'd love to hear from you in our `G+ community`_ or if you have +anything in particular you would like to have, change or comment on don't +hesitate for a second to send a pull request or open an issue. We might be quite +busy and therefore slow to reply but we love feedback! + +Chances are you have run into something annoying that you wish there was +documentation for, if you wish to gain eternal fame and glory, and a drink if we +have the pleasure to run into eachother, please send a docs pull request =) + +.. _`G+ community`: https://plus.google.com/communities/101889017375384052571 + +License +------- + +OAuthLib is yours to use and abuse according to the terms of the BSD license. +Check the LICENSE file for full details. + +Changelog +--------- + +*OAuthLib is in active development, with the core of both OAuth 1 and 2 +completed, for providers as well as clients.* See `supported features`_ for +details. + +.. _`supported features`: https://oauthlib.readthedocs.io/en/latest/feature_matrix.html + +For a full changelog see ``CHANGELOG.rst``. + + diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/RECORD b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/RECORD new file mode 100644 index 0000000..762f0f5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/RECORD @@ -0,0 +1,103 @@ +oauthlib/__init__.py,sha256=-9EAes_pwDq1unCVb7ZMdmT68N45zF_GcKpzsod7qUo,520 +oauthlib/common.py,sha256=60Q-e05N0AN8w9H5cyhKZRTzlm5ep1YzTHlstFGdbuQ,14553 +oauthlib/signals.py,sha256=WdwbOYf_bsZAIWAfxxEiKixqm9a56rCkjAmgyw1JQiE,1521 +oauthlib/uri_validate.py,sha256=PpEpmrRxA1w-H3oicm0vqsg9dM9hjGRx1wxh96Xnl7k,7611 +oauthlib/oauth1/__init__.py,sha256=DHwYb0S5eNIqm6VXql7SaoFywtW6d9MgtGZAzZyOTk4,842 +oauthlib/oauth1/rfc5849/__init__.py,sha256=pwBGWl9FV0OzcxRjS3I-RVUXRipL54k9y532Ut2Oi2U,15245 +oauthlib/oauth1/rfc5849/errors.py,sha256=9BqHAtzoVUQ7v0SBkT3COFt2JuwMZlOOEXtL0pv7d4I,2543 +oauthlib/oauth1/rfc5849/parameters.py,sha256=Et08qMVoHuLSOHTNwl32ITn24xdN-9cioyces2jnJO8,4951 +oauthlib/oauth1/rfc5849/request_validator.py,sha256=F0xmtIhQpRTbF3HCGbmmIgpw7FVT4brWGiL0_wukk6s,30459 +oauthlib/oauth1/rfc5849/signature.py,sha256=2xaP5rW64x-JMXklCJNzBNa7_B2UkbOiEmyMqe7Brn4,23580 +oauthlib/oauth1/rfc5849/utils.py,sha256=AAaon0zxFLsazJrX5767Y5JTo2fu-_aSMbxHLskRAm8,2781 +oauthlib/oauth1/rfc5849/endpoints/__init__.py,sha256=d1ZrqNd-gGcMFKILLIE3yVoQVR-bx9AQaj0OqPrX-qc,352 +oauthlib/oauth1/rfc5849/endpoints/access_token.py,sha256=Z71p0t614L8p28YnL7HcRuM1e2C0rOCXj8La4qiO6Go,9344 +oauthlib/oauth1/rfc5849/endpoints/authorization.py,sha256=MaSkwWYuClN3JH-7DNsKJFwDUT3HbJhPvKynkNQ6J2o,6823 +oauthlib/oauth1/rfc5849/endpoints/base.py,sha256=1wy8_egRexQoEbrDNEGjvO5mpbu4fUqVMmxFblfxuGE,10352 +oauthlib/oauth1/rfc5849/endpoints/pre_configured.py,sha256=ttawzUkqmjvu1OlCw-fMDEFJNJYXk0OAtZYPBQDZc4E,605 +oauthlib/oauth1/rfc5849/endpoints/request_token.py,sha256=XgU8-UNeINr4nFLKt9mgl4jz5nySkLzMnLcuVTAe6BA,9283 +oauthlib/oauth1/rfc5849/endpoints/resource.py,sha256=Xf7peXSpb82JRxp6J80Gm2r2OY_s6uewPYI6o70Kqfs,7433 +oauthlib/oauth1/rfc5849/endpoints/signature_only.py,sha256=1gVLRB6MFpa-hTlzaR0wfAKSpD53t-7sGww_xwTpV4o,3385 +oauthlib/oauth2/__init__.py,sha256=h-aY5iK2_DiM4M0yOTyCfCqQKxrK4WASGA19b0B6w6k,2141 +oauthlib/oauth2/rfc6749/__init__.py,sha256=ByRccoeZxOuCI5LVDLkzJKzyrzSYyqX6yNbAJrRWSnQ,1728 +oauthlib/oauth2/rfc6749/errors.py,sha256=ps9pEcHNT0vkquAGyJpUOesYyixLIH6EMvnWSCH6pCc,12866 +oauthlib/oauth2/rfc6749/parameters.py,sha256=hyhqQW2Yf_elrFht1Zqm3W40str6-GcecqLCuilF-D8,15910 +oauthlib/oauth2/rfc6749/request_validator.py,sha256=ljJLEfkGFq7Llups1fj9zEUCRvC6cU4Ifrwk2VIflZc,24574 +oauthlib/oauth2/rfc6749/tokens.py,sha256=biyELAT9L-hxVIGd51QDE8VdpQqNKURmrBHa1ypTiWk,9798 +oauthlib/oauth2/rfc6749/utils.py,sha256=TbyPVOw_hfBrf3A82HI94bEpHb0HA5At_uMg4neskEo,2494 +oauthlib/oauth2/rfc6749/clients/__init__.py,sha256=MBwNpK8to49QlYYMxhHooU4UnPG6hsKpAVwoCm-ufE4,562 +oauthlib/oauth2/rfc6749/clients/backend_application.py,sha256=kDE80WNI0Lkhmkzz2rz2GeYmWwb6Mze9zrgFGyyQO2g,2474 +oauthlib/oauth2/rfc6749/clients/base.py,sha256=OsMMi8uIEFAIrLvwZyzfAGjrlaQqE671DvDACevtgp8,20354 +oauthlib/oauth2/rfc6749/clients/legacy_application.py,sha256=rlG_a9z1mE-mZfT8IUm2tJ0wcJp5tQTf_9w-cfikJ1E,3313 +oauthlib/oauth2/rfc6749/clients/mobile_application.py,sha256=cWosPAJZ7w7SxGaCPuDUArbzUCAoob28e1o0Mvxy7Hg,8769 +oauthlib/oauth2/rfc6749/clients/service_application.py,sha256=KtB3m70qP8lDQ9Jf-kaEsuFOFWKNdDaMkoVm4MZMF9Q,6980 +oauthlib/oauth2/rfc6749/clients/web_application.py,sha256=4eC57KbS0Jquf8dzxJpFlg5XNC1Mn1HJX5zqGE2JCls,9201 +oauthlib/oauth2/rfc6749/endpoints/__init__.py,sha256=2cFcinSijL_H9ToMCwc8aYTYQhpMnNvUtXY8LNGMYZ0,648 +oauthlib/oauth2/rfc6749/endpoints/authorization.py,sha256=zlM8P1TaH-LqMOj-gIhtATMsHF8BqoHw8ws1P7eEs4w,4665 +oauthlib/oauth2/rfc6749/endpoints/base.py,sha256=L2QooEesOsDIlMvR2jPStG31p1_bu9qX0Ab3VCQw_So,1733 +oauthlib/oauth2/rfc6749/endpoints/pre_configured.py,sha256=OccCP8eMhrT6ReyKQs8PHxH1bDjirfyj8wWKM-tn7MA,12327 +oauthlib/oauth2/rfc6749/endpoints/resource.py,sha256=4Br4ZvzTc4mhVW9Bsw_gHQNA-BwAyr0_-r6GotaCifA,3316 +oauthlib/oauth2/rfc6749/endpoints/revocation.py,sha256=F38BPAQ_HUyavfUvwEU65TUwdJ7Msj-ILsFqnvfdftI,5796 +oauthlib/oauth2/rfc6749/endpoints/token.py,sha256=2uZlbQ6BN88UXhSS4iceJJM7Lh0kl9_NijeEPyUdN3k,4516 +oauthlib/oauth2/rfc6749/grant_types/__init__.py,sha256=Y4ejhY509WtGNwi6S2_cCiY_xdEIAxnvsQGDMdT1XCc,728 +oauthlib/oauth2/rfc6749/grant_types/authorization_code.py,sha256=0Wva1RB4lWAjUXb-YZMPE5PZvr90jeiFxwSOwwaufRY,21516 +oauthlib/oauth2/rfc6749/grant_types/base.py,sha256=uc1-nu-V6RvCvCQEMvrEj_exo00sxvuMCoNrYjdXBtg,7576 +oauthlib/oauth2/rfc6749/grant_types/client_credentials.py,sha256=mT9HnCy5HX9sJADK6cu9kr61Nl5mKIEeMKzhXfCUfAg,4948 +oauthlib/oauth2/rfc6749/grant_types/implicit.py,sha256=Pu2I-n4Qj66iowSvK8m23zYrXobRtaMx0ag_REGPKrw,17622 +oauthlib/oauth2/rfc6749/grant_types/openid_connect.py,sha256=GgcuURNoDYKihZ0xOidM8cciqwo9L6Alv21oAlxYBxY,18890 +oauthlib/oauth2/rfc6749/grant_types/refresh_token.py,sha256=8Y5zSOp_XOFnfTr7GX4B8WTHES6Bwml7qPFim0s8otY,5645 +oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py,sha256=WkW1Y3RbB1WMO-QizoPug0eMtsu8Ymdc2Q0U82sEwv8,8413 +oauthlib-2.0.6.dist-info/DESCRIPTION.rst,sha256=F1IdCzKfZjWQvAqGfqIkqQQ1dwIFRaR-Flq6KU0IdLo,4094 +oauthlib-2.0.6.dist-info/METADATA,sha256=MjWxacup6UJJnTGhvB2NglMRT8EzphRtmTYyhN5Gyl0,5793 +oauthlib-2.0.6.dist-info/RECORD,, +oauthlib-2.0.6.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110 +oauthlib-2.0.6.dist-info/metadata.json,sha256=BzRK_BaPs9KGVccmi9UQeTdIMt8Ho9IlDWdXzMAp03w,1714 +oauthlib-2.0.6.dist-info/top_level.txt,sha256=gz2py0fFs1AhG1O7KpHPcIXOgXOwdIiCaSnmLkiR12Q,9 +oauthlib-2.0.6.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +oauthlib/oauth2/rfc6749/clients/__pycache__/mobile_application.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/legacy_application.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/web_application.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/base.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/service_application.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/clients/__pycache__/backend_application.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/authorization.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/resource.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/token.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/base.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/revocation.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/endpoints/__pycache__/pre_configured.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/tokens.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/request_validator.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/parameters.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/errors.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/utils.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/refresh_token.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/authorization_code.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/client_credentials.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/resource_owner_password_credentials.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/base.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/implicit.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/openid_connect.cpython-36.pyc,, +oauthlib/oauth2/rfc6749/grant_types/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth2/__pycache__/__init__.cpython-36.pyc,, +oauthlib/__pycache__/signals.cpython-36.pyc,, +oauthlib/__pycache__/common.cpython-36.pyc,, +oauthlib/__pycache__/uri_validate.cpython-36.pyc,, +oauthlib/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/authorization.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/signature_only.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/resource.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/request_token.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/base.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/pre_configured.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/endpoints/__pycache__/access_token.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/request_validator.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/parameters.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/errors.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/utils.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/__init__.cpython-36.pyc,, +oauthlib/oauth1/rfc5849/__pycache__/signature.cpython-36.pyc,, +oauthlib/oauth1/__pycache__/__init__.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/WHEEL b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/WHEEL new file mode 100644 index 0000000..7332a41 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/metadata.json b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/metadata.json new file mode 100644 index 0000000..8cef20b --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved", "License :: OSI Approved :: BSD License", "Operating System :: MacOS", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries :: Python Modules"], "extensions": {"python.details": {"contacts": [{"email": "ib.lundgren@gmail.com", "name": "Ib Lundgren", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/idan/oauthlib"}}}, "extras": ["rsa", "signals", "signedtoken", "test"], "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "oauthlib", "platform": "any", "run_requires": [{"extra": "signals", "requires": ["blinker"]}, {"extra": "test", "requires": ["blinker", "cryptography", "nose", "pyjwt (>=1.0.0)"]}, {"extra": "rsa", "requires": ["cryptography"]}, {"extra": "signedtoken", "requires": ["cryptography", "pyjwt (>=1.0.0)"]}], "summary": "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic", "test_requires": [{"requires": ["blinker", "cryptography", "nose", "pyjwt (>=1.0.0)"]}], "version": "2.0.6"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/top_level.txt new file mode 100644 index 0000000..b5f3f0e --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib-2.0.6.dist-info/top_level.txt @@ -0,0 +1 @@ +oauthlib diff --git a/flex/lib/python3.6/site-packages/oauthlib/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/__init__.py new file mode 100644 index 0000000..620be76 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/__init__.py @@ -0,0 +1,25 @@ +""" + oauthlib + ~~~~~~~~ + + A generic, spec-compliant, thorough implementation of the OAuth + request-signing logic. + + :copyright: (c) 2011 by Idan Gazit. + :license: BSD, see LICENSE for details. +""" + +__author__ = 'Idan Gazit ' +__version__ = '2.0.6' + + +import logging +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + + def emit(self, record): + pass + +logging.getLogger('oauthlib').addHandler(NullHandler()) diff --git a/flex/lib/python3.6/site-packages/oauthlib/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0d27ece57d7bc348c55bc1b00c591931f47df22 GIT binary patch literal 905 zcmYjPPjAyO6t@$%%~FPXKnQX22~lZnx~)uWHBB318beb#3<=1^N@F+mme{G2PHGQa zx-Y|5;lw#tPJ9JUyriqP7y0G=aen!|Kl|I;Zu=Ma9Sfm9=!rDJK7o^sL2=|Gj(yB6 z-{LkWvF+Q77Fe~|PKZx1`ij&Wf<*1sHJ>mlp9{q6ym60sL)bULJ1?o9h#*g?1VZ=V;QJ?hvrjl z^gNnU2_caPl?S=his}iZ1pRyppcRG@v$@FfP9`HIRYYSQ$*{Y|ABH+z7#U6TA$=RR z>0WPlmyQ?ok_U=j1WTEBD~w?*L!mM;r0=gk02d+X)#cgw==z*aw4q$&fsC`RyL|ol zB|VaWauQW2i8x-?_PV|9!Cz=D8ntoJ9L?kSW1x5}Ojrgt0&`i<;RP51!3+@0048yd ze4E?QIbzB?VW~*wl8Jay)Qt$W;l_q+rT$4THS#XVg?ASi zZ=z!^Oy*6Dw|~2<%&b4?@g-jlvi{+1e~=6g`aO_Hd0)I#rk(Lz#=P@Rh2}8t&AiPB z9}UK&k~w4DbWsqI$oyqVu40UrttX{-%jBp{3WqTjB!aQRWh~Kr9+&nr#%A*%US~{W zL;tZ5Q!isBaM`Zv4~|v&zO$j#-YOc5aUHI~%YD-Z>?UJ(P&KR#I1N~g2o|LJwrE}^ zsW$n!G1?T3hcOjfk;t#$nlObMaQ+Z}LZ<3($*6FS)-@cvC9M`HVi8Pi2V1y>pW|1x E|L||^ApigX literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/__pycache__/common.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/common.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f982eac6ecda12cf645230812f38555c839012d GIT binary patch literal 13480 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 ZUo!WZ<*~9kVdlq*W)WW}O3#lU`%foDJT?FT literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/__pycache__/signals.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/signals.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..57d27f3eb83de2ca96748a32c865123734fdce24 GIT binary patch literal 1926 zcmZuyL2uhO6eeZaR-Cv6dxbo``-7SpAUzF(H|oDr%T9RWNYlg`UJ$~ zFbR?nK@+-sBm&XVLD5M%l!!2)KM`{j5*f}1_6JJv?|UM;qW6)Ap6uR$9_;%ff_>EN zd%qA9{7hu$h9rI2Kaa%D4NZ2$;EarRSAPx|jCaLC%R(COm{U{3wPO?Rq+q3C6Rpf# zTBdTwFS*iuqUBRo`kAy>%E{(_&NWKqI2VNoz!_98t|a*h%)<~*go9)cNQz7suZm9m1HJ_(QxtP+GDf*eF^&m})QdAn(due)6aowKS z9Wc;Dur)>a4NdQXG;c(-*=Mld3`Fe&#C;3X25rQ7M|4C8h=(nClWt4e1V|hLQ9&Y* z9gqRYZnF1lS0LU4i#GW~O31tXj+~BFW{aMgl>js2EQh?X40z_)6;O%+ZJC338?qtw zl>r{*KodCPo&k}|E1d_Ya$G0jOB&ZpfF7)I*h(oe?=xjIix7Lf`boIXFDzup?bJb!|T8RE$KG{TO zymBoXxBUpHZ@FEfD&PTmbRLo?#Te>w@5jn`Rmkr!E9w?HW-7%g zR4Y239IRVYx+&45zs_URGwNOV1%kXc`Ko*6Y0Ni3Ql=!%!e;fuu>pOp|7kw3FFDHNv~bYGlb-$ux+h zm-KJw|InGen8znzq zN+kYHbX+}vm*DokPbLyHK@v1cd+cPrhx8;7r|c9-(KJcZ49U=5(o6eT-bU;!D>7Yb8nv>`VJtEOjdQ_qzIwa9CdQ76@^tePP=n08V(vuRMqNgM} zO;1a7hMtk=EIljHIeJc_^Ypw#7w83vF4BtDYG(GET>75&ny`M0NNt(bVIF zHen_qILafo!CtCWm#beqTJaR+NsW0-<$`K*rc#Tuif?bJlyPg5QMJn5y6V@MT5)X~ zmVmT;>%m>sa@0q6Z%@z9%_;@*A$;&GJA$%G9D-^Zkampz9l!=a)7>N^`wbaqIfz^xRB&Uik;pR!fFM zO~=9~4BPSr$vfHR*q(ab#aB~mE5zl4MNZAM*=N??daI@cOZft26~l#%4BsxZ4GQKVkM?$O4AF{rf>J8YiW_U4h>>2A|+a)z4;>TxjFMeqUT6Dr${!#ydK z3}#{l$)8s zT8!O|6lFs4?Po?O-(I%hI@t5e|5q9%y{qCKy!(X-*~BH~b~6BZz=#FheYWYt3EF6w zxPe3*;82$@yl65G2?cu%ErlaqzEI_+On%7b`(Gg)QZ>#Q(bz4!jcF3jb4Y@arS_Kud=` zoQ0XX=~rrD3e*Yv5R+SGhj@eA3}B z%JUO6puxOXo_k)Nug}kxC*ay}^e1}5Y82O-mQ9N@PKD3;6EAcZ)84k$+W_8VY?`)3 z;SHt6R@iHK2GD*--SI;mp~zNc{ui|c4#G$_b9 z6ur95{0C5DC31iduf(9a9&K3zwAoLGqo6~J7yR&NhYCjlYkJsc7}Tv8MyMD@RrEl^ z;OHu0G|7mjLNel5PkN)~8}{061wO0DKu13$O1ljcrTyJNly)~J8PR3JZ2VOc_C`B_ z^tTSuWT2zlkrbQ=NE#1Qf*$v6(hp2g%O=Xo!?;}%5FGWzFx!;9FHB?83)9%^!v0pF zBz>~|fPPVTg8o8sZ4AAJ40iSjlH1V}?@ABCDEFGHIJKLMN' + 'OPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}') + +SANITIZE_PATTERN = re.compile(r'([^&;]*(?:password|token)[^=]*=)[^&;]+', re.IGNORECASE) +INVALID_HEX_PATTERN = re.compile(r'%[^0-9A-Fa-f]|%[0-9A-Fa-f][^0-9A-Fa-f]') + +always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' + 'abcdefghijklmnopqrstuvwxyz' + '0123456789' '_.-') + +log = logging.getLogger('oauthlib') + +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode_type = str + bytes_type = bytes +else: + unicode_type = unicode + bytes_type = str + + +# 'safe' must be bytes (Python 2.6 requires bytes, other versions allow either) +def quote(s, safe=b'/'): + s = s.encode('utf-8') if isinstance(s, unicode_type) else s + s = _quote(s, safe) + # PY3 always returns unicode. PY2 may return either, depending on whether + # it had to modify the string. + if isinstance(s, bytes_type): + s = s.decode('utf-8') + return s + + +def unquote(s): + s = _unquote(s) + # PY3 always returns unicode. PY2 seems to always return what you give it, + # which differs from quote's behavior. Just to be safe, make sure it is + # unicode before we return. + if isinstance(s, bytes_type): + s = s.decode('utf-8') + return s + + +def urlencode(params): + utf8_params = encode_params_utf8(params) + urlencoded = _urlencode(utf8_params) + if isinstance(urlencoded, unicode_type): # PY3 returns unicode + return urlencoded + else: + return urlencoded.decode("utf-8") + + +def encode_params_utf8(params): + """Ensures that all parameters in a list of 2-element tuples are encoded to + bytestrings using UTF-8 + """ + encoded = [] + for k, v in params: + encoded.append(( + k.encode('utf-8') if isinstance(k, unicode_type) else k, + v.encode('utf-8') if isinstance(v, unicode_type) else v)) + return encoded + + +def decode_params_utf8(params): + """Ensures that all parameters in a list of 2-element tuples are decoded to + unicode using UTF-8. + """ + decoded = [] + for k, v in params: + decoded.append(( + k.decode('utf-8') if isinstance(k, bytes_type) else k, + v.decode('utf-8') if isinstance(v, bytes_type) else v)) + return decoded + + +urlencoded = set(always_safe) | set('=&;:%+~,*@!()/?') + + +def urldecode(query): + """Decode a query string in x-www-form-urlencoded format into a sequence + of two-element tuples. + + Unlike urlparse.parse_qsl(..., strict_parsing=True) urldecode will enforce + correct formatting of the query string by validation. If validation fails + a ValueError will be raised. urllib.parse_qsl will only raise errors if + any of name-value pairs omits the equals sign. + """ + # Check if query contains invalid characters + if query and not set(query) <= urlencoded: + error = ("Error trying to decode a non urlencoded string. " + "Found invalid characters: %s " + "in the string: '%s'. " + "Please ensure the request/response body is " + "x-www-form-urlencoded.") + raise ValueError(error % (set(query) - urlencoded, query)) + + # Check for correctly hex encoded values using a regular expression + # All encoded values begin with % followed by two hex characters + # correct = %00, %A0, %0A, %FF + # invalid = %G0, %5H, %PO + if INVALID_HEX_PATTERN.search(query): + raise ValueError('Invalid hex encoding in query string.') + + # We encode to utf-8 prior to parsing because parse_qsl behaves + # differently on unicode input in python 2 and 3. + # Python 2.7 + # >>> urlparse.parse_qsl(u'%E5%95%A6%E5%95%A6') + # u'\xe5\x95\xa6\xe5\x95\xa6' + # Python 2.7, non unicode input gives the same + # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6') + # '\xe5\x95\xa6\xe5\x95\xa6' + # but now we can decode it to unicode + # >>> urlparse.parse_qsl('%E5%95%A6%E5%95%A6').decode('utf-8') + # u'\u5566\u5566' + # Python 3.3 however + # >>> urllib.parse.parse_qsl(u'%E5%95%A6%E5%95%A6') + # u'\u5566\u5566' + query = query.encode( + 'utf-8') if not PY3 and isinstance(query, unicode_type) else query + # We want to allow queries such as "c2" whereas urlparse.parse_qsl + # with the strict_parsing flag will not. + params = urlparse.parse_qsl(query, keep_blank_values=True) + + # unicode all the things + return decode_params_utf8(params) + + +def extract_params(raw): + """Extract parameters and return them as a list of 2-tuples. + + Will successfully extract parameters from urlencoded query strings, + dicts, or lists of 2-tuples. Empty strings/dicts/lists will return an + empty list of parameters. Any other input will result in a return + value of None. + """ + if isinstance(raw, bytes_type) or isinstance(raw, unicode_type): + try: + params = urldecode(raw) + except ValueError: + params = None + elif hasattr(raw, '__iter__'): + try: + dict(raw) + except ValueError: + params = None + except TypeError: + params = None + else: + params = list(raw.items() if isinstance(raw, dict) else raw) + params = decode_params_utf8(params) + else: + params = None + + return params + + +def generate_nonce(): + """Generate pseudorandom nonce that is unlikely to repeat. + + Per `section 3.3`_ of the OAuth 1 RFC 5849 spec. + Per `section 3.2.1`_ of the MAC Access Authentication spec. + + A random 64-bit number is appended to the epoch timestamp for both + randomness and to decrease the likelihood of collisions. + + .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1 + .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + """ + return unicode_type(unicode_type(random.getrandbits(64)) + generate_timestamp()) + + +def generate_timestamp(): + """Get seconds since epoch (UTC). + + Per `section 3.3`_ of the OAuth 1 RFC 5849 spec. + Per `section 3.2.1`_ of the MAC Access Authentication spec. + + .. _`section 3.2.1`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1 + .. _`section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + """ + return unicode_type(int(time.time())) + + +def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET): + """Generates a non-guessable OAuth token + + OAuth (1 and 2) does not specify the format of tokens except that they + should be strings of random characters. Tokens should not be guessable + and entropy when generating the random characters is important. Which is + why SystemRandom is used instead of the default random.choice method. + """ + rand = random.SystemRandom() + return ''.join(rand.choice(chars) for x in range(length)) + + +def generate_signed_token(private_pem, request): + import jwt + + now = datetime.datetime.utcnow() + + claims = { + 'scope': request.scope, + 'exp': now + datetime.timedelta(seconds=request.expires_in) + } + + claims.update(request.claims) + + token = jwt.encode(claims, private_pem, 'RS256') + token = to_unicode(token, "UTF-8") + + return token + + +def verify_signed_token(public_pem, token): + import jwt + + return jwt.decode(token, public_pem, algorithms=['RS256']) + + +def generate_client_id(length=30, chars=CLIENT_ID_CHARACTER_SET): + """Generates an OAuth client_id + + OAuth 2 specify the format of client_id in + http://tools.ietf.org/html/rfc6749#appendix-A. + """ + return generate_token(length, chars) + + +def add_params_to_qs(query, params): + """Extend a query with a list of two-tuples.""" + if isinstance(params, dict): + params = params.items() + queryparams = urlparse.parse_qsl(query, keep_blank_values=True) + queryparams.extend(params) + return urlencode(queryparams) + + +def add_params_to_uri(uri, params, fragment=False): + """Add a list of two-tuples to the uri query components.""" + sch, net, path, par, query, fra = urlparse.urlparse(uri) + if fragment: + fra = add_params_to_qs(fra, params) + else: + query = add_params_to_qs(query, params) + return urlparse.urlunparse((sch, net, path, par, query, fra)) + + +def safe_string_equals(a, b): + """ Near-constant time string comparison. + + Used in order to avoid timing attacks on sensitive information such + as secret keys during request verification (`rootLabs`_). + + .. _`rootLabs`: http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ + + """ + if len(a) != len(b): + return False + + result = 0 + for x, y in zip(a, b): + result |= ord(x) ^ ord(y) + return result == 0 + + +def to_unicode(data, encoding='UTF-8'): + """Convert a number of different types of objects to unicode.""" + if isinstance(data, unicode_type): + return data + + if isinstance(data, bytes_type): + return unicode_type(data, encoding=encoding) + + if hasattr(data, '__iter__'): + try: + dict(data) + except TypeError: + pass + except ValueError: + # Assume it's a one dimensional data structure + return (to_unicode(i, encoding) for i in data) + else: + # We support 2.6 which lacks dict comprehensions + if hasattr(data, 'items'): + data = data.items() + return dict(((to_unicode(k, encoding), to_unicode(v, encoding)) for k, v in data)) + + return data + + +class CaseInsensitiveDict(dict): + + """Basic case insensitive dict with strings only keys.""" + + proxy = {} + + def __init__(self, data): + self.proxy = dict((k.lower(), k) for k in data) + for k in data: + self[k] = data[k] + + def __contains__(self, k): + return k.lower() in self.proxy + + def __delitem__(self, k): + key = self.proxy[k.lower()] + super(CaseInsensitiveDict, self).__delitem__(key) + del self.proxy[k.lower()] + + def __getitem__(self, k): + key = self.proxy[k.lower()] + return super(CaseInsensitiveDict, self).__getitem__(key) + + def get(self, k, default=None): + return self[k] if k in self else default + + def __setitem__(self, k, v): + super(CaseInsensitiveDict, self).__setitem__(k, v) + self.proxy[k.lower()] = k + + def update(self, *args, **kwargs): + super(CaseInsensitiveDict, self).update(*args, **kwargs) + for k in dict(*args, **kwargs): + self.proxy[k.lower()] = k + + +class Request(object): + + """A malleable representation of a signable HTTP request. + + Body argument may contain any data, but parameters will only be decoded if + they are one of: + + * urlencoded query string + * dict + * list of 2-tuples + + Anything else will be treated as raw body data to be passed through + unmolested. + """ + + def __init__(self, uri, http_method='GET', body=None, headers=None, + encoding='utf-8'): + # Convert to unicode using encoding if given, else assume unicode + encode = lambda x: to_unicode(x, encoding) if encoding else x + + self.uri = encode(uri) + self.http_method = encode(http_method) + self.headers = CaseInsensitiveDict(encode(headers or {})) + self.body = encode(body) + self.decoded_body = extract_params(self.body) + self.oauth_params = [] + self.validator_log = {} + + self._params = { + "access_token": None, + "client": None, + "client_id": None, + "client_secret": None, + "code": None, + "extra_credentials": None, + "grant_type": None, + "redirect_uri": None, + "refresh_token": None, + "request_token": None, + "response_type": None, + "scope": None, + "scopes": None, + "state": None, + "token": None, + "user": None, + "token_type_hint": None, + + # OpenID Connect + "response_mode": None, + "nonce": None, + "display": None, + "prompt": None, + "claims": None, + "max_age": None, + "ui_locales": None, + "id_token_hint": None, + "login_hint": None, + "acr_values": None + } + self._params.update(dict(urldecode(self.uri_query))) + self._params.update(dict(self.decoded_body or [])) + self._params.update(self.headers) + + def __getattr__(self, name): + if name in self._params: + return self._params[name] + else: + raise AttributeError(name) + + def __repr__(self): + body = self.body + headers = self.headers.copy() + if body: + body = SANITIZE_PATTERN.sub('\1', str(body)) + if 'Authorization' in headers: + headers['Authorization'] = '' + return '' % ( + self.uri, self.http_method, headers, body) + + @property + def uri_query(self): + return urlparse.urlparse(self.uri).query + + @property + def uri_query_params(self): + if not self.uri_query: + return [] + return urlparse.parse_qsl(self.uri_query, keep_blank_values=True, + strict_parsing=True) + + @property + def duplicate_params(self): + seen_keys = collections.defaultdict(int) + all_keys = (p[0] + for p in (self.decoded_body or []) + self.uri_query_params) + for k in all_keys: + seen_keys[k] += 1 + return [k for k, c in seen_keys.items() if c > 1] diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/__init__.py new file mode 100644 index 0000000..f9dff74 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1 +~~~~~~~~~~~~~~ + +This module is a wrapper for the most recent implementation of OAuth 1.0 Client +and Server classes. +""" +from __future__ import absolute_import, unicode_literals + +from .rfc5849 import Client +from .rfc5849 import SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT +from .rfc5849 import SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_QUERY +from .rfc5849 import SIGNATURE_TYPE_BODY +from .rfc5849.request_validator import RequestValidator +from .rfc5849.endpoints import RequestTokenEndpoint, AuthorizationEndpoint +from .rfc5849.endpoints import AccessTokenEndpoint, ResourceEndpoint +from .rfc5849.endpoints import SignatureOnlyEndpoint, WebApplicationServer +from .rfc5849.errors import InsecureTransportError, InvalidClientError, InvalidRequestError, InvalidSignatureMethodError, OAuth1Error diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..104848e1a46331d4f4d0bb2663cd5ddc26d0bf0f GIT binary patch literal 1162 zcmZ8g$!^;)5S8qBiM+&#muweZeW(>9P7wn|Q8h6d7tK;zX&YV$MWhoVq@<8iiull5 zKc+yxq@SQ`Px*zOO3KbkK@V@<42Pp9aJy15+J6xEnANnu+LM)${GEKoZwW{P8q!gQ z>MR2?EDN%_hO%gZF0dTPr93Bj9^_d86j%`yQ!OvGMX<;WV5D(D#wAcnd6AY`1yoYL zNULlKEU_A>rLiI7Ww6Xvz)BjIWLyVz)&Pw(F4HD!ffic@t85Ldv30P{Ho%6=RmNJo zI{j$`ZX8`wa^6Wv*ZA>l8HRI7gvkOFQ*27+n%}t_hM1d^fSb`JmN^lb9DCT0Ou|Bn znPhH60^bZK=CLi?o88Wqxkrgi7_N`ZG3Gb2gGXH_f(m1W1NtWDm5zsioP&Nv9 zl+Pddqt({G(ZC9R<^uqUe>?)pMGI^aCu$;!~bQPa*$oN4vBHlKVZZqCJr zV-Ks|3WL|nht5>SM>uqNEvzUun?y4T-AQ#9?1-tJYe&-$-> zTXJ3amScq^e03fZieByb9^Z|&uC4of_JVXR2#HT32s`0zQhYPJX>XM&Le00J*D__j Mq1W`Xd}`_Q9|cHPq5uE@ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__init__.py new file mode 100644 index 0000000..06902e2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__init__.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849 +~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for signing and checking OAuth 1.0 RFC 5849 requests. +""" +from __future__ import absolute_import, unicode_literals +import base64 +import hashlib +import logging +log = logging.getLogger(__name__) + +import sys +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +if sys.version_info[0] == 3: + bytes_type = bytes +else: + bytes_type = str + +from oauthlib.common import Request, urlencode, generate_nonce +from oauthlib.common import generate_timestamp, to_unicode +from . import parameters, signature + +SIGNATURE_HMAC = "HMAC-SHA1" +SIGNATURE_RSA = "RSA-SHA1" +SIGNATURE_PLAINTEXT = "PLAINTEXT" +SIGNATURE_METHODS = (SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_PLAINTEXT) + +SIGNATURE_TYPE_AUTH_HEADER = 'AUTH_HEADER' +SIGNATURE_TYPE_QUERY = 'QUERY' +SIGNATURE_TYPE_BODY = 'BODY' + +CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' + + +class Client(object): + + """A client used to sign OAuth 1.0 RFC 5849 requests.""" + SIGNATURE_METHODS = { + SIGNATURE_HMAC: signature.sign_hmac_sha1_with_client, + SIGNATURE_RSA: signature.sign_rsa_sha1_with_client, + SIGNATURE_PLAINTEXT: signature.sign_plaintext_with_client + } + + @classmethod + def register_signature_method(cls, method_name, method_callback): + cls.SIGNATURE_METHODS[method_name] = method_callback + + def __init__(self, client_key, + client_secret=None, + resource_owner_key=None, + resource_owner_secret=None, + callback_uri=None, + signature_method=SIGNATURE_HMAC, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + rsa_key=None, verifier=None, realm=None, + encoding='utf-8', decoding=None, + nonce=None, timestamp=None): + """Create an OAuth 1 client. + + :param client_key: Client key (consumer key), mandatory. + :param resource_owner_key: Resource owner key (oauth token). + :param resource_owner_secret: Resource owner secret (oauth token secret). + :param callback_uri: Callback used when obtaining request token. + :param signature_method: SIGNATURE_HMAC, SIGNATURE_RSA or SIGNATURE_PLAINTEXT. + :param signature_type: SIGNATURE_TYPE_AUTH_HEADER (default), + SIGNATURE_TYPE_QUERY or SIGNATURE_TYPE_BODY + depending on where you want to embed the oauth + credentials. + :param rsa_key: RSA key used with SIGNATURE_RSA. + :param verifier: Verifier used when obtaining an access token. + :param realm: Realm (scope) to which access is being requested. + :param encoding: If you provide non-unicode input you may use this + to have oauthlib automatically convert. + :param decoding: If you wish that the returned uri, headers and body + from sign be encoded back from unicode, then set + decoding to your preferred encoding, i.e. utf-8. + :param nonce: Use this nonce instead of generating one. (Mainly for testing) + :param timestamp: Use this timestamp instead of using current. (Mainly for testing) + """ + # Convert to unicode using encoding if given, else assume unicode + encode = lambda x: to_unicode(x, encoding) if encoding else x + + self.client_key = encode(client_key) + self.client_secret = encode(client_secret) + self.resource_owner_key = encode(resource_owner_key) + self.resource_owner_secret = encode(resource_owner_secret) + self.signature_method = encode(signature_method) + self.signature_type = encode(signature_type) + self.callback_uri = encode(callback_uri) + self.rsa_key = encode(rsa_key) + self.verifier = encode(verifier) + self.realm = encode(realm) + self.encoding = encode(encoding) + self.decoding = encode(decoding) + self.nonce = encode(nonce) + self.timestamp = encode(timestamp) + + def __repr__(self): + attrs = vars(self).copy() + attrs['client_secret'] = '****' if attrs['client_secret'] else None + attrs['rsa_key'] = '****' if attrs['rsa_key'] else None + attrs[ + 'resource_owner_secret'] = '****' if attrs['resource_owner_secret'] else None + attribute_str = ', '.join('%s=%s' % (k, v) for k, v in attrs.items()) + return '<%s %s>' % (self.__class__.__name__, attribute_str) + + def get_oauth_signature(self, request): + """Get an OAuth signature to be used in signing a request + + To satisfy `section 3.4.1.2`_ item 2, if the request argument's + headers dict attribute contains a Host item, its value will + replace any netloc part of the request argument's uri attribute + value. + + .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 + """ + if self.signature_method == SIGNATURE_PLAINTEXT: + # fast-path + return signature.sign_plaintext(self.client_secret, + self.resource_owner_secret) + + uri, headers, body = self._render(request) + + collected_params = signature.collect_parameters( + uri_query=urlparse.urlparse(uri).query, + body=body, + headers=headers) + log.debug("Collected params: {0}".format(collected_params)) + + normalized_params = signature.normalize_parameters(collected_params) + normalized_uri = signature.normalize_base_string_uri(uri, + headers.get('Host', None)) + log.debug("Normalized params: {0}".format(normalized_params)) + log.debug("Normalized URI: {0}".format(normalized_uri)) + + base_string = signature.construct_base_string(request.http_method, + normalized_uri, normalized_params) + + log.debug("Signing: signature base string: {0}".format(base_string)) + + if self.signature_method not in self.SIGNATURE_METHODS: + raise ValueError('Invalid signature method.') + + sig = self.SIGNATURE_METHODS[self.signature_method](base_string, self) + + log.debug("Signature: {0}".format(sig)) + return sig + + def get_oauth_params(self, request): + """Get the basic OAuth parameters to be used in generating a signature. + """ + nonce = (generate_nonce() + if self.nonce is None else self.nonce) + timestamp = (generate_timestamp() + if self.timestamp is None else self.timestamp) + params = [ + ('oauth_nonce', nonce), + ('oauth_timestamp', timestamp), + ('oauth_version', '1.0'), + ('oauth_signature_method', self.signature_method), + ('oauth_consumer_key', self.client_key), + ] + if self.resource_owner_key: + params.append(('oauth_token', self.resource_owner_key)) + if self.callback_uri: + params.append(('oauth_callback', self.callback_uri)) + if self.verifier: + params.append(('oauth_verifier', self.verifier)) + + # providing body hash for requests other than x-www-form-urlencoded + # as described in http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html + # 4.1.1. When to include the body hash + # * [...] MUST NOT include an oauth_body_hash parameter on requests with form-encoded request bodies + # * [...] SHOULD include the oauth_body_hash parameter on all other requests. + content_type = request.headers.get('Content-Type', None) + content_type_eligible = content_type and content_type.find('application/x-www-form-urlencoded') < 0 + if request.body is not None and content_type_eligible: + params.append(('oauth_body_hash', base64.b64encode(hashlib.sha1(request.body.encode('utf-8')).digest()).decode('utf-8'))) + + return params + + def _render(self, request, formencode=False, realm=None): + """Render a signed request according to signature type + + Returns a 3-tuple containing the request URI, headers, and body. + + If the formencode argument is True and the body contains parameters, it + is escaped and returned as a valid formencoded string. + """ + # TODO what if there are body params on a header-type auth? + # TODO what if there are query params on a body-type auth? + + uri, headers, body = request.uri, request.headers, request.body + + # TODO: right now these prepare_* methods are very narrow in scope--they + # only affect their little thing. In some cases (for example, with + # header auth) it might be advantageous to allow these methods to touch + # other parts of the request, like the headers—so the prepare_headers + # method could also set the Content-Type header to x-www-form-urlencoded + # like the spec requires. This would be a fundamental change though, and + # I'm not sure how I feel about it. + if self.signature_type == SIGNATURE_TYPE_AUTH_HEADER: + headers = parameters.prepare_headers( + request.oauth_params, request.headers, realm=realm) + elif self.signature_type == SIGNATURE_TYPE_BODY and request.decoded_body is not None: + body = parameters.prepare_form_encoded_body( + request.oauth_params, request.decoded_body) + if formencode: + body = urlencode(body) + headers['Content-Type'] = 'application/x-www-form-urlencoded' + elif self.signature_type == SIGNATURE_TYPE_QUERY: + uri = parameters.prepare_request_uri_query( + request.oauth_params, request.uri) + else: + raise ValueError('Unknown signature type specified.') + + return uri, headers, body + + def sign(self, uri, http_method='GET', body=None, headers=None, realm=None): + """Sign a request + + Signs an HTTP request with the specified parts. + + Returns a 3-tuple of the signed request's URI, headers, and body. + Note that http_method is not returned as it is unaffected by the OAuth + signing process. Also worth noting is that duplicate parameters + will be included in the signature, regardless of where they are + specified (query, body). + + The body argument may be a dict, a list of 2-tuples, or a formencoded + string. The Content-Type header must be 'application/x-www-form-urlencoded' + if it is present. + + If the body argument is not one of the above, it will be returned + verbatim as it is unaffected by the OAuth signing process. Attempting to + sign a request with non-formencoded data using the OAuth body signature + type is invalid and will raise an exception. + + If the body does contain parameters, it will be returned as a properly- + formatted formencoded string. + + Body may not be included if the http_method is either GET or HEAD as + this changes the semantic meaning of the request. + + All string data MUST be unicode or be encoded with the same encoding + scheme supplied to the Client constructor, default utf-8. This includes + strings inside body dicts, for example. + """ + # normalize request data + request = Request(uri, http_method, body, headers, + encoding=self.encoding) + + # sanity check + content_type = request.headers.get('Content-Type', None) + multipart = content_type and content_type.startswith('multipart/') + should_have_params = content_type == CONTENT_TYPE_FORM_URLENCODED + has_params = request.decoded_body is not None + # 3.4.1.3.1. Parameter Sources + # [Parameters are collected from the HTTP request entity-body, but only + # if [...]: + # * The entity-body is single-part. + if multipart and has_params: + raise ValueError( + "Headers indicate a multipart body but body contains parameters.") + # * The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # [W3C.REC-html40-19980424]. + elif should_have_params and not has_params: + raise ValueError( + "Headers indicate a formencoded body but body was not decodable.") + # * The HTTP request entity-header includes the "Content-Type" + # header field set to "application/x-www-form-urlencoded". + elif not should_have_params and has_params: + raise ValueError( + "Body contains parameters but Content-Type header was {0} " + "instead of {1}".format(content_type or "not set", + CONTENT_TYPE_FORM_URLENCODED)) + + # 3.5.2. Form-Encoded Body + # Protocol parameters can be transmitted in the HTTP request entity- + # body, but only if the following REQUIRED conditions are met: + # o The entity-body is single-part. + # o The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # [W3C.REC-html40-19980424]. + # o The HTTP request entity-header includes the "Content-Type" header + # field set to "application/x-www-form-urlencoded". + elif self.signature_type == SIGNATURE_TYPE_BODY and not ( + should_have_params and has_params and not multipart): + raise ValueError( + 'Body signatures may only be used with form-urlencoded content') + + # We amend http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 + # with the clause that parameters from body should only be included + # in non GET or HEAD requests. Extracting the request body parameters + # and including them in the signature base string would give semantic + # meaning to the body, which it should not have according to the + # HTTP 1.1 spec. + elif http_method.upper() in ('GET', 'HEAD') and has_params: + raise ValueError('GET/HEAD requests should not include body.') + + # generate the basic OAuth parameters + request.oauth_params = self.get_oauth_params(request) + + # generate the signature + request.oauth_params.append( + ('oauth_signature', self.get_oauth_signature(request))) + + # render the signed request and return it + uri, headers, body = self._render(request, formencode=True, + realm=(realm or self.realm)) + + if self.decoding: + log.debug('Encoding URI, headers and body to %s.', self.decoding) + uri = uri.encode(self.decoding) + body = body.encode(self.decoding) if body else body + new_headers = {} + for k, v in headers.items(): + new_headers[k.encode(self.decoding)] = v.encode(self.decoding) + headers = new_headers + return uri, headers, body diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d9bde2b00fde76c81b9264bcf26ab343a51f9e5 GIT binary patch literal 10058 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/errors.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/errors.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ecc1b343172f4bd2c2f3fa774d7c008349bd60d8 GIT binary patch literal 3324 zcmb7GOHUj}5bl|seX_v%g_Af@I!KnXlGz0VF+^5mLP$a+FP6bMOcae~r+Z=W?99;J zgJHGKg-E{UZ{(7{kb8c@Tyyd%cb`(#vo8!JqNSecXLZ$AUsd<=^mL{E7qkB<6Y@8i zC>FGzK$QwUjM#)3%nXdKX`2SNb3v|a*;Y4i=MC~5VHV5(N?4v3&T@7U`UO^mevucU zUxI##m7!nO{WA0`Yzq2Qx?h2Ql}$r`TKA{e%r2?V_WrCyZlVqXzunM{rH1Hut7|Lk z4K73^WaXFtE|tm_uA_VZNU~ zGYrxZNB)pfD=AY%mh+Lh9mnWHm4Tq%k1V;)2? z7a)ixAp^1~pbi|aX_7HR9k_}{o|g#0S>sAsKe|COU=LDkUT=0LFqo&B864O`pT5}L zqph92E2a6N7bJ{R?*Q23!4^rQL`ffpG=aIga_h;%#f67Uv`J4ME-$Qpjg8fB79MRi zDx)nx%z;Nr^m=KNL3#9k0En)MAX&VXQ)m% zSnwktqXHp2gJcWF=^+}m4?)_HC(U7!n?jWn>0p|x7g9^|pd;Y&Az}tUv$&-=&?zl;xs<>t$f0G&@d5}} z$N8PSJ8s6pKL*pA$F690q5#9O*%8g$skD|8XZ$7Y;u?r zYkCxt_F--?YGCSai3-TDZ(K-Qb~zgY)#{dBq&=G!+&JbT)20l~LJ^roBeTRDj4eae z01>ll6aZYTy*rcEjUXBsh=!t(8eNnO=pweLuErKGGcCSuWT?IeXk~QuGQ=kX)$Gkp z9y?wF5VS-Q*0t(W$nVhYa^QExak>#p0&G_u=O}T5Y-S9FW{|iGThkH*D+Gx;)q05! zacp`(a*qdr~^nssEI&wJcboF!#7<-wfmnli%V0?^env@KI{cEXs zYoOvF(Ppa64pkduzw2o98EEjsghtrEmPR)R8g1%Vai-ABP@(M3xQ;&aaDHn-AKkB4 z)1u?B$jf4-lb~~e<`}v5>@W>Cyr|oaLY)o;vLG#Qop@Zo4K=?tBQ)j$%@inm94Zr8 bvh+9$$FEATLPv{bRLxqhwp`2CteW*NOeH)T literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/parameters.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/parameters.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b993fb72b117b3ee850ba585a3ccbc042198dfef GIT binary patch literal 3437 zcmb_fTXWmS72X9vh=iz162(boGE)p2bwW?XyRKt-s`xT?JF#6`9XFV?Fo<1>pm3pf z7qY~F?SuT<*Zhb6hx~&41ibbsf1yu3yP#}IR^oc5%fVt7I5=nbobP;R*OT?Z z`9}!-8(nBs82=tF{sVLbIf!7Qg*eh2Ez%tw%efvJQOPMqreng~Af<3FDm!J2PFLWq z8CIf-Q;96c!st1Qzc3J;KeX5oBl%Mj)i)B~qFDP2qUF&7Steytd4vgrp(0{UjD% z5W}2`VM4ggXy^$_Y?0V6IQ2!4#CEr_{=&7Bfh~sAe!K@}>~^E&*?dg>hGjJxwtGo- z#~unX-f1>Pl7zewP%&sE?4UUmQP^Yye|@XB{o9%HHBfH-44y@GEno8bJPA`l-5?q# zOytXH9QX;LZWzFRypV&?B0Ukz^M$MS$@5CeLK;h{pRrVf^HM4TC0d48AjN5J!6=Q@ z_>r$mbI@dD4Y=;W#a}^};jtN@DLz0`{bVOYchO%v-)U20S`ui4Gvf?TO*JaPXpZ2K zmYLbynKmtF`bf{pXBeZS51%8Vjf_kOnM$UyKG8Gn2Ffa*plO8|S>* z1Zx%{CB_nw_(^Chw<&noz8Bklu)w^SM}d%WPk9g@D4P%V_n+9cOO|WOU}4@K1T-YJ z$8ACfK@6YvPwf{^@89Wcv^QY|F9U#y1dN2`8uy1ZqIL|xv^f<3FQuxc&{VU(^gT{( zz#)x!AcA9B2VBWj-%prLPrPUx(w&{+k$ik~I0@ zJdG%G59#S{t+lbW-Y47bt=?!bJnD3={X`^(G~TUwq`lP|jmT!Vdl<5G^IBOR9K@bT z8Fh?$*9|YL({;mtayi*&O6}Zc^@hW15?TghignO>aD=G0BZl62!TgjtLN8y_#1G$f$JT z`CNf|o|lv{XH~-bbxOL$+k%o*ZpoqG&dLQP^0_NBATJ$zVM?89u@a&lIt%AB^)ff4 z0_QjHFypLTC*V{EX&BBH#s7ua$FTALOqydB9K+_C#~y1Ak`PFQHwUcQJ#5G9sJGoB z6EfN6y{*IEcC@|GYe74RoBPdiFkb7YK}gotW1nq_)=~4k+FTj5&-uOihGrV$(|l=M zu-W;VK9!VPgARRKHVnL2vanUEYKx|UP1Dd!Z0cp*z!olJ_6bP&nv{hmxm#X}vwsHz z0jv#7t7RH6G*WD;jWk)KUm>FZ69cY&t{)^F_{s{?13z(DHoDVL(s#ehR&KufwuUJ$=;&r4v?f&^a3hiqkAo9 zzm;g6_fNFWn?x%Ny(`VGR_8{yaDXqXt8DTv!|@C*z6f0gN0`sh2+H0W23PBkm=tl$ z0{c*qEg&h#23Zndh69DiqPqc-dHGi8MSbGkPNYh>(8P9&w47D>#;q{%q5j{7K_Lo? zbYStio*UBTkk%(cr7?$tLtSUfQn6XY$1#EP4=A~6ck!~3ze($5X@0&8a9qaBhVg6Y z$vwY@o&^(to&=wS7oPlZv0g*jiq`I_LkY3JPRQx6fnav9x=^eCKXF%quNRxBYlTCM zJ}l;{8$LTvPRWRFah4*kl$5dHD)lO?cF`BoOOj1h8s3IMk%r%yQ*Elt1Jl%;&Iv5E z3<*6m&+zz*19S%XfCH3~X_=lGSxFwA2Jr4}|2?HgDO5K5&rcuPe@ZF)MgRSedDDIW zyqTfCUSe{utRy>IcJjo?D$v!-g-`XA1&$6UoLrBoki;!u2*}GzunTtT=mX~cqR&~e zRTq@FO8g(7xPuJE%kFr@djSjmbI!Tx3P$Clnh+GSN^zM{V!U~z*_(S literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/request_validator.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/request_validator.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8f12adee2d2be95a19526f96bf560ea33593520 GIT binary patch literal 32995 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 BPn`e& literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/signature.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/signature.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05fb90ccbe70bf3e272f16dda046785c630b910c GIT binary patch literal 16245 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 Ge(=AERlSV> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c367672004e2e1a15454b8b8daefe851c4b0dcfa GIT binary patch literal 2972 zcmb_eOLN@D5yk-6T@ZY&DEg(OL;m@-sYkg<<3(R3N&@M8U%|hE|b8H^k zOKgEHLc7G;>=Lw>*%G@9?J~1>jLveIxp`QqK@#`8LdA)6zdiT3?(QI#G|gF&a2n;A z3gb*t+#0JfO}QH6491c(+8fiuP{es5=_nLR(!5XAfYXQTaDgjFJo4QCfHsVdSuKV= znJ0zfL7a|qp{jO~#Zk_90OxWMCbDB!Zf~r(4AgkUtHwc*D_+e_x9Q1Nk?HAe|9;qu z81L^5;^BUhX8GtqNL3sjeKRiCKiGJ%`KJ#*+WPpDKYzOY@X=?FcXmI2^2L{5`PXmU zytR7!&bxQlB7(T!F`-zKB435h-;6#%(|zYWBi|cC{|LTl~m)kzR80tKgMF?`;aQimNIC2VO|kkw&}FSys=|F( z3hR#~WpSh^T$jWWTF`DWN^o7$SV?_aKL)vJDE8pYOnG|lRJf^NuY+7#IAt6gC*+y& zJ+ystOnx$t%^y%WvfUxoBd7*QoEBevxyQ^zmOyI^wz|2X4OrlyN z&+&-%MVnhJD^?h?ih2P;Zs{BuomvDMT(R8-uSvhQn4;Trfw~)VJ_1tk)KCTw;@DOJLkN zXAuh~v_-B!=hPxgXc27zZ4uDExm{lSpE4EMnN%J746^tw94cB+fgfW`xQNr4e98X+ zi?uTYbHJG?jL+djpV2rW%LC3KL8f4sEN`q&^}K>AqlXab(8Lg&3z0xpz3G7yN*?U< zgG0z|QbF=-O_r*~i)*0H$e2NXNivd+@E`bgsL*oR(Ao!K3Al;mhznU0*PK=?6Bk>e{uwG#%u0e7#$8BY+ z#xX>wVG~2;j$s?BrJTMAKSp2IKSuTMkaC!XykY3EcKO~T)cmP)QvA=!MO=b|f`tY= zgtP?S3u*c+V%}V-b86Yte=T&oy!vUF^mCCyQhw0^otdhUCu}1Y&Bfh*z+O zwiB;GRk>Rk`O|lIY2Ei+2 znV6(amPnHbjLw~x1g=$X5cCUG2p$9iIVI3g{^B&zdJx3YJQJ8fwUg@rpdG2^#_X2< zWvgSsLd|7Q@PmRw=x$9q{%=0t-r9KhU^7_X+1T0&Ha=Q^yuPu!`8e3w+|}t)2WTA= yw8Pc9LIP>9&ql{UdrG1HDB5wn^K3)ilW*Ss-o8Za53hjMH|S`2Gr0{{xQz literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py new file mode 100644 index 0000000..b16ccba --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__init__.py @@ -0,0 +1,9 @@ +from __future__ import absolute_import + +from .base import BaseEndpoint +from .request_token import RequestTokenEndpoint +from .authorization import AuthorizationEndpoint +from .access_token import AccessTokenEndpoint +from .resource import ResourceEndpoint +from .signature_only import SignatureOnlyEndpoint +from .pre_configured import WebApplicationServer diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b495fb9bd25eb3d848963fb89134f9e4e852305f GIT binary patch literal 650 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/authorization.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/authorization.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..598d58808c80d37d795a1e48126e0d699c1e9fc7 GIT binary patch literal 6280 zcmeHLOLN@D5eBeNf=iK-?L3@vGEv2$*G27852q|vq*#$Bg?TyHK%6McP*@s8ct)>bej0C^G4Vj zwVgKJo4ge+jcms@jiWWpY=_IEj?*!XUmFpt8)D_amZ53TpBlZSM}KQX)^CiD4Zg(f zFAZ)B`?Th)^5v&Sw{!eAJNB}4FAN4f^>V!@hyHu--@4rs5g*4vlqNlz5X<1$OM^JF zfA^m~w!OU4=We9XKlq>N?q!qlIgpL>aTP}l~; zOPLHsM0|Z(xO^IT`2fFU1C1~o6T5A3)2VTb*S<8II=5**zcif&U*>JTgtsPd(4ORP zkb0HD%eB1k)ePfa#L9hv%E>s6l3)P4lBq(Grpi>HhHV4iO9f|HA|#Ana1_k*eUT(K z!;{7gGXu%SGERk0v%xdYBxcZ{$NVq=r=G2*Zy$}pGbD~|5@#W213}aFyhHq-#OyE- zlVpR%k(P%C$vE_mNF*#%iJ-lR)`!p|wXsA2Mi7H!V~H1VJs3wU$p)h!N#J><7~nMw zWRj}&*dz!;Hu0jAIAm!M2FHT!?CgrvmBI_Lop2r3m+&aq1iQYoLoc zX}*#OIu5h2(4WmvZ$Kb_k9i4;yb+X&xMIRZ;>%!60kT2tVHvRCY=WI2awxsX3`)yF zuMgpKn4^qRSc1Nf;*9x-8M6A$j$$R;ayykRp{H^Lp)^zC9C~5EvHi;Mg@PMe4#><7 zg$#y)kfg>$z<&5YmICgtDTYDhg@ti=AmJL&i+%RwkyZ-&CGbMU9AZf}@caYPnznh) zWu6Mbws`Ds?esOe^b5&ixkH*VE>bTD6Zi~F6~M67I2#EB0auP#;f0ck=u*OzG7@^K z+edh)r;e6>_wh?M(M-*KV`}W1r+~#jS|_z>ZQq(&r>050wf(vpuT9NU0PwVi5VVf} zexLSC?JwLB{_O#jCMC*1*8@@dBYEJv7TabHnVFv%StHV8>DwG>5r16+sLn! z(eK8SNJ#fU9676Mvb!tL;ia+6o7tE=E5BU0m0RtYvqF|~D~9#Fo``Umw~Cqh@{E#s zyPWrDL%xPh`seTaV;LNRHGD+&hjGXOT>YW!-#oY;$!E82Z}MY)d^@@I{=u!=quW2Z zwTWjp>TmbQ!T8!B3qpSFy~vmEr<>2Tw;`#Wd%4~(vRl9Oouoe(OTF)iaR ze$tw@_RZvKYV9LaoSI*lm{H$vQ17&LY9(73X`UMBN1scxTXIj$Y3n)r=sT@-47sOx z)Oh_X&1GrYnl^z@#;JAERup#0^<-(f#2W{e{0rrzz&x?jbdN^AhIk-Ha!6AP#e0B#j{I1?N| zM(y9ghRP}5V4$;QZ=Rz`P!*yQq>4(J2<2jwid`XR{Nj<`epDl&$~}~t(g69k zOhTEINcV+*AXQwAxacx4ud+ZUZ4^}?vKlo0{ zM&1$Sd8Gb8dQ^m^am5hjZZJGj`%hgS`!g5KTZ|vMRB4PXDU-d1sD8uh&zSSSJifw)%Zqu7|SfFhD?7m@j^& zbkiB}4v5w_7P6U%ygQ#SoI$Itt8Bf##nwH!o6-5^CG1WXs5{5?^YvfEDBn?a5w}tY zAYM^BC3lTnA0j2Lxq8G&eVIoO#zde=Hra z!1YLn8lpsp=KuY2&bI%yUk>B=&cKr|aiIUcKi-c4!clq+hd`k`d^`^6gb5ezKfgAa zOs>(ve53}aVn|C zbY6QDsWRt@9(h1l7xEIZd0iR$sHD-5mtQI7-&a{jP0yQJRcEE5pkuLogZQuMbG1Gc z(k+R!X>Gdg$Q!s;8718nxk_I(8vHg5eutXxQp2dZOwAfKSI{^eoXph`lepz;WwvyS zopV;I9@i3Sh zhVOQ5t>|f+ZPT_as`qa$Ok>}ilYFuZJ+wDf-Tx3D7wY~7dQ2Qk8~e3s19kqpDXM-| z=Tm>P>_2Hvn_SHb%%O#@Yur+Qn^0Q9|Mpg>IWr$Sc<+oWh1x*jA=t zcCPdz#64TITRR_a1Pk4kd<*{+2FUNzTLl*K2l#aCvzs!^rO=gEsW}(ADqtB6&|!Th z+TJ{)KY!Eq2h2=}&{)yoRkp{Qi-YOqthx>PE%14a2YTw)ZYyuQE?vX9Zf?7-zGB9E z$911&URaE@To*S`uB!k{{s>&;+ti%tq235OWC7d1P60v9^Q(~%u&wTljs>(I| rcJxRO`3aLrqD7+S?VoCW@8ZyHBm|{(bIn?_I=G^+0ArWTE46?%IcT}9LECK)I&NpMMa;gD<&4_L8-o zwP7^8use!kcH#Xb;Ft4@57%aDeT-ONEA@MAuHS2<-`(M1VR>GZM7igkhBO&jrJ|bZ z_|)@T)1CDRXj|xKLFseKASyTWsyxfwnpE;y-=@AjJ+GHF^hd2x)-a~0_w*z5nVNZXVjii? zIxzTOS&i9Gm5F&n0jHX``fXNcjeciR#R!cxzf!+c50@tr-o5nbv-#B#xov zO9wXpr>f9jjBCr@50pgvOqoEcNv%hi(i#6|^GVMAAkUnk&;3CN`^%gjPX`XHmLHyX z_CueJR+#y2$_~5Ey_v2%`Qb3Mo$?0&$->;pQzzdKoh%*k0H~5UkN2Zs|M}S;hdgw8 z=_p~{`QV#7P_=5gR`E*sB#1}A1ECn++_+r(ag^_SJoMuM5W*mN_!%4{Dzy=hJR!4a z2~;%GkKT0r_wZyEiU}Zkg)Bka;wI`spI6mF%>*TjQ(zRfp zYcI?f(s{HU9^lXuTbXq~iqimKb`^x2=NSnzmyDz$#TAl~QSCvtUpJ;uNT#ePztt5uq&FTwLzp+1xqbv{7!SL$)U}rS9sf>U2KZ-k>YkX6lfIL3!hHwN<%1TSu0GrxcmLNatrmzJfY!o*UK(-VJnhdv|j9AG@kEzT!G&N zl7EYW(w1c(qEJjrwN*?0H3PK#6D6Nzy`i>1Yg$LsR88%OT1y+h`(J2SKt6_kH9SNM z7(NUAo)|+jQlP6ALzWFSNbM{nFUji#t~2z0$A)?#fydX*p@^<~zC137_S9i|;=2jYZQMft?k~c;&`u7)Jq|ur!)=2TUG?Sr@3`V1JAD zaR6PG4MCb@qd~~MgYeL;%QoTqXO$#Pg3v98=g|NGoIe=4t#Y^@$Gd)TFsnZbdDM$S z?w&rjo(G4rpR%GNAbXB{6sgRSj|3IScjj|#s!laRKb)FDr`*%#DA+|eiGn(R6Lg^- z6zZd68qdE64#P78z>6=cX8<`6N0#RLJV4J7uV_*PoAPKZBH;f^N>1jKx1JCafGv{<1ChWD4nmDK1SRu z+A88ai`wEO8$4tYCp3lMbgkJ57M%sY$g|_(E64-#RVoN^S8EdYbQ)XV&+}n<&=QIT zC?j!F_&f7qVghn-i{s%dnzlL-47 z+W7TvJLq}*M{bcG0X%Pm0DK?<5@+ar%Bw*c4+tW{+Y zjJJ*lme;TJfQ{L?+ypiuG$!PvR&1BmuN_qRr{LI(dY5fI?X)8MF&bY7a_97B#tKqj z*mExCGG!kGzgP!>ZWWy0363Q>hb@G_f;ZL5V77%|_RnQ7OY+oYPXx2w&y>8Kclw

F*`!-uhq;Hed=;H!8-vSf^+cB3SL9h6&K`e9LVNtIaav9A5)SC%K{-SMk4!6eW)02+kweBkHZ@u?Y4fXu9U^<<%AMuAV9Vp9HNDlSuTg$jzZ1dd5_ zS}B)*E}oVEl)0~!3*4I}9(ROS5k|xXun2h6aRE};zx3l#DC0F9_Y8$WyX7v=_Y8Lo zy->7uq=_w{)2+)E@g1xB!a55r|BvM0wbDZ8f$Q^ODLyT`jzZBXtfC;wvSlEpf$mgn z5k6TJ3aKmvR4t9DmU#xHrnQWYXs2>~?q&Ae*Fl!mc zKGX~81>m1zeu3I*kq#B+eekyKk;SMdu(iAae~j|=?~$sRd4k|PX hU}2`aeFQyX{g;W1oL_$ZRhluQ$zZRVg|z2<<=@Eu_b>ne literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/pre_configured.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/pre_configured.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbe9728927507adafcff68ccca015824918efad9 GIT binary patch literal 829 zcmZuv&2G~`5Z?7)(kOu-g2aUf$f0$rx>X_}L_mmJ4s9i*mC(xZuFKN3*V$c1BI>O@ z@G86tkKik(z5*v^okk6av1a_OcYeR|$zaeM|77H|htMyyCmq0VLCrfb3^6QGfe9`= z;$g%*=1ac_NKk|%EFuyW9n!(*BVqvy?+^=l#6uqN&TT}xEV@AB&OPKoW3PFdE{&44 z( z=c}3<`?vG(oEuf^jPJrT*Tha>Ez zUUQwfNGI;?gT4nf2^fpM;u~~>Z-FH1?O}fp2alK`sDc;cP&>YxE~TOrmQo$TQHRcS z!2oT6;W8)B^ma_TPNiU}Rr&|geen4GJ+8D^rRPcAOg5Il}$+*}VE6kw?3E7eF-(U#118wCPb=C{{~{`;@NgRBmtD z-0i%3W&l<60T@T2aVzQi?RhaiaH;azLTkXOyV7o5%>%V9$u6oTxsBP&>uA+3dDDHp RZRd^qF^!8FdM|?~_zxQd(u)89 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/request_token.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/request_token.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7677faf1035755cad72340439a4ec32e52456d7f GIT binary patch literal 6134 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 from your_validator import your_validator + >>> from oauthlib.oauth1 import AccessTokenEndpoint + >>> endpoint = AccessTokenEndpoint(your_validator) + >>> h, b, s = endpoint.create_access_token_response( + ... 'https://your.provider/access_token?foo=bar', + ... headers={ + ... 'Authorization': 'OAuth oauth_token=234lsdkf....' + ... }, + ... credentials={ + ... 'my_specific': 'argument', + ... }) + >>> h + {'Content-Type': 'application/x-www-form-urlencoded'} + >>> b + 'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_authorized_realms=movies+pics&my_specific=argument' + >>> s + 200 + + An response to invalid request would have a different body and status:: + + >>> b + 'error=invalid_request&description=missing+resource+owner+key' + >>> s + 400 + + The same goes for an an unauthorized request: + + >>> b + '' + >>> s + 401 + """ + resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'} + try: + request = self._create_request(uri, http_method, body, headers) + valid, processed_request = self.validate_access_token_request( + request) + if valid: + token = self.create_access_token(request, credentials or {}) + self.request_validator.invalidate_request_token( + request.client_key, + request.resource_owner_key, + request) + return resp_headers, token, 200 + else: + return {}, None, 401 + except errors.OAuth1Error as e: + return resp_headers, e.urlencoded, e.status_code + + def validate_access_token_request(self, request): + """Validate an access token request. + + :param request: An oauthlib.common.Request object. + :raises: OAuth1Error if the request is invalid. + :returns: A tuple of 2 elements. + 1. The validation result (True or False). + 2. The request object. + """ + self._check_transport_security(request) + self._check_mandatory_parameters(request) + + if not request.resource_owner_key: + raise errors.InvalidRequestError( + description='Missing resource owner.') + + if not self.request_validator.check_request_token( + request.resource_owner_key): + raise errors.InvalidRequestError( + description='Invalid resource owner key format.') + + if not request.verifier: + raise errors.InvalidRequestError( + description='Missing verifier.') + + if not self.request_validator.check_verifier(request.verifier): + raise errors.InvalidRequestError( + description='Invalid verifier format.') + + if not self.request_validator.validate_timestamp_and_nonce( + request.client_key, request.timestamp, request.nonce, request, + request_token=request.resource_owner_key): + return False, request + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid client credentials. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy client is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable client enumeration + valid_client = self.request_validator.validate_client_key( + request.client_key, request) + if not valid_client: + request.client_key = self.request_validator.dummy_client + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid or expired token. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy token is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable resource owner enumeration + valid_resource_owner = self.request_validator.validate_request_token( + request.client_key, request.resource_owner_key, request) + if not valid_resource_owner: + request.resource_owner_key = self.request_validator.dummy_request_token + + # The server MUST verify (Section 3.2) the validity of the request, + # ensure that the resource owner has authorized the provisioning of + # token credentials to the client, and ensure that the temporary + # credentials have not expired or been used before. The server MUST + # also verify the verification code received from the client. + # .. _`Section 3.2`: http://tools.ietf.org/html/rfc5849#section-3.2 + # + # Note that early exit would enable resource owner authorization + # verifier enumertion. + valid_verifier = self.request_validator.validate_verifier( + request.client_key, + request.resource_owner_key, + request.verifier, + request) + + valid_signature = self._check_signature(request, is_token_request=True) + + # log the results to the validator_log + # this lets us handle internal reporting and analysis + request.validator_log['client'] = valid_client + request.validator_log['resource_owner'] = valid_resource_owner + request.validator_log['verifier'] = valid_verifier + request.validator_log['signature'] = valid_signature + + # We delay checking validity until the very end, using dummy values for + # calculations and fetching secrets/keys to ensure the flow of every + # request remains almost identical regardless of whether valid values + # have been supplied. This ensures near constant time execution and + # prevents malicious users from guessing sensitive information + v = all((valid_client, valid_resource_owner, valid_verifier, + valid_signature)) + if not v: + log.info("[Failure] request verification failed.") + log.info("Valid client:, %s", valid_client) + log.info("Valid token:, %s", valid_resource_owner) + log.info("Valid verifier:, %s", valid_verifier) + log.info("Valid signature:, %s", valid_signature) + return v, request diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py new file mode 100644 index 0000000..1751a45 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/authorization.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.endpoints.authorization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for signing and checking OAuth 1.0 RFC 5849 requests. +""" +from __future__ import absolute_import, unicode_literals + +from oauthlib.common import Request, add_params_to_uri + +from .. import errors +from .base import BaseEndpoint + +try: + from urllib import urlencode +except ImportError: + from urllib.parse import urlencode + + +class AuthorizationEndpoint(BaseEndpoint): + + """An endpoint responsible for letting authenticated users authorize access + to their protected resources to a client. + + Typical use would be to have two views, one for displaying the authorization + form and one to process said form on submission. + + The first view will want to utilize ``get_realms_and_credentials`` to fetch + requested realms and useful client credentials, such as name and + description, to be used when creating the authorization form. + + During form processing you can use ``create_authorization_response`` to + validate the request, create a verifier as well as prepare the final + redirection URI used to send the user back to the client. + + See :doc:`/oauth1/validator` for details on which validator methods to implement + for this endpoint. + """ + + def create_verifier(self, request, credentials): + """Create and save a new request token. + + :param request: An oauthlib.common.Request object. + :param credentials: A dict of extra token credentials. + :returns: The verifier as a dict. + """ + verifier = { + 'oauth_token': request.resource_owner_key, + 'oauth_verifier': self.token_generator(), + } + verifier.update(credentials) + self.request_validator.save_verifier( + request.resource_owner_key, verifier, request) + return verifier + + def create_authorization_response(self, uri, http_method='GET', body=None, + headers=None, realms=None, credentials=None): + """Create an authorization response, with a new request token if valid. + + :param uri: The full URI of the token request. + :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. + :param body: The request body as a string. + :param headers: The request headers as a dict. + :param credentials: A list of credentials to include in the verifier. + :returns: A tuple of 3 elements. + 1. A dict of headers to set on the response. + 2. The response body as a string. + 3. The response status code as an integer. + + If the callback URI tied to the current token is "oob", a response with + a 200 status code will be returned. In this case, it may be desirable to + modify the response to better display the verifier to the client. + + An example of an authorization request:: + + >>> from your_validator import your_validator + >>> from oauthlib.oauth1 import AuthorizationEndpoint + >>> endpoint = AuthorizationEndpoint(your_validator) + >>> h, b, s = endpoint.create_authorization_response( + ... 'https://your.provider/authorize?oauth_token=...', + ... credentials={ + ... 'extra': 'argument', + ... }) + >>> h + {'Location': 'https://the.client/callback?oauth_verifier=...&extra=argument'} + >>> b + None + >>> s + 302 + + An example of a request with an "oob" callback:: + + >>> from your_validator import your_validator + >>> from oauthlib.oauth1 import AuthorizationEndpoint + >>> endpoint = AuthorizationEndpoint(your_validator) + >>> h, b, s = endpoint.create_authorization_response( + ... 'https://your.provider/authorize?foo=bar', + ... credentials={ + ... 'extra': 'argument', + ... }) + >>> h + {'Content-Type': 'application/x-www-form-urlencoded'} + >>> b + 'oauth_verifier=...&extra=argument' + >>> s + 200 + """ + request = self._create_request(uri, http_method=http_method, body=body, + headers=headers) + + if not request.resource_owner_key: + raise errors.InvalidRequestError( + 'Missing mandatory parameter oauth_token.') + if not self.request_validator.verify_request_token( + request.resource_owner_key, request): + raise errors.InvalidClientError() + + request.realms = realms + if (request.realms and not self.request_validator.verify_realms( + request.resource_owner_key, request.realms, request)): + raise errors.InvalidRequestError( + description=('User granted access to realms outside of ' + 'what the client may request.')) + + verifier = self.create_verifier(request, credentials or {}) + redirect_uri = self.request_validator.get_redirect_uri( + request.resource_owner_key, request) + if redirect_uri == 'oob': + response_headers = { + 'Content-Type': 'application/x-www-form-urlencoded'} + response_body = urlencode(verifier) + return response_headers, response_body, 200 + else: + populated_redirect = add_params_to_uri( + redirect_uri, verifier.items()) + return {'Location': populated_redirect}, None, 302 + + def get_realms_and_credentials(self, uri, http_method='GET', body=None, + headers=None): + """Fetch realms and credentials for the presented request token. + + :param uri: The full URI of the token request. + :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. + :param body: The request body as a string. + :param headers: The request headers as a dict. + :returns: A tuple of 2 elements. + 1. A list of request realms. + 2. A dict of credentials which may be useful in creating the + authorization form. + """ + request = self._create_request(uri, http_method=http_method, body=body, + headers=headers) + + if not self.request_validator.verify_request_token( + request.resource_owner_key, request): + raise errors.InvalidClientError() + + realms = self.request_validator.get_realms( + request.resource_owner_key, request) + return realms, {'resource_owner_key': request.resource_owner_key} diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py new file mode 100644 index 0000000..9d51e69 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/base.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.endpoints.base +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for signing and checking OAuth 1.0 RFC 5849 requests. +""" +from __future__ import absolute_import, unicode_literals + +import time + +from oauthlib.common import Request, generate_token + +from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC, SIGNATURE_RSA, + SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY, + SIGNATURE_TYPE_QUERY, errors, signature, utils) + + +class BaseEndpoint(object): + + def __init__(self, request_validator, token_generator=None): + self.request_validator = request_validator + self.token_generator = token_generator or generate_token + + def _get_signature_type_and_params(self, request): + """Extracts parameters from query, headers and body. Signature type + is set to the source in which parameters were found. + """ + # Per RFC5849, only the Authorization header may contain the 'realm' + # optional parameter. + header_params = signature.collect_parameters(headers=request.headers, + exclude_oauth_signature=False, with_realm=True) + body_params = signature.collect_parameters(body=request.body, + exclude_oauth_signature=False) + query_params = signature.collect_parameters(uri_query=request.uri_query, + exclude_oauth_signature=False) + + params = [] + params.extend(header_params) + params.extend(body_params) + params.extend(query_params) + signature_types_with_oauth_params = list(filter(lambda s: s[2], ( + (SIGNATURE_TYPE_AUTH_HEADER, params, + utils.filter_oauth_params(header_params)), + (SIGNATURE_TYPE_BODY, params, + utils.filter_oauth_params(body_params)), + (SIGNATURE_TYPE_QUERY, params, + utils.filter_oauth_params(query_params)) + ))) + + if len(signature_types_with_oauth_params) > 1: + found_types = [s[0] for s in signature_types_with_oauth_params] + raise errors.InvalidRequestError( + description=('oauth_ params must come from only 1 signature' + 'type but were found in %s', + ', '.join(found_types))) + + try: + signature_type, params, oauth_params = signature_types_with_oauth_params[ + 0] + except IndexError: + raise errors.InvalidRequestError( + description='Missing mandatory OAuth parameters.') + + return signature_type, params, oauth_params + + def _create_request(self, uri, http_method, body, headers): + # Only include body data from x-www-form-urlencoded requests + headers = headers or {} + if ("Content-Type" in headers and + CONTENT_TYPE_FORM_URLENCODED in headers["Content-Type"]): + request = Request(uri, http_method, body, headers) + else: + request = Request(uri, http_method, '', headers) + + signature_type, params, oauth_params = ( + self._get_signature_type_and_params(request)) + + # The server SHOULD return a 400 (Bad Request) status code when + # receiving a request with duplicated protocol parameters. + if len(dict(oauth_params)) != len(oauth_params): + raise errors.InvalidRequestError( + description='Duplicate OAuth1 entries.') + + oauth_params = dict(oauth_params) + request.signature = oauth_params.get('oauth_signature') + request.client_key = oauth_params.get('oauth_consumer_key') + request.resource_owner_key = oauth_params.get('oauth_token') + request.nonce = oauth_params.get('oauth_nonce') + request.timestamp = oauth_params.get('oauth_timestamp') + request.redirect_uri = oauth_params.get('oauth_callback') + request.verifier = oauth_params.get('oauth_verifier') + request.signature_method = oauth_params.get('oauth_signature_method') + request.realm = dict(params).get('realm') + request.oauth_params = oauth_params + + # Parameters to Client depend on signature method which may vary + # for each request. Note that HMAC-SHA1 and PLAINTEXT share parameters + request.params = [(k, v) for k, v in params if k != "oauth_signature"] + + if 'realm' in request.headers.get('Authorization', ''): + request.params = [(k, v) + for k, v in request.params if k != "realm"] + + return request + + def _check_transport_security(self, request): + # TODO: move into oauthlib.common from oauth2.utils + if (self.request_validator.enforce_ssl and + not request.uri.lower().startswith("https://")): + raise errors.InsecureTransportError() + + def _check_mandatory_parameters(self, request): + # The server SHOULD return a 400 (Bad Request) status code when + # receiving a request with missing parameters. + if not all((request.signature, request.client_key, + request.nonce, request.timestamp, + request.signature_method)): + raise errors.InvalidRequestError( + description='Missing mandatory OAuth parameters.') + + # OAuth does not mandate a particular signature method, as each + # implementation can have its own unique requirements. Servers are + # free to implement and document their own custom methods. + # Recommending any particular method is beyond the scope of this + # specification. Implementers should review the Security + # Considerations section (`Section 4`_) before deciding on which + # method to support. + # .. _`Section 4`: http://tools.ietf.org/html/rfc5849#section-4 + if (not request.signature_method in + self.request_validator.allowed_signature_methods): + raise errors.InvalidSignatureMethodError( + description="Invalid signature, %s not in %r." % ( + request.signature_method, + self.request_validator.allowed_signature_methods)) + + # Servers receiving an authenticated request MUST validate it by: + # If the "oauth_version" parameter is present, ensuring its value is + # "1.0". + if ('oauth_version' in request.oauth_params and + request.oauth_params['oauth_version'] != '1.0'): + raise errors.InvalidRequestError( + description='Invalid OAuth version.') + + # The timestamp value MUST be a positive integer. Unless otherwise + # specified by the server's documentation, the timestamp is expressed + # in the number of seconds since January 1, 1970 00:00:00 GMT. + if len(request.timestamp) != 10: + raise errors.InvalidRequestError( + description='Invalid timestamp size') + + try: + ts = int(request.timestamp) + + except ValueError: + raise errors.InvalidRequestError( + description='Timestamp must be an integer.') + + else: + # To avoid the need to retain an infinite number of nonce values for + # future checks, servers MAY choose to restrict the time period after + # which a request with an old timestamp is rejected. + if abs(time.time() - ts) > self.request_validator.timestamp_lifetime: + raise errors.InvalidRequestError( + description=('Timestamp given is invalid, differ from ' + 'allowed by over %s seconds.' % ( + self.request_validator.timestamp_lifetime))) + + # Provider specific validation of parameters, used to enforce + # restrictions such as character set and length. + if not self.request_validator.check_client_key(request.client_key): + raise errors.InvalidRequestError( + description='Invalid client key format.') + + if not self.request_validator.check_nonce(request.nonce): + raise errors.InvalidRequestError( + description='Invalid nonce format.') + + def _check_signature(self, request, is_token_request=False): + # ---- RSA Signature verification ---- + if request.signature_method == SIGNATURE_RSA: + # The server verifies the signature per `[RFC3447] section 8.2.2`_ + # .. _`[RFC3447] section 8.2.2`: http://tools.ietf.org/html/rfc3447#section-8.2.1 + rsa_key = self.request_validator.get_rsa_key( + request.client_key, request) + valid_signature = signature.verify_rsa_sha1(request, rsa_key) + + # ---- HMAC or Plaintext Signature verification ---- + else: + # Servers receiving an authenticated request MUST validate it by: + # Recalculating the request signature independently as described in + # `Section 3.4`_ and comparing it to the value received from the + # client via the "oauth_signature" parameter. + # .. _`Section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + client_secret = self.request_validator.get_client_secret( + request.client_key, request) + resource_owner_secret = None + if request.resource_owner_key: + if is_token_request: + resource_owner_secret = self.request_validator.get_request_token_secret( + request.client_key, request.resource_owner_key, request) + else: + resource_owner_secret = self.request_validator.get_access_token_secret( + request.client_key, request.resource_owner_key, request) + + if request.signature_method == SIGNATURE_HMAC: + valid_signature = signature.verify_hmac_sha1(request, + client_secret, resource_owner_secret) + else: + valid_signature = signature.verify_plaintext(request, + client_secret, resource_owner_secret) + return valid_signature diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py new file mode 100644 index 0000000..f89393a --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/pre_configured.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, unicode_literals + +from . import (AccessTokenEndpoint, AuthorizationEndpoint, + RequestTokenEndpoint, ResourceEndpoint) + + +class WebApplicationServer(RequestTokenEndpoint, AuthorizationEndpoint, + AccessTokenEndpoint, ResourceEndpoint): + + def __init__(self, request_validator): + RequestTokenEndpoint.__init__(self, request_validator) + AuthorizationEndpoint.__init__(self, request_validator) + AccessTokenEndpoint.__init__(self, request_validator) + ResourceEndpoint.__init__(self, request_validator) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py new file mode 100644 index 0000000..515395b --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/request_token.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.endpoints.request_token +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of the request token provider logic of +OAuth 1.0 RFC 5849. It validates the correctness of request token requests, +creates and persists tokens as well as create the proper response to be +returned to the client. +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib.common import urlencode + +from .. import errors +from .base import BaseEndpoint + +log = logging.getLogger(__name__) + + +class RequestTokenEndpoint(BaseEndpoint): + + """An endpoint responsible for providing OAuth 1 request tokens. + + Typical use is to instantiate with a request validator and invoke the + ``create_request_token_response`` from a view function. The tuple returned + has all information necessary (body, status, headers) to quickly form + and return a proper response. See :doc:`/oauth1/validator` for details on which + validator methods to implement for this endpoint. + """ + + def create_request_token(self, request, credentials): + """Create and save a new request token. + + :param request: An oauthlib.common.Request object. + :param credentials: A dict of extra token credentials. + :returns: The token as an urlencoded string. + """ + token = { + 'oauth_token': self.token_generator(), + 'oauth_token_secret': self.token_generator(), + 'oauth_callback_confirmed': 'true' + } + token.update(credentials) + self.request_validator.save_request_token(token, request) + return urlencode(token.items()) + + def create_request_token_response(self, uri, http_method='GET', body=None, + headers=None, credentials=None): + """Create a request token response, with a new request token if valid. + + :param uri: The full URI of the token request. + :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. + :param body: The request body as a string. + :param headers: The request headers as a dict. + :param credentials: A list of extra credentials to include in the token. + :returns: A tuple of 3 elements. + 1. A dict of headers to set on the response. + 2. The response body as a string. + 3. The response status code as an integer. + + An example of a valid request:: + + >>> from your_validator import your_validator + >>> from oauthlib.oauth1 import RequestTokenEndpoint + >>> endpoint = RequestTokenEndpoint(your_validator) + >>> h, b, s = endpoint.create_request_token_response( + ... 'https://your.provider/request_token?foo=bar', + ... headers={ + ... 'Authorization': 'OAuth realm=movies user, oauth_....' + ... }, + ... credentials={ + ... 'my_specific': 'argument', + ... }) + >>> h + {'Content-Type': 'application/x-www-form-urlencoded'} + >>> b + 'oauth_token=lsdkfol23w54jlksdef&oauth_token_secret=qwe089234lkjsdf&oauth_callback_confirmed=true&my_specific=argument' + >>> s + 200 + + An response to invalid request would have a different body and status:: + + >>> b + 'error=invalid_request&description=missing+callback+uri' + >>> s + 400 + + The same goes for an an unauthorized request: + + >>> b + '' + >>> s + 401 + """ + resp_headers = {'Content-Type': 'application/x-www-form-urlencoded'} + try: + request = self._create_request(uri, http_method, body, headers) + valid, processed_request = self.validate_request_token_request( + request) + if valid: + token = self.create_request_token(request, credentials or {}) + return resp_headers, token, 200 + else: + return {}, None, 401 + except errors.OAuth1Error as e: + return resp_headers, e.urlencoded, e.status_code + + def validate_request_token_request(self, request): + """Validate a request token request. + + :param request: An oauthlib.common.Request object. + :raises: OAuth1Error if the request is invalid. + :returns: A tuple of 2 elements. + 1. The validation result (True or False). + 2. The request object. + """ + self._check_transport_security(request) + self._check_mandatory_parameters(request) + + if request.realm: + request.realms = request.realm.split(' ') + else: + request.realms = self.request_validator.get_default_realms( + request.client_key, request) + if not self.request_validator.check_realms(request.realms): + raise errors.InvalidRequestError( + description='Invalid realm %s. Allowed are %r.' % ( + request.realms, self.request_validator.realms)) + + if not request.redirect_uri: + raise errors.InvalidRequestError( + description='Missing callback URI.') + + if not self.request_validator.validate_timestamp_and_nonce( + request.client_key, request.timestamp, request.nonce, request, + request_token=request.resource_owner_key): + return False, request + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid client credentials. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy client is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable client enumeration + valid_client = self.request_validator.validate_client_key( + request.client_key, request) + if not valid_client: + request.client_key = self.request_validator.dummy_client + + # Note that `realm`_ is only used in authorization headers and how + # it should be interepreted is not included in the OAuth spec. + # However they could be seen as a scope or realm to which the + # client has access and as such every client should be checked + # to ensure it is authorized access to that scope or realm. + # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2 + # + # Note that early exit would enable client realm access enumeration. + # + # The require_realm indicates this is the first step in the OAuth + # workflow where a client requests access to a specific realm. + # This first step (obtaining request token) need not require a realm + # and can then be identified by checking the require_resource_owner + # flag and abscence of realm. + # + # Clients obtaining an access token will not supply a realm and it will + # not be checked. Instead the previously requested realm should be + # transferred from the request token to the access token. + # + # Access to protected resources will always validate the realm but note + # that the realm is now tied to the access token and not provided by + # the client. + valid_realm = self.request_validator.validate_requested_realms( + request.client_key, request.realms, request) + + # Callback is normally never required, except for requests for + # a Temporary Credential as described in `Section 2.1`_ + # .._`Section 2.1`: http://tools.ietf.org/html/rfc5849#section-2.1 + valid_redirect = self.request_validator.validate_redirect_uri( + request.client_key, request.redirect_uri, request) + if not request.redirect_uri: + raise NotImplementedError('Redirect URI must either be provided ' + 'or set to a default during validation.') + + valid_signature = self._check_signature(request) + + # log the results to the validator_log + # this lets us handle internal reporting and analysis + request.validator_log['client'] = valid_client + request.validator_log['realm'] = valid_realm + request.validator_log['callback'] = valid_redirect + request.validator_log['signature'] = valid_signature + + # We delay checking validity until the very end, using dummy values for + # calculations and fetching secrets/keys to ensure the flow of every + # request remains almost identical regardless of whether valid values + # have been supplied. This ensures near constant time execution and + # prevents malicious users from guessing sensitive information + v = all((valid_client, valid_realm, valid_redirect, valid_signature)) + if not v: + log.info("[Failure] request verification failed.") + log.info("Valid client: %s.", valid_client) + log.info("Valid realm: %s.", valid_realm) + log.info("Valid callback: %s.", valid_redirect) + log.info("Valid signature: %s.", valid_signature) + return v, request diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py new file mode 100644 index 0000000..53f9562 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/resource.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.endpoints.resource +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of the resource protection provider logic of +OAuth 1.0 RFC 5849. +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from .. import errors +from .base import BaseEndpoint + +log = logging.getLogger(__name__) + + +class ResourceEndpoint(BaseEndpoint): + + """An endpoint responsible for protecting resources. + + Typical use is to instantiate with a request validator and invoke the + ``validate_protected_resource_request`` in a decorator around a view + function. If the request is valid, invoke and return the response of the + view. If invalid create and return an error response directly from the + decorator. + + See :doc:`/oauth1/validator` for details on which validator methods to implement + for this endpoint. + + An example decorator:: + + from functools import wraps + from your_validator import your_validator + from oauthlib.oauth1 import ResourceEndpoint + endpoint = ResourceEndpoint(your_validator) + + def require_oauth(realms=None): + def decorator(f): + @wraps(f) + def wrapper(request, *args, **kwargs): + v, r = provider.validate_protected_resource_request( + request.url, + http_method=request.method, + body=request.data, + headers=request.headers, + realms=realms or []) + if v: + return f(*args, **kwargs) + else: + return abort(403) + """ + + def validate_protected_resource_request(self, uri, http_method='GET', + body=None, headers=None, realms=None): + """Create a request token response, with a new request token if valid. + + :param uri: The full URI of the token request. + :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. + :param body: The request body as a string. + :param headers: The request headers as a dict. + :param realms: A list of realms the resource is protected under. + This will be supplied to the ``validate_realms`` + method of the request validator. + :returns: A tuple of 2 elements. + 1. True if valid, False otherwise. + 2. An oauthlib.common.Request object. + """ + try: + request = self._create_request(uri, http_method, body, headers) + except errors.OAuth1Error: + return False, None + + try: + self._check_transport_security(request) + self._check_mandatory_parameters(request) + except errors.OAuth1Error: + return False, request + + if not request.resource_owner_key: + return False, request + + if not self.request_validator.check_access_token( + request.resource_owner_key): + return False, request + + if not self.request_validator.validate_timestamp_and_nonce( + request.client_key, request.timestamp, request.nonce, request, + access_token=request.resource_owner_key): + return False, request + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid client credentials. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy client is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable client enumeration + valid_client = self.request_validator.validate_client_key( + request.client_key, request) + if not valid_client: + request.client_key = self.request_validator.dummy_client + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid or expired token. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy token is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable resource owner enumeration + valid_resource_owner = self.request_validator.validate_access_token( + request.client_key, request.resource_owner_key, request) + if not valid_resource_owner: + request.resource_owner_key = self.request_validator.dummy_access_token + + # Note that `realm`_ is only used in authorization headers and how + # it should be interepreted is not included in the OAuth spec. + # However they could be seen as a scope or realm to which the + # client has access and as such every client should be checked + # to ensure it is authorized access to that scope or realm. + # .. _`realm`: http://tools.ietf.org/html/rfc2617#section-1.2 + # + # Note that early exit would enable client realm access enumeration. + # + # The require_realm indicates this is the first step in the OAuth + # workflow where a client requests access to a specific realm. + # This first step (obtaining request token) need not require a realm + # and can then be identified by checking the require_resource_owner + # flag and abscence of realm. + # + # Clients obtaining an access token will not supply a realm and it will + # not be checked. Instead the previously requested realm should be + # transferred from the request token to the access token. + # + # Access to protected resources will always validate the realm but note + # that the realm is now tied to the access token and not provided by + # the client. + valid_realm = self.request_validator.validate_realms(request.client_key, + request.resource_owner_key, request, uri=request.uri, + realms=realms) + + valid_signature = self._check_signature(request) + + # log the results to the validator_log + # this lets us handle internal reporting and analysis + request.validator_log['client'] = valid_client + request.validator_log['resource_owner'] = valid_resource_owner + request.validator_log['realm'] = valid_realm + request.validator_log['signature'] = valid_signature + + # We delay checking validity until the very end, using dummy values for + # calculations and fetching secrets/keys to ensure the flow of every + # request remains almost identical regardless of whether valid values + # have been supplied. This ensures near constant time execution and + # prevents malicious users from guessing sensitive information + v = all((valid_client, valid_resource_owner, valid_realm, + valid_signature)) + if not v: + log.info("[Failure] request verification failed.") + log.info("Valid client: %s", valid_client) + log.info("Valid token: %s", valid_resource_owner) + log.info("Valid realm: %s", valid_realm) + log.info("Valid signature: %s", valid_signature) + return v, request diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py new file mode 100644 index 0000000..4297770 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/signature_only.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.endpoints.signature_only +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of the signing logic of OAuth 1.0 RFC 5849. +""" + +from __future__ import absolute_import, unicode_literals + +import logging + +from .. import errors +from .base import BaseEndpoint + +log = logging.getLogger(__name__) + + +class SignatureOnlyEndpoint(BaseEndpoint): + + """An endpoint only responsible for verifying an oauth signature.""" + + def validate_request(self, uri, http_method='GET', + body=None, headers=None): + """Validate a signed OAuth request. + + :param uri: The full URI of the token request. + :param http_method: A valid HTTP verb, i.e. GET, POST, PUT, HEAD, etc. + :param body: The request body as a string. + :param headers: The request headers as a dict. + :returns: A tuple of 2 elements. + 1. True if valid, False otherwise. + 2. An oauthlib.common.Request object. + """ + try: + request = self._create_request(uri, http_method, body, headers) + except errors.OAuth1Error as err: + log.info( + 'Exception caught while validating request, %s.' % err) + return False, None + + try: + self._check_transport_security(request) + self._check_mandatory_parameters(request) + except errors.OAuth1Error as err: + log.info( + 'Exception caught while validating request, %s.' % err) + return False, request + + if not self.request_validator.validate_timestamp_and_nonce( + request.client_key, request.timestamp, request.nonce, request): + log.debug('[Failure] verification failed: timestamp/nonce') + return False, request + + # The server SHOULD return a 401 (Unauthorized) status code when + # receiving a request with invalid client credentials. + # Note: This is postponed in order to avoid timing attacks, instead + # a dummy client is assigned and used to maintain near constant + # time request verification. + # + # Note that early exit would enable client enumeration + valid_client = self.request_validator.validate_client_key( + request.client_key, request) + if not valid_client: + request.client_key = self.request_validator.dummy_client + + valid_signature = self._check_signature(request) + + # log the results to the validator_log + # this lets us handle internal reporting and analysis + request.validator_log['client'] = valid_client + request.validator_log['signature'] = valid_signature + + # We delay checking validity until the very end, using dummy values for + # calculations and fetching secrets/keys to ensure the flow of every + # request remains almost identical regardless of whether valid values + # have been supplied. This ensures near constant time execution and + # prevents malicious users from guessing sensitive information + v = all((valid_client, valid_signature)) + if not v: + log.info("[Failure] request verification failed.") + log.info("Valid client: %s", valid_client) + log.info("Valid signature: %s", valid_signature) + return v, request diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/errors.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/errors.py new file mode 100644 index 0000000..a5c59bd --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/errors.py @@ -0,0 +1,79 @@ +# coding=utf-8 +""" +oauthlib.oauth1.rfc5849.errors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Error used both by OAuth 1 clients and provicers to represent the spec +defined error responses for all four core grant types. +""" +from __future__ import unicode_literals + +from oauthlib.common import add_params_to_uri, urlencode + + +class OAuth1Error(Exception): + error = None + description = '' + + def __init__(self, description=None, uri=None, status_code=400, + request=None): + """ + description: A human-readable ASCII [USASCII] text providing + additional information, used to assist the client + developer in understanding the error that occurred. + Values for the "error_description" parameter MUST NOT + include characters outside the set + x20-21 / x23-5B / x5D-7E. + + uri: A URI identifying a human-readable web page with information + about the error, used to provide the client developer with + additional information about the error. Values for the + "error_uri" parameter MUST conform to the URI- Reference + syntax, and thus MUST NOT include characters outside the set + x21 / x23-5B / x5D-7E. + + state: A CSRF protection value received from the client. + + request: Oauthlib Request object + """ + self.description = description or self.description + message = '(%s) %s' % (self.error, self.description) + if request: + message += ' ' + repr(request) + super(OAuth1Error, self).__init__(message) + + self.uri = uri + self.status_code = status_code + + def in_uri(self, uri): + return add_params_to_uri(uri, self.twotuples) + + @property + def twotuples(self): + error = [('error', self.error)] + if self.description: + error.append(('error_description', self.description)) + if self.uri: + error.append(('error_uri', self.uri)) + return error + + @property + def urlencoded(self): + return urlencode(self.twotuples) + + +class InsecureTransportError(OAuth1Error): + error = 'insecure_transport_protocol' + description = 'Only HTTPS connections are permitted.' + + +class InvalidSignatureMethodError(OAuth1Error): + error = 'invalid_signature_method' + + +class InvalidRequestError(OAuth1Error): + error = 'invalid_request' + + +class InvalidClientError(OAuth1Error): + error = 'invalid_client' diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/parameters.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/parameters.py new file mode 100644 index 0000000..dcb23dc --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/parameters.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.parameters +~~~~~~~~~~~~~~~~~~~ + +This module contains methods related to `section 3.5`_ of the OAuth 1.0a spec. + +.. _`section 3.5`: http://tools.ietf.org/html/rfc5849#section-3.5 +""" +from __future__ import absolute_import, unicode_literals + +from oauthlib.common import extract_params, urlencode + +from . import utils + +try: + from urlparse import urlparse, urlunparse +except ImportError: + from urllib.parse import urlparse, urlunparse + + +# TODO: do we need filter_params now that oauth_params are handled by Request? +# We can easily pass in just oauth protocol params. +@utils.filter_params +def prepare_headers(oauth_params, headers=None, realm=None): + """**Prepare the Authorization header.** + Per `section 3.5.1`_ of the spec. + + Protocol parameters can be transmitted using the HTTP "Authorization" + header field as defined by `RFC2617`_ with the auth-scheme name set to + "OAuth" (case insensitive). + + For example:: + + Authorization: OAuth realm="Example", + oauth_consumer_key="0685bd9184jfhq22", + oauth_token="ad180jjd733klru7", + oauth_signature_method="HMAC-SHA1", + oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", + oauth_timestamp="137131200", + oauth_nonce="4572616e48616d6d65724c61686176", + oauth_version="1.0" + + + .. _`section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 + .. _`RFC2617`: http://tools.ietf.org/html/rfc2617 + """ + headers = headers or {} + + # Protocol parameters SHALL be included in the "Authorization" header + # field as follows: + authorization_header_parameters_parts = [] + for oauth_parameter_name, value in oauth_params: + # 1. Parameter names and values are encoded per Parameter Encoding + # (`Section 3.6`_) + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + escaped_name = utils.escape(oauth_parameter_name) + escaped_value = utils.escape(value) + + # 2. Each parameter's name is immediately followed by an "=" character + # (ASCII code 61), a """ character (ASCII code 34), the parameter + # value (MAY be empty), and another """ character (ASCII code 34). + part = '{0}="{1}"'.format(escaped_name, escaped_value) + + authorization_header_parameters_parts.append(part) + + # 3. Parameters are separated by a "," character (ASCII code 44) and + # OPTIONAL linear whitespace per `RFC2617`_. + # + # .. _`RFC2617`: http://tools.ietf.org/html/rfc2617 + authorization_header_parameters = ', '.join( + authorization_header_parameters_parts) + + # 4. The OPTIONAL "realm" parameter MAY be added and interpreted per + # `RFC2617 section 1.2`_. + # + # .. _`RFC2617 section 1.2`: http://tools.ietf.org/html/rfc2617#section-1.2 + if realm: + # NOTE: realm should *not* be escaped + authorization_header_parameters = ('realm="%s", ' % realm + + authorization_header_parameters) + + # the auth-scheme name set to "OAuth" (case insensitive). + authorization_header = 'OAuth %s' % authorization_header_parameters + + # contribute the Authorization header to the given headers + full_headers = {} + full_headers.update(headers) + full_headers['Authorization'] = authorization_header + return full_headers + + +def _append_params(oauth_params, params): + """Append OAuth params to an existing set of parameters. + + Both params and oauth_params is must be lists of 2-tuples. + + Per `section 3.5.2`_ and `3.5.3`_ of the spec. + + .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2 + .. _`3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 + + """ + merged = list(params) + merged.extend(oauth_params) + # The request URI / entity-body MAY include other request-specific + # parameters, in which case, the protocol parameters SHOULD be appended + # following the request-specific parameters, properly separated by an "&" + # character (ASCII code 38) + merged.sort(key=lambda i: i[0].startswith('oauth_')) + return merged + + +def prepare_form_encoded_body(oauth_params, body): + """Prepare the Form-Encoded Body. + + Per `section 3.5.2`_ of the spec. + + .. _`section 3.5.2`: http://tools.ietf.org/html/rfc5849#section-3.5.2 + + """ + # append OAuth params to the existing body + return _append_params(oauth_params, body) + + +def prepare_request_uri_query(oauth_params, uri): + """Prepare the Request URI Query. + + Per `section 3.5.3`_ of the spec. + + .. _`section 3.5.3`: http://tools.ietf.org/html/rfc5849#section-3.5.3 + + """ + # append OAuth params to the existing set of query components + sch, net, path, par, query, fra = urlparse(uri) + query = urlencode( + _append_params(oauth_params, extract_params(query) or [])) + return urlunparse((sch, net, path, par, query, fra)) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/request_validator.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/request_validator.py new file mode 100644 index 0000000..2ccb367 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/request_validator.py @@ -0,0 +1,833 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849 +~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for signing and checking OAuth 1.0 RFC 5849 requests. +""" +from __future__ import absolute_import, unicode_literals + +import sys + +from . import SIGNATURE_METHODS, utils + + +class RequestValidator(object): + + """A validator/datastore interaction base class for OAuth 1 providers. + + OAuth providers should inherit from RequestValidator and implement the + methods and properties outlined below. Further details are provided in the + documentation for each method and property. + + Methods used to check the format of input parameters. Common tests include + length, character set, membership, range or pattern. These tests are + referred to as `whitelisting or blacklisting`_. Whitelisting is better + but blacklisting can be usefull to spot malicious activity. + The following have methods a default implementation: + + - check_client_key + - check_request_token + - check_access_token + - check_nonce + - check_verifier + - check_realms + + The methods above default to whitelist input parameters, checking that they + are alphanumerical and between a minimum and maximum length. Rather than + overloading the methods a few properties can be used to configure these + methods. + + * @safe_characters -> (character set) + * @client_key_length -> (min, max) + * @request_token_length -> (min, max) + * @access_token_length -> (min, max) + * @nonce_length -> (min, max) + * @verifier_length -> (min, max) + * @realms -> [list, of, realms] + + Methods used to validate/invalidate input parameters. These checks usually + hit either persistent or temporary storage such as databases or the + filesystem. See each methods documentation for detailed usage. + The following methods must be implemented: + + - validate_client_key + - validate_request_token + - validate_access_token + - validate_timestamp_and_nonce + - validate_redirect_uri + - validate_requested_realms + - validate_realms + - validate_verifier + - invalidate_request_token + + Methods used to retrieve sensitive information from storage. + The following methods must be implemented: + + - get_client_secret + - get_request_token_secret + - get_access_token_secret + - get_rsa_key + - get_realms + - get_default_realms + - get_redirect_uri + + Methods used to save credentials. + The following methods must be implemented: + + - save_request_token + - save_verifier + - save_access_token + + Methods used to verify input parameters. This methods are used during + authorizing request token by user (AuthorizationEndpoint), to check if + parameters are valid. During token authorization request is not signed, + thus 'validation' methods can not be used. The following methods must be + implemented: + + - verify_realms + - verify_request_token + + To prevent timing attacks it is necessary to not exit early even if the + client key or resource owner key is invalid. Instead dummy values should + be used during the remaining verification process. It is very important + that the dummy client and token are valid input parameters to the methods + get_client_secret, get_rsa_key and get_(access/request)_token_secret and + that the running time of those methods when given a dummy value remain + equivalent to the running time when given a valid client/resource owner. + The following properties must be implemented: + + * @dummy_client + * @dummy_request_token + * @dummy_access_token + + Example implementations have been provided, note that the database used is + a simple dictionary and serves only an illustrative purpose. Use whichever + database suits your project and how to access it is entirely up to you. + The methods are introduced in an order which should make understanding + their use more straightforward and as such it could be worth reading what + follows in chronological order. + + .. _`whitelisting or blacklisting`: http://www.schneier.com/blog/archives/2011/01/whitelisting_vs.html + """ + + def __init__(self): + pass + + @property + def allowed_signature_methods(self): + return SIGNATURE_METHODS + + @property + def safe_characters(self): + return set(utils.UNICODE_ASCII_CHARACTER_SET) + + @property + def client_key_length(self): + return 20, 30 + + @property + def request_token_length(self): + return 20, 30 + + @property + def access_token_length(self): + return 20, 30 + + @property + def timestamp_lifetime(self): + return 600 + + @property + def nonce_length(self): + return 20, 30 + + @property + def verifier_length(self): + return 20, 30 + + @property + def realms(self): + return [] + + @property + def enforce_ssl(self): + return True + + def check_client_key(self, client_key): + """Check that the client key only contains safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.client_key_length + return (set(client_key) <= self.safe_characters and + lower <= len(client_key) <= upper) + + def check_request_token(self, request_token): + """Checks that the request token contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.request_token_length + return (set(request_token) <= self.safe_characters and + lower <= len(request_token) <= upper) + + def check_access_token(self, request_token): + """Checks that the token contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.access_token_length + return (set(request_token) <= self.safe_characters and + lower <= len(request_token) <= upper) + + def check_nonce(self, nonce): + """Checks that the nonce only contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.nonce_length + return (set(nonce) <= self.safe_characters and + lower <= len(nonce) <= upper) + + def check_verifier(self, verifier): + """Checks that the verifier contains only safe characters + and is no shorter than lower and no longer than upper. + """ + lower, upper = self.verifier_length + return (set(verifier) <= self.safe_characters and + lower <= len(verifier) <= upper) + + def check_realms(self, realms): + """Check that the realm is one of a set allowed realms.""" + return all((r in self.realms for r in realms)) + + def _subclass_must_implement(self, fn): + """ + Returns a NotImplementedError for a function that should be implemented. + :param fn: name of the function + """ + m = "Missing function implementation in {}: {}".format(type(self), fn) + return NotImplementedError(m) + + @property + def dummy_client(self): + """Dummy client used when an invalid client key is supplied. + + :returns: The dummy client key string. + + The dummy client should be associated with either a client secret, + a rsa key or both depending on which signature methods are supported. + Providers should make sure that + + get_client_secret(dummy_client) + get_rsa_key(dummy_client) + + return a valid secret or key for the dummy client. + + This method is used by + + * AccessTokenEndpoint + * RequestTokenEndpoint + * ResourceEndpoint + * SignatureOnlyEndpoint + """ + raise self._subclass_must_implement("dummy_client") + + @property + def dummy_request_token(self): + """Dummy request token used when an invalid token was supplied. + + :returns: The dummy request token string. + + The dummy request token should be associated with a request token + secret such that get_request_token_secret(.., dummy_request_token) + returns a valid secret. + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("dummy_request_token") + + @property + def dummy_access_token(self): + """Dummy access token used when an invalid token was supplied. + + :returns: The dummy access token string. + + The dummy access token should be associated with an access token + secret such that get_access_token_secret(.., dummy_access_token) + returns a valid secret. + + This method is used by + + * ResourceEndpoint + """ + raise self._subclass_must_implement("dummy_access_token") + + def get_client_secret(self, client_key, request): + """Retrieves the client secret associated with the client key. + + :param client_key: The client/consumer key. + :param request: An oauthlib.common.Request object. + :returns: The client secret as a string. + + This method must allow the use of a dummy client_key value. + Fetching the secret using the dummy key must take the same amount of + time as fetching a secret for a valid client:: + + # Unlikely to be near constant time as it uses two database + # lookups for a valid client, and only one for an invalid. + from your_datastore import ClientSecret + if ClientSecret.has(client_key): + return ClientSecret.get(client_key) + else: + return 'dummy' + + # Aim to mimic number of latency inducing operations no matter + # whether the client is valid or not. + from your_datastore import ClientSecret + return ClientSecret.get(client_key, 'dummy') + + Note that the returned key must be in plaintext. + + This method is used by + + * AccessTokenEndpoint + * RequestTokenEndpoint + * ResourceEndpoint + * SignatureOnlyEndpoint + """ + raise self._subclass_must_implement('get_client_secret') + + def get_request_token_secret(self, client_key, token, request): + """Retrieves the shared secret associated with the request token. + + :param client_key: The client/consumer key. + :param token: The request token string. + :param request: An oauthlib.common.Request object. + :returns: The token secret as a string. + + This method must allow the use of a dummy values and the running time + must be roughly equivalent to that of the running time of valid values:: + + # Unlikely to be near constant time as it uses two database + # lookups for a valid client, and only one for an invalid. + from your_datastore import RequestTokenSecret + if RequestTokenSecret.has(client_key): + return RequestTokenSecret.get((client_key, request_token)) + else: + return 'dummy' + + # Aim to mimic number of latency inducing operations no matter + # whether the client is valid or not. + from your_datastore import RequestTokenSecret + return ClientSecret.get((client_key, request_token), 'dummy') + + Note that the returned key must be in plaintext. + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement('get_request_token_secret') + + def get_access_token_secret(self, client_key, token, request): + """Retrieves the shared secret associated with the access token. + + :param client_key: The client/consumer key. + :param token: The access token string. + :param request: An oauthlib.common.Request object. + :returns: The token secret as a string. + + This method must allow the use of a dummy values and the running time + must be roughly equivalent to that of the running time of valid values:: + + # Unlikely to be near constant time as it uses two database + # lookups for a valid client, and only one for an invalid. + from your_datastore import AccessTokenSecret + if AccessTokenSecret.has(client_key): + return AccessTokenSecret.get((client_key, request_token)) + else: + return 'dummy' + + # Aim to mimic number of latency inducing operations no matter + # whether the client is valid or not. + from your_datastore import AccessTokenSecret + return ClientSecret.get((client_key, request_token), 'dummy') + + Note that the returned key must be in plaintext. + + This method is used by + + * ResourceEndpoint + """ + raise self._subclass_must_implement("get_access_token_secret") + + def get_default_realms(self, client_key, request): + """Get the default realms for a client. + + :param client_key: The client/consumer key. + :param request: An oauthlib.common.Request object. + :returns: The list of default realms associated with the client. + + The list of default realms will be set during client registration and + is outside the scope of OAuthLib. + + This method is used by + + * RequestTokenEndpoint + """ + raise self._subclass_must_implement("get_default_realms") + + def get_realms(self, token, request): + """Get realms associated with a request token. + + :param token: The request token string. + :param request: An oauthlib.common.Request object. + :returns: The list of realms associated with the request token. + + This method is used by + + * AuthorizationEndpoint + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("get_realms") + + def get_redirect_uri(self, token, request): + """Get the redirect URI associated with a request token. + + :param token: The request token string. + :param request: An oauthlib.common.Request object. + :returns: The redirect URI associated with the request token. + + It may be desirable to return a custom URI if the redirect is set to "oob". + In this case, the user will be redirected to the returned URI and at that + endpoint the verifier can be displayed. + + This method is used by + + * AuthorizationEndpoint + """ + raise self._subclass_must_implement("get_redirect_uri") + + def get_rsa_key(self, client_key, request): + """Retrieves a previously stored client provided RSA key. + + :param client_key: The client/consumer key. + :param request: An oauthlib.common.Request object. + :returns: The rsa public key as a string. + + This method must allow the use of a dummy client_key value. Fetching + the rsa key using the dummy key must take the same amount of time + as fetching a key for a valid client. The dummy key must also be of + the same bit length as client keys. + + Note that the key must be returned in plaintext. + + This method is used by + + * AccessTokenEndpoint + * RequestTokenEndpoint + * ResourceEndpoint + * SignatureOnlyEndpoint + """ + raise self._subclass_must_implement("get_rsa_key") + + def invalidate_request_token(self, client_key, request_token, request): + """Invalidates a used request token. + + :param client_key: The client/consumer key. + :param request_token: The request token string. + :param request: An oauthlib.common.Request object. + :returns: None + + Per `Section 2.3`__ of the spec: + + "The server MUST (...) ensure that the temporary + credentials have not expired or been used before." + + .. _`Section 2.3`: http://tools.ietf.org/html/rfc5849#section-2.3 + + This method should ensure that provided token won't validate anymore. + It can be simply removing RequestToken from storage or setting + specific flag that makes it invalid (note that such flag should be + also validated during request token validation). + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("invalidate_request_token") + + def validate_client_key(self, client_key, request): + """Validates that supplied client key is a registered and valid client. + + :param client_key: The client/consumer key. + :param request: An oauthlib.common.Request object. + :returns: True or False + + Note that if the dummy client is supplied it should validate in same + or nearly the same amount of time as a valid one. + + Ensure latency inducing tasks are mimiced even for dummy clients. + For example, use:: + + from your_datastore import Client + try: + return Client.exists(client_key, access_token) + except DoesNotExist: + return False + + Rather than:: + + from your_datastore import Client + if access_token == self.dummy_access_token: + return False + else: + return Client.exists(client_key, access_token) + + This method is used by + + * AccessTokenEndpoint + * RequestTokenEndpoint + * ResourceEndpoint + * SignatureOnlyEndpoint + """ + raise self._subclass_must_implement("validate_client_key") + + def validate_request_token(self, client_key, token, request): + """Validates that supplied request token is registered and valid. + + :param client_key: The client/consumer key. + :param token: The request token string. + :param request: An oauthlib.common.Request object. + :returns: True or False + + Note that if the dummy request_token is supplied it should validate in + the same nearly the same amount of time as a valid one. + + Ensure latency inducing tasks are mimiced even for dummy clients. + For example, use:: + + from your_datastore import RequestToken + try: + return RequestToken.exists(client_key, access_token) + except DoesNotExist: + return False + + Rather than:: + + from your_datastore import RequestToken + if access_token == self.dummy_access_token: + return False + else: + return RequestToken.exists(client_key, access_token) + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("validate_request_token") + + def validate_access_token(self, client_key, token, request): + """Validates that supplied access token is registered and valid. + + :param client_key: The client/consumer key. + :param token: The access token string. + :param request: An oauthlib.common.Request object. + :returns: True or False + + Note that if the dummy access token is supplied it should validate in + the same or nearly the same amount of time as a valid one. + + Ensure latency inducing tasks are mimiced even for dummy clients. + For example, use:: + + from your_datastore import AccessToken + try: + return AccessToken.exists(client_key, access_token) + except DoesNotExist: + return False + + Rather than:: + + from your_datastore import AccessToken + if access_token == self.dummy_access_token: + return False + else: + return AccessToken.exists(client_key, access_token) + + This method is used by + + * ResourceEndpoint + """ + raise self._subclass_must_implement("validate_access_token") + + def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, + request, request_token=None, access_token=None): + """Validates that the nonce has not been used before. + + :param client_key: The client/consumer key. + :param timestamp: The ``oauth_timestamp`` parameter. + :param nonce: The ``oauth_nonce`` parameter. + :param request_token: Request token string, if any. + :param access_token: Access token string, if any. + :param request: An oauthlib.common.Request object. + :returns: True or False + + Per `Section 3.3`_ of the spec. + + "A nonce is a random string, uniquely generated by the client to allow + the server to verify that a request has never been made before and + helps prevent replay attacks when requests are made over a non-secure + channel. The nonce value MUST be unique across all requests with the + same timestamp, client credentials, and token combinations." + + .. _`Section 3.3`: http://tools.ietf.org/html/rfc5849#section-3.3 + + One of the first validation checks that will be made is for the validity + of the nonce and timestamp, which are associated with a client key and + possibly a token. If invalid then immediately fail the request + by returning False. If the nonce/timestamp pair has been used before and + you may just have detected a replay attack. Therefore it is an essential + part of OAuth security that you not allow nonce/timestamp reuse. + Note that this validation check is done before checking the validity of + the client and token.:: + + nonces_and_timestamps_database = [ + (u'foo', 1234567890, u'rannoMstrInghere', u'bar') + ] + + def validate_timestamp_and_nonce(self, client_key, timestamp, nonce, + request_token=None, access_token=None): + + return ((client_key, timestamp, nonce, request_token or access_token) + not in self.nonces_and_timestamps_database) + + This method is used by + + * AccessTokenEndpoint + * RequestTokenEndpoint + * ResourceEndpoint + * SignatureOnlyEndpoint + """ + raise self._subclass_must_implement("validate_timestamp_and_nonce") + + def validate_redirect_uri(self, client_key, redirect_uri, request): + """Validates the client supplied redirection URI. + + :param client_key: The client/consumer key. + :param redirect_uri: The URI the client which to redirect back to after + authorization is successful. + :param request: An oauthlib.common.Request object. + :returns: True or False + + It is highly recommended that OAuth providers require their clients + to register all redirection URIs prior to using them in requests and + register them as absolute URIs. See `CWE-601`_ for more information + about open redirection attacks. + + By requiring registration of all redirection URIs it should be + straightforward for the provider to verify whether the supplied + redirect_uri is valid or not. + + Alternatively per `Section 2.1`_ of the spec: + + "If the client is unable to receive callbacks or a callback URI has + been established via other means, the parameter value MUST be set to + "oob" (case sensitive), to indicate an out-of-band configuration." + + .. _`CWE-601`: http://cwe.mitre.org/top25/index.html#CWE-601 + .. _`Section 2.1`: https://tools.ietf.org/html/rfc5849#section-2.1 + + This method is used by + + * RequestTokenEndpoint + """ + raise self._subclass_must_implement("validate_redirect_uri") + + def validate_requested_realms(self, client_key, realms, request): + """Validates that the client may request access to the realm. + + :param client_key: The client/consumer key. + :param realms: The list of realms that client is requesting access to. + :param request: An oauthlib.common.Request object. + :returns: True or False + + This method is invoked when obtaining a request token and should + tie a realm to the request token and after user authorization + this realm restriction should transfer to the access token. + + This method is used by + + * RequestTokenEndpoint + """ + raise self._subclass_must_implement("validate_requested_realms") + + def validate_realms(self, client_key, token, request, uri=None, + realms=None): + """Validates access to the request realm. + + :param client_key: The client/consumer key. + :param token: A request token string. + :param request: An oauthlib.common.Request object. + :param uri: The URI the realms is protecting. + :param realms: A list of realms that must have been granted to + the access token. + :returns: True or False + + How providers choose to use the realm parameter is outside the OAuth + specification but it is commonly used to restrict access to a subset + of protected resources such as "photos". + + realms is a convenience parameter which can be used to provide + a per view method pre-defined list of allowed realms. + + Can be as simple as:: + + from your_datastore import RequestToken + request_token = RequestToken.get(token, None) + + if not request_token: + return False + return set(request_token.realms).issuperset(set(realms)) + + This method is used by + + * ResourceEndpoint + """ + raise self._subclass_must_implement("validate_realms") + + def validate_verifier(self, client_key, token, verifier, request): + """Validates a verification code. + + :param client_key: The client/consumer key. + :param token: A request token string. + :param verifier: The authorization verifier string. + :param request: An oauthlib.common.Request object. + :returns: True or False + + OAuth providers issue a verification code to clients after the + resource owner authorizes access. This code is used by the client to + obtain token credentials and the provider must verify that the + verifier is valid and associated with the client as well as the + resource owner. + + Verifier validation should be done in near constant time + (to avoid verifier enumeration). To achieve this we need a + constant time string comparison which is provided by OAuthLib + in ``oauthlib.common.safe_string_equals``:: + + from your_datastore import Verifier + correct_verifier = Verifier.get(client_key, request_token) + from oauthlib.common import safe_string_equals + return safe_string_equals(verifier, correct_verifier) + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("validate_verifier") + + def verify_request_token(self, token, request): + """Verify that the given OAuth1 request token is valid. + + :param token: A request token string. + :param request: An oauthlib.common.Request object. + :returns: True or False + + This method is used only in AuthorizationEndpoint to check whether the + oauth_token given in the authorization URL is valid or not. + This request is not signed and thus similar ``validate_request_token`` + method can not be used. + + This method is used by + + * AuthorizationEndpoint + """ + raise self._subclass_must_implement("verify_request_token") + + def verify_realms(self, token, realms, request): + """Verify authorized realms to see if they match those given to token. + + :param token: An access token string. + :param realms: A list of realms the client attempts to access. + :param request: An oauthlib.common.Request object. + :returns: True or False + + This prevents the list of authorized realms sent by the client during + the authorization step to be altered to include realms outside what + was bound with the request token. + + Can be as simple as:: + + valid_realms = self.get_realms(token) + return all((r in valid_realms for r in realms)) + + This method is used by + + * AuthorizationEndpoint + """ + raise self._subclass_must_implement("verify_realms") + + def save_access_token(self, token, request): + """Save an OAuth1 access token. + + :param token: A dict with token credentials. + :param request: An oauthlib.common.Request object. + + The token dictionary will at minimum include + + * ``oauth_token`` the access token string. + * ``oauth_token_secret`` the token specific secret used in signing. + * ``oauth_authorized_realms`` a space separated list of realms. + + Client key can be obtained from ``request.client_key``. + + The list of realms (not joined string) can be obtained from + ``request.realm``. + + This method is used by + + * AccessTokenEndpoint + """ + raise self._subclass_must_implement("save_access_token") + + def save_request_token(self, token, request): + """Save an OAuth1 request token. + + :param token: A dict with token credentials. + :param request: An oauthlib.common.Request object. + + The token dictionary will at minimum include + + * ``oauth_token`` the request token string. + * ``oauth_token_secret`` the token specific secret used in signing. + * ``oauth_callback_confirmed`` the string ``true``. + + Client key can be obtained from ``request.client_key``. + + This method is used by + + * RequestTokenEndpoint + """ + raise self._subclass_must_implement("save_request_token") + + def save_verifier(self, token, verifier, request): + """Associate an authorization verifier with a request token. + + :param token: A request token string. + :param verifier A dictionary containing the oauth_verifier and + oauth_token + :param request: An oauthlib.common.Request object. + + We need to associate verifiers with tokens for validation during the + access token request. + + Note that unlike save_x_token token here is the ``oauth_token`` token + string from the request token saved previously. + + This method is used by + + * AuthorizationEndpoint + """ + raise self._subclass_must_implement("save_verifier") diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/signature.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/signature.py new file mode 100644 index 0000000..10d057f --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/signature.py @@ -0,0 +1,627 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth1.rfc5849.signature +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module represents a direct implementation of `section 3.4`_ of the spec. + +Terminology: + * Client: software interfacing with an OAuth API + * Server: the API provider + * Resource Owner: the user who is granting authorization to the client + +Steps for signing a request: + +1. Collect parameters from the uri query, auth header, & body +2. Normalize those parameters +3. Normalize the uri +4. Pass the normalized uri, normalized parameters, and http method to + construct the base string +5. Pass the base string and any keys needed to a signing function + +.. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 +""" +from __future__ import absolute_import, unicode_literals + +import binascii +import hashlib +import hmac +import logging + +from oauthlib.common import (bytes_type, extract_params, safe_string_equals, + unicode_type, urldecode) + +from . import utils + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +log = logging.getLogger(__name__) + +def construct_base_string(http_method, base_string_uri, + normalized_encoded_request_parameters): + """**String Construction** + Per `section 3.4.1.1`_ of the spec. + + For example, the HTTP request:: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="bYT5CMsGcbgUdFHObYMEfcx6bsw%3D" + + c2&a3=2+q + + is represented by the following signature base string (line breaks + are for display purposes only):: + + POST&http%3A%2F%2Fexample.com%2Frequest&a2%3Dr%2520b%26a3%3D2%2520q + %26a3%3Da%26b5%3D%253D%25253D%26c%2540%3D%26c2%3D%26oauth_consumer_ + key%3D9djdj82h48djs9d2%26oauth_nonce%3D7d8f3e4a%26oauth_signature_m + ethod%3DHMAC-SHA1%26oauth_timestamp%3D137131201%26oauth_token%3Dkkk + 9d7dh3k39sjv7 + + .. _`section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 + """ + + # The signature base string is constructed by concatenating together, + # in order, the following HTTP request elements: + + # 1. The HTTP request method in uppercase. For example: "HEAD", + # "GET", "POST", etc. If the request uses a custom HTTP method, it + # MUST be encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + base_string = utils.escape(http_method.upper()) + + # 2. An "&" character (ASCII code 38). + base_string += '&' + + # 3. The base string URI from `Section 3.4.1.2`_, after being encoded + # (`Section 3.6`_). + # + # .. _`Section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 + # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6 + base_string += utils.escape(base_string_uri) + + # 4. An "&" character (ASCII code 38). + base_string += '&' + + # 5. The request parameters as normalized in `Section 3.4.1.3.2`_, after + # being encoded (`Section 3.6`). + # + # .. _`Section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 + # .. _`Section 3.4.6`: http://tools.ietf.org/html/rfc5849#section-3.4.6 + base_string += utils.escape(normalized_encoded_request_parameters) + + return base_string + + +def normalize_base_string_uri(uri, host=None): + """**Base String URI** + Per `section 3.4.1.2`_ of the spec. + + For example, the HTTP request:: + + GET /r%20v/X?id=123 HTTP/1.1 + Host: EXAMPLE.COM:80 + + is represented by the base string URI: "http://example.com/r%20v/X". + + In another example, the HTTPS request:: + + GET /?q=1 HTTP/1.1 + Host: www.example.net:8080 + + is represented by the base string URI: "https://www.example.net:8080/". + + .. _`section 3.4.1.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.2 + + The host argument overrides the netloc part of the uri argument. + """ + if not isinstance(uri, unicode_type): + raise ValueError('uri must be a unicode object.') + + # FIXME: urlparse does not support unicode + scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri) + + # The scheme, authority, and path of the request resource URI `RFC3986` + # are included by constructing an "http" or "https" URI representing + # the request resource (without the query or fragment) as follows: + # + # .. _`RFC3986`: http://tools.ietf.org/html/rfc3986 + + if not scheme or not netloc: + raise ValueError('uri must include a scheme and netloc') + + # Per `RFC 2616 section 5.1.2`_: + # + # Note that the absolute path cannot be empty; if none is present in + # the original URI, it MUST be given as "/" (the server root). + # + # .. _`RFC 2616 section 5.1.2`: http://tools.ietf.org/html/rfc2616#section-5.1.2 + if not path: + path = '/' + + # 1. The scheme and host MUST be in lowercase. + scheme = scheme.lower() + netloc = netloc.lower() + + # 2. The host and port values MUST match the content of the HTTP + # request "Host" header field. + if host is not None: + netloc = host.lower() + + # 3. The port MUST be included if it is not the default port for the + # scheme, and MUST be excluded if it is the default. Specifically, + # the port MUST be excluded when making an HTTP request `RFC2616`_ + # to port 80 or when making an HTTPS request `RFC2818`_ to port 443. + # All other non-default port numbers MUST be included. + # + # .. _`RFC2616`: http://tools.ietf.org/html/rfc2616 + # .. _`RFC2818`: http://tools.ietf.org/html/rfc2818 + default_ports = ( + ('http', '80'), + ('https', '443'), + ) + if ':' in netloc: + host, port = netloc.split(':', 1) + if (scheme, port) in default_ports: + netloc = host + + return urlparse.urlunparse((scheme, netloc, path, params, '', '')) + + +# ** Request Parameters ** +# +# Per `section 3.4.1.3`_ of the spec. +# +# In order to guarantee a consistent and reproducible representation of +# the request parameters, the parameters are collected and decoded to +# their original decoded form. They are then sorted and encoded in a +# particular manner that is often different from their original +# encoding scheme, and concatenated into a single string. +# +# .. _`section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 + +def collect_parameters(uri_query='', body=[], headers=None, + exclude_oauth_signature=True, with_realm=False): + """**Parameter Sources** + + Parameters starting with `oauth_` will be unescaped. + + Body parameters must be supplied as a dict, a list of 2-tuples, or a + formencoded query string. + + Headers must be supplied as a dict. + + Per `section 3.4.1.3.1`_ of the spec. + + For example, the HTTP request:: + + POST /request?b5=%3D%253D&a3=a&c%40=&a2=r%20b HTTP/1.1 + Host: example.com + Content-Type: application/x-www-form-urlencoded + Authorization: OAuth realm="Example", + oauth_consumer_key="9djdj82h48djs9d2", + oauth_token="kkk9d7dh3k39sjv7", + oauth_signature_method="HMAC-SHA1", + oauth_timestamp="137131201", + oauth_nonce="7d8f3e4a", + oauth_signature="djosJKDKJSD8743243%2Fjdk33klY%3D" + + c2&a3=2+q + + contains the following (fully decoded) parameters used in the + signature base sting:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | =%3D | + | a3 | a | + | c@ | | + | a2 | r b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2 q | + +------------------------+------------------+ + + Note that the value of "b5" is "=%3D" and not "==". Both "c@" and + "c2" have empty values. While the encoding rules specified in this + specification for the purpose of constructing the signature base + string exclude the use of a "+" character (ASCII code 43) to + represent an encoded space character (ASCII code 32), this practice + is widely used in "application/x-www-form-urlencoded" encoded values, + and MUST be properly decoded, as demonstrated by one of the "a3" + parameter instances (the "a3" parameter is used twice in this + request). + + .. _`section 3.4.1.3.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.1 + """ + headers = headers or {} + params = [] + + # The parameters from the following sources are collected into a single + # list of name/value pairs: + + # * The query component of the HTTP request URI as defined by + # `RFC3986, Section 3.4`_. The query component is parsed into a list + # of name/value pairs by treating it as an + # "application/x-www-form-urlencoded" string, separating the names + # and values and decoding them as defined by + # `W3C.REC-html40-19980424`_, Section 17.13.4. + # + # .. _`RFC3986, Section 3.4`: http://tools.ietf.org/html/rfc3986#section-3.4 + # .. _`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + if uri_query: + params.extend(urldecode(uri_query)) + + # * The OAuth HTTP "Authorization" header field (`Section 3.5.1`_) if + # present. The header's content is parsed into a list of name/value + # pairs excluding the "realm" parameter if present. The parameter + # values are decoded as defined by `Section 3.5.1`_. + # + # .. _`Section 3.5.1`: http://tools.ietf.org/html/rfc5849#section-3.5.1 + if headers: + headers_lower = dict((k.lower(), v) for k, v in headers.items()) + authorization_header = headers_lower.get('authorization') + if authorization_header is not None: + params.extend([i for i in utils.parse_authorization_header( + authorization_header) if with_realm or i[0] != 'realm']) + + # * The HTTP request entity-body, but only if all of the following + # conditions are met: + # * The entity-body is single-part. + # + # * The entity-body follows the encoding requirements of the + # "application/x-www-form-urlencoded" content-type as defined by + # `W3C.REC-html40-19980424`_. + + # * The HTTP request entity-header includes the "Content-Type" + # header field set to "application/x-www-form-urlencoded". + # + # .._`W3C.REC-html40-19980424`: http://tools.ietf.org/html/rfc5849#ref-W3C.REC-html40-19980424 + + # TODO: enforce header param inclusion conditions + bodyparams = extract_params(body) or [] + params.extend(bodyparams) + + # ensure all oauth params are unescaped + unescaped_params = [] + for k, v in params: + if k.startswith('oauth_'): + v = utils.unescape(v) + unescaped_params.append((k, v)) + + # The "oauth_signature" parameter MUST be excluded from the signature + # base string if present. + if exclude_oauth_signature: + unescaped_params = list(filter(lambda i: i[0] != 'oauth_signature', + unescaped_params)) + + return unescaped_params + + +def normalize_parameters(params): + """**Parameters Normalization** + Per `section 3.4.1.3.2`_ of the spec. + + For example, the list of parameters from the previous section would + be normalized as follows: + + Encoded:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | b5 | %3D%253D | + | a3 | a | + | c%40 | | + | a2 | r%20b | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_token | kkk9d7dh3k39sjv7 | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_nonce | 7d8f3e4a | + | c2 | | + | a3 | 2%20q | + +------------------------+------------------+ + + Sorted:: + + +------------------------+------------------+ + | Name | Value | + +------------------------+------------------+ + | a2 | r%20b | + | a3 | 2%20q | + | a3 | a | + | b5 | %3D%253D | + | c%40 | | + | c2 | | + | oauth_consumer_key | 9djdj82h48djs9d2 | + | oauth_nonce | 7d8f3e4a | + | oauth_signature_method | HMAC-SHA1 | + | oauth_timestamp | 137131201 | + | oauth_token | kkk9d7dh3k39sjv7 | + +------------------------+------------------+ + + Concatenated Pairs:: + + +-------------------------------------+ + | Name=Value | + +-------------------------------------+ + | a2=r%20b | + | a3=2%20q | + | a3=a | + | b5=%3D%253D | + | c%40= | + | c2= | + | oauth_consumer_key=9djdj82h48djs9d2 | + | oauth_nonce=7d8f3e4a | + | oauth_signature_method=HMAC-SHA1 | + | oauth_timestamp=137131201 | + | oauth_token=kkk9d7dh3k39sjv7 | + +-------------------------------------+ + + and concatenated together into a single string (line breaks are for + display purposes only):: + + a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj + dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1 + &oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7 + + .. _`section 3.4.1.3.2`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3.2 + """ + + # The parameters collected in `Section 3.4.1.3`_ are normalized into a + # single string as follows: + # + # .. _`Section 3.4.1.3`: http://tools.ietf.org/html/rfc5849#section-3.4.1.3 + + # 1. First, the name and value of each parameter are encoded + # (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key_values = [(utils.escape(k), utils.escape(v)) for k, v in params] + + # 2. The parameters are sorted by name, using ascending byte value + # ordering. If two or more parameters share the same name, they + # are sorted by their value. + key_values.sort() + + # 3. The name of each parameter is concatenated to its corresponding + # value using an "=" character (ASCII code 61) as a separator, even + # if the value is empty. + parameter_parts = ['{0}={1}'.format(k, v) for k, v in key_values] + + # 4. The sorted name/value pairs are concatenated together into a + # single string by using an "&" character (ASCII code 38) as + # separator. + return '&'.join(parameter_parts) + + +def sign_hmac_sha1_with_client(base_string, client): + return sign_hmac_sha1(base_string, + client.client_secret, + client.resource_owner_secret + ) + + +def sign_hmac_sha1(base_string, client_secret, resource_owner_secret): + """**HMAC-SHA1** + + The "HMAC-SHA1" signature method uses the HMAC-SHA1 signature + algorithm as defined in `RFC2104`_:: + + digest = HMAC-SHA1 (key, text) + + Per `section 3.4.2`_ of the spec. + + .. _`RFC2104`: http://tools.ietf.org/html/rfc2104 + .. _`section 3.4.2`: http://tools.ietf.org/html/rfc5849#section-3.4.2 + """ + + # The HMAC-SHA1 function variables are used in following way: + + # text is set to the value of the signature base string from + # `Section 3.4.1.1`_. + # + # .. _`Section 3.4.1.1`: http://tools.ietf.org/html/rfc5849#section-3.4.1.1 + text = base_string + + # key is set to the concatenated values of: + # 1. The client shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key = utils.escape(client_secret or '') + + # 2. An "&" character (ASCII code 38), which MUST be included + # even when either secret is empty. + key += '&' + + # 3. The token shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + key += utils.escape(resource_owner_secret or '') + + # FIXME: HMAC does not support unicode! + key_utf8 = key.encode('utf-8') + text_utf8 = text.encode('utf-8') + signature = hmac.new(key_utf8, text_utf8, hashlib.sha1) + + # digest is used to set the value of the "oauth_signature" protocol + # parameter, after the result octet string is base64-encoded + # per `RFC2045, Section 6.8`. + # + # .. _`RFC2045, Section 6.8`: http://tools.ietf.org/html/rfc2045#section-6.8 + return binascii.b2a_base64(signature.digest())[:-1].decode('utf-8') + +_jwtrs1 = None + +#jwt has some nice pycrypto/cryptography abstractions +def _jwt_rs1_signing_algorithm(): + global _jwtrs1 + if _jwtrs1 is None: + import jwt.algorithms as jwtalgo + _jwtrs1 = jwtalgo.RSAAlgorithm(jwtalgo.hashes.SHA1) + return _jwtrs1 + +def sign_rsa_sha1(base_string, rsa_private_key): + """**RSA-SHA1** + + Per `section 3.4.3`_ of the spec. + + The "RSA-SHA1" signature method uses the RSASSA-PKCS1-v1_5 signature + algorithm as defined in `RFC3447, Section 8.2`_ (also known as + PKCS#1), using SHA-1 as the hash function for EMSA-PKCS1-v1_5. To + use this method, the client MUST have established client credentials + with the server that included its RSA public key (in a manner that is + beyond the scope of this specification). + + .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3 + .. _`RFC3447, Section 8.2`: http://tools.ietf.org/html/rfc3447#section-8.2 + + """ + if isinstance(base_string, unicode_type): + base_string = base_string.encode('utf-8') + # TODO: finish RSA documentation + alg = _jwt_rs1_signing_algorithm() + key = _prepare_key_plus(alg, rsa_private_key) + s=alg.sign(base_string, key) + return binascii.b2a_base64(s)[:-1].decode('utf-8') + + +def sign_rsa_sha1_with_client(base_string, client): + if not client.rsa_key: + raise ValueError('rsa_key is required when using RSA signature method.') + return sign_rsa_sha1(base_string, client.rsa_key) + + +def sign_plaintext(client_secret, resource_owner_secret): + """Sign a request using plaintext. + + Per `section 3.4.4`_ of the spec. + + The "PLAINTEXT" method does not employ a signature algorithm. It + MUST be used with a transport-layer mechanism such as TLS or SSL (or + sent over a secure channel with equivalent protections). It does not + utilize the signature base string or the "oauth_timestamp" and + "oauth_nonce" parameters. + + .. _`section 3.4.4`: http://tools.ietf.org/html/rfc5849#section-3.4.4 + + """ + + # The "oauth_signature" protocol parameter is set to the concatenated + # value of: + + # 1. The client shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + signature = utils.escape(client_secret or '') + + # 2. An "&" character (ASCII code 38), which MUST be included even + # when either secret is empty. + signature += '&' + + # 3. The token shared-secret, after being encoded (`Section 3.6`_). + # + # .. _`Section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + signature += utils.escape(resource_owner_secret or '') + + return signature + + +def sign_plaintext_with_client(base_string, client): + return sign_plaintext(client.client_secret, client.resource_owner_secret) + + +def verify_hmac_sha1(request, client_secret=None, + resource_owner_secret=None): + """Verify a HMAC-SHA1 signature. + + Per `section 3.4`_ of the spec. + + .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + + To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri + attribute MUST be an absolute URI whose netloc part identifies the + origin server or gateway on which the resource resides. Any Host + item of the request argument's headers dict attribute will be + ignored. + + .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2 + + """ + norm_params = normalize_parameters(request.params) + uri = normalize_base_string_uri(request.uri) + base_string = construct_base_string(request.http_method, uri, norm_params) + signature = sign_hmac_sha1(base_string, client_secret, + resource_owner_secret) + match = safe_string_equals(signature, request.signature) + if not match: + log.debug('Verify HMAC-SHA1 failed: sig base string: %s', base_string) + return match + + +def _prepare_key_plus(alg, keystr): + if isinstance(keystr, bytes_type): + keystr = keystr.decode('utf-8') + return alg.prepare_key(keystr) + +def verify_rsa_sha1(request, rsa_public_key): + """Verify a RSASSA-PKCS #1 v1.5 base64 encoded signature. + + Per `section 3.4.3`_ of the spec. + + Note this method requires the jwt and cryptography libraries. + + .. _`section 3.4.3`: http://tools.ietf.org/html/rfc5849#section-3.4.3 + + To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri + attribute MUST be an absolute URI whose netloc part identifies the + origin server or gateway on which the resource resides. Any Host + item of the request argument's headers dict attribute will be + ignored. + + .. _`RFC2616 section 5.2`: http://tools.ietf.org/html/rfc2616#section-5.2 + """ + norm_params = normalize_parameters(request.params) + uri = normalize_base_string_uri(request.uri) + message = construct_base_string(request.http_method, uri, norm_params).encode('utf-8') + sig = binascii.a2b_base64(request.signature.encode('utf-8')) + + alg = _jwt_rs1_signing_algorithm() + key = _prepare_key_plus(alg, rsa_public_key) + + verify_ok = alg.verify(message, key, sig) + if not verify_ok: + log.debug('Verify RSA-SHA1 failed: sig base string: %s', message) + return verify_ok + + +def verify_plaintext(request, client_secret=None, resource_owner_secret=None): + """Verify a PLAINTEXT signature. + + Per `section 3.4`_ of the spec. + + .. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4 + """ + signature = sign_plaintext(client_secret, resource_owner_secret) + match = safe_string_equals(signature, request.signature) + if not match: + log.debug('Verify PLAINTEXT failed') + return match diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/utils.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/utils.py new file mode 100644 index 0000000..979e5f6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/utils.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.utils +~~~~~~~~~~~~~~ + +This module contains utility methods used by various parts of the OAuth +spec. +""" +from __future__ import absolute_import, unicode_literals + +from oauthlib.common import bytes_type, quote, unicode_type, unquote + +try: + import urllib2 +except ImportError: + import urllib.request as urllib2 + + +UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + '0123456789') + + +def filter_params(target): + """Decorator which filters params to remove non-oauth_* parameters + + Assumes the decorated method takes a params dict or list of tuples as its + first argument. + """ + def wrapper(params, *args, **kwargs): + params = filter_oauth_params(params) + return target(params, *args, **kwargs) + + wrapper.__doc__ = target.__doc__ + return wrapper + + +def filter_oauth_params(params): + """Removes all non oauth parameters from a dict or a list of params.""" + is_oauth = lambda kv: kv[0].startswith("oauth_") + if isinstance(params, dict): + return list(filter(is_oauth, list(params.items()))) + else: + return list(filter(is_oauth, params)) + + +def escape(u): + """Escape a unicode string in an OAuth-compatible fashion. + + Per `section 3.6`_ of the spec. + + .. _`section 3.6`: http://tools.ietf.org/html/rfc5849#section-3.6 + + """ + if not isinstance(u, unicode_type): + raise ValueError('Only unicode objects are escapable. ' + + 'Got %r of type %s.' % (u, type(u))) + # Letters, digits, and the characters '_.-' are already treated as safe + # by urllib.quote(). We need to add '~' to fully support rfc5849. + return quote(u, safe=b'~') + + +def unescape(u): + if not isinstance(u, unicode_type): + raise ValueError('Only unicode objects are unescapable.') + return unquote(u) + + +def parse_keqv_list(l): + """A unicode-safe version of urllib2.parse_keqv_list""" + # With Python 2.6, parse_http_list handles unicode fine + return urllib2.parse_keqv_list(l) + + +def parse_http_list(u): + """A unicode-safe version of urllib2.parse_http_list""" + # With Python 2.6, parse_http_list handles unicode fine + return urllib2.parse_http_list(u) + + +def parse_authorization_header(authorization_header): + """Parse an OAuth authorization header into a list of 2-tuples""" + auth_scheme = 'OAuth '.lower() + if authorization_header[:len(auth_scheme)].lower().startswith(auth_scheme): + items = parse_http_list(authorization_header[len(auth_scheme):]) + try: + return list(parse_keqv_list(items).items()) + except (IndexError, ValueError): + pass + raise ValueError('Malformed authorization header') diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/__init__.py new file mode 100644 index 0000000..c8d934e --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/__init__.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2 +~~~~~~~~~~~~~~ + +This module is a wrapper for the most recent implementation of OAuth 2.0 Client +and Server classes. +""" +from __future__ import absolute_import, unicode_literals + +from .rfc6749.clients import Client +from .rfc6749.clients import WebApplicationClient +from .rfc6749.clients import MobileApplicationClient +from .rfc6749.clients import LegacyApplicationClient +from .rfc6749.clients import BackendApplicationClient +from .rfc6749.clients import ServiceApplicationClient +from .rfc6749.endpoints import AuthorizationEndpoint +from .rfc6749.endpoints import TokenEndpoint +from .rfc6749.endpoints import ResourceEndpoint +from .rfc6749.endpoints import RevocationEndpoint +from .rfc6749.endpoints import Server +from .rfc6749.endpoints import WebApplicationServer +from .rfc6749.endpoints import MobileApplicationServer +from .rfc6749.endpoints import LegacyApplicationServer +from .rfc6749.endpoints import BackendApplicationServer +from .rfc6749.errors import AccessDeniedError, AccountSelectionRequired, ConsentRequired, FatalClientError, FatalOpenIDClientError, InsecureTransportError, InteractionRequired, InvalidClientError, InvalidClientIdError, InvalidGrantError, InvalidRedirectURIError, InvalidRequestError, InvalidRequestFatalError, InvalidScopeError, LoginRequired, MismatchingRedirectURIError, MismatchingStateError, MissingClientIdError, MissingCodeError, MissingRedirectURIError, MissingResponseTypeError, MissingTokenError, MissingTokenTypeError, OAuth2Error, OpenIDClientError, ServerError, TemporarilyUnavailableError, TokenExpiredError, UnauthorizedClientError, UnsupportedGrantTypeError, UnsupportedResponseTypeError, UnsupportedTokenTypeError +from .rfc6749.grant_types import AuthorizationCodeGrant +from .rfc6749.grant_types import ImplicitGrant +from .rfc6749.grant_types import ResourceOwnerPasswordCredentialsGrant +from .rfc6749.grant_types import ClientCredentialsGrant +from .rfc6749.grant_types import RefreshTokenGrant +from .rfc6749.request_validator import RequestValidator +from .rfc6749.tokens import BearerToken, OAuth2Token +from .rfc6749.utils import is_secure_transport diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60bfaaa2f3c9743b9d84da0794c145e5be67098d GIT binary patch literal 2553 zcmaKt+iu%N5QZf?KFO9PS+XRb66W9}N@7Wt<%4pzowN{;G==T7zyJk7lPh}>F3Bt@ zr}jnf3iMU_27QJ;f^K`2SLjt|mZU7jMrruNubJiS?9R?|Bb`oF{8CqK}zYAJuPNnM(JtPX-F$QgL)Qbl}@A1Kt}0V)LF2sDH^nWuB{a~)ZMZG& zz#VZH?uvVGPprbKxDWTm19%`F!b4Gkig*N%#AA3Yp1>2a25X`URZ)YQs6$<>!@6id zLu|l?XhKtL!lufjU~h?S*j9QT^$zSPT}0i2meM8EPvNQ3Wz^5$nbHgPbMXRRD7|RE z6tCcwcnz=B?L30YjlHCn;%z|Xrz7{dQz#QQ|@cRwYoObP#M}6 zX?Px!TE~@IaLUl<2byFia{`SE&t?LZ5pdVh+>Z8P7sG3fYF*p6IXY5?V`+y>o?!&j zHhiD?)znuyS(TAq#yIv}yBjba1GzHjWxEbHU5n{9512G;|0^E1L?zi952K+f{LGGb zJ=;;mzOE_VV< zFMZ@8~cnMq|mV4u5OGOL%_cO4&V+BamsGXld7OAx+XRF)4ObJ}l) z>}`D0>`Hbd4acWihvV~YhYCGB&s^I%Gi+`R1}F^K+EK2#$of4-jUtvJ(*d(EX*2kE z(C$yAqEhzEk4BgKqm@x*9}bU0ADXVmLR0#kd%`cbW9dEjg%Oyi+&Q^2+x&p@5ZfaP zQ9!eg-I*+rvN;4Nu_NzzdBgz~HMZLj>D!A~`@w*{71 zmkBKoxUgNmS=iu|iMPFQL?PKP6 zra4|V@xWqOoohU~1J;quKUHbO!?DBF;rWTqtP!{pZ?@?@W=JO0xL&m1>IWv4=e`~` zgC4})@Iz(4H=*m6YwCJ0rR$w;fX$-ol0Lk06ag98F}Jpxt*WUmR$q>hYn(7az)yBU zP7zWF$i>51bUFEQnw&F)G+~yIA!G?T!Wh46^*nDB(KMyL{MggRlJ&>(CeARp&@LU(~4 zU|;yMN$yR;7GayPgMfv2v(xBT&#>WZXNIhGT-##OuXSW?%zKd`OTeA&6a3wHS74CIJKji z$Gx@VF1M|Yeu{pNF68mvjPr+L0T{?8r>=|Nj0M I@@A9&0W2yV{Qv*} literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__init__.py new file mode 100644 index 0000000..aff0ed8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__init__.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import functools +import logging + +from .errors import TemporarilyUnavailableError, ServerError +from .errors import FatalClientError, OAuth2Error + + +log = logging.getLogger(__name__) + + +class BaseEndpoint(object): + + def __init__(self): + self._available = True + self._catch_errors = False + + @property + def available(self): + return self._available + + @available.setter + def available(self, available): + self._available = available + + @property + def catch_errors(self): + return self._catch_errors + + @catch_errors.setter + def catch_errors(self, catch_errors): + self._catch_errors = catch_errors + + +def catch_errors_and_unavailability(f): + @functools.wraps(f) + def wrapper(endpoint, uri, *args, **kwargs): + if not endpoint.available: + e = TemporarilyUnavailableError() + log.info('Endpoint unavailable, ignoring request %s.' % uri) + return {}, e.json, 503 + + if endpoint.catch_errors: + try: + return f(endpoint, uri, *args, **kwargs) + except OAuth2Error: + raise + except FatalClientError: + raise + except Exception as e: + error = ServerError() + log.warning( + 'Exception caught while processing request, %s.' % e) + return {}, error.json, 500 + else: + return f(endpoint, uri, *args, **kwargs) + return wrapper diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a88a6d9de87d4228fe3560b23d46f652a467eeaf GIT binary patch literal 2195 zcma)7&u`pB6rQoYUazxBw~^9{_D};VXi>5o(ohdbjgn8TM&P5%;WF!wp}Tg-e&+6$u}+&nCcgEZOi z=*31y#L?}KH@DnpuWzoqcaTVLkh3!79#&!IC4*tg2RtjnBFQr^kG)unJ>=4n~* z064iQs#cjLQOZf~xm^|J0>cbE*<_RAz?YaeBixKpejbk^cNER|CedRdaZByYw#?-{5y_*b65Ua+F zkn0eoVv!i5E9bywDv>@`8q==Pl=bn1@bieq1W^U#0<2JbH_)emZ|24VF8qfZ&G}fq z`^xa>t#h8tYrVNM8h7f4

?=- zfqeUp#8hXYb4s7mG1ZwdBSJl!SzvyHpx%&tub`*osZ|(#6KO@Szzih`8C!je88K=i zv})q#ljRp!jcII*jUSD%`8|wVq_7WNu}6`ilzdCRAxFPrua3Qg3wsXsexkskWaJeM zAl88BGIMOr5NklR6e5G(GwTtoJtEm_aCOSpc1ItaRSK_sRoYg)q?hGFl~BQtN-hiU zrtBntBYQ@j2TvkC)Fn9z%iclZJw8ZKv=O9;OF4IERbToGm)Z+DbfN~rDih_qvXU&$ zE2}T_Y!XNZN20FI8#4f**ski2Ly^I3+x8tkEo+roilnka(UX;Z2nEcPS2bOzP^V}- zm%sJLA{^oYt-+dn2Lmx|l#3`Udd(QE&!<6vze+HpC&3Tmq;ogT zBm5-pb*9EqIU8}AMMa*cvZ|?F**3)6Kobg6e-AQKZ|Jge<9{Glacf%JuB?cgklL54 zdJwQYn&eDe7NtP`2=$hQddNbhbyWw!JrGvele(>G*Ia6Xg`&D@^my?F^q{h=ZmLRJ q+4+8-M}^K5jX0Oav;3-@yOV6arwr+TiA7t6Lmi_*i{)Ub!D z?xDm9l|v}Nz#f7Giy%M}AcqAKiTUH#Yh|MkDB`r6P?q4Y1J{Mk$*@n4D9Uk2y5@$vpVl}Lz0IU$lFRZDKA z%Bdusr)%kQ8sC{(W-D9HZsp3kWa9IL$co&{gvi-_`{{BXbf3tB&f9s={h<3r0d#@s z0_XuT2zrp|0nkHY81yjHgP>1{BIqL1L!d{*Nzf;m9tM3%oCbZG=@X#Oh*8j^_6a<% z2>Prz2l|{{1U&-!ytn}R0@Ei!Ulf->Ut;t9=N-_Wirb)XGyN{;Iq@0j&zQapdR{DmUSRqP z=sV&r=(|i`1^v0W2l^hgI;EOQalnXV)bRRJSEaAiPG22e<;+=hQCpB)+YI6W>QwI8z0WznzW^?ORw-d zNBNGip5fOGX>Uv0!*7Pa zVH@7IZ54!FbzJnpkHrPvcHQ-C�nvvsS}*LmF0H+Qzyx>Bik{+nX%>H_gCOvQ=!j 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py new file mode 100644 index 0000000..17d0023 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from .base import Client, AUTH_HEADER, URI_QUERY, BODY +from .web_application import WebApplicationClient +from .mobile_application import MobileApplicationClient +from .legacy_application import LegacyApplicationClient +from .backend_application import BackendApplicationClient +from .service_application import ServiceApplicationClient diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6b26f5b426836c9dcc7d668de8094d5441308b8 GIT binary patch literal 870 zcmZuv%Wl&^6tx}a0Tgr%6_80yRs=;>0wq=kWmW+dl`qmNvw)>|iPl&h)D#So3c}OarhGwsJH!}aWCdK982-1nB!kjghr?8%3MpI@9vkd>QYM|Ri=I7L{*P9U74 z<+D)S4M%;nM(a^LJY5b)Y;-d0%UFcY%!x>J=%s|B!{g8wr%C_L`8|36kb(0!f{w46 h8#@q^kR%ZHqWi4!Vl#R#^ZybzC762KIMC~l*DnX{2_XOg literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/backend_application.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/backend_application.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb2bca280f6a8189f5182e61d3adeaea25da3d5d GIT binary patch literal 2907 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/mobile_application.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/mobile_application.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dbbf7e65fd3adb61a21a751857d2b60325425c2 GIT binary patch literal 9169 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/service_application.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/service_application.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a43f262d4ccb628ccb5ffa3fb340f00c4ab35f7e GIT binary patch literal 6526 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 ObEVR#EL2v^1?xYXB!C|P literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/web_application.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/web_application.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f0be6a59ab3aaf90f5f54bfeb88462c8d6b476c GIT binary patch literal 9570 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py new file mode 100644 index 0000000..7505b0d --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/backend_application.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from ..parameters import parse_token_response, prepare_token_request +from .base import Client + + +class BackendApplicationClient(Client): + + """A public client utilizing the client credentials grant workflow. + + The client can request an access token using only its client + credentials (or other supported means of authentication) when the + client is requesting access to the protected resources under its + control, or those of another resource owner which has been previously + arranged with the authorization server (the method of which is beyond + the scope of this specification). + + The client credentials grant type MUST only be used by confidential + clients. + + Since the client authentication is used as the authorization grant, + no additional authorization request is needed. + """ + + def prepare_request_body(self, body='', scope=None, **kwargs): + """Add the client credentials to the request body. + + The client makes a request to the token endpoint by adding the + following parameters using the "application/x-www-form-urlencoded" + format per `Appendix B`_ in the HTTP request entity-body: + + :param scope: The scope of the access request as described by + `Section 3.3`_. + :param kwargs: Extra credentials to include in the token request. + + The client MUST authenticate with the authorization server as + described in `Section 3.2.1`_. + + The prepared body will include all provided credentials as well as + the ``grant_type`` parameter set to ``client_credentials``:: + + >>> from oauthlib.oauth2 import BackendApplicationClient + >>> client = BackendApplicationClient('your_id') + >>> client.prepare_request_body(scope=['hello', 'world']) + 'grant_type=client_credentials&scope=hello+world' + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 + """ + return prepare_token_request('client_credentials', body=body, + scope=scope, **kwargs) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py new file mode 100644 index 0000000..c2f8809 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/base.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import time + +from oauthlib.common import generate_token +from oauthlib.oauth2.rfc6749 import tokens +from oauthlib.oauth2.rfc6749.errors import (InsecureTransportError, + TokenExpiredError) +from oauthlib.oauth2.rfc6749.parameters import (parse_token_response, + prepare_token_request, + prepare_token_revocation_request) +from oauthlib.oauth2.rfc6749.utils import is_secure_transport + +AUTH_HEADER = 'auth_header' +URI_QUERY = 'query' +BODY = 'body' + +FORM_ENC_HEADERS = { + 'Content-Type': 'application/x-www-form-urlencoded' +} + +class Client(object): + + """Base OAuth2 client responsible for access token management. + + This class also acts as a generic interface providing methods common to all + client types such as ``prepare_authorization_request`` and + ``prepare_token_revocation_request``. The ``prepare_x_request`` methods are + the recommended way of interacting with clients (as opposed to the abstract + prepare uri/body/etc methods). They are recommended over the older set + because they are easier to use (more consistent) and add a few additional + security checks, such as HTTPS and state checking. + + Some of these methods require further implementation only provided by the + specific purpose clients such as + :py:class:`oauthlib.oauth2.MobileApplicationClient` and thus you should always + seek to use the client class matching the OAuth workflow you need. For + Python, this is usually :py:class:`oauthlib.oauth2.WebApplicationClient`. + + """ + + def __init__(self, client_id, + default_token_placement=AUTH_HEADER, + token_type='Bearer', + access_token=None, + refresh_token=None, + mac_key=None, + mac_algorithm=None, + token=None, + scope=None, + state=None, + redirect_url=None, + state_generator=generate_token, + **kwargs): + """Initialize a client with commonly used attributes. + + :param client_id: Client identifier given by the OAuth provider upon + registration. + + :param default_token_placement: Tokens can be supplied in the Authorization + header (default), the URL query component (``query``) or the request + body (``body``). + + :param token_type: OAuth 2 token type. Defaults to Bearer. Change this + if you specify the ``access_token`` parameter and know it is of a + different token type, such as a MAC, JWT or SAML token. Can + also be supplied as ``token_type`` inside the ``token`` dict parameter. + + :param access_token: An access token (string) used to authenticate + requests to protected resources. Can also be supplied inside the + ``token`` dict parameter. + + :param refresh_token: A refresh token (string) used to refresh expired + tokens. Can also be supplied inside the ``token`` dict parameter. + + :param mac_key: Encryption key used with MAC tokens. + + :param mac_algorithm: Hashing algorithm for MAC tokens. + + :param token: A dict of token attributes such as ``access_token``, + ``token_type`` and ``expires_at``. + + :param scope: A list of default scopes to request authorization for. + + :param state: A CSRF protection string used during authorization. + + :param redirect_url: The redirection endpoint on the client side to which + the user returns after authorization. + + :param state_generator: A no argument state generation callable. Defaults + to :py:meth:`oauthlib.common.generate_token`. + """ + + self.client_id = client_id + self.default_token_placement = default_token_placement + self.token_type = token_type + self.access_token = access_token + self.refresh_token = refresh_token + self.mac_key = mac_key + self.mac_algorithm = mac_algorithm + self.token = token or {} + self.scope = scope + self.state_generator = state_generator + self.state = state + self.redirect_url = redirect_url + self._expires_at = None + self._populate_attributes(self.token) + + @property + def token_types(self): + """Supported token types and their respective methods + + Additional tokens can be supported by extending this dictionary. + + The Bearer token spec is stable and safe to use. + + The MAC token spec is not yet stable and support for MAC tokens + is experimental and currently matching version 00 of the spec. + """ + return { + 'Bearer': self._add_bearer_token, + 'MAC': self._add_mac_token + } + + def prepare_request_uri(self, *args, **kwargs): + """Abstract method used to create request URIs.""" + raise NotImplementedError("Must be implemented by inheriting classes.") + + def prepare_request_body(self, *args, **kwargs): + """Abstract method used to create request bodies.""" + raise NotImplementedError("Must be implemented by inheriting classes.") + + def parse_request_uri_response(self, *args, **kwargs): + """Abstract method used to parse redirection responses.""" + + def add_token(self, uri, http_method='GET', body=None, headers=None, + token_placement=None, **kwargs): + """Add token to the request uri, body or authorization header. + + The access token type provides the client with the information + required to successfully utilize the access token to make a protected + resource request (along with type-specific attributes). The client + MUST NOT use an access token if it does not understand the token + type. + + For example, the "bearer" token type defined in + [`I-D.ietf-oauth-v2-bearer`_] is utilized by simply including the access + token string in the request: + + .. code-block:: http + + GET /resource/1 HTTP/1.1 + Host: example.com + Authorization: Bearer mF_9.B5f-4.1JqM + + while the "mac" token type defined in [`I-D.ietf-oauth-v2-http-mac`_] is + utilized by issuing a MAC key together with the access token which is + used to sign certain components of the HTTP requests: + + .. code-block:: http + + GET /resource/1 HTTP/1.1 + Host: example.com + Authorization: MAC id="h480djs93hd8", + nonce="274312:dj83hs9s", + mac="kDZvddkndxvhGRXZhvuDjEWhGeE=" + + .. _`I-D.ietf-oauth-v2-bearer`: http://tools.ietf.org/html/rfc6749#section-12.2 + .. _`I-D.ietf-oauth-v2-http-mac`: http://tools.ietf.org/html/rfc6749#section-12.2 + """ + if not is_secure_transport(uri): + raise InsecureTransportError() + + token_placement = token_placement or self.default_token_placement + + case_insensitive_token_types = dict( + (k.lower(), v) for k, v in self.token_types.items()) + if not self.token_type.lower() in case_insensitive_token_types: + raise ValueError("Unsupported token type: %s" % self.token_type) + + if not self.access_token: + raise ValueError("Missing access token.") + + if self._expires_at and self._expires_at < time.time(): + raise TokenExpiredError() + + return case_insensitive_token_types[self.token_type.lower()](uri, http_method, body, + headers, token_placement, **kwargs) + + def prepare_authorization_request(self, authorization_url, state=None, + redirect_url=None, scope=None, **kwargs): + """Prepare the authorization request. + + This is the first step in many OAuth flows in which the user is + redirected to a certain authorization URL. This method adds + required parameters to the authorization URL. + + :param authorization_url: Provider authorization endpoint URL. + + :param state: CSRF protection string. Will be automatically created if + not provided. The generated state is available via the ``state`` + attribute. Clients should verify that the state is unchanged and + present in the authorization response. This verification is done + automatically if using the ``authorization_response`` parameter + with ``prepare_token_request``. + + :param redirect_url: Redirect URL to which the user will be returned + after authorization. Must be provided unless previously setup with + the provider. If provided then it must also be provided in the + token request. + + :param kwargs: Additional parameters to included in the request. + + :returns: The prepared request tuple with (url, headers, body). + """ + if not is_secure_transport(authorization_url): + raise InsecureTransportError() + + self.state = state or self.state_generator() + self.redirect_url = redirect_url or self.redirect_url + self.scope = scope or self.scope + auth_url = self.prepare_request_uri( + authorization_url, redirect_uri=self.redirect_url, + scope=self.scope, state=self.state, **kwargs) + return auth_url, FORM_ENC_HEADERS, '' + + def prepare_token_request(self, token_url, authorization_response=None, + redirect_url=None, state=None, body='', **kwargs): + """Prepare a token creation request. + + Note that these requests usually require client authentication, either + by including client_id or a set of provider specific authentication + credentials. + + :param token_url: Provider token creation endpoint URL. + + :param authorization_response: The full redirection URL string, i.e. + the location to which the user was redirected after successfull + authorization. Used to mine credentials needed to obtain a token + in this step, such as authorization code. + + :param redirect_url: The redirect_url supplied with the authorization + request (if there was one). + + :param body: Request body (URL encoded string). + + :param kwargs: Additional parameters to included in the request. + + :returns: The prepared request tuple with (url, headers, body). + """ + if not is_secure_transport(token_url): + raise InsecureTransportError() + + state = state or self.state + if authorization_response: + self.parse_request_uri_response( + authorization_response, state=state) + self.redirect_url = redirect_url or self.redirect_url + body = self.prepare_request_body(body=body, + redirect_uri=self.redirect_url, **kwargs) + + return token_url, FORM_ENC_HEADERS, body + + def prepare_refresh_token_request(self, token_url, refresh_token=None, + body='', scope=None, **kwargs): + """Prepare an access token refresh request. + + Expired access tokens can be replaced by new access tokens without + going through the OAuth dance if the client obtained a refresh token. + This refresh token and authentication credentials can be used to + obtain a new access token, and possibly a new refresh token. + + :param token_url: Provider token refresh endpoint URL. + + :param refresh_token: Refresh token string. + + :param body: Request body (URL encoded string). + + :param scope: List of scopes to request. Must be equal to + or a subset of the scopes granted when obtaining the refresh + token. + + :param kwargs: Additional parameters to included in the request. + + :returns: The prepared request tuple with (url, headers, body). + """ + if not is_secure_transport(token_url): + raise InsecureTransportError() + + self.scope = scope or self.scope + body = self.prepare_refresh_body(body=body, + refresh_token=refresh_token, scope=self.scope, **kwargs) + return token_url, FORM_ENC_HEADERS, body + + def prepare_token_revocation_request(self, revocation_url, token, + token_type_hint="access_token", body='', callback=None, **kwargs): + """Prepare a token revocation request. + + :param revocation_url: Provider token revocation endpoint URL. + + :param token: The access or refresh token to be revoked (string). + + :param token_type_hint: ``"access_token"`` (default) or + ``"refresh_token"``. This is optional and if you wish to not pass it you + must provide ``token_type_hint=None``. + + :param callback: A jsonp callback such as ``package.callback`` to be invoked + upon receiving the response. Not that it should not include a () suffix. + + :param kwargs: Additional parameters to included in the request. + + :returns: The prepared request tuple with (url, headers, body). + + Note that JSONP request may use GET requests as the parameters will + be added to the request URL query as opposed to the request body. + + An example of a revocation request + + .. code-block: http + + POST /revoke HTTP/1.1 + Host: server.example.com + Content-Type: application/x-www-form-urlencoded + Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW + + token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token + + An example of a jsonp revocation request + + .. code-block: http + + GET /revoke?token=agabcdefddddafdd&callback=package.myCallback HTTP/1.1 + Host: server.example.com + Content-Type: application/x-www-form-urlencoded + Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW + + and an error response + + .. code-block: http + + package.myCallback({"error":"unsupported_token_type"}); + + Note that these requests usually require client credentials, client_id in + the case for public clients and provider specific authentication + credentials for confidential clients. + """ + if not is_secure_transport(revocation_url): + raise InsecureTransportError() + + return prepare_token_revocation_request(revocation_url, token, + token_type_hint=token_type_hint, body=body, callback=callback, + **kwargs) + + def parse_request_body_response(self, body, scope=None, **kwargs): + """Parse the JSON response body. + + If the access token request is valid and authorized, the + authorization server issues an access token as described in + `Section 5.1`_. A refresh token SHOULD NOT be included. If the request + failed client authentication or is invalid, the authorization server + returns an error response as described in `Section 5.2`_. + + :param body: The response body from the token request. + :param scope: Scopes originally requested. + :return: Dictionary of token parameters. + :raises: Warning if scope has changed. OAuth2Error if response is invalid. + + These response are json encoded and could easily be parsed without + the assistance of OAuthLib. However, there are a few subtle issues + to be aware of regarding the response which are helpfully addressed + through the raising of various errors. + + A successful response should always contain + + **access_token** + The access token issued by the authorization server. Often + a random string. + + **token_type** + The type of the token issued as described in `Section 7.1`_. + Commonly ``Bearer``. + + While it is not mandated it is recommended that the provider include + + **expires_in** + The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + + **scope** + Providers may supply this in all responses but are required to only + if it has changed since the authorization request. + + .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1 + .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2 + .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 + """ + self.token = parse_token_response(body, scope=scope) + self._populate_attributes(self.token) + return self.token + + def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs): + """Prepare an access token request, using a refresh token. + + If the authorization server issued a refresh token to the client, the + client makes a refresh request to the token endpoint by adding the + following parameters using the "application/x-www-form-urlencoded" + format in the HTTP request entity-body: + + grant_type + REQUIRED. Value MUST be set to "refresh_token". + refresh_token + REQUIRED. The refresh token issued to the client. + scope + OPTIONAL. The scope of the access request as described by + Section 3.3. The requested scope MUST NOT include any scope + not originally granted by the resource owner, and if omitted is + treated as equal to the scope originally granted by the + resource owner. + """ + refresh_token = refresh_token or self.refresh_token + return prepare_token_request('refresh_token', body=body, scope=scope, + refresh_token=refresh_token, **kwargs) + + def _add_bearer_token(self, uri, http_method='GET', body=None, + headers=None, token_placement=None): + """Add a bearer token to the request uri, body or authorization header.""" + if token_placement == AUTH_HEADER: + headers = tokens.prepare_bearer_headers(self.access_token, headers) + + elif token_placement == URI_QUERY: + uri = tokens.prepare_bearer_uri(self.access_token, uri) + + elif token_placement == BODY: + body = tokens.prepare_bearer_body(self.access_token, body) + + else: + raise ValueError("Invalid token placement.") + return uri, headers, body + + def _add_mac_token(self, uri, http_method='GET', body=None, + headers=None, token_placement=AUTH_HEADER, ext=None, **kwargs): + """Add a MAC token to the request authorization header. + + Warning: MAC token support is experimental as the spec is not yet stable. + """ + headers = tokens.prepare_mac_header(self.access_token, uri, + self.mac_key, http_method, headers=headers, body=body, ext=ext, + hash_algorithm=self.mac_algorithm, **kwargs) + return uri, headers, body + + def _populate_attributes(self, response): + """Add commonly used values such as access_token to self.""" + + if 'access_token' in response: + self.access_token = response.get('access_token') + + if 'refresh_token' in response: + self.refresh_token = response.get('refresh_token') + + if 'token_type' in response: + self.token_type = response.get('token_type') + + if 'expires_in' in response: + self.expires_in = response.get('expires_in') + self._expires_at = time.time() + int(self.expires_in) + + if 'expires_at' in response: + self._expires_at = int(response.get('expires_at')) + + if 'code' in response: + self.code = response.get('code') + + if 'mac_key' in response: + self.mac_key = response.get('mac_key') + + if 'mac_algorithm' in response: + self.mac_algorithm = response.get('mac_algorithm') diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py new file mode 100644 index 0000000..57fe99e --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/legacy_application.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from ..parameters import parse_token_response, prepare_token_request +from .base import Client + + +class LegacyApplicationClient(Client): + + """A public client using the resource owner password and username directly. + + The resource owner password credentials grant type is suitable in + cases where the resource owner has a trust relationship with the + client, such as the device operating system or a highly privileged + application. The authorization server should take special care when + enabling this grant type, and only allow it when other flows are not + viable. + + The grant type is suitable for clients capable of obtaining the + resource owner's credentials (username and password, typically using + an interactive form). It is also used to migrate existing clients + using direct authentication schemes such as HTTP Basic or Digest + authentication to OAuth by converting the stored credentials to an + access token. + + The method through which the client obtains the resource owner + credentials is beyond the scope of this specification. The client + MUST discard the credentials once an access token has been obtained. + """ + + def __init__(self, client_id, **kwargs): + super(LegacyApplicationClient, self).__init__(client_id, **kwargs) + + def prepare_request_body(self, username, password, body='', scope=None, **kwargs): + """Add the resource owner password and username to the request body. + + The client makes a request to the token endpoint by adding the + following parameters using the "application/x-www-form-urlencoded" + format per `Appendix B`_ in the HTTP request entity-body: + + :param username: The resource owner username. + :param password: The resource owner password. + :param scope: The scope of the access request as described by + `Section 3.3`_. + :param kwargs: Extra credentials to include in the token request. + + If the client type is confidential or the client was issued client + credentials (or assigned other authentication requirements), the + client MUST authenticate with the authorization server as described + in `Section 3.2.1`_. + + The prepared body will include all provided credentials as well as + the ``grant_type`` parameter set to ``password``:: + + >>> from oauthlib.oauth2 import LegacyApplicationClient + >>> client = LegacyApplicationClient('your_id') + >>> client.prepare_request_body(username='foo', password='bar', scope=['hello', 'world']) + 'grant_type=password&username=foo&scope=hello+world&password=bar' + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 + """ + return prepare_token_request('password', body=body, username=username, + password=password, scope=scope, **kwargs) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py new file mode 100644 index 0000000..490efcd --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/mobile_application.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from ..parameters import parse_implicit_response, prepare_grant_uri +from .base import Client + + +class MobileApplicationClient(Client): + + """A public client utilizing the implicit code grant workflow. + + A user-agent-based application is a public client in which the + client code is downloaded from a web server and executes within a + user-agent (e.g. web browser) on the device used by the resource + owner. Protocol data and credentials are easily accessible (and + often visible) to the resource owner. Since such applications + reside within the user-agent, they can make seamless use of the + user-agent capabilities when requesting authorization. + + The implicit grant type is used to obtain access tokens (it does not + support the issuance of refresh tokens) and is optimized for public + clients known to operate a particular redirection URI. These clients + are typically implemented in a browser using a scripting language + such as JavaScript. + + As a redirection-based flow, the client must be capable of + interacting with the resource owner's user-agent (typically a web + browser) and capable of receiving incoming requests (via redirection) + from the authorization server. + + Unlike the authorization code grant type in which the client makes + separate requests for authorization and access token, the client + receives the access token as the result of the authorization request. + + The implicit grant type does not include client authentication, and + relies on the presence of the resource owner and the registration of + the redirection URI. Because the access token is encoded into the + redirection URI, it may be exposed to the resource owner and other + applications residing on the same device. + """ + + def prepare_request_uri(self, uri, redirect_uri=None, scope=None, + state=None, **kwargs): + """Prepare the implicit grant request URI. + + The client constructs the request URI by adding the following + parameters to the query component of the authorization endpoint URI + using the "application/x-www-form-urlencoded" format, per `Appendix B`_: + + :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI + and it should have been registerd with the OAuth + provider prior to use. As described in `Section 3.1.2`_. + + :param scope: OPTIONAL. The scope of the access request as described by + Section 3.3`_. These may be any string but are commonly + URIs or various categories such as ``videos`` or ``documents``. + + :param state: RECOMMENDED. An opaque value used by the client to maintain + state between the request and callback. The authorization + server includes this value when redirecting the user-agent back + to the client. The parameter SHOULD be used for preventing + cross-site request forgery as described in `Section 10.12`_. + + :param kwargs: Extra arguments to include in the request URI. + + In addition to supplied parameters, OAuthLib will append the ``client_id`` + that was provided in the constructor as well as the mandatory ``response_type`` + argument, set to ``token``:: + + >>> from oauthlib.oauth2 import MobileApplicationClient + >>> client = MobileApplicationClient('your_id') + >>> client.prepare_request_uri('https://example.com') + 'https://example.com?client_id=your_id&response_type=token' + >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback') + 'https://example.com?client_id=your_id&response_type=token&redirect_uri=https%3A%2F%2Fa.b%2Fcallback' + >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures']) + 'https://example.com?client_id=your_id&response_type=token&scope=profile+pictures' + >>> client.prepare_request_uri('https://example.com', foo='bar') + 'https://example.com?client_id=your_id&response_type=token&foo=bar' + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 + .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 + """ + return prepare_grant_uri(uri, self.client_id, 'token', + redirect_uri=redirect_uri, state=state, scope=scope, **kwargs) + + def parse_request_uri_response(self, uri, state=None, scope=None): + """Parse the response URI fragment. + + If the resource owner grants the access request, the authorization + server issues an access token and delivers it to the client by adding + the following parameters to the fragment component of the redirection + URI using the "application/x-www-form-urlencoded" format: + + :param uri: The callback URI that resulted from the user being redirected + back from the provider to you, the client. + :param state: The state provided in the authorization request. + :param scope: The scopes provided in the authorization request. + :return: Dictionary of token parameters. + :raises: OAuth2Error if response is invalid. + + A successful response should always contain + + **access_token** + The access token issued by the authorization server. Often + a random string. + + **token_type** + The type of the token issued as described in `Section 7.1`_. + Commonly ``Bearer``. + + **state** + If you provided the state parameter in the authorization phase, then + the provider is required to include that exact state value in the + response. + + While it is not mandated it is recommended that the provider include + + **expires_in** + The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + + **scope** + Providers may supply this in all responses but are required to only + if it has changed since the authorization request. + + A few example responses can be seen below:: + + >>> response_uri = 'https://example.com/callback#access_token=sdlfkj452&state=ss345asyht&token_type=Bearer&scope=hello+world' + >>> from oauthlib.oauth2 import MobileApplicationClient + >>> client = MobileApplicationClient('your_id') + >>> client.parse_request_uri_response(response_uri) + { + 'access_token': 'sdlfkj452', + 'token_type': 'Bearer', + 'state': 'ss345asyht', + 'scope': [u'hello', u'world'] + } + >>> client.parse_request_uri_response(response_uri, state='other') + Traceback (most recent call last): + File "", line 1, in + File "oauthlib/oauth2/rfc6749/__init__.py", line 598, in parse_request_uri_response + **scope** + File "oauthlib/oauth2/rfc6749/parameters.py", line 197, in parse_implicit_response + raise ValueError("Mismatching or missing state in params.") + ValueError: Mismatching or missing state in params. + >>> def alert_scope_changed(message, old, new): + ... print(message, old, new) + ... + >>> oauthlib.signals.scope_changed.connect(alert_scope_changed) + >>> client.parse_request_body_response(response_body, scope=['other']) + ('Scope has changed from "other" to "hello world".', ['other'], ['hello', 'world']) + + .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + """ + self.token = parse_implicit_response(uri, state=state, scope=scope) + self._populate_attributes(self.token) + return self.token diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py new file mode 100644 index 0000000..e6c3270 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/service_application.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import time + +from oauthlib.common import to_unicode + +from ..parameters import parse_token_response, prepare_token_request +from .base import Client + + +class ServiceApplicationClient(Client): + """A public client utilizing the JWT bearer grant. + + JWT bearer tokes can be used to request an access token when a client + wishes to utilize an existing trust relationship, expressed through the + semantics of (and digital signature or keyed message digest calculated + over) the JWT, without a direct user approval step at the authorization + server. + + This grant type does not involve an authorization step. It may be + used by both public and confidential clients. + """ + + grant_type = 'urn:ietf:params:oauth:grant-type:jwt-bearer' + + def __init__(self, client_id, private_key=None, subject=None, issuer=None, + audience=None, **kwargs): + """Initalize a JWT client with defaults for implicit use later. + + :param client_id: Client identifier given by the OAuth provider upon + registration. + + :param private_key: Private key used for signing and encrypting. + Must be given as a string. + + :param subject: The principal that is the subject of the JWT, i.e. + which user is the token requested on behalf of. + For example, ``foo@example.com. + + :param issuer: The JWT MUST contain an "iss" (issuer) claim that + contains a unique identifier for the entity that issued + the JWT. For example, ``your-client@provider.com``. + + :param audience: A value identifying the authorization server as an + intended audience, e.g. + ``https://provider.com/oauth2/token``. + + :param kwargs: Additional arguments to pass to base client, such as + state and token. See Client.__init__.__doc__ for + details. + """ + super(ServiceApplicationClient, self).__init__(client_id, **kwargs) + self.private_key = private_key + self.subject = subject + self.issuer = issuer + self.audience = audience + + def prepare_request_body(self, + private_key=None, + subject=None, + issuer=None, + audience=None, + expires_at=None, + issued_at=None, + extra_claims=None, + body='', + scope=None, + **kwargs): + """Create and add a JWT assertion to the request body. + + :param private_key: Private key used for signing and encrypting. + Must be given as a string. + + :param subject: (sub) The principal that is the subject of the JWT, + i.e. which user is the token requested on behalf of. + For example, ``foo@example.com. + + :param issuer: (iss) The JWT MUST contain an "iss" (issuer) claim that + contains a unique identifier for the entity that issued + the JWT. For example, ``your-client@provider.com``. + + :param audience: (aud) A value identifying the authorization server as an + intended audience, e.g. + ``https://provider.com/oauth2/token``. + + :param expires_at: A unix expiration timestamp for the JWT. Defaults + to an hour from now, i.e. ``time.time() + 3600``. + + :param issued_at: A unix timestamp of when the JWT was created. + Defaults to now, i.e. ``time.time()``. + + :param not_before: A unix timestamp after which the JWT may be used. + Not included unless provided. + + :param jwt_id: A unique JWT token identifier. Not included unless + provided. + + :param extra_claims: A dict of additional claims to include in the JWT. + + :param scope: The scope of the access request. + + :param body: Request body (string) with extra parameters. + + :param kwargs: Extra credentials to include in the token request. + + The "scope" parameter may be used, as defined in the Assertion + Framework for OAuth 2.0 Client Authentication and Authorization Grants + [I-D.ietf-oauth-assertions] specification, to indicate the requested + scope. + + Authentication of the client is optional, as described in + `Section 3.2.1`_ of OAuth 2.0 [RFC6749] and consequently, the + "client_id" is only needed when a form of client authentication that + relies on the parameter is used. + + The following non-normative example demonstrates an Access Token + Request with a JWT as an authorization grant (with extra line breaks + for display purposes only): + + .. code-block: http + + POST /token.oauth2 HTTP/1.1 + Host: as.example.com + Content-Type: application/x-www-form-urlencoded + + grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer + &assertion=eyJhbGciOiJFUzI1NiJ9. + eyJpc3Mi[...omitted for brevity...]. + J9l-ZhwP[...omitted for brevity...] + + .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 + """ + import jwt + + key = private_key or self.private_key + if not key: + raise ValueError('An encryption key must be supplied to make JWT' + ' token requests.') + claim = { + 'iss': issuer or self.issuer, + 'aud': audience or self.issuer, + 'sub': subject or self.issuer, + 'exp': int(expires_at or time.time() + 3600), + 'iat': int(issued_at or time.time()), + } + + for attr in ('iss', 'aud', 'sub'): + if claim[attr] is None: + raise ValueError( + 'Claim must include %s but none was given.' % attr) + + if 'not_before' in kwargs: + claim['nbf'] = kwargs.pop('not_before') + + if 'jwt_id' in kwargs: + claim['jti'] = kwargs.pop('jwt_id') + + claim.update(extra_claims or {}) + + assertion = jwt.encode(claim, key, 'RS256') + assertion = to_unicode(assertion) + + return prepare_token_request(self.grant_type, + body=body, + assertion=assertion, + scope=scope, + **kwargs) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py new file mode 100644 index 0000000..c099d99 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/web_application.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from ..parameters import (parse_authorization_code_response, + parse_token_response, prepare_grant_uri, + prepare_token_request) +from .base import Client + + +class WebApplicationClient(Client): + + """A client utilizing the authorization code grant workflow. + + A web application is a confidential client running on a web + server. Resource owners access the client via an HTML user + interface rendered in a user-agent on the device used by the + resource owner. The client credentials as well as any access + token issued to the client are stored on the web server and are + not exposed to or accessible by the resource owner. + + The authorization code grant type is used to obtain both access + tokens and refresh tokens and is optimized for confidential clients. + As a redirection-based flow, the client must be capable of + interacting with the resource owner's user-agent (typically a web + browser) and capable of receiving incoming requests (via redirection) + from the authorization server. + """ + + def __init__(self, client_id, code=None, **kwargs): + super(WebApplicationClient, self).__init__(client_id, **kwargs) + self.code = code + + def prepare_request_uri(self, uri, redirect_uri=None, scope=None, + state=None, **kwargs): + """Prepare the authorization code request URI + + The client constructs the request URI by adding the following + parameters to the query component of the authorization endpoint URI + using the "application/x-www-form-urlencoded" format, per `Appendix B`_: + + :param redirect_uri: OPTIONAL. The redirect URI must be an absolute URI + and it should have been registerd with the OAuth + provider prior to use. As described in `Section 3.1.2`_. + + :param scope: OPTIONAL. The scope of the access request as described by + Section 3.3`_. These may be any string but are commonly + URIs or various categories such as ``videos`` or ``documents``. + + :param state: RECOMMENDED. An opaque value used by the client to maintain + state between the request and callback. The authorization + server includes this value when redirecting the user-agent back + to the client. The parameter SHOULD be used for preventing + cross-site request forgery as described in `Section 10.12`_. + + :param kwargs: Extra arguments to include in the request URI. + + In addition to supplied parameters, OAuthLib will append the ``client_id`` + that was provided in the constructor as well as the mandatory ``response_type`` + argument, set to ``code``:: + + >>> from oauthlib.oauth2 import WebApplicationClient + >>> client = WebApplicationClient('your_id') + >>> client.prepare_request_uri('https://example.com') + 'https://example.com?client_id=your_id&response_type=code' + >>> client.prepare_request_uri('https://example.com', redirect_uri='https://a.b/callback') + 'https://example.com?client_id=your_id&response_type=code&redirect_uri=https%3A%2F%2Fa.b%2Fcallback' + >>> client.prepare_request_uri('https://example.com', scope=['profile', 'pictures']) + 'https://example.com?client_id=your_id&response_type=code&scope=profile+pictures' + >>> client.prepare_request_uri('https://example.com', foo='bar') + 'https://example.com?client_id=your_id&response_type=code&foo=bar' + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 + .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 + """ + return prepare_grant_uri(uri, self.client_id, 'code', + redirect_uri=redirect_uri, scope=scope, state=state, **kwargs) + + def prepare_request_body(self, client_id=None, code=None, body='', + redirect_uri=None, **kwargs): + """Prepare the access token request body. + + The client makes a request to the token endpoint by adding the + following parameters using the "application/x-www-form-urlencoded" + format in the HTTP request entity-body: + + :param client_id: REQUIRED, if the client is not authenticating with the + authorization server as described in `Section 3.2.1`_. + + :param code: REQUIRED. The authorization code received from the + authorization server. + + :param redirect_uri: REQUIRED, if the "redirect_uri" parameter was included in the + authorization request as described in `Section 4.1.1`_, and their + values MUST be identical. + + :param kwargs: Extra parameters to include in the token request. + + In addition OAuthLib will add the ``grant_type`` parameter set to + ``authorization_code``. + + If the client type is confidential or the client was issued client + credentials (or assigned other authentication requirements), the + client MUST authenticate with the authorization server as described + in `Section 3.2.1`_:: + + >>> from oauthlib.oauth2 import WebApplicationClient + >>> client = WebApplicationClient('your_id') + >>> client.prepare_request_body(code='sh35ksdf09sf') + 'grant_type=authorization_code&code=sh35ksdf09sf' + >>> client.prepare_request_body(code='sh35ksdf09sf', foo='bar') + 'grant_type=authorization_code&code=sh35ksdf09sf&foo=bar' + + .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1 + .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 + """ + code = code or self.code + return prepare_token_request('authorization_code', code=code, body=body, + client_id=self.client_id, redirect_uri=redirect_uri, **kwargs) + + def parse_request_uri_response(self, uri, state=None): + """Parse the URI query for code and state. + + If the resource owner grants the access request, the authorization + server issues an authorization code and delivers it to the client by + adding the following parameters to the query component of the + redirection URI using the "application/x-www-form-urlencoded" format: + + :param uri: The callback URI that resulted from the user being redirected + back from the provider to you, the client. + :param state: The state provided in the authorization request. + + **code** + The authorization code generated by the authorization server. + The authorization code MUST expire shortly after it is issued + to mitigate the risk of leaks. A maximum authorization code + lifetime of 10 minutes is RECOMMENDED. The client MUST NOT + use the authorization code more than once. If an authorization + code is used more than once, the authorization server MUST deny + the request and SHOULD revoke (when possible) all tokens + previously issued based on that authorization code. + The authorization code is bound to the client identifier and + redirection URI. + + **state** + If the "state" parameter was present in the authorization request. + + This method is mainly intended to enforce strict state checking with + the added benefit of easily extracting parameters from the URI:: + + >>> from oauthlib.oauth2 import WebApplicationClient + >>> client = WebApplicationClient('your_id') + >>> uri = 'https://example.com/callback?code=sdfkjh345&state=sfetw45' + >>> client.parse_request_uri_response(uri, state='sfetw45') + {'state': 'sfetw45', 'code': 'sdfkjh345'} + >>> client.parse_request_uri_response(uri, state='other') + Traceback (most recent call last): + File "", line 1, in + File "oauthlib/oauth2/rfc6749/__init__.py", line 357, in parse_request_uri_response + back from the provider to you, the client. + File "oauthlib/oauth2/rfc6749/parameters.py", line 153, in parse_authorization_code_response + raise MismatchingStateError() + oauthlib.oauth2.rfc6749.errors.MismatchingStateError + """ + response = parse_authorization_code_response(uri, state=state) + self._populate_attributes(response) + return response diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py new file mode 100644 index 0000000..848bec6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__init__.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from .authorization import AuthorizationEndpoint +from .token import TokenEndpoint +from .resource import ResourceEndpoint +from .revocation import RevocationEndpoint +from .pre_configured import Server +from .pre_configured import WebApplicationServer +from .pre_configured import MobileApplicationServer +from .pre_configured import LegacyApplicationServer +from .pre_configured import BackendApplicationServer diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..baabb759cf125c370d8acdbaaab49016021cab32 GIT binary patch literal 897 zcmZva&2G~`5XbF&$BC1)6v5Hg92$o-ltw~`g5bo5lvYC8%gXVt(^b~H?%0mh95^5j zJOgjQtMCZEa_TE^V%9BDDOt;Z{dQ(|c4udfT-O=?WaL}LvVK{22F|UQ_@`e{m_;mR zvx>Ci3aP|ZQnf8sWi?rg>!eGtQhygBNO*=CM#q+{e5G^ja`8`*@1C z@Ft_NH-G!uoIi4|@s+G$rRWEXRLeZ$6wlF+6@y%fP%}=Y$T$Qty|&FtyLf1VXb_w7 z9&Z#&v``s3+;hF+>W*PB=c&#ik=*n3y0Du&vM|Df@vS9q7<$v*)~t88(Tdy zDMG_=3vRQ6CQgY1+r*${47~&cVkIK6#Lx`dXavVd$4J+Ri}J&Q2ZoLO{^BPf)>rzv;UdCXMs}rDhK*UHjkf54%JUEdT%j literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/authorization.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/authorization.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7163b3eadade430bc1b969896fae366673b12016 GIT binary patch literal 4741 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/pre_configured.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/pre_configured.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9781cc85adbe19a817a066f6334e8a920132a5c GIT binary patch literal 8581 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/revocation.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/revocation.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bc5ecea05733d622abce5e8b310c4f5e08af6e2 GIT binary patch literal 5350 zcmbtY&2JmW72jQcilRPjH;I!J$t0~4v$aS|k<(ZyiYl(%8i-|AR@#(7uv+d6#if?J z^vqI<2s$WYUjyXOKcSagik|xy6#WzS+LN#Ct*8Fp%yLP|iqW7;aCSH|@6DU{@q2Ih z-qKR7@ekhq^UId?PiyWeqW?>LRfL9HZHwF73GIQ?b_RuZ!KUAZu-Go*T?|Wua=VPN z5-*39LA6~SEVLH}wRUZ=*j~hVg;&G+V5z+{SZ*)dmMHdD#Of2fec{&@U*NT;7O#n= zCx!M!UU+CV7RUdt#oj3Cg+ZsOI~z^e_22*5=FO&v_%IHlq$$Nw?0ZQNN3}2i_or6d zj#w}lhGHP1M3b|)%aWd8_qVYl+hF^9@u7&=Zq9$-WojsVc9l!7o3QouMzdCHHkteU z$}QGQlHpdXmBewVnt@2V%~&3^ddVPcY4SB1ThoVHn;l_oeZBS!inba~y5eFOv7{FnY&b-PFY@|Rt6k+q zIKparab||85aYiX1I@I@a;K=(7@p}$Hd2DK&Iu!5f>r`Zj{#q+NU^KNR))8#z$zSzh`y1-CZrHn; zWxAB@V1Dl0@kvee(3^~T3t#mPn#Ahc6YGg>TNAupXW|SmPpp0c{o;h$61Sivd#6#B zi&&plT{nn=#C6jP(s+lfeZouN3>S3Q?CK^kvr1peZGURGE+VfJ3b(J~Xb9ITDiL<& z1?(U%QS$>d-&zugVg2W6Ybb*wsI_(E$yPTGxsa;Wm96&<*CY98^X7Fv=Hr`c^Mk|9 zn}eJ0Z(hf95ViJN!(h1983iFFgXH?7)->kNbT(R9@VBORR;`)wo5K^i3Q^v| zS5?tiPR%Ymb$k5!^S;x}HB@|X|KXlrqV;%)?jpWg_kTktA)PzWGVtx|;^WfEYtXzs zDUmf2r(fU>`i^b=-im%#w36asP5#lg9s>i%TYA1QDNV{xfRmGQzx22Q>ay%lO8tuL z^CG<`m9F!$^|%sPlM2>UyFfl(dR+dp{HYZ=pIV?;=-04v5#Ks?Bg`#loc^jg>6TYs z$+&RMqp{%q3vNVbhi8+8;qH9u;;}sK;XpF&;)oiy2N0x4nK`l&@<AZO-#5z+M! z{~@W&@6D)#heo*s*ugWb9rnMGVzk)9i*LIgFLa`DF=Z=A&Ss!TQ=%V&Ft{ zU}%9Nfjb#X0feY&)M>9aq zNSq_dd0v(aL@3N-p|mX_6Ie{`c1ok zBniRO*E1U7&ZI?G+dnOY@j+VRqBA;>ggI%EKvpAaTJ>R;j^`hyizd2UQnbe1cHKmf zf$6klGRca5X)Je1-HobziFg(uyL=Tr`5HB^(^zFHfi2p|Kt^{iX-MO^XZ@K->X$i<1D2Bt))&9Fzp|ru z^qMO8EYRF{t;2jxenTzRFCLcje*Lf_ze$#+{CEi|x9r&!%esx)Ng6=Ov`en8%|90& zI#6}RK@p`AIr0flX zso^7oP)(UjJj)Oiv@-_<7ZbdS-P`^Hl!jVPmuk8CNCB}#G(pImT~CBVeTwtoh)xp^ zlVA{xjfu_4!#F-14YA?f(+Mxjl(0VvV#*UB59xj~Pdqc-L2!h$NI?s{PuRUr9`5C0 zi7x=^KwW552~Sco@_iXoeTu>piqSLEsvR5AZABM!q|I6CJoi+jACnf&Xy}@*XH6zT zc1CK3T=S_YI~0dE`kA5fovCbyDRU5nQGVQ`LYXd2K{s>I{CHe+g9zxJ9LFZrB4BWl znRC4@@hG(TsOspO~h ztRui^g~bR_X_BzgdG1V5Xn%Z8Sq7R80zlQtyngo|W(wSFt~WRK-SaUH*V8zIGZ>j% zmHaTfe3AN*1UUV~LMlbLP`(U0y3EK$$+y~QzK4zILNeX0d#e$Qdp!0y^!$^xV%D(=^|)ux(n#x zC{$Hx!Oql5@(oPv;;Vj$#=26ri*^+^PxD!&Ldc=g2zSxuIpxp`3Po(X432UIO~cu_ z(1l13=pps1%XtaXr_5jsM zdu4i)Z~FSL6H^(N&EH1a7{;_z0!vadigo@aY_>xA6!2-|(x^JI*?!BiQA8&8FoquufA6G2v z-`2`eNBdKpYJdy3Jd4}hiS5MkoTTQ}Z2Dh|>s}rAdfZ5wUei2l#I2<5wb5?!R=kn8 zo||lXo3G;Vd}Fue20 z?R)NDEkRp%tVoN+z*LM5+TFWhu*s>?Dv;mMa!#qbOvl5L|F~m`N69 zsmhZm9bp`26PcYvoc=v|02J(2_a=Mx`_E|puKPVWusTk$8w^wy=UVufCzHC^%hM># zxbWjh3mL@fuwxgkXW~UJlt#0W>nK*=+vV`Bhk+80Qa;I|R2M%B109ZiA!R0&4-9^u z1}8xj2ZIRarXfu_KU!#fic|dw7h!oeLgH}St8s_dURqvVGtH?eb0Y9MtnYzQcE4+IX|cw-JIX#dbe?F48%|z{RZQ3CLABaU&}5UDste`eVT~ z4OzUPFyu;bHkdMvw_zM180f^ZfexaSVibg-P^ua@m>NqHDHJ-=9FayO5rRxc=VcT} zGJp+)Oo}1;jmx3kmHCa0Ik*Le(+GhSCqhmyH7L$PG0}{20Z@$zN}nicfX!e|(kMsBb{4!T#&mVRNX=>&b^cv_`8fFm5noRB9G zJmguJFG^>{mj$n1Kl>y5>cO8eE{t<-D7h9)h%w_p@9g08^z;CHCI`8UMM{;yuNeUJ zFwh#4UY`ibzI`y6z-)BJ9)9b07|I1P3NsW{iY&NchLogz_4QF7z9aOMJ{W&GM!E&u zlq%hbx*)DhOad7s0;Q{zGK;3s>BPx?zI*iZ%|@_Py3{{1q)DaXfJPt^ThPn-p$ zEnT4OO#(gsodNxu1!(HKIQH3-FU;KZvSQ2}nG}?zu2AWiz^zxw zB1|ZeJk}IlRHFPJ<6kM=fR~y0oq{_;#G#$JzNdzKKn+n(F5;$F4w*oU+kjXHQ}aD}ub zqJ!=Z&d+cvhKsh2?HN{H+nUv8mha3QTx&Bt^XuHg%4#2W8j_aqqV4-p8fo7zwtR!q zr!__uZ~0sd1C*_AQr0|OdARJ_sTXx6;$d-hK4AH7`E13==JLJzrzNif#ee?Vo5<({ zl=ZMX>J77)Los_p+57nUN2z>q``%4{&d=|u+jpPezL(s)bNeQaQQGVGCeh?zkVi2; z_*ELpyZYvf-h9{gO5@g?$KG6+6xhaIp*xvM7bqU!RBc==+qIkcoxj^(-F|fERE4yC zqJ!>q8seL1%&g;zUR!SBf5(hfTE&D{q); z7q7m+IiHHHPxX4t)@EE}6?s9Gmd;&M=I z`1<%;{kyidkDXcjxOQRB+J1f3z_l@J%I>U*R&&;pcV{iMS|GE1yn&ugwA>5lyA8d0 zyv1p3bGC6&Lji9bZ_nCzyK~`y^xF9+kIu9V!fHoo(3MLY1eW${Mk}#EK?aQkRYZ3? zwPKUj3cr-_qCUv@v}lb*K(%&${qsm+7ZHwWMOY>v`%o=}6ShB;-OjEgy^#%Eylu1d zgGAtaLus#J{(4PwQ{EM<%QEm+R%Xv#nl74Q97KsSIV9gEoA2OK)bL?cG`JY#qv9Qm zg%_0u%~x*|zm984tElBNlJC*zAJOG~GO@Wj<=e|CeJoPG0VhA8eywUv@UE`(sR(O;g_%P-I$NZ|DfWp_ zVb}M|??>-ts;4D=RDFg%!;Pdwm4tZ33ldssEW=rbP*^|G(jSvlUolBsTqzAG_Pg{u62)W|z&gGUiqDhvT z?C{=OJYD~EHfe+0!*{1ye+<%M0|N3T?BUb%=QP8Q=|UJTgI~0NT{84r@=}p9)$Ki} N?QGdw&Yt7e{tMn`$oBvM literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py new file mode 100644 index 0000000..b6e0734 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/authorization.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import utils + +from .base import BaseEndpoint, catch_errors_and_unavailability + +log = logging.getLogger(__name__) + + +class AuthorizationEndpoint(BaseEndpoint): + + """Authorization endpoint - used by the client to obtain authorization + from the resource owner via user-agent redirection. + + The authorization endpoint is used to interact with the resource + owner and obtain an authorization grant. The authorization server + MUST first verify the identity of the resource owner. The way in + which the authorization server authenticates the resource owner (e.g. + username and password login, session cookies) is beyond the scope of + this specification. + + The endpoint URI MAY include an "application/x-www-form-urlencoded" + formatted (per `Appendix B`_) query component, + which MUST be retained when adding additional query parameters. The + endpoint URI MUST NOT include a fragment component:: + + https://example.com/path?query=component # OK + https://example.com/path?query=component#fragment # Not OK + + Since requests to the authorization endpoint result in user + authentication and the transmission of clear-text credentials (in the + HTTP response), the authorization server MUST require the use of TLS + as described in Section 1.6 when sending requests to the + authorization endpoint:: + + # We will deny any request which URI schema is not with https + + The authorization server MUST support the use of the HTTP "GET" + method [RFC2616] for the authorization endpoint, and MAY support the + use of the "POST" method as well:: + + # HTTP method is currently not enforced + + Parameters sent without a value MUST be treated as if they were + omitted from the request. The authorization server MUST ignore + unrecognized request parameters. Request and response parameters + MUST NOT be included more than once:: + + # Enforced through the design of oauthlib.common.Request + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + """ + + def __init__(self, default_response_type, default_token_type, + response_types): + BaseEndpoint.__init__(self) + self._response_types = response_types + self._default_response_type = default_response_type + self._default_token_type = default_token_type + + @property + def response_types(self): + return self._response_types + + @property + def default_response_type(self): + return self._default_response_type + + @property + def default_response_type_handler(self): + return self.response_types.get(self.default_response_type) + + @property + def default_token_type(self): + return self._default_token_type + + @catch_errors_and_unavailability + def create_authorization_response(self, uri, http_method='GET', body=None, + headers=None, scopes=None, credentials=None): + """Extract response_type and route to the designated handler.""" + request = Request( + uri, http_method=http_method, body=body, headers=headers) + request.scopes = scopes + # TODO: decide whether this should be a required argument + request.user = None # TODO: explain this in docs + for k, v in (credentials or {}).items(): + setattr(request, k, v) + response_type_handler = self.response_types.get( + request.response_type, self.default_response_type_handler) + log.debug('Dispatching response_type %s request to %r.', + request.response_type, response_type_handler) + return response_type_handler.create_authorization_response( + request, self.default_token_type) + + @catch_errors_and_unavailability + def validate_authorization_request(self, uri, http_method='GET', body=None, + headers=None): + """Extract response_type and route to the designated handler.""" + request = Request( + uri, http_method=http_method, body=body, headers=headers) + + request.scopes = utils.scope_to_list(request.scope) + + response_type_handler = self.response_types.get( + request.response_type, self.default_response_type_handler) + return response_type_handler.validate_authorization_request(request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py new file mode 100644 index 0000000..cdb015f --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/base.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import functools +import logging + +from ..errors import (FatalClientError, OAuth2Error, ServerError, + TemporarilyUnavailableError) + +log = logging.getLogger(__name__) + + +class BaseEndpoint(object): + + def __init__(self): + self._available = True + self._catch_errors = False + + @property + def available(self): + return self._available + + @available.setter + def available(self, available): + self._available = available + + @property + def catch_errors(self): + return self._catch_errors + + @catch_errors.setter + def catch_errors(self, catch_errors): + self._catch_errors = catch_errors + + +def catch_errors_and_unavailability(f): + @functools.wraps(f) + def wrapper(endpoint, uri, *args, **kwargs): + if not endpoint.available: + e = TemporarilyUnavailableError() + log.info('Endpoint unavailable, ignoring request %s.' % uri) + return {}, e.json, 503 + + if endpoint.catch_errors: + try: + return f(endpoint, uri, *args, **kwargs) + except OAuth2Error: + raise + except FatalClientError: + raise + except Exception as e: + error = ServerError() + log.warning( + 'Exception caught while processing request, %s.' % e) + return {}, error.json, 500 + else: + return f(endpoint, uri, *args, **kwargs) + return wrapper diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py new file mode 100644 index 0000000..6428b8d --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/pre_configured.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +from ..grant_types import (AuthCodeGrantDispatcher, AuthorizationCodeGrant, + ClientCredentialsGrant, ImplicitGrant, + OpenIDConnectAuthCode, OpenIDConnectImplicit, + RefreshTokenGrant, + ResourceOwnerPasswordCredentialsGrant) +from ..tokens import BearerToken +from .authorization import AuthorizationEndpoint +from .resource import ResourceEndpoint +from .revocation import RevocationEndpoint +from .token import TokenEndpoint + + +class Server(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint, + RevocationEndpoint): + + """An all-in-one endpoint featuring all four major grant types.""" + + def __init__(self, request_validator, token_expires_in=None, + token_generator=None, refresh_token_generator=None, + *args, **kwargs): + """Construct a new all-grants-in-one server. + + :param request_validator: An implementation of + oauthlib.oauth2.RequestValidator. + :param token_expires_in: An int or a function to generate a token + expiration offset (in seconds) given a + oauthlib.common.Request object. + :param token_generator: A function to generate a token from a request. + :param refresh_token_generator: A function to generate a token from a + request for the refresh token. + :param kwargs: Extra parameters to pass to authorization-, + token-, resource-, and revocation-endpoint constructors. + """ + auth_grant = AuthorizationCodeGrant(request_validator) + implicit_grant = ImplicitGrant(request_validator) + password_grant = ResourceOwnerPasswordCredentialsGrant( + request_validator) + credentials_grant = ClientCredentialsGrant(request_validator) + refresh_grant = RefreshTokenGrant(request_validator) + openid_connect_auth = OpenIDConnectAuthCode(request_validator) + openid_connect_implicit = OpenIDConnectImplicit(request_validator) + + bearer = BearerToken(request_validator, token_generator, + token_expires_in, refresh_token_generator) + + auth_grant_choice = AuthCodeGrantDispatcher( default_auth_grant=auth_grant, oidc_auth_grant=openid_connect_auth) + + # See http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#Combinations for valid combinations + # internally our AuthorizationEndpoint will ensure they can appear in any order for any valid combination + AuthorizationEndpoint.__init__(self, default_response_type='code', + response_types={ + 'code': auth_grant_choice, + 'token': implicit_grant, + 'id_token': openid_connect_implicit, + 'id_token token': openid_connect_implicit, + 'code token': openid_connect_auth, + 'code id_token': openid_connect_auth, + 'code token id_token': openid_connect_auth, + 'none': auth_grant + }, + default_token_type=bearer) + TokenEndpoint.__init__(self, default_grant_type='authorization_code', + grant_types={ + 'authorization_code': auth_grant, + 'password': password_grant, + 'client_credentials': credentials_grant, + 'refresh_token': refresh_grant, + 'openid': openid_connect_auth + }, + default_token_type=bearer) + ResourceEndpoint.__init__(self, default_token='Bearer', + token_types={'Bearer': bearer}) + RevocationEndpoint.__init__(self, request_validator) + + +class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint, + RevocationEndpoint): + + """An all-in-one endpoint featuring Authorization code grant and Bearer tokens.""" + + def __init__(self, request_validator, token_generator=None, + token_expires_in=None, refresh_token_generator=None, **kwargs): + """Construct a new web application server. + + :param request_validator: An implementation of + oauthlib.oauth2.RequestValidator. + :param token_expires_in: An int or a function to generate a token + expiration offset (in seconds) given a + oauthlib.common.Request object. + :param token_generator: A function to generate a token from a request. + :param refresh_token_generator: A function to generate a token from a + request for the refresh token. + :param kwargs: Extra parameters to pass to authorization-, + token-, resource-, and revocation-endpoint constructors. + """ + auth_grant = AuthorizationCodeGrant(request_validator) + refresh_grant = RefreshTokenGrant(request_validator) + bearer = BearerToken(request_validator, token_generator, + token_expires_in, refresh_token_generator) + AuthorizationEndpoint.__init__(self, default_response_type='code', + response_types={'code': auth_grant}, + default_token_type=bearer) + TokenEndpoint.__init__(self, default_grant_type='authorization_code', + grant_types={ + 'authorization_code': auth_grant, + 'refresh_token': refresh_grant, + }, + default_token_type=bearer) + ResourceEndpoint.__init__(self, default_token='Bearer', + token_types={'Bearer': bearer}) + RevocationEndpoint.__init__(self, request_validator) + + +class MobileApplicationServer(AuthorizationEndpoint, ResourceEndpoint, + RevocationEndpoint): + + """An all-in-one endpoint featuring Implicit code grant and Bearer tokens.""" + + def __init__(self, request_validator, token_generator=None, + token_expires_in=None, refresh_token_generator=None, **kwargs): + """Construct a new implicit grant server. + + :param request_validator: An implementation of + oauthlib.oauth2.RequestValidator. + :param token_expires_in: An int or a function to generate a token + expiration offset (in seconds) given a + oauthlib.common.Request object. + :param token_generator: A function to generate a token from a request. + :param refresh_token_generator: A function to generate a token from a + request for the refresh token. + :param kwargs: Extra parameters to pass to authorization-, + token-, resource-, and revocation-endpoint constructors. + """ + implicit_grant = ImplicitGrant(request_validator) + bearer = BearerToken(request_validator, token_generator, + token_expires_in, refresh_token_generator) + AuthorizationEndpoint.__init__(self, default_response_type='token', + response_types={ + 'token': implicit_grant}, + default_token_type=bearer) + ResourceEndpoint.__init__(self, default_token='Bearer', + token_types={'Bearer': bearer}) + RevocationEndpoint.__init__(self, request_validator, + supported_token_types=['access_token']) + + +class LegacyApplicationServer(TokenEndpoint, ResourceEndpoint, + RevocationEndpoint): + + """An all-in-one endpoint featuring Resource Owner Password Credentials grant and Bearer tokens.""" + + def __init__(self, request_validator, token_generator=None, + token_expires_in=None, refresh_token_generator=None, **kwargs): + """Construct a resource owner password credentials grant server. + + :param request_validator: An implementation of + oauthlib.oauth2.RequestValidator. + :param token_expires_in: An int or a function to generate a token + expiration offset (in seconds) given a + oauthlib.common.Request object. + :param token_generator: A function to generate a token from a request. + :param refresh_token_generator: A function to generate a token from a + request for the refresh token. + :param kwargs: Extra parameters to pass to authorization-, + token-, resource-, and revocation-endpoint constructors. + """ + password_grant = ResourceOwnerPasswordCredentialsGrant( + request_validator) + refresh_grant = RefreshTokenGrant(request_validator) + bearer = BearerToken(request_validator, token_generator, + token_expires_in, refresh_token_generator) + TokenEndpoint.__init__(self, default_grant_type='password', + grant_types={ + 'password': password_grant, + 'refresh_token': refresh_grant, + }, + default_token_type=bearer) + ResourceEndpoint.__init__(self, default_token='Bearer', + token_types={'Bearer': bearer}) + RevocationEndpoint.__init__(self, request_validator) + + +class BackendApplicationServer(TokenEndpoint, ResourceEndpoint, + RevocationEndpoint): + + """An all-in-one endpoint featuring Client Credentials grant and Bearer tokens.""" + + def __init__(self, request_validator, token_generator=None, + token_expires_in=None, refresh_token_generator=None, **kwargs): + """Construct a client credentials grant server. + + :param request_validator: An implementation of + oauthlib.oauth2.RequestValidator. + :param token_expires_in: An int or a function to generate a token + expiration offset (in seconds) given a + oauthlib.common.Request object. + :param token_generator: A function to generate a token from a request. + :param refresh_token_generator: A function to generate a token from a + request for the refresh token. + :param kwargs: Extra parameters to pass to authorization-, + token-, resource-, and revocation-endpoint constructors. + """ + credentials_grant = ClientCredentialsGrant(request_validator) + bearer = BearerToken(request_validator, token_generator, + token_expires_in, refresh_token_generator) + TokenEndpoint.__init__(self, default_grant_type='client_credentials', + grant_types={ + 'client_credentials': credentials_grant}, + default_token_type=bearer) + ResourceEndpoint.__init__(self, default_token='Bearer', + token_types={'Bearer': bearer}) + RevocationEndpoint.__init__(self, request_validator, + supported_token_types=['access_token']) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py new file mode 100644 index 0000000..d03ed21 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/resource.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib.common import Request + +from .base import BaseEndpoint, catch_errors_and_unavailability + +log = logging.getLogger(__name__) + + +class ResourceEndpoint(BaseEndpoint): + + """Authorizes access to protected resources. + + The client accesses protected resources by presenting the access + token to the resource server. The resource server MUST validate the + access token and ensure that it has not expired and that its scope + covers the requested resource. The methods used by the resource + server to validate the access token (as well as any error responses) + are beyond the scope of this specification but generally involve an + interaction or coordination between the resource server and the + authorization server:: + + # For most cases, returning a 403 should suffice. + + The method in which the client utilizes the access token to + authenticate with the resource server depends on the type of access + token issued by the authorization server. Typically, it involves + using the HTTP "Authorization" request header field [RFC2617] with an + authentication scheme defined by the specification of the access + token type used, such as [RFC6750]:: + + # Access tokens may also be provided in query and body + https://example.com/protected?access_token=kjfch2345sdf # Query + access_token=sdf23409df # Body + """ + + def __init__(self, default_token, token_types): + BaseEndpoint.__init__(self) + self._tokens = token_types + self._default_token = default_token + + @property + def default_token(self): + return self._default_token + + @property + def default_token_type_handler(self): + return self.tokens.get(self.default_token) + + @property + def tokens(self): + return self._tokens + + @catch_errors_and_unavailability + def verify_request(self, uri, http_method='GET', body=None, headers=None, + scopes=None): + """Validate client, code etc, return body + headers""" + request = Request(uri, http_method, body, headers) + request.token_type = self.find_token_type(request) + request.scopes = scopes + token_type_handler = self.tokens.get(request.token_type, + self.default_token_type_handler) + log.debug('Dispatching token_type %s request to %r.', + request.token_type, token_type_handler) + return token_type_handler.validate_request(request), request + + def find_token_type(self, request): + """Token type identification. + + RFC 6749 does not provide a method for easily differentiating between + different token types during protected resource access. We estimate + the most likely token type (if any) by asking each known token type + to give an estimation based on the request. + """ + estimates = sorted(((t.estimate_type(request), n) + for n, t in self.tokens.items())) + return estimates[0][1] if len(estimates) else None diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py new file mode 100644 index 0000000..4364b81 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/revocation.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.endpoint.revocation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An implementation of the OAuth 2 `Token Revocation`_ spec (draft 11). + +.. _`Token Revocation`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11 +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib.common import Request + +from ..errors import (InvalidClientError, InvalidRequestError, OAuth2Error, + UnsupportedTokenTypeError) +from .base import BaseEndpoint, catch_errors_and_unavailability + +log = logging.getLogger(__name__) + + +class RevocationEndpoint(BaseEndpoint): + + """Token revocation endpoint. + + Endpoint used by authenticated clients to revoke access and refresh tokens. + Commonly this will be part of the Authorization Endpoint. + """ + + valid_token_types = ('access_token', 'refresh_token') + + def __init__(self, request_validator, supported_token_types=None, + enable_jsonp=False): + BaseEndpoint.__init__(self) + self.request_validator = request_validator + self.supported_token_types = ( + supported_token_types or self.valid_token_types) + self.enable_jsonp = enable_jsonp + + @catch_errors_and_unavailability + def create_revocation_response(self, uri, http_method='POST', body=None, + headers=None): + """Revoke supplied access or refresh token. + + + The authorization server responds with HTTP status code 200 if the + token has been revoked sucessfully or if the client submitted an + invalid token. + + Note: invalid tokens do not cause an error response since the client + cannot handle such an error in a reasonable way. Moreover, the purpose + of the revocation request, invalidating the particular token, is + already achieved. + + The content of the response body is ignored by the client as all + necessary information is conveyed in the response code. + + An invalid token type hint value is ignored by the authorization server + and does not influence the revocation response. + """ + request = Request( + uri, http_method=http_method, body=body, headers=headers) + try: + self.validate_revocation_request(request) + log.debug('Token revocation valid for %r.', request) + except OAuth2Error as e: + log.debug('Client error during validation of %r. %r.', request, e) + response_body = e.json + if self.enable_jsonp and request.callback: + response_body = '%s(%s);' % (request.callback, response_body) + return {}, response_body, e.status_code + + self.request_validator.revoke_token(request.token, + request.token_type_hint, request) + + response_body = '' + if self.enable_jsonp and request.callback: + response_body = request.callback + '();' + return {}, response_body, 200 + + def validate_revocation_request(self, request): + """Ensure the request is valid. + + The client constructs the request by including the following parameters + using the "application/x-www-form-urlencoded" format in the HTTP + request entity-body: + + token (REQUIRED). The token that the client wants to get revoked. + + token_type_hint (OPTIONAL). A hint about the type of the token + submitted for revocation. Clients MAY pass this parameter in order to + help the authorization server to optimize the token lookup. If the + server is unable to locate the token using the given hint, it MUST + extend its search accross all of its supported token types. An + authorization server MAY ignore this parameter, particularly if it is + able to detect the token type automatically. This specification + defines two such values: + + * access_token: An Access Token as defined in [RFC6749], + `section 1.4`_ + + * refresh_token: A Refresh Token as defined in [RFC6749], + `section 1.5`_ + + Specific implementations, profiles, and extensions of this + specification MAY define other values for this parameter using + the registry defined in `Section 4.1.2`_. + + The client also includes its authentication credentials as described in + `Section 2.3`_. of [`RFC6749`_]. + + .. _`section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4 + .. _`section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 + .. _`section 2.3`: http://tools.ietf.org/html/rfc6749#section-2.3 + .. _`Section 4.1.2`: http://tools.ietf.org/html/draft-ietf-oauth-revocation-11#section-4.1.2 + .. _`RFC6749`: http://tools.ietf.org/html/rfc6749 + """ + if not request.token: + raise InvalidRequestError(request=request, + description='Missing token parameter.') + + if self.request_validator.client_authentication_required(request): + if not self.request_validator.authenticate_client(request): + log.debug('Client authentication failed, %r.', request) + raise InvalidClientError(request=request) + elif not self.request_validator.authenticate_client_id(request.client_id, request): + log.debug('Client authentication failed, %r.', request) + raise InvalidClientError(request=request) + + if (request.token_type_hint and + request.token_type_hint in self.valid_token_types and + request.token_type_hint not in self.supported_token_types): + raise UnsupportedTokenTypeError(request=request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py new file mode 100644 index 0000000..ece6325 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/token.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749 +~~~~~~~~~~~~~~~~~~~~~~~ + +This module is an implementation of various logic needed +for consuming and providing OAuth 2.0 RFC6749. +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib.common import Request +from oauthlib.oauth2.rfc6749 import utils + +from .base import BaseEndpoint, catch_errors_and_unavailability + +log = logging.getLogger(__name__) + + +class TokenEndpoint(BaseEndpoint): + + """Token issuing endpoint. + + The token endpoint is used by the client to obtain an access token by + presenting its authorization grant or refresh token. The token + endpoint is used with every authorization grant except for the + implicit grant type (since an access token is issued directly). + + The means through which the client obtains the location of the token + endpoint are beyond the scope of this specification, but the location + is typically provided in the service documentation. + + The endpoint URI MAY include an "application/x-www-form-urlencoded" + formatted (per `Appendix B`_) query component, + which MUST be retained when adding additional query parameters. The + endpoint URI MUST NOT include a fragment component:: + + https://example.com/path?query=component # OK + https://example.com/path?query=component#fragment # Not OK + + Since requests to the authorization endpoint result in user + Since requests to the token endpoint result in the transmission of + clear-text credentials (in the HTTP request and response), the + authorization server MUST require the use of TLS as described in + Section 1.6 when sending requests to the token endpoint:: + + # We will deny any request which URI schema is not with https + + The client MUST use the HTTP "POST" method when making access token + requests:: + + # HTTP method is currently not enforced + + Parameters sent without a value MUST be treated as if they were + omitted from the request. The authorization server MUST ignore + unrecognized request parameters. Request and response parameters + MUST NOT be included more than once:: + + # Delegated to each grant type. + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + """ + + def __init__(self, default_grant_type, default_token_type, grant_types): + BaseEndpoint.__init__(self) + self._grant_types = grant_types + self._default_token_type = default_token_type + self._default_grant_type = default_grant_type + + @property + def grant_types(self): + return self._grant_types + + @property + def default_grant_type(self): + return self._default_grant_type + + @property + def default_grant_type_handler(self): + return self.grant_types.get(self.default_grant_type) + + @property + def default_token_type(self): + return self._default_token_type + + @catch_errors_and_unavailability + def create_token_response(self, uri, http_method='GET', body=None, + headers=None, credentials=None, grant_type_for_scope=None, + claims=None): + """Extract grant_type and route to the designated handler.""" + request = Request( + uri, http_method=http_method, body=body, headers=headers) + + # 'scope' is an allowed Token Request param in both the "Resource Owner Password Credentials Grant" + # and "Client Credentials Grant" flows + # https://tools.ietf.org/html/rfc6749#section-4.3.2 + # https://tools.ietf.org/html/rfc6749#section-4.4.2 + request.scopes = utils.scope_to_list(request.scope) + + request.extra_credentials = credentials + if grant_type_for_scope: + request.grant_type = grant_type_for_scope + + # OpenID Connect claims, if provided. The server using oauthlib might choose + # to implement the claims parameter of the Authorization Request. In this case + # it should retrieve those claims and pass them via the claims argument here, + # as a dict. + if claims: + request.claims = claims + + grant_type_handler = self.grant_types.get(request.grant_type, + self.default_grant_type_handler) + log.debug('Dispatching grant_type %s request to %r.', + request.grant_type, grant_type_handler) + return grant_type_handler.create_token_response( + request, self.default_token_type) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/errors.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/errors.py new file mode 100644 index 0000000..180f636 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/errors.py @@ -0,0 +1,415 @@ +# coding=utf-8 +""" +oauthlib.oauth2.rfc6749.errors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Error used both by OAuth 2 clients and providers to represent the spec +defined error responses for all four core grant types. +""" +from __future__ import unicode_literals + +import json + +from oauthlib.common import add_params_to_uri, urlencode + + +class OAuth2Error(Exception): + error = None + status_code = 400 + description = '' + + def __init__(self, description=None, uri=None, state=None, + status_code=None, request=None): + """ + description: A human-readable ASCII [USASCII] text providing + additional information, used to assist the client + developer in understanding the error that occurred. + Values for the "error_description" parameter MUST NOT + include characters outside the set + x20-21 / x23-5B / x5D-7E. + + uri: A URI identifying a human-readable web page with information + about the error, used to provide the client developer with + additional information about the error. Values for the + "error_uri" parameter MUST conform to the URI- Reference + syntax, and thus MUST NOT include characters outside the set + x21 / x23-5B / x5D-7E. + + state: A CSRF protection value received from the client. + + request: Oauthlib Request object + """ + if description is not None: + self.description = description + + message = '(%s) %s' % (self.error, self.description) + if request: + message += ' ' + repr(request) + super(OAuth2Error, self).__init__(message) + + self.uri = uri + self.state = state + + if status_code: + self.status_code = status_code + + if request: + self.redirect_uri = request.redirect_uri + self.client_id = request.client_id + self.scopes = request.scopes + self.response_type = request.response_type + self.response_mode = request.response_mode + self.grant_type = request.grant_type + if not state: + self.state = request.state + else: + self.redirect_uri = None + self.client_id = None + self.scopes = None + self.response_type = None + self.response_mode = None + self.grant_type = None + + def in_uri(self, uri): + fragment = self.response_mode == "fragment" + return add_params_to_uri(uri, self.twotuples, fragment) + + @property + def twotuples(self): + error = [('error', self.error)] + if self.description: + error.append(('error_description', self.description)) + if self.uri: + error.append(('error_uri', self.uri)) + if self.state: + error.append(('state', self.state)) + return error + + @property + def urlencoded(self): + return urlencode(self.twotuples) + + @property + def json(self): + return json.dumps(dict(self.twotuples)) + + +class TokenExpiredError(OAuth2Error): + error = 'token_expired' + + +class InsecureTransportError(OAuth2Error): + error = 'insecure_transport' + description = 'OAuth 2 MUST utilize https.' + + +class MismatchingStateError(OAuth2Error): + error = 'mismatching_state' + description = 'CSRF Warning! State not equal in request and response.' + + +class MissingCodeError(OAuth2Error): + error = 'missing_code' + + +class MissingTokenError(OAuth2Error): + error = 'missing_token' + + +class MissingTokenTypeError(OAuth2Error): + error = 'missing_token_type' + + +class FatalClientError(OAuth2Error): + """ + Errors during authorization where user should not be redirected back. + + If the request fails due to a missing, invalid, or mismatching + redirection URI, or if the client identifier is missing or invalid, + the authorization server SHOULD inform the resource owner of the + error and MUST NOT automatically redirect the user-agent to the + invalid redirection URI. + + Instead the user should be informed of the error by the provider itself. + """ + pass + + +class InvalidRequestFatalError(FatalClientError): + """ + For fatal errors, the request is missing a required parameter, includes + an invalid parameter value, includes a parameter more than once, or is + otherwise malformed. + """ + error = 'invalid_request' + + +class InvalidRedirectURIError(InvalidRequestFatalError): + description = 'Invalid redirect URI.' + + +class MissingRedirectURIError(InvalidRequestFatalError): + description = 'Missing redirect URI.' + + +class MismatchingRedirectURIError(InvalidRequestFatalError): + description = 'Mismatching redirect URI.' + + +class InvalidClientIdError(InvalidRequestFatalError): + description = 'Invalid client_id parameter value.' + + +class MissingClientIdError(InvalidRequestFatalError): + description = 'Missing client_id parameter.' + + +class InvalidRequestError(OAuth2Error): + """ + The request is missing a required parameter, includes an invalid + parameter value, includes a parameter more than once, or is + otherwise malformed. + """ + error = 'invalid_request' + + +class MissingResponseTypeError(InvalidRequestError): + description = 'Missing response_type parameter.' + + +class AccessDeniedError(OAuth2Error): + """ + The resource owner or authorization server denied the request. + """ + error = 'access_denied' + status_code = 401 + + +class UnsupportedResponseTypeError(OAuth2Error): + """ + The authorization server does not support obtaining an authorization + code using this method. + """ + error = 'unsupported_response_type' + + +class InvalidScopeError(OAuth2Error): + """ + The requested scope is invalid, unknown, or malformed. + """ + error = 'invalid_scope' + status_code = 401 + + +class ServerError(OAuth2Error): + """ + The authorization server encountered an unexpected condition that + prevented it from fulfilling the request. (This error code is needed + because a 500 Internal Server Error HTTP status code cannot be returned + to the client via a HTTP redirect.) + """ + error = 'server_error' + + +class TemporarilyUnavailableError(OAuth2Error): + """ + The authorization server is currently unable to handle the request + due to a temporary overloading or maintenance of the server. + (This error code is needed because a 503 Service Unavailable HTTP + status code cannot be returned to the client via a HTTP redirect.) + """ + error = 'temporarily_unavailable' + + +class InvalidClientError(OAuth2Error): + """ + Client authentication failed (e.g. unknown client, no client + authentication included, or unsupported authentication method). + The authorization server MAY return an HTTP 401 (Unauthorized) status + code to indicate which HTTP authentication schemes are supported. + If the client attempted to authenticate via the "Authorization" request + header field, the authorization server MUST respond with an + HTTP 401 (Unauthorized) status code, and include the "WWW-Authenticate" + response header field matching the authentication scheme used by the + client. + """ + error = 'invalid_client' + status_code = 401 + + +class InvalidGrantError(OAuth2Error): + """ + The provided authorization grant (e.g. authorization code, resource + owner credentials) or refresh token is invalid, expired, revoked, does + not match the redirection URI used in the authorization request, or was + issued to another client. + """ + error = 'invalid_grant' + status_code = 401 + + +class UnauthorizedClientError(OAuth2Error): + """ + The authenticated client is not authorized to use this authorization + grant type. + """ + error = 'unauthorized_client' + status_code = 401 + + +class UnsupportedGrantTypeError(OAuth2Error): + """ + The authorization grant type is not supported by the authorization + server. + """ + error = 'unsupported_grant_type' + + +class UnsupportedTokenTypeError(OAuth2Error): + """ + The authorization server does not support the revocation of the + presented token type. I.e. the client tried to revoke an access token + on a server not supporting this feature. + """ + error = 'unsupported_token_type' + + +class FatalOpenIDClientError(FatalClientError): + pass + + +class OpenIDClientError(OAuth2Error): + pass + + +class InteractionRequired(OpenIDClientError): + """ + The Authorization Server requires End-User interaction to proceed. + + This error MAY be returned when the prompt parameter value in the + Authentication Request is none, but the Authentication Request cannot be + completed without displaying a user interface for End-User interaction. + """ + error = 'interaction_required' + status_code = 401 + + +class LoginRequired(OpenIDClientError): + """ + The Authorization Server requires End-User authentication. + + This error MAY be returned when the prompt parameter value in the + Authentication Request is none, but the Authentication Request cannot be + completed without displaying a user interface for End-User authentication. + """ + error = 'login_required' + status_code = 401 + + +class AccountSelectionRequired(OpenIDClientError): + """ + The End-User is REQUIRED to select a session at the Authorization Server. + + The End-User MAY be authenticated at the Authorization Server with + different associated accounts, but the End-User did not select a session. + This error MAY be returned when the prompt parameter value in the + Authentication Request is none, but the Authentication Request cannot be + completed without displaying a user interface to prompt for a session to + use. + """ + error = 'account_selection_required' + + +class ConsentRequired(OpenIDClientError): + """ + The Authorization Server requires End-User consent. + + This error MAY be returned when the prompt parameter value in the + Authentication Request is none, but the Authentication Request cannot be + completed without displaying a user interface for End-User consent. + """ + error = 'consent_required' + status_code = 401 + + +class InvalidRequestURI(OpenIDClientError): + """ + The request_uri in the Authorization Request returns an error or + contains invalid data. + """ + error = 'invalid_request_uri' + description = 'The request_uri in the Authorization Request returns an ' \ + 'error or contains invalid data.' + + +class InvalidRequestObject(OpenIDClientError): + """ + The request parameter contains an invalid Request Object. + """ + error = 'invalid_request_object' + description = 'The request parameter contains an invalid Request Object.' + + +class RequestNotSupported(OpenIDClientError): + """ + The OP does not support use of the request parameter. + """ + error = 'request_not_supported' + description = 'The request parameter is not supported.' + + +class RequestURINotSupported(OpenIDClientError): + """ + The OP does not support use of the request_uri parameter. + """ + error = 'request_uri_not_supported' + description = 'The request_uri parameter is not supported.' + + +class RegistrationNotSupported(OpenIDClientError): + """ + The OP does not support use of the registration parameter. + """ + error = 'registration_not_supported' + description = 'The registration parameter is not supported.' + + +class InvalidTokenError(OAuth2Error): + """ + The access token provided is expired, revoked, malformed, or + invalid for other reasons. The resource SHOULD respond with + the HTTP 401 (Unauthorized) status code. The client MAY + request a new access token and retry the protected resource + request. + """ + error = 'invalid_token' + status_code = 401 + description = ("The access token provided is expired, revoked, malformed, " + "or invalid for other reasons.") + + +class InsufficientScopeError(OAuth2Error): + """ + The request requires higher privileges than provided by the + access token. The resource server SHOULD respond with the HTTP + 403 (Forbidden) status code and MAY include the "scope" + attribute with the scope necessary to access the protected + resource. + """ + error = 'insufficient_scope' + status_code = 403 + description = ("The request requires higher privileges than provided by " + "the access token.") + + +def raise_from_error(error, params=None): + import inspect + import sys + kwargs = { + 'description': params.get('error_description'), + 'uri': params.get('error_uri'), + 'state': params.get('state') + } + for _, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass): + if cls.error == error: + raise cls(**kwargs) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py new file mode 100644 index 0000000..1da1281 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import unicode_literals, absolute_import + +from .authorization_code import AuthorizationCodeGrant +from .implicit import ImplicitGrant +from .resource_owner_password_credentials import ResourceOwnerPasswordCredentialsGrant +from .client_credentials import ClientCredentialsGrant +from .refresh_token import RefreshTokenGrant +from .openid_connect import OpenIDConnectBase +from .openid_connect import OpenIDConnectAuthCode +from .openid_connect import OpenIDConnectImplicit +from .openid_connect import OpenIDConnectHybrid +from .openid_connect import OIDCNoPrompt +from .openid_connect import AuthCodeGrantDispatcher diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27aae960b571813346c348bb76ea7aae8c81f5c5 GIT binary patch literal 983 zcma))&2rN)5XbGLX%Z*SS3ls+aL9qUw4pRJ3YEMJI#R; zkHHJ@Dm(#`E2q8!Csv}NlyX2G|L5mjNh|%mTVWXVzcTWzBe=%fdi~P8Q)D7hj8iELhgj-)JDzAI3&W{Ma|Pzww=@0u`fDt$ zK+IwzR5r#nZ*5V`gnMZ&MIy{1@c4*pRY1ZIE;9~?vDTLg*cdqDnGslIF>!q?1&02S z=^pVZaD95L&Um(%-aW{9wzoT0S;iCdCe|FQuiXjSL)x?5ODxWaiIqDm-d#;VFbp>j zaO-__2rA9(wH_??a^-i0&SR6Da@g;$&8w6$l~6hlD4iCj08S~`R)R7L>Dk}(+be1f zhKfzp&{Jm>EZfgVctvLvRn{q*-&jSHTd$z%c1)a8cM>(52;rAe)L5sWZeuNlJ_&|( zoly{A5E@pRRzfQ+D{Yi79`r0*`SB^rL0rU!M;9?fQzh}>I+{YXb@nua^WkWN&Dd@i) XwHK*kh2$@-XZj(^lHVyc{rk^P=U*zv literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/authorization_code.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/authorization_code.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..927905d39480fc79777a898c666f133cf4d779a0 GIT binary patch literal 15322 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 Vhw})#X7uS)9+?3-U6`Qs{{jXpc0>RG literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/base.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..451bd1ada0f2742a5a8ad1b6dcefc77d96dfbddb GIT binary patch literal 7214 zcmcIpS(Dqw5ynLl#FBexbvV8P+mXEV!IfqCkfJEIWXbVS>y*59T&Se5SPV#l3lh`} zC~0Y~D;28p6zAE0A@BJCc~1TTUi0L?;HP{&AizVriX2zK!eC}FnCbqyr@N>5X1#7T z{>hy0o>P>6D|45D`g^#=Hz=6mC`@Hqr1msN>*}aMOqpr zVI;oSXl6BCzZ2XY1|s>=i$dllG52Z7{L)c*54X68B2XL^;A%{Dbfz)=q2d@pi5aZ) zPysw;)XS_Q>wphXRMX93T5&V(C!h|*)+>r)+0(1&w&VS7;3sw~=5{BJSlDmd{!k=w z&%T$_X8T?gdHYcy>?F4L13T>Biw^=;%;pIjY$tZyvXf3=OT2Bu+uoZ_61XIrn1>@T z3FE%YGxoO{dv?r=d7fZ&FTgZR*l}Mjb%bpK;2AWXSs%!$LoK&A#6W{f^!94sAcf*@j;9a+shF0VBu=l)dS! zZrE?ddj*Nvgc#Q1fV@CYYOC$Fpk+DTfNhSoT?n9`zk+`KWR`Z~elR!xjX3Gp3o@Hu z43L1YWeNZT_d*fapAqnv3Lb+8elQ@h3p;G()1XP9u`qkl^1^6t`4c5qV7fQpnNsc9 z_roY6Y4Ty;zH#gJXLoKDOB0|X0l6M$b1MsbYk!zb@ue|72OZD`=oM)>DT8FCn5g9? zc$@9zhCQe7d;e9#pFkhyxQaz0_sSw$@890Nb*CWaM6Uivx*k!cIeLJyl=LD!PYUy~ z1j+g4Ou!7F#}Pun97D-ZLFQyaZo?b~54_$W3a-w`yMFz;&4QNQJCq`F3k$h5-Q10< zg^ZfbCfVAl!T9>+-XTW6eAV9N!{DeGMKjVabEQ|w4Ck-x1?|5HfAb-EuzZo(vW>^h z)6C5HY_w?RTc+s^20@=~J;KN0gi}^Cl%9ZB+THyaJ0~;l&EQ_Z50egDiW?tzhmT+* zeNkpHvW?Zu496|_jjo8x?;c#f(!28Z z<%_u5{q5cDK{$AQe;7vW^|$&ye>b^!cRLS*mMWLFr}5DCTs*YB?}?x}I803!jwwuB z_m`kYm?#vjtkQ4v?3Z~UFpDOd`EXyQp}6Qtaf<|zxJ;C>(glQFZK7qc>6{|Q8TSp9 zuTcjXQE}*=Dh9J2p+US|%Xqb-sA4n)faRLdZoEn0?Jx9Z_IOGF&8YW@b z_j-Zrrk3mWVm6GZUUS{MLob@XskknSeb?ouFf*x9JP3Gl$T!jCr>U5sA!`H)w@}ar zQZVMrhGtf3hV1pxTU>HMWjtoo%pFc&@Tdb{fw$c7~nBbDf=IPvN=2e#V~0^AuAd?Pt@v zv{}0d0&hUYc2cv&y>>6?C%dUZ#?F_L1)a3}=>7z^_zJ~X9hiwSf%;(gL4D-@Q@XS# zad(ZeHc?fjP47#xEI+WvTDLM$K2ZK}`uEzAeq zo|23T(+Z~Wl7u%(ncm|(rKIJ9`yOwLR6Dqz>L6!M{!;WL_B*%*X@jEHbW=4|i*}}V zN*$eDKzX(+b8r#!OK|0`yobhE?c!M0Rwmk!ChMB4>$0xPx*_X^td|mqfR!ei*dJ@+ z#@OIru`-FIt9OlwdQ?tI%xRurHFpV@+&Oy&WWJrQ->bLR!X| zr{^VU)ph;I69Sv^3=m2!*qUJ=osisO&c-7`Z&We~p2zs(7)$k@XfI3B#$Xq>cnSq1 zIsI9XrvZtY8cB6@VM(GVs9mvrNYZpm36qXMHe`2I+9bK!Yy5e&J zUm`a>%Y$|Z=r|&^xnX@wlv!nU^ovDWkN@DuQu+cG;7t@cnP$F`PNq;sPx-LBPe9?q z2S1j=7ndmHZ&1g~ZqvR^&lPB7!ADMAGDqF{6YyL7!Y8v%7d;D6lM73*m1*-yF>5=c z3!e}7r7B~`$RjS&7EwS(7e|^9ee&?!ME+rOCr&=fUj|GndqdCEqB1p(X+5K(gX2?# z0BwoE=X@R@VtM3XpXtg2ke92;tBY(t_PhTBv-~n>nKH-ULY==&#S%MbretM2=PLj8 z92+kk%f{k(pCTN(=t*8pZp#a(jCB~^%0Y#%j^Q9-VpaGo7)o-(-%5{0{S9*`Ey*c= zut-NUbN0h($TFugZEwnVi=DR-%lsS*H4J_Q4}O&jistw=>ecx+9%*?vK)xw(YV+*k z6B&rf?<6=9rX_@`I2C48P`+;(BU-AVomNNZ7O^Qt7F&ySx4`2!XdpaPkrz}ZIvf!U z7O)_PM2-j!Xkv&Dl2W(KsIQXsb@hp%Dv8;x;!FM8T)2}wu97)hLu*}AILRu>6teR=Oo9=xWKVy^|YKV zo9a>APD?DMA_Lx;lDc z5#?j(EAS>eQQ+M{0~jjem4t8x$`f^PLDmsZbxY_e19NEMSap@8(lr@sUHS(7-74Wu zkyqE6_Rt$h0a9YgO1B1iXoWoVozW$DP)ci3GI55w@Db|@8{(j3V}>2INH~QM>P|Tu z$0>Ai>aqc?L&jAkPDwVUc$`xZmWv~*H;fW@`i+E>wjBR}iW?}>wF0K;ASs=VkNUFz zb0R8}OXd|bxh#p~7AE>D?r9K4!ZM%UjYcfP0ms&`m%hffE`+G6YI&NH2Wh>IMh%3i4ecUyS>T z%tx`#{-GTs;lRU;f-Lceq@KN(D@i_-BJ*Gm-mteH@MbYnT0%;mAM$4)n$fGD&E`V7 zoAh~!u=4_mMeN3x&(naG@?nc015?aBBfI7X;U zhZti&W`}8+SQ-jvCG&Ff7{keWFMH4DabCw?04GHQ-{BMS3JRsXD#KhDHH`w^8baTi zW}v32r&OzWYU=2<#a;FI1t=@CDZ_VumdHDY!l^B z#U(1G@D1!M|1*=(*;Zv`Hu4eOa!>3^mqjw2y+C(c@)+-dr8uNU_-$(}2E z7ytQ^jz}6(X)?33Zxwd)Q%nzV=(r=5CM{=w1IhD_r0+O3Q}Y_M4_S@tq)&oQriQw% PnUG)&&zfdw>sS5*#wf;k literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/client_credentials.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/client_credentials.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25874e1b159a7958b73c7a1dfa93deb2d714ebf1 GIT binary patch literal 4628 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 AuK)l5 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/openid_connect.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/openid_connect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ceff648f62705408affbffa1039a4090b16e25ed GIT binary patch literal 16654 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 z zJE_R4xsa;jJfDO=swhL&&a6GgY2)FKo_Ko$Swa#_}zh@T`15IBY4!|g*Prc7es6G_khKXVqRlH zZcBfU>wb3W4#W*vU{Lj5hR_%38!}+wT)ZHfir0&OL#b>o)%$MHwIu;E?v0CvaX>JXWS7KxIBB@_&jB0d7VI{7y_IrziWT)VkA}Vq#3_ zS7_tvb1I&wS?Evbn0jZ@v+8JPvxTQ9_ssib0-DqbP(D>W&f#dm$JFCWIzlT-KZ9C= z^tGw7cEgd8HQ>Q!sFJ#-mNaj{Nnwb!yY?`Q;^?nS)|p=jF+# zMgZCxUp1p&y|zwVkWL5CR!hZVku|46j}#Q(BQGB#4e&&c52i7u?p0_KZZr^da2bT> zOR`h`lu)v3s6#5^Cp(tyhe=Uw~o?5T^{xldv^ zNaIz8Gj%(_;;c%WyAq6`hz|~r(9*39MNtw}p13Cq&)(DG)H+q#+@ERxdq+d43TY@f z6KBA7aTZ?g$vF}ZXL3AZxF-t6IuW!!tFIu0swvJZE;E6za9Q#2oD;^!$Y+x7tzVh7JiK?RP*!DJE2CQx&x2w;t$P0xRh-*t9ajvp#pebZhMGnqX_$T z&)XgOQ882TJeCBWr!F+&P0&~wkT;5ZSHPnPW@k7BHD?8hl&N^D!x@R4YRNJPU941_ z>bBQh5iJ-+(VY<-mO-?@og{F-iZz_70F=WP?B+xzxxlz;#Kk*OZf(QCR@AUtgXn$? o#9W9=NTkvdg&rz)xPSBNO>OKgJO{{Scv*BATv|1h%MIK7Ki7Hn761SM literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/resource_owner_password_credentials.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/resource_owner_password_credentials.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7bb75ad418ee306266faca7ffcc05a01adcf66e9 GIT binary patch literal 7625 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 B-va;u literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py new file mode 100644 index 0000000..8661c35 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/authorization_code.py @@ -0,0 +1,430 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from oauthlib import common +from oauthlib.uri_validate import is_absolute_uri + +from .. import errors +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class AuthorizationCodeGrant(GrantTypeBase): + + """`Authorization Code Grant`_ + + The authorization code grant type is used to obtain both access + tokens and refresh tokens and is optimized for confidential clients. + Since this is a redirection-based flow, the client must be capable of + interacting with the resource owner's user-agent (typically a web + browser) and capable of receiving incoming requests (via redirection) + from the authorization server:: + + +----------+ + | Resource | + | Owner | + | | + +----------+ + ^ + | + (B) + +----|-----+ Client Identifier +---------------+ + | -+----(A)-- & Redirection URI ---->| | + | User- | | Authorization | + | Agent -+----(B)-- User authenticates --->| Server | + | | | | + | -+----(C)-- Authorization Code ---<| | + +-|----|---+ +---------------+ + | | ^ v + (A) (C) | | + | | | | + ^ v | | + +---------+ | | + | |>---(D)-- Authorization Code ---------' | + | Client | & Redirection URI | + | | | + | |<---(E)----- Access Token -------------------' + +---------+ (w/ Optional Refresh Token) + + Note: The lines illustrating steps (A), (B), and (C) are broken into + two parts as they pass through the user-agent. + + Figure 3: Authorization Code Flow + + The flow illustrated in Figure 3 includes the following steps: + + (A) The client initiates the flow by directing the resource owner's + user-agent to the authorization endpoint. The client includes + its client identifier, requested scope, local state, and a + redirection URI to which the authorization server will send the + user-agent back once access is granted (or denied). + + (B) The authorization server authenticates the resource owner (via + the user-agent) and establishes whether the resource owner + grants or denies the client's access request. + + (C) Assuming the resource owner grants access, the authorization + server redirects the user-agent back to the client using the + redirection URI provided earlier (in the request or during + client registration). The redirection URI includes an + authorization code and any local state provided by the client + earlier. + + (D) The client requests an access token from the authorization + server's token endpoint by including the authorization code + received in the previous step. When making the request, the + client authenticates with the authorization server. The client + includes the redirection URI used to obtain the authorization + code for verification. + + (E) The authorization server authenticates the client, validates the + authorization code, and ensures that the redirection URI + received matches the URI used to redirect the client in + step (C). If valid, the authorization server responds back with + an access token and, optionally, a refresh token. + + .. _`Authorization Code Grant`: http://tools.ietf.org/html/rfc6749#section-4.1 + """ + + default_response_mode = 'query' + response_types = ['code'] + + def create_authorization_code(self, request): + """Generates an authorization grant represented as a dictionary.""" + grant = {'code': common.generate_token()} + if hasattr(request, 'state') and request.state: + grant['state'] = request.state + log.debug('Created authorization code grant %r for request %r.', + grant, request) + return grant + + def create_authorization_response(self, request, token_handler): + """ + The client constructs the request URI by adding the following + parameters to the query component of the authorization endpoint URI + using the "application/x-www-form-urlencoded" format, per `Appendix B`_: + + response_type + REQUIRED. Value MUST be set to "code" for standard OAuth2 + authorization flow. For OpenID Connect it must be one of + "code token", "code id_token", or "code token id_token" - we + essentially test that "code" appears in the response_type. + client_id + REQUIRED. The client identifier as described in `Section 2.2`_. + redirect_uri + OPTIONAL. As described in `Section 3.1.2`_. + scope + OPTIONAL. The scope of the access request as described by + `Section 3.3`_. + state + RECOMMENDED. An opaque value used by the client to maintain + state between the request and callback. The authorization + server includes this value when redirecting the user-agent back + to the client. The parameter SHOULD be used for preventing + cross-site request forgery as described in `Section 10.12`_. + + The client directs the resource owner to the constructed URI using an + HTTP redirection response, or by other means available to it via the + user-agent. + + :param request: oauthlib.commong.Request + :param token_handler: A token handler instace, for example of type + oauthlib.oauth2.BearerToken. + :returns: headers, body, status + :raises: FatalClientError on invalid redirect URI or client id. + ValueError if scopes are not set on the request object. + + A few examples:: + + >>> from your_validator import your_validator + >>> request = Request('https://example.com/authorize?client_id=valid' + ... '&redirect_uri=http%3A%2F%2Fclient.com%2F') + >>> from oauthlib.common import Request + >>> from oauthlib.oauth2 import AuthorizationCodeGrant, BearerToken + >>> token = BearerToken(your_validator) + >>> grant = AuthorizationCodeGrant(your_validator) + >>> grant.create_authorization_response(request, token) + Traceback (most recent call last): + File "", line 1, in + File "oauthlib/oauth2/rfc6749/grant_types.py", line 513, in create_authorization_response + raise ValueError('Scopes must be set on post auth.') + ValueError: Scopes must be set on post auth. + >>> request.scopes = ['authorized', 'in', 'some', 'form'] + >>> grant.create_authorization_response(request, token) + (u'http://client.com/?error=invalid_request&error_description=Missing+response_type+parameter.', None, None, 400) + >>> request = Request('https://example.com/authorize?client_id=valid' + ... '&redirect_uri=http%3A%2F%2Fclient.com%2F' + ... '&response_type=code') + >>> request.scopes = ['authorized', 'in', 'some', 'form'] + >>> grant.create_authorization_response(request, token) + (u'http://client.com/?code=u3F05aEObJuP2k7DordviIgW5wl52N', None, None, 200) + >>> # If the client id or redirect uri fails validation + >>> grant.create_authorization_response(request, token) + Traceback (most recent call last): + File "", line 1, in + File "oauthlib/oauth2/rfc6749/grant_types.py", line 515, in create_authorization_response + >>> grant.create_authorization_response(request, token) + File "oauthlib/oauth2/rfc6749/grant_types.py", line 591, in validate_authorization_request + oauthlib.oauth2.rfc6749.errors.InvalidClientIdError + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 + .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 + """ + try: + # request.scopes is only mandated in post auth and both pre and + # post auth use validate_authorization_request + if not request.scopes: + raise ValueError('Scopes must be set on post auth.') + + self.validate_authorization_request(request) + log.debug('Pre resource owner authorization validation ok for %r.', + request) + + # If the request fails due to a missing, invalid, or mismatching + # redirection URI, or if the client identifier is missing or invalid, + # the authorization server SHOULD inform the resource owner of the + # error and MUST NOT automatically redirect the user-agent to the + # invalid redirection URI. + except errors.FatalClientError as e: + log.debug('Fatal client error during validation of %r. %r.', + request, e) + raise + + # If the resource owner denies the access request or if the request + # fails for reasons other than a missing or invalid redirection URI, + # the authorization server informs the client by adding the following + # parameters to the query component of the redirection URI using the + # "application/x-www-form-urlencoded" format, per Appendix B: + # http://tools.ietf.org/html/rfc6749#appendix-B + except errors.OAuth2Error as e: + log.debug('Client error during validation of %r. %r.', request, e) + request.redirect_uri = request.redirect_uri or self.error_uri + redirect_uri = common.add_params_to_uri( + request.redirect_uri, e.twotuples, + fragment=request.response_mode == "fragment") + return {'Location': redirect_uri}, None, 302 + + grant = self.create_authorization_code(request) + for modifier in self._code_modifiers: + grant = modifier(grant, token_handler, request) + log.debug('Saving grant %r for %r.', grant, request) + self.request_validator.save_authorization_code( + request.client_id, grant, request) + return self.prepare_authorization_response( + request, grant, {}, None, 302) + + def create_token_response(self, request, token_handler): + """Validate the authorization code. + + The client MUST NOT use the authorization code more than once. If an + authorization code is used more than once, the authorization server + MUST deny the request and SHOULD revoke (when possible) all tokens + previously issued based on that authorization code. The authorization + code is bound to the client identifier and redirection URI. + """ + headers = { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + 'Pragma': 'no-cache', + } + try: + self.validate_token_request(request) + log.debug('Token request validation ok for %r.', request) + except errors.OAuth2Error as e: + log.debug('Client error during validation of %r. %r.', request, e) + return headers, e.json, e.status_code + + token = token_handler.create_token(request, refresh_token=self.refresh_token, save_token=False) + for modifier in self._token_modifiers: + token = modifier(token, token_handler, request) + self.request_validator.save_token(token, request) + self.request_validator.invalidate_authorization_code( + request.client_id, request.code, request) + return headers, json.dumps(token), 200 + + def validate_authorization_request(self, request): + """Check the authorization request for normal and fatal errors. + + A normal error could be a missing response_type parameter or the client + attempting to access scope it is not allowed to ask authorization for. + Normal errors can safely be included in the redirection URI and + sent back to the client. + + Fatal errors occur when the client_id or redirect_uri is invalid or + missing. These must be caught by the provider and handled, how this + is done is outside of the scope of OAuthLib but showing an error + page describing the issue is a good idea. + """ + + # First check for fatal errors + + # If the request fails due to a missing, invalid, or mismatching + # redirection URI, or if the client identifier is missing or invalid, + # the authorization server SHOULD inform the resource owner of the + # error and MUST NOT automatically redirect the user-agent to the + # invalid redirection URI. + + # First check duplicate parameters + for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'): + try: + duplicate_params = request.duplicate_params + except ValueError: + raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request) + if param in duplicate_params: + raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request) + + # REQUIRED. The client identifier as described in Section 2.2. + # http://tools.ietf.org/html/rfc6749#section-2.2 + if not request.client_id: + raise errors.MissingClientIdError(request=request) + + if not self.request_validator.validate_client_id(request.client_id, request): + raise errors.InvalidClientIdError(request=request) + + # OPTIONAL. As described in Section 3.1.2. + # http://tools.ietf.org/html/rfc6749#section-3.1.2 + log.debug('Validating redirection uri %s for client %s.', + request.redirect_uri, request.client_id) + if request.redirect_uri is not None: + request.using_default_redirect_uri = False + log.debug('Using provided redirect_uri %s', request.redirect_uri) + if not is_absolute_uri(request.redirect_uri): + raise errors.InvalidRedirectURIError(request=request) + + if not self.request_validator.validate_redirect_uri( + request.client_id, request.redirect_uri, request): + raise errors.MismatchingRedirectURIError(request=request) + else: + request.redirect_uri = self.request_validator.get_default_redirect_uri( + request.client_id, request) + request.using_default_redirect_uri = True + log.debug('Using default redirect_uri %s.', request.redirect_uri) + if not request.redirect_uri: + raise errors.MissingRedirectURIError(request=request) + + # Then check for normal errors. + + # If the resource owner denies the access request or if the request + # fails for reasons other than a missing or invalid redirection URI, + # the authorization server informs the client by adding the following + # parameters to the query component of the redirection URI using the + # "application/x-www-form-urlencoded" format, per Appendix B. + # http://tools.ietf.org/html/rfc6749#appendix-B + + # Note that the correct parameters to be added are automatically + # populated through the use of specific exceptions. + + request_info = {} + for validator in self.custom_validators.pre_auth: + request_info.update(validator(request)) + + # REQUIRED. + if request.response_type is None: + raise errors.MissingResponseTypeError(request=request) + # Value MUST be set to "code" or one of the OpenID authorization code including + # response_types "code token", "code id_token", "code token id_token" + elif not 'code' in request.response_type and request.response_type != 'none': + raise errors.UnsupportedResponseTypeError(request=request) + + if not self.request_validator.validate_response_type(request.client_id, + request.response_type, + request.client, request): + + log.debug('Client %s is not authorized to use response_type %s.', + request.client_id, request.response_type) + raise errors.UnauthorizedClientError(request=request) + + # OPTIONAL. The scope of the access request as described by Section 3.3 + # http://tools.ietf.org/html/rfc6749#section-3.3 + self.validate_scopes(request) + + request_info.update({ + 'client_id': request.client_id, + 'redirect_uri': request.redirect_uri, + 'response_type': request.response_type, + 'state': request.state, + 'request': request + }) + + for validator in self.custom_validators.post_auth: + request_info.update(validator(request)) + + return request.scopes, request_info + + def validate_token_request(self, request): + # REQUIRED. Value MUST be set to "authorization_code". + if request.grant_type not in ('authorization_code', 'openid'): + raise errors.UnsupportedGrantTypeError(request=request) + + for validator in self.custom_validators.pre_token: + validator(request) + + if request.code is None: + raise errors.InvalidRequestError( + description='Missing code parameter.', request=request) + + for param in ('client_id', 'grant_type', 'redirect_uri'): + if param in request.duplicate_params: + raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, + request=request) + + if self.request_validator.client_authentication_required(request): + # If the client type is confidential or the client was issued client + # credentials (or assigned other authentication requirements), the + # client MUST authenticate with the authorization server as described + # in Section 3.2.1. + # http://tools.ietf.org/html/rfc6749#section-3.2.1 + if not self.request_validator.authenticate_client(request): + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + elif not self.request_validator.authenticate_client_id(request.client_id, request): + # REQUIRED, if the client is not authenticating with the + # authorization server as described in Section 3.2.1. + # http://tools.ietf.org/html/rfc6749#section-3.2.1 + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + + if not hasattr(request.client, 'client_id'): + raise NotImplementedError('Authenticate client must set the ' + 'request.client.client_id attribute ' + 'in authenticate_client.') + + request.client_id = request.client_id or request.client.client_id + + # Ensure client is authorized use of this grant type + self.validate_grant_type(request) + + # REQUIRED. The authorization code received from the + # authorization server. + if not self.request_validator.validate_code(request.client_id, + request.code, request.client, request): + log.debug('Client, %r (%r), is not allowed access to scopes %r.', + request.client_id, request.client, request.scopes) + raise errors.InvalidGrantError(request=request) + + for attr in ('user', 'scopes'): + if getattr(request, attr, None) is None: + log.debug('request.%s was not set on code validation.', attr) + + # REQUIRED, if the "redirect_uri" parameter was included in the + # authorization request as described in Section 4.1.1, and their + # values MUST be identical. + if not self.request_validator.confirm_redirect_uri(request.client_id, request.code, + request.redirect_uri, request.client): + log.debug('Redirect_uri (%r) invalid for client %r (%r).', + request.redirect_uri, request.client_id, request.client) + raise errors.MismatchingRedirectURIError(request=request) + + for validator in self.custom_validators.post_token: + validator(request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py new file mode 100644 index 0000000..e5d8ddd --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/base.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import logging +from itertools import chain + +from oauthlib.common import add_params_to_uri +from oauthlib.oauth2.rfc6749 import errors, utils + +from ..request_validator import RequestValidator + +log = logging.getLogger(__name__) + +class ValidatorsContainer(object): + """ + Container object for holding custom validator callables to be invoked + as part of the grant type `validate_authorization_request()` or + `validate_authorization_request()` methods on the various grant types. + + Authorization validators must be callables that take a request object and + return a dict, which may contain items to be added to the `request_info` + returned from the grant_type after validation. + + Token validators must be callables that take a request object and + return None. + + Both authorization validators and token validators may raise OAuth2 + exceptions if validation conditions fail. + + Authorization validators added to `pre_auth` will be run BEFORE + the standard validations (but after the critical ones that raise + fatal errors) as part of `validate_authorization_request()` + + Authorization validators added to `post_auth` will be run AFTER + the standard validations as part of `validate_authorization_request()` + + Token validators added to `pre_token` will be run BEFORE + the standard validations as part of `validate_token_request()` + + Token validators added to `post_token` will be run AFTER + the standard validations as part of `validate_token_request()` + + For example: + + >>> def my_auth_validator(request): + ... return {'myval': True} + >>> auth_code_grant = AuthorizationCodeGrant(request_validator) + >>> auth_code_grant.custom_validators.pre_auth.append(my_auth_validator) + >>> def my_token_validator(request): + ... if not request.everything_okay: + ... raise errors.OAuth2Error("uh-oh") + >>> auth_code_grant.custom_validators.post_token.append(my_token_validator) + """ + + def __init__(self, post_auth, post_token, + pre_auth, pre_token): + self.pre_auth = pre_auth + self.post_auth = post_auth + self.pre_token = pre_token + self.post_token = post_token + + @property + def all_pre(self): + return chain(self.pre_auth, self.pre_token) + + @property + def all_post(self): + return chain(self.post_auth, self.post_token) + + +class GrantTypeBase(object): + error_uri = None + request_validator = None + default_response_mode = 'fragment' + refresh_token = True + response_types = ['code'] + + def __init__(self, request_validator=None, **kwargs): + self.request_validator = request_validator or RequestValidator() + + # Transforms class variables into instance variables: + self.response_types = self.response_types + self.refresh_token = self.refresh_token + self._setup_custom_validators(kwargs) + self._code_modifiers = [] + self._token_modifiers = [] + + for kw, val in kwargs.items(): + setattr(self, kw, val) + + def _setup_custom_validators(self, kwargs): + post_auth = kwargs.get('post_auth', []) + post_token = kwargs.get('post_token', []) + pre_auth = kwargs.get('pre_auth', []) + pre_token = kwargs.get('pre_token', []) + if not hasattr(self, 'validate_authorization_request'): + if post_auth or pre_auth: + msg = ("{} does not support authorization validators. Use " + "token validators instead.").format(self.__class__.__name__) + raise ValueError(msg) + # Using tuples here because they can't be appended to: + post_auth, pre_auth = (), () + self.custom_validators = ValidatorsContainer(post_auth, post_token, + pre_auth, pre_token) + + def register_response_type(self, response_type): + self.response_types.append(response_type) + + def register_code_modifier(self, modifier): + self._code_modifiers.append(modifier) + + def register_token_modifier(self, modifier): + self._token_modifiers.append(modifier) + + + def create_authorization_response(self, request, token_handler): + raise NotImplementedError('Subclasses must implement this method.') + + def create_token_response(self, request, token_handler): + raise NotImplementedError('Subclasses must implement this method.') + + def add_token(self, token, token_handler, request): + # Only add a hybrid access token on auth step if asked for + if not request.response_type in ["token", "code token", "id_token token", "code id_token token"]: + return token + + token.update(token_handler.create_token(request, refresh_token=False)) + return token + + def validate_grant_type(self, request): + client_id = getattr(request, 'client_id', None) + if not self.request_validator.validate_grant_type(client_id, + request.grant_type, request.client, request): + log.debug('Unauthorized from %r (%r) access to grant type %s.', + request.client_id, request.client, request.grant_type) + raise errors.UnauthorizedClientError(request=request) + + def validate_scopes(self, request): + if not request.scopes: + request.scopes = utils.scope_to_list(request.scope) or utils.scope_to_list( + self.request_validator.get_default_scopes(request.client_id, request)) + log.debug('Validating access to scopes %r for client %r (%r).', + request.scopes, request.client_id, request.client) + if not self.request_validator.validate_scopes(request.client_id, + request.scopes, request.client, request): + raise errors.InvalidScopeError(request=request) + + def prepare_authorization_response(self, request, token, headers, body, status): + """Place token according to response mode. + + Base classes can define a default response mode for their authorization + response by overriding the static `default_response_mode` member. + """ + request.response_mode = request.response_mode or self.default_response_mode + + if request.response_mode not in ('query', 'fragment'): + log.debug('Overriding invalid response mode %s with %s', + request.response_mode, self.default_response_mode) + request.response_mode = self.default_response_mode + + token_items = token.items() + + if request.response_type == 'none': + state = token.get('state', None) + if state: + token_items = [('state', state)] + else: + token_items = [] + + if request.response_mode == 'query': + headers['Location'] = add_params_to_uri( + request.redirect_uri, token_items, fragment=False) + return headers, body, status + + if request.response_mode == 'fragment': + headers['Location'] = add_params_to_uri( + request.redirect_uri, token_items, fragment=True) + return headers, body, status + + raise NotImplementedError( + 'Subclasses must set a valid default_response_mode') diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py new file mode 100644 index 0000000..bf6c87f --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/client_credentials.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from .. import errors +from ..request_validator import RequestValidator +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class ClientCredentialsGrant(GrantTypeBase): + + """`Client Credentials Grant`_ + + The client can request an access token using only its client + credentials (or other supported means of authentication) when the + client is requesting access to the protected resources under its + control, or those of another resource owner that have been previously + arranged with the authorization server (the method of which is beyond + the scope of this specification). + + The client credentials grant type MUST only be used by confidential + clients:: + + +---------+ +---------------+ + : : : : + : :>-- A - Client Authentication --->: Authorization : + : Client : : Server : + : :<-- B ---- Access Token ---------<: : + : : : : + +---------+ +---------------+ + + Figure 6: Client Credentials Flow + + The flow illustrated in Figure 6 includes the following steps: + + (A) The client authenticates with the authorization server and + requests an access token from the token endpoint. + + (B) The authorization server authenticates the client, and if valid, + issues an access token. + + .. _`Client Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.4 + """ + + def create_token_response(self, request, token_handler): + """Return token or error in JSON format. + + If the access token request is valid and authorized, the + authorization server issues an access token as described in + `Section 5.1`_. A refresh token SHOULD NOT be included. If the request + failed client authentication or is invalid, the authorization server + returns an error response as described in `Section 5.2`_. + + .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1 + .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2 + """ + headers = { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + 'Pragma': 'no-cache', + } + try: + log.debug('Validating access token request, %r.', request) + self.validate_token_request(request) + except errors.OAuth2Error as e: + log.debug('Client error in token request. %s.', e) + return headers, e.json, e.status_code + + token = token_handler.create_token(request, refresh_token=False, save_token=False) + + for modifier in self._token_modifiers: + token = modifier(token) + self.request_validator.save_token(token, request) + + log.debug('Issuing token to client id %r (%r), %r.', + request.client_id, request.client, token) + return headers, json.dumps(token), 200 + + def validate_token_request(self, request): + for validator in self.custom_validators.pre_token: + validator(request) + + if not getattr(request, 'grant_type', None): + raise errors.InvalidRequestError('Request is missing grant type.', + request=request) + + if not request.grant_type == 'client_credentials': + raise errors.UnsupportedGrantTypeError(request=request) + + for param in ('grant_type', 'scope'): + if param in request.duplicate_params: + raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, + request=request) + + log.debug('Authenticating client, %r.', request) + if not self.request_validator.authenticate_client(request): + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + else: + if not hasattr(request.client, 'client_id'): + raise NotImplementedError('Authenticate client must set the ' + 'request.client.client_id attribute ' + 'in authenticate_client.') + # Ensure client is authorized use of this grant type + self.validate_grant_type(request) + + log.debug('Authorizing access to user %r.', request.user) + request.client_id = request.client_id or request.client.client_id + self.validate_scopes(request) + + for validator in self.custom_validators.post_token: + validator(request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py new file mode 100644 index 0000000..858ef77 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/implicit.py @@ -0,0 +1,382 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import logging + +from oauthlib import common +from oauthlib.uri_validate import is_absolute_uri + +from .. import errors +from ..request_validator import RequestValidator +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class ImplicitGrant(GrantTypeBase): + + """`Implicit Grant`_ + + The implicit grant type is used to obtain access tokens (it does not + support the issuance of refresh tokens) and is optimized for public + clients known to operate a particular redirection URI. These clients + are typically implemented in a browser using a scripting language + such as JavaScript. + + Unlike the authorization code grant type, in which the client makes + separate requests for authorization and for an access token, the + client receives the access token as the result of the authorization + request. + + The implicit grant type does not include client authentication, and + relies on the presence of the resource owner and the registration of + the redirection URI. Because the access token is encoded into the + redirection URI, it may be exposed to the resource owner and other + applications residing on the same device:: + + +----------+ + | Resource | + | Owner | + | | + +----------+ + ^ + | + (B) + +----|-----+ Client Identifier +---------------+ + | -+----(A)-- & Redirection URI --->| | + | User- | | Authorization | + | Agent -|----(B)-- User authenticates -->| Server | + | | | | + | |<---(C)--- Redirection URI ----<| | + | | with Access Token +---------------+ + | | in Fragment + | | +---------------+ + | |----(D)--- Redirection URI ---->| Web-Hosted | + | | without Fragment | Client | + | | | Resource | + | (F) |<---(E)------- Script ---------<| | + | | +---------------+ + +-|--------+ + | | + (A) (G) Access Token + | | + ^ v + +---------+ + | | + | Client | + | | + +---------+ + + Note: The lines illustrating steps (A) and (B) are broken into two + parts as they pass through the user-agent. + + Figure 4: Implicit Grant Flow + + The flow illustrated in Figure 4 includes the following steps: + + (A) The client initiates the flow by directing the resource owner's + user-agent to the authorization endpoint. The client includes + its client identifier, requested scope, local state, and a + redirection URI to which the authorization server will send the + user-agent back once access is granted (or denied). + + (B) The authorization server authenticates the resource owner (via + the user-agent) and establishes whether the resource owner + grants or denies the client's access request. + + (C) Assuming the resource owner grants access, the authorization + server redirects the user-agent back to the client using the + redirection URI provided earlier. The redirection URI includes + the access token in the URI fragment. + + (D) The user-agent follows the redirection instructions by making a + request to the web-hosted client resource (which does not + include the fragment per [RFC2616]). The user-agent retains the + fragment information locally. + + (E) The web-hosted client resource returns a web page (typically an + HTML document with an embedded script) capable of accessing the + full redirection URI including the fragment retained by the + user-agent, and extracting the access token (and other + parameters) contained in the fragment. + + (F) The user-agent executes the script provided by the web-hosted + client resource locally, which extracts the access token. + + (G) The user-agent passes the access token to the client. + + See `Section 10.3`_ and `Section 10.16`_ for important security considerations + when using the implicit grant. + + .. _`Implicit Grant`: http://tools.ietf.org/html/rfc6749#section-4.2 + .. _`Section 10.3`: http://tools.ietf.org/html/rfc6749#section-10.3 + .. _`Section 10.16`: http://tools.ietf.org/html/rfc6749#section-10.16 + """ + + response_types = ['token'] + grant_allows_refresh_token = False + + def create_authorization_response(self, request, token_handler): + """Create an authorization response. + The client constructs the request URI by adding the following + parameters to the query component of the authorization endpoint URI + using the "application/x-www-form-urlencoded" format, per `Appendix B`_: + + response_type + REQUIRED. Value MUST be set to "token" for standard OAuth2 implicit flow + or "id_token token" or just "id_token" for OIDC implicit flow + + client_id + REQUIRED. The client identifier as described in `Section 2.2`_. + + redirect_uri + OPTIONAL. As described in `Section 3.1.2`_. + + scope + OPTIONAL. The scope of the access request as described by + `Section 3.3`_. + + state + RECOMMENDED. An opaque value used by the client to maintain + state between the request and callback. The authorization + server includes this value when redirecting the user-agent back + to the client. The parameter SHOULD be used for preventing + cross-site request forgery as described in `Section 10.12`_. + + The authorization server validates the request to ensure that all + required parameters are present and valid. The authorization server + MUST verify that the redirection URI to which it will redirect the + access token matches a redirection URI registered by the client as + described in `Section 3.1.2`_. + + .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 + .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + """ + return self.create_token_response(request, token_handler) + + def create_token_response(self, request, token_handler): + """Return token or error embedded in the URI fragment. + + If the resource owner grants the access request, the authorization + server issues an access token and delivers it to the client by adding + the following parameters to the fragment component of the redirection + URI using the "application/x-www-form-urlencoded" format, per + `Appendix B`_: + + access_token + REQUIRED. The access token issued by the authorization server. + + token_type + REQUIRED. The type of the token issued as described in + `Section 7.1`_. Value is case insensitive. + + expires_in + RECOMMENDED. The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + + scope + OPTIONAL, if identical to the scope requested by the client; + otherwise, REQUIRED. The scope of the access token as + described by `Section 3.3`_. + + state + REQUIRED if the "state" parameter was present in the client + authorization request. The exact value received from the + client. + + The authorization server MUST NOT issue a refresh token. + + .. _`Appendix B`: http://tools.ietf.org/html/rfc6749#appendix-B + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 + """ + try: + # request.scopes is only mandated in post auth and both pre and + # post auth use validate_authorization_request + if not request.scopes: + raise ValueError('Scopes must be set on post auth.') + + self.validate_token_request(request) + + # If the request fails due to a missing, invalid, or mismatching + # redirection URI, or if the client identifier is missing or invalid, + # the authorization server SHOULD inform the resource owner of the + # error and MUST NOT automatically redirect the user-agent to the + # invalid redirection URI. + except errors.FatalClientError as e: + log.debug('Fatal client error during validation of %r. %r.', + request, e) + raise + + # If the resource owner denies the access request or if the request + # fails for reasons other than a missing or invalid redirection URI, + # the authorization server informs the client by adding the following + # parameters to the fragment component of the redirection URI using the + # "application/x-www-form-urlencoded" format, per Appendix B: + # http://tools.ietf.org/html/rfc6749#appendix-B + except errors.OAuth2Error as e: + log.debug('Client error during validation of %r. %r.', request, e) + return {'Location': common.add_params_to_uri(request.redirect_uri, e.twotuples, + fragment=True)}, None, 302 + + # In OIDC implicit flow it is possible to have a request_type that does not include the access token! + # "id_token token" - return the access token and the id token + # "id_token" - don't return the access token + if "token" in request.response_type.split(): + token = token_handler.create_token(request, refresh_token=False, save_token=False) + else: + token = {} + + for modifier in self._token_modifiers: + token = modifier(token, token_handler, request) + self.request_validator.save_token(token, request) + return self.prepare_authorization_response( + request, token, {}, None, 302) + + def validate_authorization_request(self, request): + return self.validate_token_request(request) + + def validate_token_request(self, request): + """Check the token request for normal and fatal errors. + + This method is very similar to validate_authorization_request in + the AuthorizationCodeGrant but differ in a few subtle areas. + + A normal error could be a missing response_type parameter or the client + attempting to access scope it is not allowed to ask authorization for. + Normal errors can safely be included in the redirection URI and + sent back to the client. + + Fatal errors occur when the client_id or redirect_uri is invalid or + missing. These must be caught by the provider and handled, how this + is done is outside of the scope of OAuthLib but showing an error + page describing the issue is a good idea. + """ + + # First check for fatal errors + + # If the request fails due to a missing, invalid, or mismatching + # redirection URI, or if the client identifier is missing or invalid, + # the authorization server SHOULD inform the resource owner of the + # error and MUST NOT automatically redirect the user-agent to the + # invalid redirection URI. + + # First check duplicate parameters + for param in ('client_id', 'response_type', 'redirect_uri', 'scope', 'state'): + try: + duplicate_params = request.duplicate_params + except ValueError: + raise errors.InvalidRequestFatalError(description='Unable to parse query string', request=request) + if param in duplicate_params: + raise errors.InvalidRequestFatalError(description='Duplicate %s parameter.' % param, request=request) + + # REQUIRED. The client identifier as described in Section 2.2. + # http://tools.ietf.org/html/rfc6749#section-2.2 + if not request.client_id: + raise errors.MissingClientIdError(request=request) + + if not self.request_validator.validate_client_id(request.client_id, request): + raise errors.InvalidClientIdError(request=request) + + # OPTIONAL. As described in Section 3.1.2. + # http://tools.ietf.org/html/rfc6749#section-3.1.2 + if request.redirect_uri is not None: + request.using_default_redirect_uri = False + log.debug('Using provided redirect_uri %s', request.redirect_uri) + if not is_absolute_uri(request.redirect_uri): + raise errors.InvalidRedirectURIError(request=request) + + # The authorization server MUST verify that the redirection URI + # to which it will redirect the access token matches a + # redirection URI registered by the client as described in + # Section 3.1.2. + # http://tools.ietf.org/html/rfc6749#section-3.1.2 + if not self.request_validator.validate_redirect_uri( + request.client_id, request.redirect_uri, request): + raise errors.MismatchingRedirectURIError(request=request) + else: + request.redirect_uri = self.request_validator.get_default_redirect_uri( + request.client_id, request) + request.using_default_redirect_uri = True + log.debug('Using default redirect_uri %s.', request.redirect_uri) + if not request.redirect_uri: + raise errors.MissingRedirectURIError(request=request) + if not is_absolute_uri(request.redirect_uri): + raise errors.InvalidRedirectURIError(request=request) + + # Then check for normal errors. + + request_info = self._run_custom_validators(request, + self.custom_validators.all_pre) + + + # If the resource owner denies the access request or if the request + # fails for reasons other than a missing or invalid redirection URI, + # the authorization server informs the client by adding the following + # parameters to the fragment component of the redirection URI using the + # "application/x-www-form-urlencoded" format, per Appendix B. + # http://tools.ietf.org/html/rfc6749#appendix-B + + # Note that the correct parameters to be added are automatically + # populated through the use of specific exceptions + + # REQUIRED. + if request.response_type is None: + raise errors.MissingResponseTypeError(request=request) + # Value MUST be one of our registered types: "token" by default or if using OIDC "id_token" or "id_token token" + elif not set(request.response_type.split()).issubset(self.response_types): + raise errors.UnsupportedResponseTypeError(request=request) + + log.debug('Validating use of response_type token for client %r (%r).', + request.client_id, request.client) + if not self.request_validator.validate_response_type(request.client_id, + request.response_type, + request.client, request): + + log.debug('Client %s is not authorized to use response_type %s.', + request.client_id, request.response_type) + raise errors.UnauthorizedClientError(request=request) + + # OPTIONAL. The scope of the access request as described by Section 3.3 + # http://tools.ietf.org/html/rfc6749#section-3.3 + self.validate_scopes(request) + + request_info.update({ + 'client_id': request.client_id, + 'redirect_uri': request.redirect_uri, + 'response_type': request.response_type, + 'state': request.state, + 'request': request, + }) + + request_info = self._run_custom_validators(request, + self.custom_validators.all_post, + request_info) + + return request.scopes, request_info + + + def _run_custom_validators(self, + request, + validations, + request_info=None): + # Make a copy so we don't modify the existing request_info dict + request_info = {} if request_info is None else request_info.copy() + # For implicit grant, auth_validators and token_validators are + # basically equivalent since the token is returned from the + # authorization endpoint. + for validator in validations: + result = validator(request) + if result is not None: + request_info.update(result) + return request_info diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py new file mode 100644 index 0000000..4c98864 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/openid_connect.py @@ -0,0 +1,390 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types.openid_connect +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import datetime +import logging +from json import loads + +from ..errors import ConsentRequired, InvalidRequestError, LoginRequired +from ..request_validator import RequestValidator +from .authorization_code import AuthorizationCodeGrant +from .base import GrantTypeBase +from .implicit import ImplicitGrant + +log = logging.getLogger(__name__) + +class OIDCNoPrompt(Exception): + """Exception used to inform users that no explicit authorization is needed. + + Normally users authorize requests after validation of the request is done. + Then post-authorization validation is again made and a response containing + an auth code or token is created. However, when OIDC clients request + no prompting of user authorization the final response is created directly. + + Example (without the shortcut for no prompt) + + scopes, req_info = endpoint.validate_authorization_request(url, ...) + authorization_view = create_fancy_auth_form(scopes, req_info) + return authorization_view + + Example (with the no prompt shortcut) + try: + scopes, req_info = endpoint.validate_authorization_request(url, ...) + authorization_view = create_fancy_auth_form(scopes, req_info) + return authorization_view + except OIDCNoPrompt: + # Note: Location will be set for you + headers, body, status = endpoint.create_authorization_response(url, ...) + redirect_view = create_redirect(headers, body, status) + return redirect_view + """ + + def __init__(self): + msg = ("OIDC request for no user interaction received. Do not ask user " + "for authorization, it should been done using silent " + "authentication through create_authorization_response. " + "See OIDCNoPrompt.__doc__ for more details.") + super(OIDCNoPrompt, self).__init__(msg) + + +class AuthCodeGrantDispatcher(object): + """ + This is an adapter class that will route simple Authorization Code requests, those that have response_type=code and a scope + including 'openid' to either the default_auth_grant or the oidc_auth_grant based on the scopes requested. + """ + def __init__(self, default_auth_grant=None, oidc_auth_grant=None): + self.default_auth_grant = default_auth_grant + self.oidc_auth_grant = oidc_auth_grant + + def _handler_for_request(self, request): + handler = self.default_auth_grant + + if request.scopes and "openid" in request.scopes: + handler = self.oidc_auth_grant + + log.debug('Selecting handler for request %r.', handler) + return handler + + def create_authorization_response(self, request, token_handler): + return self._handler_for_request(request).create_authorization_response(request, token_handler) + + def validate_authorization_request(self, request): + return self._handler_for_request(request).validate_authorization_request(request) + + +class OpenIDConnectBase(object): + + # Just proxy the majority of method calls through to the + # proxy_target grant type handler, which will usually be either + # the standard OAuth2 AuthCode or Implicit grant types. + def __getattr__(self, attr): + return getattr(self.proxy_target, attr) + + def __setattr__(self, attr, value): + proxied_attrs = set(('refresh_token', 'response_types')) + if attr in proxied_attrs: + setattr(self.proxy_target, attr, value) + else: + super(OpenIDConnectBase, self).__setattr__(attr, value) + + def validate_authorization_request(self, request): + """Validates the OpenID Connect authorization request parameters. + + :returns: (list of scopes, dict of request info) + """ + # If request.prompt is 'none' then no login/authorization form should + # be presented to the user. Instead, a silent login/authorization + # should be performed. + if request.prompt == 'none': + raise OIDCNoPrompt() + else: + return self.proxy_target.validate_authorization_request(request) + + def _inflate_claims(self, request): + # this may be called multiple times in a single request so make sure we only de-serialize the claims once + if request.claims and not isinstance(request.claims, dict): + # specific claims are requested during the Authorization Request and may be requested for inclusion + # in either the id_token or the UserInfo endpoint response + # see http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter + try: + request.claims = loads(request.claims) + except Exception as ex: + raise InvalidRequestError(description="Malformed claims parameter", + uri="http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter") + + def add_id_token(self, token, token_handler, request): + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: + return token + + # Only add an id token on auth/token step if asked for. + if request.response_type and 'id_token' not in request.response_type: + return token + + if 'state' not in token: + token['state'] = request.state + + if request.max_age: + d = datetime.datetime.utcnow() + token['auth_time'] = d.isoformat("T") + "Z" + + # TODO: acr claims (probably better handled by server code using oauthlib in get_id_token) + + token['id_token'] = self.request_validator.get_id_token(token, token_handler, request) + + return token + + def openid_authorization_validator(self, request): + """Perform OpenID Connect specific authorization request validation. + + nonce + OPTIONAL. String value used to associate a Client session with + an ID Token, and to mitigate replay attacks. The value is + passed through unmodified from the Authentication Request to + the ID Token. Sufficient entropy MUST be present in the nonce + values used to prevent attackers from guessing values + + display + OPTIONAL. ASCII string value that specifies how the + Authorization Server displays the authentication and consent + user interface pages to the End-User. The defined values are: + + page - The Authorization Server SHOULD display the + authentication and consent UI consistent with a full User + Agent page view. If the display parameter is not specified, + this is the default display mode. + + popup - The Authorization Server SHOULD display the + authentication and consent UI consistent with a popup User + Agent window. The popup User Agent window should be of an + appropriate size for a login-focused dialog and should not + obscure the entire window that it is popping up over. + + touch - The Authorization Server SHOULD display the + authentication and consent UI consistent with a device that + leverages a touch interface. + + wap - The Authorization Server SHOULD display the + authentication and consent UI consistent with a "feature + phone" type display. + + The Authorization Server MAY also attempt to detect the + capabilities of the User Agent and present an appropriate + display. + + prompt + OPTIONAL. Space delimited, case sensitive list of ASCII string + values that specifies whether the Authorization Server prompts + the End-User for reauthentication and consent. The defined + values are: + + none - The Authorization Server MUST NOT display any + authentication or consent user interface pages. An error is + returned if an End-User is not already authenticated or the + Client does not have pre-configured consent for the + requested Claims or does not fulfill other conditions for + processing the request. The error code will typically be + login_required, interaction_required, or another code + defined in Section 3.1.2.6. This can be used as a method to + check for existing authentication and/or consent. + + login - The Authorization Server SHOULD prompt the End-User + for reauthentication. If it cannot reauthenticate the + End-User, it MUST return an error, typically + login_required. + + consent - The Authorization Server SHOULD prompt the + End-User for consent before returning information to the + Client. If it cannot obtain consent, it MUST return an + error, typically consent_required. + + select_account - The Authorization Server SHOULD prompt the + End-User to select a user account. This enables an End-User + who has multiple accounts at the Authorization Server to + select amongst the multiple accounts that they might have + current sessions for. If it cannot obtain an account + selection choice made by the End-User, it MUST return an + error, typically account_selection_required. + + The prompt parameter can be used by the Client to make sure + that the End-User is still present for the current session or + to bring attention to the request. If this parameter contains + none with any other value, an error is returned. + + max_age + OPTIONAL. Maximum Authentication Age. Specifies the allowable + elapsed time in seconds since the last time the End-User was + actively authenticated by the OP. If the elapsed time is + greater than this value, the OP MUST attempt to actively + re-authenticate the End-User. (The max_age request parameter + corresponds to the OpenID 2.0 PAPE [OpenID.PAPE] max_auth_age + request parameter.) When max_age is used, the ID Token returned + MUST include an auth_time Claim Value. + + ui_locales + OPTIONAL. End-User's preferred languages and scripts for the + user interface, represented as a space-separated list of BCP47 + [RFC5646] language tag values, ordered by preference. For + instance, the value "fr-CA fr en" represents a preference for + French as spoken in Canada, then French (without a region + designation), followed by English (without a region + designation). An error SHOULD NOT result if some or all of the + requested locales are not supported by the OpenID Provider. + + id_token_hint + OPTIONAL. ID Token previously issued by the Authorization + Server being passed as a hint about the End-User's current or + past authenticated session with the Client. If the End-User + identified by the ID Token is logged in or is logged in by the + request, then the Authorization Server returns a positive + response; otherwise, it SHOULD return an error, such as + login_required. When possible, an id_token_hint SHOULD be + present when prompt=none is used and an invalid_request error + MAY be returned if it is not; however, the server SHOULD + respond successfully when possible, even if it is not present. + The Authorization Server need not be listed as an audience of + the ID Token when it is used as an id_token_hint value. If the + ID Token received by the RP from the OP is encrypted, to use it + as an id_token_hint, the Client MUST decrypt the signed ID + Token contained within the encrypted ID Token. The Client MAY + re-encrypt the signed ID token to the Authentication Server + using a key that enables the server to decrypt the ID Token, + and use the re-encrypted ID token as the id_token_hint value. + + login_hint + OPTIONAL. Hint to the Authorization Server about the login + identifier the End-User might use to log in (if necessary). + This hint can be used by an RP if it first asks the End-User + for their e-mail address (or other identifier) and then wants + to pass that value as a hint to the discovered authorization + service. It is RECOMMENDED that the hint value match the value + used for discovery. This value MAY also be a phone number in + the format specified for the phone_number Claim. The use of + this parameter is left to the OP's discretion. + + acr_values + OPTIONAL. Requested Authentication Context Class Reference + values. Space-separated string that specifies the acr values + that the Authorization Server is being requested to use for + processing this Authentication Request, with the values + appearing in order of preference. The Authentication Context + Class satisfied by the authentication performed is returned as + the acr Claim Value, as specified in Section 2. The acr Claim + is requested as a Voluntary Claim by this parameter. + """ + + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: + return {} + + prompt = request.prompt if request.prompt else [] + if hasattr(prompt, 'split'): + prompt = prompt.strip().split() + prompt = set(prompt) + + if 'none' in prompt: + + if len(prompt) > 1: + msg = "Prompt none is mutually exclusive with other values." + raise InvalidRequestError(request=request, description=msg) + + # prompt other than 'none' should be handled by the server code that + # uses oauthlib + if not request.id_token_hint: + msg = "Prompt is set to none yet id_token_hint is missing." + raise InvalidRequestError(request=request, description=msg) + + if not self.request_validator.validate_silent_login(request): + raise LoginRequired(request=request) + + if not self.request_validator.validate_silent_authorization(request): + raise ConsentRequired(request=request) + + self._inflate_claims(request) + + if not self.request_validator.validate_user_match( + request.id_token_hint, request.scopes, request.claims, request): + msg = "Session user does not match client supplied user." + raise LoginRequired(request=request, description=msg) + + request_info = { + 'display': request.display, + 'nonce': request.nonce, + 'prompt': prompt, + 'ui_locales': request.ui_locales.split() if request.ui_locales else [], + 'id_token_hint': request.id_token_hint, + 'login_hint': request.login_hint, + 'claims': request.claims + } + + return request_info + + def openid_implicit_authorization_validator(self, request): + """Additional validation when following the implicit flow. + """ + # Undefined in OpenID Connect, fall back to OAuth2 definition. + if request.response_type == 'token': + return {} + + # Treat it as normal OAuth 2 auth code request if openid is not present + if not request.scopes or 'openid' not in request.scopes: + return {} + + # REQUIRED. String value used to associate a Client session with an ID + # Token, and to mitigate replay attacks. The value is passed through + # unmodified from the Authentication Request to the ID Token. + # Sufficient entropy MUST be present in the nonce values used to + # prevent attackers from guessing values. For implementation notes, see + # Section 15.5.2. + if not request.nonce: + desc = 'Request is missing mandatory nonce parameter.' + raise InvalidRequestError(request=request, description=desc) + + return {} + + +class OpenIDConnectAuthCode(OpenIDConnectBase): + + def __init__(self, request_validator=None, **kwargs): + self.proxy_target = AuthorizationCodeGrant( + request_validator=request_validator, **kwargs) + self.custom_validators.post_auth.append( + self.openid_authorization_validator) + self.register_token_modifier(self.add_id_token) + +class OpenIDConnectImplicit(OpenIDConnectBase): + + def __init__(self, request_validator=None, **kwargs): + self.proxy_target = ImplicitGrant( + request_validator=request_validator, **kwargs) + self.register_response_type('id_token') + self.register_response_type('id_token token') + self.custom_validators.post_auth.append( + self.openid_authorization_validator) + self.custom_validators.post_auth.append( + self.openid_implicit_authorization_validator) + self.register_token_modifier(self.add_id_token) + +class OpenIDConnectHybrid(OpenIDConnectBase): + + def __init__(self, request_validator=None, **kwargs): + self.request_validator = request_validator or RequestValidator() + + self.proxy_target = AuthorizationCodeGrant( + request_validator=request_validator, **kwargs) + # All hybrid response types should be fragment-encoded. + self.proxy_target.default_response_mode = "fragment" + self.register_response_type('code id_token') + self.register_response_type('code token') + self.register_response_type('code id_token token') + self.custom_validators.post_auth.append( + self.openid_authorization_validator) + # Hybrid flows can return the id_token from the authorization + # endpoint as part of the 'code' response + self.register_code_modifier(self.add_token) + self.register_code_modifier(self.add_id_token) + self.register_token_modifier(self.add_id_token) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py new file mode 100644 index 0000000..6233e7c --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/refresh_token.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from .. import errors, utils +from ..request_validator import RequestValidator +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class RefreshTokenGrant(GrantTypeBase): + + """`Refresh token grant`_ + + .. _`Refresh token grant`: http://tools.ietf.org/html/rfc6749#section-6 + """ + + def __init__(self, request_validator=None, + issue_new_refresh_tokens=True, + **kwargs): + super(RefreshTokenGrant, self).__init__( + request_validator, + issue_new_refresh_tokens=issue_new_refresh_tokens, + **kwargs) + + def create_token_response(self, request, token_handler): + """Create a new access token from a refresh_token. + + If valid and authorized, the authorization server issues an access + token as described in `Section 5.1`_. If the request failed + verification or is invalid, the authorization server returns an error + response as described in `Section 5.2`_. + + The authorization server MAY issue a new refresh token, in which case + the client MUST discard the old refresh token and replace it with the + new refresh token. The authorization server MAY revoke the old + refresh token after issuing a new refresh token to the client. If a + new refresh token is issued, the refresh token scope MUST be + identical to that of the refresh token included by the client in the + request. + + .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1 + .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2 + """ + headers = { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + 'Pragma': 'no-cache', + } + try: + log.debug('Validating refresh token request, %r.', request) + self.validate_token_request(request) + except errors.OAuth2Error as e: + return headers, e.json, e.status_code + + token = token_handler.create_token(request, + refresh_token=self.issue_new_refresh_tokens, save_token=False) + + for modifier in self._token_modifiers: + token = modifier(token) + self.request_validator.save_token(token, request) + + log.debug('Issuing new token to client id %r (%r), %r.', + request.client_id, request.client, token) + return headers, json.dumps(token), 200 + + def validate_token_request(self, request): + # REQUIRED. Value MUST be set to "refresh_token". + if request.grant_type != 'refresh_token': + raise errors.UnsupportedGrantTypeError(request=request) + + for validator in self.custom_validators.pre_token: + validator(request) + + if request.refresh_token is None: + raise errors.InvalidRequestError( + description='Missing refresh token parameter.', + request=request) + + # Because refresh tokens are typically long-lasting credentials used to + # request additional access tokens, the refresh token is bound to the + # client to which it was issued. If the client type is confidential or + # the client was issued client credentials (or assigned other + # authentication requirements), the client MUST authenticate with the + # authorization server as described in Section 3.2.1. + # http://tools.ietf.org/html/rfc6749#section-3.2.1 + if self.request_validator.client_authentication_required(request): + log.debug('Authenticating client, %r.', request) + if not self.request_validator.authenticate_client(request): + log.debug('Invalid client (%r), denying access.', request) + raise errors.InvalidClientError(request=request) + elif not self.request_validator.authenticate_client_id(request.client_id, request): + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + + # Ensure client is authorized use of this grant type + self.validate_grant_type(request) + + # REQUIRED. The refresh token issued to the client. + log.debug('Validating refresh token %s for client %r.', + request.refresh_token, request.client) + if not self.request_validator.validate_refresh_token( + request.refresh_token, request.client, request): + log.debug('Invalid refresh token, %s, for client %r.', + request.refresh_token, request.client) + raise errors.InvalidGrantError(request=request) + + original_scopes = utils.scope_to_list( + self.request_validator.get_original_scopes( + request.refresh_token, request)) + + if request.scope: + request.scopes = utils.scope_to_list(request.scope) + if (not all((s in original_scopes for s in request.scopes)) + and not self.request_validator.is_within_original_scope( + request.scopes, request.refresh_token, request)): + log.debug('Refresh token %s lack requested scopes, %r.', + request.refresh_token, request.scopes) + raise errors.InvalidScopeError(request=request) + else: + request.scopes = original_scopes + + for validator in self.custom_validators.post_token: + validator(request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py new file mode 100644 index 0000000..ede779a --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import json +import logging + +from .. import errors +from ..request_validator import RequestValidator +from .base import GrantTypeBase + +log = logging.getLogger(__name__) + + +class ResourceOwnerPasswordCredentialsGrant(GrantTypeBase): + + """`Resource Owner Password Credentials Grant`_ + + The resource owner password credentials grant type is suitable in + cases where the resource owner has a trust relationship with the + client, such as the device operating system or a highly privileged + application. The authorization server should take special care when + enabling this grant type and only allow it when other flows are not + viable. + + This grant type is suitable for clients capable of obtaining the + resource owner's credentials (username and password, typically using + an interactive form). It is also used to migrate existing clients + using direct authentication schemes such as HTTP Basic or Digest + authentication to OAuth by converting the stored credentials to an + access token:: + + +----------+ + | Resource | + | Owner | + | | + +----------+ + v + | Resource Owner + (A) Password Credentials + | + v + +---------+ +---------------+ + | |>--(B)---- Resource Owner ------->| | + | | Password Credentials | Authorization | + | Client | | Server | + | |<--(C)---- Access Token ---------<| | + | | (w/ Optional Refresh Token) | | + +---------+ +---------------+ + + Figure 5: Resource Owner Password Credentials Flow + + The flow illustrated in Figure 5 includes the following steps: + + (A) The resource owner provides the client with its username and + password. + + (B) The client requests an access token from the authorization + server's token endpoint by including the credentials received + from the resource owner. When making the request, the client + authenticates with the authorization server. + + (C) The authorization server authenticates the client and validates + the resource owner credentials, and if valid, issues an access + token. + + .. _`Resource Owner Password Credentials Grant`: http://tools.ietf.org/html/rfc6749#section-4.3 + """ + + def create_token_response(self, request, token_handler): + """Return token or error in json format. + + If the access token request is valid and authorized, the + authorization server issues an access token and optional refresh + token as described in `Section 5.1`_. If the request failed client + authentication or is invalid, the authorization server returns an + error response as described in `Section 5.2`_. + + .. _`Section 5.1`: http://tools.ietf.org/html/rfc6749#section-5.1 + .. _`Section 5.2`: http://tools.ietf.org/html/rfc6749#section-5.2 + """ + headers = { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-store', + 'Pragma': 'no-cache', + } + try: + if self.request_validator.client_authentication_required(request): + log.debug('Authenticating client, %r.', request) + if not self.request_validator.authenticate_client(request): + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + elif not self.request_validator.authenticate_client_id(request.client_id, request): + log.debug('Client authentication failed, %r.', request) + raise errors.InvalidClientError(request=request) + log.debug('Validating access token request, %r.', request) + self.validate_token_request(request) + except errors.OAuth2Error as e: + log.debug('Client error in token request, %s.', e) + return headers, e.json, e.status_code + + token = token_handler.create_token(request, self.refresh_token, save_token=False) + + for modifier in self._token_modifiers: + token = modifier(token) + self.request_validator.save_token(token, request) + + log.debug('Issuing token %r to client id %r (%r) and username %s.', + token, request.client_id, request.client, request.username) + return headers, json.dumps(token), 200 + + def validate_token_request(self, request): + """ + The client makes a request to the token endpoint by adding the + following parameters using the "application/x-www-form-urlencoded" + format per Appendix B with a character encoding of UTF-8 in the HTTP + request entity-body: + + grant_type + REQUIRED. Value MUST be set to "password". + + username + REQUIRED. The resource owner username. + + password + REQUIRED. The resource owner password. + + scope + OPTIONAL. The scope of the access request as described by + `Section 3.3`_. + + If the client type is confidential or the client was issued client + credentials (or assigned other authentication requirements), the + client MUST authenticate with the authorization server as described + in `Section 3.2.1`_. + + The authorization server MUST: + + o require client authentication for confidential clients or for any + client that was issued client credentials (or with other + authentication requirements), + + o authenticate the client if client authentication is included, and + + o validate the resource owner password credentials using its + existing password validation algorithm. + + Since this access token request utilizes the resource owner's + password, the authorization server MUST protect the endpoint against + brute force attacks (e.g., using rate-limitation or generating + alerts). + + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`Section 3.2.1`: http://tools.ietf.org/html/rfc6749#section-3.2.1 + """ + for validator in self.custom_validators.pre_token: + validator(request) + + for param in ('grant_type', 'username', 'password'): + if not getattr(request, param, None): + raise errors.InvalidRequestError( + 'Request is missing %s parameter.' % param, request=request) + + for param in ('grant_type', 'username', 'password', 'scope'): + if param in request.duplicate_params: + raise errors.InvalidRequestError(description='Duplicate %s parameter.' % param, request=request) + + # This error should rarely (if ever) occur if requests are routed to + # grant type handlers based on the grant_type parameter. + if not request.grant_type == 'password': + raise errors.UnsupportedGrantTypeError(request=request) + + log.debug('Validating username %s.', request.username) + if not self.request_validator.validate_user(request.username, + request.password, request.client, request): + raise errors.InvalidGrantError( + 'Invalid credentials given.', request=request) + else: + if not hasattr(request.client, 'client_id'): + raise NotImplementedError( + 'Validate user must set the ' + 'request.client.client_id attribute ' + 'in authenticate_client.') + log.debug('Authorizing access to user %r.', request.user) + + # Ensure client is authorized use of this grant type + self.validate_grant_type(request) + + if request.client: + request.client_id = request.client_id or request.client.client_id + self.validate_scopes(request) + + for validator in self.custom_validators.post_token: + validator(request) diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py new file mode 100644 index 0000000..b87b146 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/parameters.py @@ -0,0 +1,409 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains methods related to `Section 4`_ of the OAuth 2 RFC. + +.. _`Section 4`: http://tools.ietf.org/html/rfc6749#section-4 +""" +from __future__ import absolute_import, unicode_literals + +import json +import os +import time + +from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type +from oauthlib.signals import scope_changed + +from .errors import (InsecureTransportError, MismatchingStateError, + MissingCodeError, MissingTokenError, + MissingTokenTypeError, raise_from_error) +from .tokens import OAuth2Token +from .utils import is_secure_transport, list_to_scope, scope_to_list + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + + +def prepare_grant_uri(uri, client_id, response_type, redirect_uri=None, + scope=None, state=None, **kwargs): + """Prepare the authorization grant request URI. + + The client constructs the request URI by adding the following + parameters to the query component of the authorization endpoint URI + using the ``application/x-www-form-urlencoded`` format as defined by + [`W3C.REC-html401-19991224`_]: + + :param response_type: To indicate which OAuth 2 grant/flow is required, + "code" and "token". + :param client_id: The client identifier as described in `Section 2.2`_. + :param redirect_uri: The client provided URI to redirect back to after + authorization as described in `Section 3.1.2`_. + :param scope: The scope of the access request as described by + `Section 3.3`_. + + :param state: An opaque value used by the client to maintain + state between the request and callback. The authorization + server includes this value when redirecting the user-agent + back to the client. The parameter SHOULD be used for + preventing cross-site request forgery as described in + `Section 10.12`_. + :param kwargs: Extra arguments to embed in the grant/authorization URL. + + An example of an authorization code grant authorization URL: + + .. code-block:: http + + GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz + &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1 + Host: server.example.com + + .. _`W3C.REC-html401-19991224`: http://tools.ietf.org/html/rfc6749#ref-W3C.REC-html401-19991224 + .. _`Section 2.2`: http://tools.ietf.org/html/rfc6749#section-2.2 + .. _`Section 3.1.2`: http://tools.ietf.org/html/rfc6749#section-3.1.2 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`section 10.12`: http://tools.ietf.org/html/rfc6749#section-10.12 + """ + if not is_secure_transport(uri): + raise InsecureTransportError() + + params = [(('response_type', response_type)), + (('client_id', client_id))] + + if redirect_uri: + params.append(('redirect_uri', redirect_uri)) + if scope: + params.append(('scope', list_to_scope(scope))) + if state: + params.append(('state', state)) + + for k in kwargs: + if kwargs[k]: + params.append((unicode_type(k), kwargs[k])) + + return add_params_to_uri(uri, params) + + +def prepare_token_request(grant_type, body='', **kwargs): + """Prepare the access token request. + + The client makes a request to the token endpoint by adding the + following parameters using the ``application/x-www-form-urlencoded`` + format in the HTTP request entity-body: + + :param grant_type: To indicate grant type being used, i.e. "password", + "authorization_code" or "client_credentials". + :param body: Existing request body to embed parameters in. + :param code: If using authorization code grant, pass the previously + obtained authorization code as the ``code`` argument. + :param redirect_uri: If the "redirect_uri" parameter was included in the + authorization request as described in + `Section 4.1.1`_, and their values MUST be identical. + :param kwargs: Extra arguments to embed in the request body. + + An example of an authorization code token request body: + + .. code-block:: http + + grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA + &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb + + .. _`Section 4.1.1`: http://tools.ietf.org/html/rfc6749#section-4.1.1 + """ + params = [('grant_type', grant_type)] + + if 'scope' in kwargs: + kwargs['scope'] = list_to_scope(kwargs['scope']) + + for k in kwargs: + if kwargs[k]: + params.append((unicode_type(k), kwargs[k])) + + return add_params_to_qs(body, params) + + +def prepare_token_revocation_request(url, token, token_type_hint="access_token", + callback=None, body='', **kwargs): + """Prepare a token revocation request. + + The client constructs the request by including the following parameters + using the "application/x-www-form-urlencoded" format in the HTTP request + entity-body: + + token REQUIRED. The token that the client wants to get revoked. + + token_type_hint OPTIONAL. A hint about the type of the token submitted + for revocation. Clients MAY pass this parameter in order to help the + authorization server to optimize the token lookup. If the server is unable + to locate the token using the given hint, it MUST extend its search across + all of its supported token types. An authorization server MAY ignore this + parameter, particularly if it is able to detect the token type + automatically. This specification defines two such values: + + * access_token: An access token as defined in [RFC6749], + `Section 1.4`_ + + * refresh_token: A refresh token as defined in [RFC6749], + `Section 1.5`_ + + Specific implementations, profiles, and extensions of this + specification MAY define other values for this parameter using the + registry defined in `Section 4.1.2`_. + + .. _`Section 1.4`: http://tools.ietf.org/html/rfc6749#section-1.4 + .. _`Section 1.5`: http://tools.ietf.org/html/rfc6749#section-1.5 + .. _`Section 4.1.2`: http://tools.ietf.org/html/rfc7009#section-4.1.2 + + """ + if not is_secure_transport(url): + raise InsecureTransportError() + + params = [('token', token)] + + if token_type_hint: + params.append(('token_type_hint', token_type_hint)) + + for k in kwargs: + if kwargs[k]: + params.append((unicode_type(k), kwargs[k])) + + headers = {'Content-Type': 'application/x-www-form-urlencoded'} + + if callback: + params.append(('callback', callback)) + return add_params_to_uri(url, params), headers, body + else: + return url, headers, add_params_to_qs(body, params) + + +def parse_authorization_code_response(uri, state=None): + """Parse authorization grant response URI into a dict. + + If the resource owner grants the access request, the authorization + server issues an authorization code and delivers it to the client by + adding the following parameters to the query component of the + redirection URI using the ``application/x-www-form-urlencoded`` format: + + **code** + REQUIRED. The authorization code generated by the + authorization server. The authorization code MUST expire + shortly after it is issued to mitigate the risk of leaks. A + maximum authorization code lifetime of 10 minutes is + RECOMMENDED. The client MUST NOT use the authorization code + more than once. If an authorization code is used more than + once, the authorization server MUST deny the request and SHOULD + revoke (when possible) all tokens previously issued based on + that authorization code. The authorization code is bound to + the client identifier and redirection URI. + + **state** + REQUIRED if the "state" parameter was present in the client + authorization request. The exact value received from the + client. + + :param uri: The full redirect URL back to the client. + :param state: The state parameter from the authorization request. + + For example, the authorization server redirects the user-agent by + sending the following HTTP response: + + .. code-block:: http + + HTTP/1.1 302 Found + Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA + &state=xyz + + """ + if not is_secure_transport(uri): + raise InsecureTransportError() + + query = urlparse.urlparse(uri).query + params = dict(urlparse.parse_qsl(query)) + + if not 'code' in params: + raise MissingCodeError("Missing code parameter in response.") + + if state and params.get('state', None) != state: + raise MismatchingStateError() + + return params + + +def parse_implicit_response(uri, state=None, scope=None): + """Parse the implicit token response URI into a dict. + + If the resource owner grants the access request, the authorization + server issues an access token and delivers it to the client by adding + the following parameters to the fragment component of the redirection + URI using the ``application/x-www-form-urlencoded`` format: + + **access_token** + REQUIRED. The access token issued by the authorization server. + + **token_type** + REQUIRED. The type of the token issued as described in + Section 7.1. Value is case insensitive. + + **expires_in** + RECOMMENDED. The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + + **scope** + OPTIONAL, if identical to the scope requested by the client, + otherwise REQUIRED. The scope of the access token as described + by Section 3.3. + + **state** + REQUIRED if the "state" parameter was present in the client + authorization request. The exact value received from the + client. + + Similar to the authorization code response, but with a full token provided + in the URL fragment: + + .. code-block:: http + + HTTP/1.1 302 Found + Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA + &state=xyz&token_type=example&expires_in=3600 + """ + if not is_secure_transport(uri): + raise InsecureTransportError() + + fragment = urlparse.urlparse(uri).fragment + params = dict(urlparse.parse_qsl(fragment, keep_blank_values=True)) + + if 'scope' in params: + params['scope'] = scope_to_list(params['scope']) + + if 'expires_in' in params: + params['expires_at'] = time.time() + int(params['expires_in']) + + if state and params.get('state', None) != state: + raise ValueError("Mismatching or missing state in params.") + + params = OAuth2Token(params, old_scope=scope) + validate_token_parameters(params) + return params + + +def parse_token_response(body, scope=None): + """Parse the JSON token response body into a dict. + + The authorization server issues an access token and optional refresh + token, and constructs the response by adding the following parameters + to the entity body of the HTTP response with a 200 (OK) status code: + + access_token + REQUIRED. The access token issued by the authorization server. + token_type + REQUIRED. The type of the token issued as described in + `Section 7.1`_. Value is case insensitive. + expires_in + RECOMMENDED. The lifetime in seconds of the access token. For + example, the value "3600" denotes that the access token will + expire in one hour from the time the response was generated. + If omitted, the authorization server SHOULD provide the + expiration time via other means or document the default value. + refresh_token + OPTIONAL. The refresh token which can be used to obtain new + access tokens using the same authorization grant as described + in `Section 6`_. + scope + OPTIONAL, if identical to the scope requested by the client, + otherwise REQUIRED. The scope of the access token as described + by `Section 3.3`_. + + The parameters are included in the entity body of the HTTP response + using the "application/json" media type as defined by [`RFC4627`_]. The + parameters are serialized into a JSON structure by adding each + parameter at the highest structure level. Parameter names and string + values are included as JSON strings. Numerical values are included + as JSON numbers. The order of parameters does not matter and can + vary. + + :param body: The full json encoded response body. + :param scope: The scope requested during authorization. + + For example: + + .. code-block:: http + + HTTP/1.1 200 OK + Content-Type: application/json + Cache-Control: no-store + Pragma: no-cache + + { + "access_token":"2YotnFZFEjr1zCsicMWpAA", + "token_type":"example", + "expires_in":3600, + "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", + "example_parameter":"example_value" + } + + .. _`Section 7.1`: http://tools.ietf.org/html/rfc6749#section-7.1 + .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6 + .. _`Section 3.3`: http://tools.ietf.org/html/rfc6749#section-3.3 + .. _`RFC4627`: http://tools.ietf.org/html/rfc4627 + """ + try: + params = json.loads(body) + except ValueError: + + # Fall back to URL-encoded string, to support old implementations, + # including (at time of writing) Facebook. See: + # https://github.com/idan/oauthlib/issues/267 + + params = dict(urlparse.parse_qsl(body)) + for key in ('expires_in', 'expires'): + if key in params: # cast a couple things to int + params[key] = int(params[key]) + + if 'scope' in params: + params['scope'] = scope_to_list(params['scope']) + + if 'expires' in params: + params['expires_in'] = params.pop('expires') + + if 'expires_in' in params: + params['expires_at'] = time.time() + int(params['expires_in']) + + params = OAuth2Token(params, old_scope=scope) + validate_token_parameters(params) + return params + + +def validate_token_parameters(params): + """Ensures token precence, token type, expiration and scope in params.""" + if 'error' in params: + raise_from_error(params.get('error'), params) + + if not 'access_token' in params: + raise MissingTokenError(description="Missing access token parameter.") + + if not 'token_type' in params: + if os.environ.get('OAUTHLIB_STRICT_TOKEN_TYPE'): + raise MissingTokenTypeError() + + # If the issued access token scope is different from the one requested by + # the client, the authorization server MUST include the "scope" response + # parameter to inform the client of the actual scope granted. + # http://tools.ietf.org/html/rfc6749#section-3.3 + if params.scope_changed: + message = 'Scope has changed from "{old}" to "{new}".'.format( + old=params.old_scope, new=params.scope, + ) + scope_changed.send(message=message, old=params.old_scopes, new=params.scopes) + if not os.environ.get('OAUTHLIB_RELAX_TOKEN_SCOPE', None): + w = Warning(message) + w.token = params + w.old_scope = params.old_scopes + w.new_scope = params.scopes + raise w diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/request_validator.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/request_validator.py new file mode 100644 index 0000000..0adfa1b --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/request_validator.py @@ -0,0 +1,573 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.oauth2.rfc6749.grant_types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +""" +from __future__ import absolute_import, unicode_literals + +import logging + +log = logging.getLogger(__name__) + + +class RequestValidator(object): + + def client_authentication_required(self, request, *args, **kwargs): + """Determine if client authentication is required for current request. + + According to the rfc6749, client authentication is required in the following cases: + - Resource Owner Password Credentials Grant, when Client type is Confidential or when + Client was issued client credentials or whenever Client provided client + authentication, see `Section 4.3.2`_. + - Authorization Code Grant, when Client type is Confidential or when Client was issued + client credentials or whenever Client provided client authentication, + see `Section 4.1.3`_. + - Refresh Token Grant, when Client type is Confidential or when Client was issued + client credentials or whenever Client provided client authentication, see + `Section 6`_ + + :param request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Resource Owner Password Credentials Grant + - Refresh Token Grant + + .. _`Section 4.3.2`: http://tools.ietf.org/html/rfc6749#section-4.3.2 + .. _`Section 4.1.3`: http://tools.ietf.org/html/rfc6749#section-4.1.3 + .. _`Section 6`: http://tools.ietf.org/html/rfc6749#section-6 + """ + return True + + def authenticate_client(self, request, *args, **kwargs): + """Authenticate client through means outside the OAuth 2 spec. + + Means of authentication is negotiated beforehand and may for example + be `HTTP Basic Authentication Scheme`_ which utilizes the Authorization + header. + + Headers may be accesses through request.headers and parameters found in + both body and query can be obtained by direct attribute access, i.e. + request.client_id for client_id in the URL query. + + :param request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Resource Owner Password Credentials Grant (may be disabled) + - Client Credentials Grant + - Refresh Token Grant + + .. _`HTTP Basic Authentication Scheme`: http://tools.ietf.org/html/rfc1945#section-11.1 + """ + raise NotImplementedError('Subclasses must implement this method.') + + def authenticate_client_id(self, client_id, request, *args, **kwargs): + """Ensure client_id belong to a non-confidential client. + + A non-confidential client is one that is not required to authenticate + through other means, such as using HTTP Basic. + + Note, while not strictly necessary it can often be very convenient + to set request.client to the client object associated with the + given client_id. + + :param request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + - Authorization Code Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def confirm_redirect_uri(self, client_id, code, redirect_uri, client, + *args, **kwargs): + """Ensure that the authorization process represented by this authorization + code began with this 'redirect_uri'. + + If the client specifies a redirect_uri when obtaining code then that + redirect URI must be bound to the code and verified equal in this + method, according to RFC 6749 section 4.1.3. Do not compare against + the client's allowed redirect URIs, but against the URI used when the + code was saved. + + :param client_id: Unicode client identifier + :param code: Unicode authorization_code. + :param redirect_uri: Unicode absolute URI + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant (during token request) + """ + raise NotImplementedError('Subclasses must implement this method.') + + def get_default_redirect_uri(self, client_id, request, *args, **kwargs): + """Get the default redirect URI for the client. + + :param client_id: Unicode client identifier + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: The default redirect URI for the client + + Method is used by: + - Authorization Code Grant + - Implicit Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def get_default_scopes(self, client_id, request, *args, **kwargs): + """Get the default scopes for the client. + + :param client_id: Unicode client identifier + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: List of default scopes + + Method is used by all core grant types: + - Authorization Code Grant + - Implicit Grant + - Resource Owner Password Credentials Grant + - Client Credentials grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def get_original_scopes(self, refresh_token, request, *args, **kwargs): + """Get the list of scopes associated with the refresh token. + + :param refresh_token: Unicode refresh token + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: List of scopes. + + Method is used by: + - Refresh token grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def is_within_original_scope(self, request_scopes, refresh_token, request, *args, **kwargs): + """Check if requested scopes are within a scope of the refresh token. + + When access tokens are refreshed the scope of the new token + needs to be within the scope of the original token. This is + ensured by checking that all requested scopes strings are on + the list returned by the get_original_scopes. If this check + fails, is_within_original_scope is called. The method can be + used in situations where returning all valid scopes from the + get_original_scopes is not practical. + + :param request_scopes: A list of scopes that were requested by client + :param refresh_token: Unicode refresh_token + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Refresh token grant + """ + return False + + def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs): + """Invalidate an authorization code after use. + + :param client_id: Unicode client identifier + :param code: The authorization code grant (request.code). + :param request: The HTTP Request (oauthlib.common.Request) + + Method is used by: + - Authorization Code Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def revoke_token(self, token, token_type_hint, request, *args, **kwargs): + """Revoke an access or refresh token. + + :param token: The token string. + :param token_type_hint: access_token or refresh_token. + :param request: The HTTP Request (oauthlib.common.Request) + + Method is used by: + - Revocation Endpoint + """ + raise NotImplementedError('Subclasses must implement this method.') + + def rotate_refresh_token(self, request): + """Determine whether to rotate the refresh token. Default, yes. + + When access tokens are refreshed the old refresh token can be kept + or replaced with a new one (rotated). Return True to rotate and + and False for keeping original. + + :param request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + - Refresh Token Grant + """ + return True + + def save_authorization_code(self, client_id, code, request, *args, **kwargs): + """Persist the authorization_code. + + The code should at minimum be stored with: + - the client_id (client_id) + - the redirect URI used (request.redirect_uri) + - a resource owner / user (request.user) + - the authorized scopes (request.scopes) + - the client state, if given (code.get('state')) + + The 'code' argument is actually a dictionary, containing at least a + 'code' key with the actual authorization code: + + {'code': 'sdf345jsdf0934f'} + + It may also have a 'state' key containing a nonce for the client, if it + chose to send one. That value should be saved and used in + 'validate_code'. + + It may also have a 'claims' parameter which, when present, will be a dict + deserialized from JSON as described at + http://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter + This value should be saved in this method and used again in 'validate_code'. + + :param client_id: Unicode client identifier + :param code: A dict of the authorization code grant and, optionally, state. + :param request: The HTTP Request (oauthlib.common.Request) + + Method is used by: + - Authorization Code Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def save_token(self, token, request, *args, **kwargs): + """Persist the token with a token type specific method. + + Currently, only save_bearer_token is supported. + """ + return self.save_bearer_token(token, request, *args, **kwargs) + + def save_bearer_token(self, token, request, *args, **kwargs): + """Persist the Bearer token. + + The Bearer token should at minimum be associated with: + - a client and it's client_id, if available + - a resource owner / user (request.user) + - authorized scopes (request.scopes) + - an expiration time + - a refresh token, if issued + - a claims document, if present in request.claims + + The Bearer token dict may hold a number of items:: + + { + 'token_type': 'Bearer', + 'access_token': 'askfjh234as9sd8', + 'expires_in': 3600, + 'scope': 'string of space separated authorized scopes', + 'refresh_token': '23sdf876234', # if issued + 'state': 'given_by_client', # if supplied by client + } + + Note that while "scope" is a string-separated list of authorized scopes, + the original list is still available in request.scopes + + Also note that if an Authorization Code grant request included a valid claims + parameter (for OpenID Connect) then the request.claims property will contain + the claims dict, which should be saved for later use when generating the + id_token and/or UserInfo response content. + + :param client_id: Unicode client identifier + :param token: A Bearer token dict + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: The default redirect URI for the client + + Method is used by all core grant types issuing Bearer tokens: + - Authorization Code Grant + - Implicit Grant + - Resource Owner Password Credentials Grant (might not associate a client) + - Client Credentials grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def get_id_token(self, token, token_handler, request): + """ + In the OpenID Connect workflows when an ID Token is requested this method is called. + Subclasses should implement the construction, signing and optional encryption of the + ID Token as described in the OpenID Connect spec. + + In addition to the standard OAuth2 request properties, the request may also contain + these OIDC specific properties which are useful to this method: + + - nonce, if workflow is implicit or hybrid and it was provided + - claims, if provided to the original Authorization Code request + + The token parameter is a dict which may contain an ``access_token`` entry, in which + case the resulting ID Token *should* include a calculated ``at_hash`` claim. + + Similarly, when the request parameter has a ``code`` property defined, the ID Token + *should* include a calculated ``c_hash`` claim. + + http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_) + + .. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken + .. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken + .. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken + + :param token: A Bearer token dict + :param token_handler: the token handler (BearerToken class) + :param request: the HTTP Request (oauthlib.common.Request) + :return: The ID Token (a JWS signed JWT) + """ + # the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token + raise NotImplementedError('Subclasses must implement this method.') + + def validate_bearer_token(self, token, scopes, request): + """Ensure the Bearer token is valid and authorized access to scopes. + + :param token: A string of random characters. + :param scopes: A list of scopes associated with the protected resource. + :param request: The HTTP Request (oauthlib.common.Request) + + A key to OAuth 2 security and restricting impact of leaked tokens is + the short expiration time of tokens, *always ensure the token has not + expired!*. + + Two different approaches to scope validation: + + 1) all(scopes). The token must be authorized access to all scopes + associated with the resource. For example, the + token has access to ``read-only`` and ``images``, + thus the client can view images but not upload new. + Allows for fine grained access control through + combining various scopes. + + 2) any(scopes). The token must be authorized access to one of the + scopes associated with the resource. For example, + token has access to ``read-only-images``. + Allows for fine grained, although arguably less + convenient, access control. + + A powerful way to use scopes would mimic UNIX ACLs and see a scope + as a group with certain privileges. For a restful API these might + map to HTTP verbs instead of read, write and execute. + + Note, the request.user attribute can be set to the resource owner + associated with this token. Similarly the request.client and + request.scopes attribute can be set to associated client object + and authorized scopes. If you then use a decorator such as the + one provided for django these attributes will be made available + in all protected views as keyword arguments. + + :param token: Unicode Bearer token + :param scopes: List of scopes (defined by you) + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is indirectly used by all core Bearer token issuing grant types: + - Authorization Code Grant + - Implicit Grant + - Resource Owner Password Credentials Grant + - Client Credentials Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_client_id(self, client_id, request, *args, **kwargs): + """Ensure client_id belong to a valid and active client. + + Note, while not strictly necessary it can often be very convenient + to set request.client to the client object associated with the + given client_id. + + :param request: oauthlib.common.Request + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Implicit Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_code(self, client_id, code, client, request, *args, **kwargs): + """Verify that the authorization_code is valid and assigned to the given + client. + + Before returning true, set the following based on the information stored + with the code in 'save_authorization_code': + + - request.user + - request.state (if given) + - request.scopes + - request.claims (if given) + OBS! The request.user attribute should be set to the resource owner + associated with this authorization code. Similarly request.scopes + must also be set. + + The request.claims property, if it was given, should assigned a dict. + + :param client_id: Unicode client identifier + :param code: Unicode authorization code + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs): + """Ensure client is authorized to use the grant_type requested. + + :param client_id: Unicode client identifier + :param grant_type: Unicode grant type, i.e. authorization_code, password. + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Resource Owner Password Credentials Grant + - Client Credentials Grant + - Refresh Token Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_redirect_uri(self, client_id, redirect_uri, request, *args, **kwargs): + """Ensure client is authorized to redirect to the redirect_uri requested. + + All clients should register the absolute URIs of all URIs they intend + to redirect to. The registration is outside of the scope of oauthlib. + + :param client_id: Unicode client identifier + :param redirect_uri: Unicode absolute URI + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Implicit Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs): + """Ensure the Bearer token is valid and authorized access to scopes. + + OBS! The request.user attribute should be set to the resource owner + associated with this refresh token. + + :param refresh_token: Unicode refresh token + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant (indirectly by issuing refresh tokens) + - Resource Owner Password Credentials Grant (also indirectly) + - Refresh Token Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs): + """Ensure client is authorized to use the response_type requested. + + :param client_id: Unicode client identifier + :param response_type: Unicode response type, i.e. code, token. + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Authorization Code Grant + - Implicit Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): + """Ensure the client is authorized access to requested scopes. + + :param client_id: Unicode client identifier + :param scopes: List of scopes (defined by you) + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by all core grant types: + - Authorization Code Grant + - Implicit Grant + - Resource Owner Password Credentials Grant + - Client Credentials Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_silent_authorization(self, request): + """Ensure the logged in user has authorized silent OpenID authorization. + + Silent OpenID authorization allows access tokens and id tokens to be + granted to clients without any user prompt or interaction. + + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_silent_login(self, request): + """Ensure session user has authorized silent OpenID login. + + If no user is logged in or has not authorized silent login, this + method should return False. + + If the user is logged in but associated with multiple accounts and + not selected which one to link to the token then this method should + raise an oauthlib.oauth2.AccountSelectionRequired error. + + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_user(self, username, password, client, request, *args, **kwargs): + """Ensure the username and password is valid. + + OBS! The validation should also set the user attribute of the request + to a valid resource owner, i.e. request.user = username or similar. If + not set you will be unable to associate a token with a user in the + persistance method used (commonly, save_bearer_token). + + :param username: Unicode username + :param password: Unicode password + :param client: Client object set by you, see authenticate_client. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - Resource Owner Password Credentials Grant + """ + raise NotImplementedError('Subclasses must implement this method.') + + def validate_user_match(self, id_token_hint, scopes, claims, request): + """Ensure client supplied user id hint matches session user. + + If the sub claim or id_token_hint is supplied then the session + user must match the given ID. + + :param id_token_hint: User identifier string. + :param scopes: List of OAuth 2 scopes and OpenID claims (strings). + :param claims: OpenID Connect claims dict. + :param request: The HTTP Request (oauthlib.common.Request) + :rtype: True or False + + Method is used by: + - OpenIDConnectAuthCode + - OpenIDConnectImplicit + - OpenIDConnectHybrid + """ + raise NotImplementedError('Subclasses must implement this method.') diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py new file mode 100644 index 0000000..e0ac431 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/tokens.py @@ -0,0 +1,305 @@ +""" +oauthlib.oauth2.rfc6749.tokens +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains methods for adding two types of access tokens to requests. + +- Bearer http://tools.ietf.org/html/rfc6750 +- MAC http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 +""" +from __future__ import absolute_import, unicode_literals + +import hashlib +import hmac +from binascii import b2a_base64 + +from oauthlib import common +from oauthlib.common import add_params_to_qs, add_params_to_uri, unicode_type + +from . import utils + +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + + + + +class OAuth2Token(dict): + + def __init__(self, params, old_scope=None): + super(OAuth2Token, self).__init__(params) + self._new_scope = None + if 'scope' in params and params['scope']: + self._new_scope = set(utils.scope_to_list(params['scope'])) + if old_scope is not None: + self._old_scope = set(utils.scope_to_list(old_scope)) + if self._new_scope is None: + # the rfc says that if the scope hasn't changed, it's optional + # in params so set the new scope to the old scope + self._new_scope = self._old_scope + else: + self._old_scope = self._new_scope + + @property + def scope_changed(self): + return self._new_scope != self._old_scope + + @property + def old_scope(self): + return utils.list_to_scope(self._old_scope) + + @property + def old_scopes(self): + return list(self._old_scope) + + @property + def scope(self): + return utils.list_to_scope(self._new_scope) + + @property + def scopes(self): + return list(self._new_scope) + + @property + def missing_scopes(self): + return list(self._old_scope - self._new_scope) + + @property + def additional_scopes(self): + return list(self._new_scope - self._old_scope) + + +def prepare_mac_header(token, uri, key, http_method, + nonce=None, + headers=None, + body=None, + ext='', + hash_algorithm='hmac-sha-1', + issue_time=None, + draft=0): + """Add an `MAC Access Authentication`_ signature to headers. + + Unlike OAuth 1, this HMAC signature does not require inclusion of the + request payload/body, neither does it use a combination of client_secret + and token_secret but rather a mac_key provided together with the access + token. + + Currently two algorithms are supported, "hmac-sha-1" and "hmac-sha-256", + `extension algorithms`_ are not supported. + + Example MAC Authorization header, linebreaks added for clarity + + Authorization: MAC id="h480djs93hd8", + nonce="1336363200:dj83hs9s", + mac="bhCQXTVyfj5cmA9uKkPFx1zeOXM=" + + .. _`MAC Access Authentication`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01 + .. _`extension algorithms`: http://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-7.1 + + :param uri: Request URI. + :param headers: Request headers as a dictionary. + :param http_method: HTTP Request method. + :param key: MAC given provided by token endpoint. + :param hash_algorithm: HMAC algorithm provided by token endpoint. + :param issue_time: Time when the MAC credentials were issued (datetime). + :param draft: MAC authentication specification version. + :return: headers dictionary with the authorization field added. + """ + http_method = http_method.upper() + host, port = utils.host_from_uri(uri) + + if hash_algorithm.lower() == 'hmac-sha-1': + h = hashlib.sha1 + elif hash_algorithm.lower() == 'hmac-sha-256': + h = hashlib.sha256 + else: + raise ValueError('unknown hash algorithm') + + if draft == 0: + nonce = nonce or '{0}:{1}'.format(utils.generate_age(issue_time), + common.generate_nonce()) + else: + ts = common.generate_timestamp() + nonce = common.generate_nonce() + + sch, net, path, par, query, fra = urlparse(uri) + + if query: + request_uri = path + '?' + query + else: + request_uri = path + + # Hash the body/payload + if body is not None and draft == 0: + body = body.encode('utf-8') + bodyhash = b2a_base64(h(body).digest())[:-1].decode('utf-8') + else: + bodyhash = '' + + # Create the normalized base string + base = [] + if draft == 0: + base.append(nonce) + else: + base.append(ts) + base.append(nonce) + base.append(http_method.upper()) + base.append(request_uri) + base.append(host) + base.append(port) + if draft == 0: + base.append(bodyhash) + base.append(ext or '') + base_string = '\n'.join(base) + '\n' + + # hmac struggles with unicode strings - http://bugs.python.org/issue5285 + if isinstance(key, unicode_type): + key = key.encode('utf-8') + sign = hmac.new(key, base_string.encode('utf-8'), h) + sign = b2a_base64(sign.digest())[:-1].decode('utf-8') + + header = [] + header.append('MAC id="%s"' % token) + if draft != 0: + header.append('ts="%s"' % ts) + header.append('nonce="%s"' % nonce) + if bodyhash: + header.append('bodyhash="%s"' % bodyhash) + if ext: + header.append('ext="%s"' % ext) + header.append('mac="%s"' % sign) + + headers = headers or {} + headers['Authorization'] = ', '.join(header) + return headers + + +def prepare_bearer_uri(token, uri): + """Add a `Bearer Token`_ to the request URI. + Not recommended, use only if client can't use authorization header or body. + + http://www.example.com/path?access_token=h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750 + """ + return add_params_to_uri(uri, [(('access_token', token))]) + + +def prepare_bearer_headers(token, headers=None): + """Add a `Bearer Token`_ to the request URI. + Recommended method of passing bearer tokens. + + Authorization: Bearer h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750 + """ + headers = headers or {} + headers['Authorization'] = 'Bearer %s' % token + return headers + + +def prepare_bearer_body(token, body=''): + """Add a `Bearer Token`_ to the request body. + + access_token=h480djs93hd8 + + .. _`Bearer Token`: http://tools.ietf.org/html/rfc6750 + """ + return add_params_to_qs(body, [(('access_token', token))]) + + +def random_token_generator(request, refresh_token=False): + return common.generate_token() + + +def signed_token_generator(private_pem, **kwargs): + def signed_token_generator(request): + request.claims = kwargs + return common.generate_signed_token(private_pem, request) + + return signed_token_generator + + +class TokenBase(object): + + def __call__(self, request, refresh_token=False): + raise NotImplementedError('Subclasses must implement this method.') + + def validate_request(self, request): + raise NotImplementedError('Subclasses must implement this method.') + + def estimate_type(self, request): + raise NotImplementedError('Subclasses must implement this method.') + + +class BearerToken(TokenBase): + __slots__ = ( + 'request_validator', 'token_generator', + 'refresh_token_generator', 'expires_in' + ) + + def __init__(self, request_validator=None, token_generator=None, + expires_in=None, refresh_token_generator=None): + self.request_validator = request_validator + self.token_generator = token_generator or random_token_generator + self.refresh_token_generator = ( + refresh_token_generator or self.token_generator + ) + self.expires_in = expires_in or 3600 + + def create_token(self, request, refresh_token=False, save_token=True): + """Create a BearerToken, by default without refresh token.""" + + if callable(self.expires_in): + expires_in = self.expires_in(request) + else: + expires_in = self.expires_in + + request.expires_in = expires_in + + token = { + 'access_token': self.token_generator(request), + 'expires_in': expires_in, + 'token_type': 'Bearer', + } + + # If provided, include - this is optional in some cases https://tools.ietf.org/html/rfc6749#section-3.3 but + # there is currently no mechanism to coordinate issuing a token for only a subset of the requested scopes so + # all tokens issued are for the entire set of requested scopes. + if request.scopes is not None: + token['scope'] = ' '.join(request.scopes) + + if request.state is not None: + token['state'] = request.state + + if refresh_token: + if (request.refresh_token and + not self.request_validator.rotate_refresh_token(request)): + token['refresh_token'] = request.refresh_token + else: + token['refresh_token'] = self.refresh_token_generator(request) + + token.update(request.extra_credentials or {}) + token = OAuth2Token(token) + if save_token: + self.request_validator.save_bearer_token(token, request) + return token + + def validate_request(self, request): + token = None + if 'Authorization' in request.headers: + token = request.headers.get('Authorization')[7:] + else: + token = request.access_token + return self.request_validator.validate_bearer_token( + token, request.scopes, request) + + def estimate_type(self, request): + if request.headers.get('Authorization', '').startswith('Bearer'): + return 9 + elif request.access_token is not None: + return 5 + else: + return 0 diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/utils.py b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/utils.py new file mode 100644 index 0000000..f67019d --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/utils.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +""" +oauthlib.utils +~~~~~~~~~~~~~~ + +This module contains utility methods used by various parts of the OAuth 2 spec. +""" +from __future__ import absolute_import, unicode_literals + +import datetime +import os + +from oauthlib.common import unicode_type, urldecode + +try: + from urllib import quote +except ImportError: + from urllib.parse import quote +try: + from urlparse import urlparse +except ImportError: + from urllib.parse import urlparse + + +def list_to_scope(scope): + """Convert a list of scopes to a space separated string.""" + if isinstance(scope, unicode_type) or scope is None: + return scope + elif isinstance(scope, (set, tuple, list)): + return " ".join([unicode_type(s) for s in scope]) + else: + raise ValueError("Invalid scope (%s), must be string, tuple, set, or list." % scope) + + +def scope_to_list(scope): + """Convert a space separated string to a list of scopes.""" + if isinstance(scope, (tuple, list, set)): + return [unicode_type(s) for s in scope] + elif scope is None: + return None + else: + return scope.strip().split(" ") + + +def params_from_uri(uri): + params = dict(urldecode(urlparse(uri).query)) + if 'scope' in params: + params['scope'] = scope_to_list(params['scope']) + return params + + +def host_from_uri(uri): + """Extract hostname and port from URI. + + Will use default port for HTTP and HTTPS if none is present in the URI. + """ + default_ports = { + 'HTTP': '80', + 'HTTPS': '443', + } + + sch, netloc, path, par, query, fra = urlparse(uri) + if ':' in netloc: + netloc, port = netloc.split(':', 1) + else: + port = default_ports.get(sch.upper()) + + return netloc, port + + +def escape(u): + """Escape a string in an OAuth-compatible fashion. + + TODO: verify whether this can in fact be used for OAuth 2 + + """ + if not isinstance(u, unicode_type): + raise ValueError('Only unicode objects are escapable.') + return quote(u.encode('utf-8'), safe=b'~') + + +def generate_age(issue_time): + """Generate a age parameter for MAC authentication draft 00.""" + td = datetime.datetime.now() - issue_time + age = (td.microseconds + (td.seconds + td.days * 24 * 3600) + * 10 ** 6) / 10 ** 6 + return unicode_type(age) + + +def is_secure_transport(uri): + """Check if the uri is over ssl.""" + if os.environ.get('OAUTHLIB_INSECURE_TRANSPORT'): + return True + return uri.lower().startswith('https://') diff --git a/flex/lib/python3.6/site-packages/oauthlib/signals.py b/flex/lib/python3.6/site-packages/oauthlib/signals.py new file mode 100644 index 0000000..2f86650 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/signals.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +""" + Implements signals based on blinker if available, otherwise + falls silently back to a noop. Shamelessly stolen from flask.signals: + https://github.com/mitsuhiko/flask/blob/master/flask/signals.py +""" +signals_available = False +try: + from blinker import Namespace + signals_available = True +except ImportError: + class Namespace(object): + def signal(self, name, doc=None): + return _FakeSignal(name, doc) + + class _FakeSignal(object): + """If blinker is unavailable, create a fake class with the same + interface that allows sending of signals but will fail with an + error on anything else. Instead of doing anything on send, it + will just ignore the arguments and do nothing instead. + """ + + def __init__(self, name, doc=None): + self.name = name + self.__doc__ = doc + def _fail(self, *args, **kwargs): + raise RuntimeError('signalling support is unavailable ' + 'because the blinker library is ' + 'not installed.') + send = lambda *a, **kw: None + connect = disconnect = has_receivers_for = receivers_for = \ + temporarily_connected_to = connected_to = _fail + del _fail + +# The namespace for code signals. If you are not oauthlib code, do +# not put signals in here. Create your own namespace instead. +_signals = Namespace() + + +# Core signals. +scope_changed = _signals.signal('scope-changed') diff --git a/flex/lib/python3.6/site-packages/oauthlib/uri_validate.py b/flex/lib/python3.6/site-packages/oauthlib/uri_validate.py new file mode 100644 index 0000000..ce8ea40 --- /dev/null +++ b/flex/lib/python3.6/site-packages/oauthlib/uri_validate.py @@ -0,0 +1,216 @@ +""" +Regex for URIs + +These regex are directly derived from the collected ABNF in RFC3986 +(except for DIGIT, ALPHA and HEXDIG, defined by RFC2234). + +They should be processed with re.VERBOSE. + +Thanks Mark Nottingham for this code - https://gist.github.com/138549 +""" +from __future__ import unicode_literals + +import re + +# basics + +DIGIT = r"[\x30-\x39]" + +ALPHA = r"[\x41-\x5A\x61-\x7A]" + +HEXDIG = r"[\x30-\x39A-Fa-f]" + +# pct-encoded = "%" HEXDIG HEXDIG +pct_encoded = r" %% %(HEXDIG)s %(HEXDIG)s" % locals() + +# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +unreserved = r"(?: %(ALPHA)s | %(DIGIT)s | \- | \. | _ | ~ )" % locals() + +# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +gen_delims = r"(?: : | / | \? | \# | \[ | \] | @ )" + +# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +# / "*" / "+" / "," / ";" / "=" +sub_delims = r"""(?: ! | \$ | & | ' | \( | \) | + \* | \+ | , | ; | = )""" + +# pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +pchar = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : | @ )" % locals( +) + +# reserved = gen-delims / sub-delims +reserved = r"(?: %(gen_delims)s | %(sub_delims)s )" % locals() + + +# scheme + +# scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +scheme = r"%(ALPHA)s (?: %(ALPHA)s | %(DIGIT)s | \+ | \- | \. )*" % locals() + + +# authority + +# dec-octet = DIGIT ; 0-9 +# / %x31-39 DIGIT ; 10-99 +# / "1" 2DIGIT ; 100-199 +# / "2" %x30-34 DIGIT ; 200-249 +# / "25" %x30-35 ; 250-255 +dec_octet = r"""(?: %(DIGIT)s | + [\x31-\x39] %(DIGIT)s | + 1 %(DIGIT)s{2} | + 2 [\x30-\x34] %(DIGIT)s | + 25 [\x30-\x35] + ) +""" % locals() + +# IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet +IPv4address = r"%(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s \. %(dec_octet)s" % locals( +) + +# h16 = 1*4HEXDIG +h16 = r"(?: %(HEXDIG)s ){1,4}" % locals() + +# ls32 = ( h16 ":" h16 ) / IPv4address +ls32 = r"(?: (?: %(h16)s : %(h16)s ) | %(IPv4address)s )" % locals() + +# IPv6address = 6( h16 ":" ) ls32 +# / "::" 5( h16 ":" ) ls32 +# / [ h16 ] "::" 4( h16 ":" ) ls32 +# / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 +# / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 +# / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 +# / [ *4( h16 ":" ) h16 ] "::" ls32 +# / [ *5( h16 ":" ) h16 ] "::" h16 +# / [ *6( h16 ":" ) h16 ] "::" +IPv6address = r"""(?: (?: %(h16)s : ){6} %(ls32)s | + :: (?: %(h16)s : ){5} %(ls32)s | + %(h16)s :: (?: %(h16)s : ){4} %(ls32)s | + (?: %(h16)s : ) %(h16)s :: (?: %(h16)s : ){3} %(ls32)s | + (?: %(h16)s : ){2} %(h16)s :: (?: %(h16)s : ){2} %(ls32)s | + (?: %(h16)s : ){3} %(h16)s :: %(h16)s : %(ls32)s | + (?: %(h16)s : ){4} %(h16)s :: %(ls32)s | + (?: %(h16)s : ){5} %(h16)s :: %(h16)s | + (?: %(h16)s : ){6} %(h16)s :: + ) +""" % locals() + +# IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) +IPvFuture = r"v %(HEXDIG)s+ \. (?: %(unreserved)s | %(sub_delims)s | : )+" % locals() + +# IP-literal = "[" ( IPv6address / IPvFuture ) "]" +IP_literal = r"\[ (?: %(IPv6address)s | %(IPvFuture)s ) \]" % locals() + +# reg-name = *( unreserved / pct-encoded / sub-delims ) +reg_name = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s )*" % locals() + +# userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +userinfo = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | : )" % locals( +) + +# host = IP-literal / IPv4address / reg-name +host = r"(?: %(IP_literal)s | %(IPv4address)s | %(reg_name)s )" % locals() + +# port = *DIGIT +port = r"(?: %(DIGIT)s )*" % locals() + +# authority = [ userinfo "@" ] host [ ":" port ] +authority = r"(?: %(userinfo)s @)? %(host)s (?: : %(port)s)?" % locals() + +# Path + +# segment = *pchar +segment = r"%(pchar)s*" % locals() + +# segment-nz = 1*pchar +segment_nz = r"%(pchar)s+" % locals() + +# segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) +# ; non-zero-length segment without any colon ":" +segment_nz_nc = r"(?: %(unreserved)s | %(pct_encoded)s | %(sub_delims)s | @ )+" % locals() + +# path-abempty = *( "/" segment ) +path_abempty = r"(?: / %(segment)s )*" % locals() + +# path-absolute = "/" [ segment-nz *( "/" segment ) ] +path_absolute = r"/ (?: %(segment_nz)s (?: / %(segment)s )* )?" % locals() + +# path-noscheme = segment-nz-nc *( "/" segment ) +path_noscheme = r"%(segment_nz_nc)s (?: / %(segment)s )*" % locals() + +# path-rootless = segment-nz *( "/" segment ) +path_rootless = r"%(segment_nz)s (?: / %(segment)s )*" % locals() + +# path-empty = 0 +path_empty = r"" # FIXME + +# path = path-abempty ; begins with "/" or is empty +# / path-absolute ; begins with "/" but not "//" +# / path-noscheme ; begins with a non-colon segment +# / path-rootless ; begins with a segment +# / path-empty ; zero characters +path = r"""(?: %(path_abempty)s | + %(path_absolute)s | + %(path_noscheme)s | + %(path_rootless)s | + %(path_empty)s + ) +""" % locals() + +### Query and Fragment + +# query = *( pchar / "/" / "?" ) +query = r"(?: %(pchar)s | / | \? )*" % locals() + +# fragment = *( pchar / "/" / "?" ) +fragment = r"(?: %(pchar)s | / | \? )*" % locals() + +# URIs + +# hier-part = "//" authority path-abempty +# / path-absolute +# / path-rootless +# / path-empty +hier_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) | + %(path_absolute)s | + %(path_rootless)s | + %(path_empty)s + ) +""" % locals() + +# relative-part = "//" authority path-abempty +# / path-absolute +# / path-noscheme +# / path-empty +relative_part = r"""(?: (?: // %(authority)s %(path_abempty)s ) | + %(path_absolute)s | + %(path_noscheme)s | + %(path_empty)s + ) +""" % locals() + +# relative-ref = relative-part [ "?" query ] [ "#" fragment ] +relative_ref = r"%(relative_part)s (?: \? %(query)s)? (?: \# %(fragment)s)?" % locals( +) + +# URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] +URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? (?: \# %(fragment)s )? )$" % locals( +) + +# URI-reference = URI / relative-ref +URI_reference = r"^(?: %(URI)s | %(relative_ref)s )$" % locals() + +# absolute-URI = scheme ":" hier-part [ "?" query ] +absolute_URI = r"^(?: %(scheme)s : %(hier_part)s (?: \? %(query)s )? )$" % locals( +) + + +def is_uri(uri): + return re.match(URI, uri, re.VERBOSE) + + +def is_uri_reference(uri): + return re.match(URI_reference, uri, re.VERBOSE) + + +def is_absolute_uri(uri): + return re.match(absolute_URI, uri, re.VERBOSE) diff --git a/flex/lib/python3.6/site-packages/openid/__init__.py b/flex/lib/python3.6/site-packages/openid/__init__.py new file mode 100644 index 0000000..465498f --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/__init__.py @@ -0,0 +1,48 @@ +#-*- coding: utf-8 -*- +""" +This package is an implementation of the OpenID specification in +Python. It contains code for both server and consumer +implementations. For information on implementing an OpenID consumer, +see the C{L{openid.consumer.consumer}} module. For information on +implementing an OpenID server, see the C{L{openid.server.server}} +module. + +@contact: U{http://github.com/necaris/python3-openid/} + +@copyright: (C) 2005-2008 JanRain, Inc., 2012-2017 Rami Chowdhury + +@license: Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + U{http://www.apache.org/licenses/LICENSE-2.0} + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions + and limitations under the License. +""" + +version_info = (3, 1, 0) + + + +__version__ = ".".join(str(x) for x in version_info) + +__all__ = [ + 'association', + 'consumer', + 'cryptutil', + 'dh', + 'extension', + 'extensions', + 'fetchers', + 'kvform', + 'message', + 'oidutil', + 'server', + 'sreg', + 'store', + 'urinorm', + 'yadis', +] diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b719b7f1c32cf7fee60b6feb285a335c7644bd5b GIT binary patch literal 1639 zcmZ`(-EP}96qX#PNtCAPwyx{dK;)_h9Lf3VmSjayH%T#VfiuKTi)t7ITBKz*5~+|> z95?k<_cD8xJ%Vm`)mJEXu|vsD({-c4ANd>}e&;(sTPrJ0<4+R&I&WEjSvSTioS(u} z&tM@|V3BVEo77*|$UM3AyG88->z)114(7-LStLuuA-Vc%#1W0MQI6c|LW*U06<3Eneh5BnV?jVJfZ*j#z!EZzENRr0`|{ds6=kH zO(xFNy5l@8{g3n>I=vcdo$YyE%=9qt1D4d|G{TZ8FDnV#TCebWlk#3RmMk8^&4>F9 zwApGsT8Cu^{e<}m5ZXjOkK85*Ha0=9@jW`hDMR~1F(SiUj=@C2BFYurLr2wtpqvvR z-Gr_U@r7+FX`&ZYDihgFw}l=Wg|(?@t?`3{V2nkMQananXp}2M0A$91B^1%ih-Mn% z1d)U^VVFl0jljwBp{W_SH1cX{MD$HgAPhIM@hqFBnh5Js_nNQKXyoD&t}A5hC6zPE zJM#ArJKe)|fDD8gjHf)IN+C(lb0#T4{V~EBAc}Awa3*+!AfIq7Daf=ih)0rX2r|S} z4D<*~TG}B@Y03Jz2EC7z&G3QWHx+<1NW-;u7x~>a^sL?WyUo(dOTYKy@o5jeY@eL8 zJ3arfi;hpw{&DBP@A=0a_rQiS2Er7Q z3Dd0hz&jr2reI?Mg~d(gW>lt3nXsx7kSVr=rK~Dq^|@=V^B(HodT*Nrrd8N(WIoKO z!8Fncrn(QyTkE$w6I)wvp~bwgZ0o#6ENCKjr(qX!KwzU*)ZA8KU;bgqMbQ6{dYNPw zSX1u;OK%_&NJixiq_=gp!R7h(Zi~DjZ+6x8&e`^Ey8GRB3r5Vno|my~y`Qs$tUuzB z+|jM`f0xX(a>9^tro+(9#vhg*$CMjBpL}Z|8~k-kI?nX7|#9; mjV4Z#Ff6&fu`Ge1V)1d+fu1~oFSQ0sy;iT^o2%LX|N95zssD!n literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/association.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/association.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66132496d9ab3dca4aaf7c9547ce768fbac10eea GIT binary patch literal 16973 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/cryptutil.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/cryptutil.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67e5f6aa71ae40df73a6140e38dde842d9e96999 GIT binary patch literal 4104 zcmcgvTW{OQ73PqXMAMge9dBai%H6hU+}ctuPMQQkut_%QZjrhR*v+;C0)pm@BuW%1 z4@dFJP*8N0VzUK`qWu^BD}C-0U;DJrecPvgXDG{w+C|Zql`v<9!Hy0P( zjlXmMk4uK}PvfoMJjNfP zkI$i>=PTSszsyf_7yTk%_1_z=g5&$iO7FCPy0_}DGUIb2-Lwtye%F=%Ubn z{0y&R-&uZ^o9NemYosf`G5FeKcDdFGVg=RrikHc~O{p`vQg=q`c${+lWv`ZC9kNI2D`T{y^`I22x@# z98@k2)jij3E7iZ(XdE6M*27-69)*p*_ERIuxM-B;yT!tOn2X!nd)YH+hbOJ|VxjcZt~OdRM^9)iQP?@Lse*#VHx$@A8)o6C zV>WJ6X1$VSL~oCOfO8r5s~89a@ogZs5&OWr#VugoE*3ut^Y-Hs?LtPhg?`jidyJOT z_7NKy0qEH1&JFn6q<{H2vi^cn81)a4?8eCjVy074Z5t zZS-aQGE}1RGL(&GmT-Wg(Ugsw-Rr45*uHy>AMvBR`SzXe_TApy+uPUhYo(37MnCRv z?GNIFZ{12Gc}HD4m^kLh}?Y|1mr9O=~jC-W7}GCjMASw>F7EK^p;9?_f1*y>cr89NHDb!d6H^vx-^Rbqy_d zK@8@y1>GWCEBZ(MRJR}E;X8EGT8vqzGOK}H!={a@Tt_F*5jjufA`ucZ z7M34O<VgFs=8(266^XkaxAdf;+Ls=xW7TMM!S2|Ty zBeAvM<}^5+6NnVmQdW5dOyvd<%1g+hf^HYB!bI}Y4L_wNGe9A8zCfD-#f9uM($gAk z?rjXTxed9j3~r9umq;$hl5@6haI0&}O%8*fMN%2T=sLkycC_J-w)A~Hz3{UXx8`@A!KE=65(x{s;uNcb|vJKs=E$lkx|8N|N^rSHnc#b+fvWXPRjF0*FC+ znGRBoV@`cTK$vQG6%QjUhGww(m(Yuw6OBB~*(cfpN|l`>?@6yu^_xE1>xX$RI8`*5 zDDQ5qv#f*d|Ec(fDI3@&DfMkINvU#HH=mL5@jxG>+sk~D6$ijZK!F> z8mX*EH@&Exq3-jj{=pc9u- z{L~(!K-JjVDSu23P%@xfjIapNmGXLGP6yat*|vyenfmw zJbFbf2zVBuw88)%NkJg#>MYDG2Y3BhLT|J9A)pI5M38O`g>dPeFFRIyoM?^{& zT9PV(vSOh7N`6R$a4E?;{_^f84^c%251&8$^x4Bl&z|@z>k_`ydmgXGSnXg({Z9BF*_it+5wsHUf literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/dh.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/dh.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd19ab54a885144a59866423636e8e7652d20500 GIT binary patch literal 2213 zcmZuyO>7%Q6rR~XuN^0~N}HC_!ct0Gqs9(uC<&^fHcgr$N=lmml}WVP?2PSA*1OKk zxYU+&!Ik(a7Y=aahAJU$2nlYz0!J=LBUJ_UfGSm7kt!kaX4gsT&~fI?`1xh_d*6QV z&FSIcT=7>@XRKzmtvvyOoL=gI#a=}om3G)I>ejSWJUDU&1aSyNS?4ZB|b{}D(b&=3}8avfR zYX;GJcoTsSC%$uC-1lK&5}g>gkg&Sy3~d%p>tR4)E_7abGitU3cSP)P!EVOPX|%X- z>fR0Nh&pw+*Pu>FqnfC9%$BH*P5KxhsiDJE*)PK62f@?;=SpE&+r*oIqN|-pUuYY~ zBKj6F6Gwo6I0`)LpoD@ zVsd?AsxftDVjM;-Dle6rL33=i6@+B$wa90aVtk|AY>RpvogRyuGzv($zmR&V*?ycm z8wOnXaicjstRO3%Ez~)b=3P1)#=aNwY1nLq6*E$Y+-2@PFKp2{#$qOO0S_WByvV0g zhkun8jr^EUX|A>fvoBj;7eMZUF51Vg+hcK z6z7^Qj5U+sE)?uR)B``X1*n&zw(w9TRkWlYL_%g7F=>S@F0(ZnQRWHI*^tYrcqgq4 zrl83z^CAMXMxl}nPQxmD4*cw}G73>@5~bO$8$^L{-9zB#(6b;jP?^>l9n5YkY@qol zeI{H#4;v_CAuJ_jaO$dG2?&a=DH*x_nGHHx<+ z6V;yj=<&K1kM^_C*ZcZ0QYw`WQfQ;FE8-N=AR4*11s$N)mrl|d_{$KTg2Q>-aR$Nt zKm6%z7vK=3Bfyrh-P2QT>+Gn4l@OyC>oWyc2eh)72ZHY6OYW&ru~_#QC5zN&R1_7w zzPyhNg4-48Ck#Wll8nkbVNX3jVGpj2O{1#|3|cVMC9-J>y{mQ2tvWQ=afnUyTFHlT zR9lK?g2-d-q`4KXlu$)i#o`IJO1AFtx?62UKJ==(%M;v{g7Q-0ZhLm;n1#D4&F&PO zwk`1y+&M6q;db0YF)yJmBDm39SLR$dsXDdKyY5EI3)5d%A_DdPnNp1$@fuNxoYe-HZ()Cbz8G7+q4Z^Pkx8Ws95PWm9UoZ dB&~RLjrt;qc-qm@KAVD{R&69vUS7BJ{{iTb6m9?j literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/extension.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/extension.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..764a211ad3f4a3bd4a8668d0e4d1078c15b5082c GIT binary patch literal 1917 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/kvform.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/kvform.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..115484b5951d237981ca80ea22fee06c81dd415e GIT binary patch literal 3137 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/urinorm.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/urinorm.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b45e85dfbfd6cdf0f0fff38c706aebecca189391 GIT binary patch literal 3112 zcmZ`*&2JmW6`z^?;F6*!>cf&{I|&;{w&<9m8mon+I8Ei=w9<=TxAV3g};;OZ%IpM9W6A#JqX)-kUcw zZ{GYK?~IQZs(<6|lSxAUMJ{|M_;s}CO-cwSoVJOpd)B5M<}ymS;TnRpOkoVDYiW*~ zp!R^dIc~X5rgNanbK5OsqXI@_Jhw-=Geg|s=QqT-DDnJ&fqS1TTuB zn7Bs{gD2$7ZalLqeTUueM|KmLUHLog{_=!mzDs;^K<}ONQy2K@3;ayRU*faBB5s+_ z@yqBZ`8>aZeu`h^*U(RMx=E_9o#^0gRH@20LoS-V*l#DRJBy23J8Sj(*SFuUu2t_Z z*6SGS;jJo0TRR!I_*AoJvg?aWT9>@FwNragbG=%!TwB#1ww`XUfA6guHy5k7-oE{# zcb6JpENmxpJD9n?eQQ1Qwg$J0YnXpOnOs=h!o&x)hhA-W`6vzu{2ZBtm>C<=hS!raNYz$`AH>SqlVPu` z#u~jqib%*K!Ijb6m&)pOyF#kMr(U}!K9n+)Ri;=cRMh{P$jfkX+OBt{f8@oYe&ose zZrJ8RM)h4;zjN^YKpw8FF7t#ZtI^8cgO%0J>JL|z@$Cim$Mvq?tv&1cZC?99(3E%M z<->aSINlF~JGHPY0-x8U zrQ|u44mJ*~7*Kg51y*qKYc@~%-Oi%^zRV6b@MQEiA@h@n>GtKKXbbd*3CRDA%q9bhF>RT!Cn?is8c?0huo<0D<4VgKzJh9bBQchi>L)r{2?L{_QexDX z>Ithaozi+@)}hyuCFqxA5qd?WPKV)}_%A-Zi9!^6GLAm;<9%gx!>%%S{QxhbEdphG zco7(wt`Ch`DrOS;gM+Y@P*+j?4mUgLQPhn z1zi1kbEplzNKH@&RQF|SGc%JFb#BusT4r()vq!0-&ooMvf8uBwJYqUvau%?GG~w(k zhRY%?gBwT+>nnyLg5>D~G+K5Rc8aU~IL>h!;dchO*j9c(cG)a0;7b}8T4QII!&(l{ zVWyE9+&OS$j5#PQU((cQ6$d1fUp>gl-$7nX$u1pwNcB7vmcE&q*}QL|FTK#m<5epBuk9zI;#EHR*`rLBSiN|+b{7TG$uQFo_LSvC zP2YE?LZ3GP?oH%tLqtulD_~{|4f0WP`%$|s_Plnbx$jA@83WTRexUuXRTgB$i!0qQ z@?$>?DhrW(4N13=OmBphsJSmXq7rpQ)8F+`M$G*uC|TGDFxASYQd)X)ocySBsnW~!4$JqRWyK*YY1!Ma7L@^yZsB|bozTseK%KUS7ozeb z=kA4dVBqvn@BLsm#7%`!3_J^EEN^1V$~x=`d90jW>1C+)DxT%e>he<W`4jlZjW8)( zd-%UVYJ7LcbT9$JNYOy$nqjBww}pHcDwX?W;D4|763Q^ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/association.py b/flex/lib/python3.6/site-packages/openid/association.py new file mode 100644 index 0000000..94d68d7 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/association.py @@ -0,0 +1,562 @@ +#-*-test-case-name: openid.test.test_association-*- +#-*- coding: utf-8 -*- +""" +This module contains code for dealing with associations between +consumers and servers. Associations contain a shared secret that is +used to sign C{openid.mode=id_res} messages. + +Users of the library should not usually need to interact directly with +associations. The L{store}, L{server} +and L{consumer} objects will create and manage +the associations. The consumer and server code will make use of a +C{L{SessionNegotiator}} when managing associations, which enables +users to express a preference for what kind of associations should be +allowed, and what kind of exchange should be done to establish the +association. + +@var default_negotiator: A C{L{SessionNegotiator}} that allows all + association types that are specified by the OpenID + specification. It prefers to use HMAC-SHA1/DH-SHA1, if it's + available. If HMAC-SHA256 is not supported by your Python runtime, + HMAC-SHA256 and DH-SHA256 will not be available. + +@var encrypted_negotiator: A C{L{SessionNegotiator}} that + does not support C{'no-encryption'} associations. It prefers + HMAC-SHA1/DH-SHA1 association types if available. +""" +import time +import functools + +from openid import cryptutil +from openid import kvform +from openid import oidutil +from openid.message import OPENID_NS + +__all__ = [ + 'default_negotiator', + 'encrypted_negotiator', + 'SessionNegotiator', + 'Association', +] + +all_association_types = [ + 'HMAC-SHA1', + 'HMAC-SHA256', +] + +if hasattr(cryptutil, 'hmacSha256'): + supported_association_types = list(all_association_types) + + default_association_order = [ + ('HMAC-SHA1', 'DH-SHA1'), + ('HMAC-SHA1', 'no-encryption'), + ('HMAC-SHA256', 'DH-SHA256'), + ('HMAC-SHA256', 'no-encryption'), + ] + + only_encrypted_association_order = [ + ('HMAC-SHA1', 'DH-SHA1'), + ('HMAC-SHA256', 'DH-SHA256'), + ] +else: + supported_association_types = ['HMAC-SHA1'] + + default_association_order = [ + ('HMAC-SHA1', 'DH-SHA1'), + ('HMAC-SHA1', 'no-encryption'), + ] + + only_encrypted_association_order = [ + ('HMAC-SHA1', 'DH-SHA1'), + ] + + +def getSessionTypes(assoc_type): + """Return the allowed session types for a given association type""" + assoc_to_session = { + 'HMAC-SHA1': ['DH-SHA1', 'no-encryption'], + 'HMAC-SHA256': ['DH-SHA256', 'no-encryption'], + } + return assoc_to_session.get(assoc_type, []) + + +def checkSessionType(assoc_type, session_type): + """Check to make sure that this pair of assoc type and session + type are allowed""" + if session_type not in getSessionTypes(assoc_type): + raise ValueError('Session type %r not valid for assocation type %r' % + (session_type, assoc_type)) + + +class SessionNegotiator(object): + """A session negotiator controls the allowed and preferred + association types and association session types. Both the + C{L{Consumer}} and + C{L{Server}} use negotiators when + creating associations. + + You can create and use negotiators if you: + + - Do not want to do Diffie-Hellman key exchange because you use + transport-layer encryption (e.g. SSL) + + - Want to use only SHA-256 associations + + - Do not want to support plain-text associations over a non-secure + channel + + It is up to you to set a policy for what kinds of associations to + accept. By default, the library will make any kind of association + that is allowed in the OpenID 2.0 specification. + + Use of negotiators in the library + ================================= + + When a consumer makes an association request, it calls + C{L{getAllowedType}} to get the preferred association type and + association session type. + + The server gets a request for a particular association/session + type and calls C{L{isAllowed}} to determine if it should + create an association. If it is supported, negotiation is + complete. If it is not, the server calls C{L{getAllowedType}} to + get an allowed association type to return to the consumer. + + If the consumer gets an error response indicating that the + requested association/session type is not supported by the server + that contains an assocation/session type to try, it calls + C{L{isAllowed}} to determine if it should try again with the + given combination of association/session type. + + @ivar allowed_types: A list of association/session types that are + allowed by the server. The order of the pairs in this list + determines preference. If an association/session type comes + earlier in the list, the library is more likely to use that + type. + @type allowed_types: [(str, str)] + """ + + def __init__(self, allowed_types): + self.setAllowedTypes(allowed_types) + + def copy(self): + return self.__class__(list(self.allowed_types)) + + def setAllowedTypes(self, allowed_types): + """Set the allowed association types, checking to make sure + each combination is valid.""" + for (assoc_type, session_type) in allowed_types: + checkSessionType(assoc_type, session_type) + + self.allowed_types = allowed_types + + def addAllowedType(self, assoc_type, session_type=None): + """Add an association type and session type to the allowed + types list. The assocation/session pairs are tried in the + order that they are added.""" + if self.allowed_types is None: + self.allowed_types = [] + + if session_type is None: + available = getSessionTypes(assoc_type) + + if not available: + raise ValueError('No session available for association type %r' + % (assoc_type, )) + + for session_type in getSessionTypes(assoc_type): + self.addAllowedType(assoc_type, session_type) + else: + checkSessionType(assoc_type, session_type) + self.allowed_types.append((assoc_type, session_type)) + + def isAllowed(self, assoc_type, session_type): + """Is this combination of association type and session type allowed?""" + assoc_good = (assoc_type, session_type) in self.allowed_types + matches = session_type in getSessionTypes(assoc_type) + return assoc_good and matches + + def getAllowedType(self): + """Get a pair of assocation type and session type that are + supported""" + try: + return self.allowed_types[0] + except IndexError: + return (None, None) + + +default_negotiator = SessionNegotiator(default_association_order) +encrypted_negotiator = SessionNegotiator(only_encrypted_association_order) + + +def getSecretSize(assoc_type): + if assoc_type == 'HMAC-SHA1': + return 20 + elif assoc_type == 'HMAC-SHA256': + return 32 + else: + raise ValueError('Unsupported association type: %r' % (assoc_type, )) + + +@functools.total_ordering +class Association(object): + """ + This class represents an association between a server and a + consumer. In general, users of this library will never see + instances of this object. The only exception is if you implement + a custom C{L{OpenIDStore}}. + + If you do implement such a store, it will need to store the values + of the C{L{handle}}, C{L{secret}}, C{L{issued}}, C{L{lifetime}}, and + C{L{assoc_type}} instance variables. + + @ivar handle: This is the handle the server gave this association. + + @type handle: C{str} + + + @ivar secret: This is the shared secret the server generated for + this association. + + @type secret: C{str} + + + @ivar issued: This is the time this association was issued, in + seconds since 00:00 GMT, January 1, 1970. (ie, a unix + timestamp) + + @type issued: C{int} + + + @ivar lifetime: This is the amount of time this association is + good for, measured in seconds since the association was + issued. + + @type lifetime: C{int} + + + @ivar assoc_type: This is the type of association this instance + represents. The only valid value of this field at this time + is C{'HMAC-SHA1'}, but new types may be defined in the future. + + @type assoc_type: C{str} + + + @sort: __init__, fromExpiresIn, expiresIn, __eq__, __ne__, + handle, secret, issued, lifetime, assoc_type + """ + + # The ordering and name of keys as stored by serialize + assoc_keys = [ + 'version', + 'handle', + 'secret', + 'issued', + 'lifetime', + 'assoc_type', + ] + + _macs = { + 'HMAC-SHA1': cryptutil.hmacSha1, + 'HMAC-SHA256': cryptutil.hmacSha256, + } + + @classmethod + def fromExpiresIn(cls, expires_in, handle, secret, assoc_type): + """ + This is an alternate constructor used by the OpenID consumer + library to create associations. C{L{OpenIDStore + }} implementations + shouldn't use this constructor. + + + @param expires_in: This is the amount of time this association + is good for, measured in seconds since the association was + issued. + + @type expires_in: C{int} + + + @param handle: This is the handle the server gave this + association. + + @type handle: C{str} + + + @param secret: This is the shared secret the server generated + for this association. + + @type secret: C{str} + + + @param assoc_type: This is the type of association this + instance represents. The only valid value of this field + at this time is C{'HMAC-SHA1'}, but new types may be + defined in the future. + + @type assoc_type: C{str} + """ + issued = int(time.time()) + lifetime = expires_in + return cls(handle, secret, issued, lifetime, assoc_type) + + def __init__(self, handle, secret, issued, lifetime, assoc_type): + """ + This is the standard constructor for creating an association. + + + @param handle: This is the handle the server gave this + association. + + @type handle: C{str} + + + @param secret: This is the shared secret the server generated + for this association. + + @type secret: C{str} + + + @param issued: This is the time this association was issued, + in seconds since 00:00 GMT, January 1, 1970. (ie, a unix + timestamp) + + @type issued: C{int} + + + @param lifetime: This is the amount of time this association + is good for, measured in seconds since the association was + issued. + + @type lifetime: C{int} + + + @param assoc_type: This is the type of association this + instance represents. The only valid value of this field + at this time is C{'HMAC-SHA1'}, but new types may be + defined in the future. + + @type assoc_type: C{str} + """ + if assoc_type not in all_association_types: + fmt = '%r is not a supported association type' + raise ValueError(fmt % (assoc_type, )) + + # secret_size = getSecretSize(assoc_type) + # if len(secret) != secret_size: + # fmt = 'Wrong size secret (%s bytes) for association type %s' + # raise ValueError(fmt % (len(secret), assoc_type)) + + self.handle = handle + + if isinstance(secret, str): + secret = secret.encode("utf-8") # should be bytes + self.secret = secret + + self.issued = issued + self.lifetime = lifetime + self.assoc_type = assoc_type + + @property + def expiresIn(self, now=None): + """ + This returns the number of seconds this association is still + valid for, or C{0} if the association is no longer valid. + + + @return: The number of seconds this association is still valid + for, or C{0} if the association is no longer valid. + + @rtype: C{int} + """ + if now is None: + now = int(time.time()) + + return max(0, self.issued + self.lifetime - now) + + def __lt__(self, other): + """ + Compare two C{L{Association}} instances to determine relative + ordering. + + Currently compares object lifetimes -- C{L{Association}} A < B + if A.lifetime < B.lifetime. + """ + return self.lifetime < other.lifetime + + def __eq__(self, other): + """ + This checks to see if two C{L{Association}} instances + represent the same association. + + + @return: C{True} if the two instances represent the same + association, C{False} otherwise. + + @rtype: C{bool} + """ + return type(self) is type(other) and self.__dict__ == other.__dict__ + + def __ne__(self, other): + """ + This checks to see if two C{L{Association}} instances + represent different associations. + + + @return: C{True} if the two instances represent different + associations, C{False} otherwise. + + @rtype: C{bool} + """ + return not (self == other) + + def serialize(self): + """ + Convert an association to KV form. + + @return: String in KV form suitable for deserialization by + deserialize. + + @rtype: str + """ + data = { + 'version': '2', + 'handle': self.handle, + 'secret': oidutil.toBase64(self.secret), + 'issued': str(int(self.issued)), + 'lifetime': str(int(self.lifetime)), + 'assoc_type': self.assoc_type + } + + assert len(data) == len(self.assoc_keys) + + pairs = [] + for field_name in self.assoc_keys: + pairs.append((field_name, data[field_name])) + + return kvform.seqToKV(pairs, strict=True) + + @classmethod + def deserialize(cls, assoc_s): + """ + Parse an association as stored by serialize(). + + inverse of serialize + + @param assoc_s: Association as serialized by serialize() + @type assoc_s: bytes + + @return: instance of this class + """ + pairs = kvform.kvToSeq(assoc_s, strict=True) + keys = [] + values = [] + for k, v in pairs: + keys.append(k) + values.append(v) + + if keys != cls.assoc_keys: + raise ValueError('Unexpected key values: %r', keys) + + version, handle, secret, issued, lifetime, assoc_type = values + if version != '2': + raise ValueError('Unknown version: %r' % version) + issued = int(issued) + lifetime = int(lifetime) + secret = oidutil.fromBase64(secret) + return cls(handle, secret, issued, lifetime, assoc_type) + + def sign(self, pairs): + """ + Generate a signature for a sequence of (key, value) pairs + + + @param pairs: The pairs to sign, in order + @type pairs: sequence of (str, str) + + + @return: The binary signature of this sequence of pairs + @rtype: bytes + """ + kv = kvform.seqToKV(pairs) + + try: + mac = self._macs[self.assoc_type] + except KeyError: + raise ValueError('Unknown association type: %r' % + (self.assoc_type, )) + + return mac(self.secret, kv) + + def getMessageSignature(self, message): + """Return the signature of a message. + + If I am not a sign-all association, the message must have a + signed list. + + @return: the signature, base64 encoded + + @rtype: bytes + + @raises ValueError: If there is no signed list and I am not a sign-all + type of association. + """ + pairs = self._makePairs(message) + return oidutil.toBase64(self.sign(pairs)) + + def signMessage(self, message): + """Add a signature (and a signed list) to a message. + + @return: a new Message object with a signature + @rtype: L{openid.message.Message} + """ + if (message.hasKey(OPENID_NS, 'sig') or + message.hasKey(OPENID_NS, 'signed')): + raise ValueError('Message already has signed list or signature') + + extant_handle = message.getArg(OPENID_NS, 'assoc_handle') + if extant_handle and extant_handle != self.handle: + raise ValueError("Message has a different association handle") + + signed_message = message.copy() + signed_message.setArg(OPENID_NS, 'assoc_handle', self.handle) + message_keys = list(signed_message.toPostArgs().keys()) + signed_list = [k[7:] for k in message_keys if k.startswith('openid.')] + signed_list.append('signed') + signed_list.sort() + signed_message.setArg(OPENID_NS, 'signed', ','.join(signed_list)) + sig = self.getMessageSignature(signed_message) + signed_message.setArg(OPENID_NS, 'sig', sig) + return signed_message + + def checkMessageSignature(self, message): + """Given a message with a signature, calculate a new signature + and return whether it matches the signature in the message. + + @raises ValueError: if the message has no signature or no signature + can be calculated for it. + """ + message_sig = message.getArg(OPENID_NS, 'sig') + if not message_sig: + raise ValueError("%s has no sig." % (message, )) + calculated_sig = self.getMessageSignature(message) + # remember, getMessageSignature returns bytes + calculated_sig = calculated_sig.decode('utf-8') + return cryptutil.const_eq(calculated_sig, message_sig) + + def _makePairs(self, message): + signed = message.getArg(OPENID_NS, 'signed') + if not signed: + raise ValueError('Message has no signed list: %s' % (message, )) + + signed_list = signed.split(',') + pairs = [] + data = message.toPostArgs() + for field in signed_list: + pairs.append((field, data.get('openid.' + field, ''))) + return pairs + + def __repr__(self): + return "<%s.%s %s %s>" % (self.__class__.__module__, + self.__class__.__name__, self.assoc_type, + self.handle) diff --git a/flex/lib/python3.6/site-packages/openid/codecutil.py b/flex/lib/python3.6/site-packages/openid/codecutil.py new file mode 100644 index 0000000..d8b9fe9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/codecutil.py @@ -0,0 +1,91 @@ +import codecs + +try: + chr(0x10000) +except ValueError: + # narrow python build + UCSCHAR = [ + (0xA0, 0xD7FF), + (0xF900, 0xFDCF), + (0xFDF0, 0xFFEF), + ] + + IPRIVATE = [ + (0xE000, 0xF8FF), + ] +else: + UCSCHAR = [ + (0xA0, 0xD7FF), + (0xF900, 0xFDCF), + (0xFDF0, 0xFFEF), + (0x10000, 0x1FFFD), + (0x20000, 0x2FFFD), + (0x30000, 0x3FFFD), + (0x40000, 0x4FFFD), + (0x50000, 0x5FFFD), + (0x60000, 0x6FFFD), + (0x70000, 0x7FFFD), + (0x80000, 0x8FFFD), + (0x90000, 0x9FFFD), + (0xA0000, 0xAFFFD), + (0xB0000, 0xBFFFD), + (0xC0000, 0xCFFFD), + (0xD0000, 0xDFFFD), + (0xE1000, 0xEFFFD), + ] + + IPRIVATE = [ + (0xE000, 0xF8FF), + (0xF0000, 0xFFFFD), + (0x100000, 0x10FFFD), + ] + +_ESCAPE_RANGES = UCSCHAR + IPRIVATE + + +def _in_escape_range(octet): + for start, end in _ESCAPE_RANGES: + if start <= octet <= end: + return True + return False + + +def _starts_surrogate_pair(character): + char_value = ord(character) + return 0xD800 <= char_value <= 0xDBFF + + +def _ends_surrogate_pair(character): + char_value = ord(character) + return 0xDC00 <= char_value <= 0xDFFF + + +def _pct_encoded_replacements(chunk): + replacements = [] + chunk_iter = iter(chunk) + for character in chunk_iter: + codepoint = ord(character) + if _in_escape_range(codepoint): + for char in chr(codepoint).encode("utf-8"): + replacements.append("%%%X" % char) + elif _starts_surrogate_pair(character): + next_character = next(chunk_iter) + for char in (character + next_character).encode("utf-8"): + replacements.append("%%%X" % char) + else: + replacements.append(chr(codepoint)) + return replacements + + +def _pct_escape_handler(err): + ''' + Encoding error handler that does percent-escaping of Unicode, to be used + with codecs.register_error + TODO: replace use of this with urllib.parse.quote as appropriate + ''' + chunk = err.object[err.start:err.end] + replacements = _pct_encoded_replacements(chunk) + return ("".join(replacements), err.end) + + +codecs.register_error("oid_percent_escape", _pct_escape_handler) diff --git a/flex/lib/python3.6/site-packages/openid/consumer/__init__.py b/flex/lib/python3.6/site-packages/openid/consumer/__init__.py new file mode 100644 index 0000000..aab51a2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/consumer/__init__.py @@ -0,0 +1,6 @@ +""" +This package contains the portions of the library used only when +implementing an OpenID consumer. +""" + +__all__ = ['consumer', 'discover'] diff --git a/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f2da2ec67b4ba4502bfe7903db219384320b725f GIT binary patch literal 351 zcmX|7u};G<6m-)_M3E}-30^z2E^0uHgb)J@3k-D1V!3&4V#Tpt+X+fPg8$%G_=K+f z1rtt1IqB}*El;Og&t~cT3yKef(0AZJqTn6_-E|N^1%e4gRa8u1`iAE5u}rtMaM-Z2 zVHL-vR-Osvu&+5b+Ipcw(ED?iVrQ8haOXH+t>l2;Ypzn!8p&I(yigTp3cnhzo}b27 zu4}nnrbTk`MFPT=y65&~9-Xcz1zl1)8YU&BpU6%_&9nUU#8}ZY&q>cL*=q^hI3kg&K_rQP_|ftbP9TCEp4bQ3`S&ZQQi ZR4CyoU7F#P+_xG!$sgizwsF5W`UU;{Y=Zy* literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/consumer.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/consumer.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c14e8cf9fcd2fab72c906d12069bdadec4b4606 GIT binary patch literal 61329 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 R;=lalvy=BGpPZche*m9%fMfsw literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/discover.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/discover.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f6af6c787fd8c0b826c3bd4324b2b7cf3e95ca0 GIT binary patch literal 12627 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 U2Ds9%TzI=sE_$D%XR2KOf3wXfqyPW_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/consumer/consumer.py b/flex/lib/python3.6/site-packages/openid/consumer/consumer.py new file mode 100644 index 0000000..c081621 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/consumer/consumer.py @@ -0,0 +1,1924 @@ +# -*- test-case-name: openid.test.test_consumer -*- +"""OpenID support for Relying Parties (aka Consumers). + +This module documents the main interface with the OpenID consumer +library. The only part of the library which has to be used and isn't +documented in full here is the store required to create an +C{L{Consumer}} instance. More on the abstract store type and +concrete implementations of it that are provided in the documentation +for the C{L{__init__}} method of the +C{L{Consumer}} class. + + +OVERVIEW +======== + + The OpenID identity verification process most commonly uses the + following steps, as visible to the user of this library: + + 1. The user enters their OpenID into a field on the consumer's + site, and hits a login button. + + 2. The consumer site discovers the user's OpenID provider using + the Yadis protocol. + + 3. The consumer site sends the browser a redirect to the + OpenID provider. This is the authentication request as + described in the OpenID specification. + + 4. The OpenID provider's site sends the browser a redirect + back to the consumer site. This redirect contains the + provider's response to the authentication request. + + The most important part of the flow to note is the consumer's site + must handle two separate HTTP requests in order to perform the + full identity check. + + +LIBRARY DESIGN +============== + + This consumer library is designed with that flow in mind. The + goal is to make it as easy as possible to perform the above steps + securely. + + At a high level, there are two important parts in the consumer + library. The first important part is this module, which contains + the interface to actually use this library. The second is the + C{L{openid.store.interface}} module, which describes the + interface to use if you need to create a custom method for storing + the state this library needs to maintain between requests. + + In general, the second part is less important for users of the + library to know about, as several implementations are provided + which cover a wide variety of situations in which consumers may + use the library. + + This module contains a class, C{L{Consumer}}, with methods + corresponding to the actions necessary in each of steps 2, 3, and + 4 described in the overview. Use of this library should be as easy + as creating an C{L{Consumer}} instance and calling the methods + appropriate for the action the site wants to take. + + +SESSIONS, STORES, AND STATELESS MODE +==================================== + + The C{L{Consumer}} object keeps track of two types of state: + + 1. State of the user's current authentication attempt. Things like + the identity URL, the list of endpoints discovered for that + URL, and in case where some endpoints are unreachable, the list + of endpoints already tried. This state needs to be held from + Consumer.begin() to Consumer.complete(), but it is only applicable + to a single session with a single user agent, and at the end of + the authentication process (i.e. when an OP replies with either + C{id_res} or C{cancel}) it may be discarded. + + 2. State of relationships with servers, i.e. shared secrets + (associations) with servers and nonces seen on signed messages. + This information should persist from one session to the next and + should not be bound to a particular user-agent. + + + These two types of storage are reflected in the first two arguments of + Consumer's constructor, C{session} and C{store}. C{session} is a + dict-like object and we hope your web framework provides you with one + of these bound to the user agent. C{store} is an instance of + L{openid.store.interface.OpenIDStore}. + + Since the store does hold secrets shared between your application and the + OpenID provider, you should be careful about how you use it in a shared + hosting environment. If the filesystem or database permissions of your + web host allow strangers to read from them, do not store your data there! + If you have no safe place to store your data, construct your consumer + with C{None} for the store, and it will operate only in stateless mode. + Stateless mode may be slower, put more load on the OpenID provider, and + trusts the provider to keep you safe from replay attacks. + + + Several store implementation are provided, and the interface is + fully documented so that custom stores can be used as well. See + the documentation for the C{L{Consumer}} class for more + information on the interface for stores. The implementations that + are provided allow the consumer site to store the necessary data + in several different ways, including several SQL databases and + normal files on disk. + + +IMMEDIATE MODE +============== + + In the flow described above, the user may need to confirm to the + OpenID provider that it's ok to disclose his or her identity. + The provider may draw pages asking for information from the user + before it redirects the browser back to the consumer's site. This + is generally transparent to the consumer site, so it is typically + ignored as an implementation detail. + + There can be times, however, where the consumer site wants to get + a response immediately. When this is the case, the consumer can + put the library in immediate mode. In immediate mode, there is an + extra response possible from the server, which is essentially the + server reporting that it doesn't have enough information to answer + the question yet. + + +USING THIS LIBRARY +================== + + Integrating this library into an application is usually a + relatively straightforward process. The process should basically + follow this plan: + + Add an OpenID login field somewhere on your site. When an OpenID + is entered in that field and the form is submitted, it should make + a request to your site which includes that OpenID URL. + + First, the application should L{instantiate a Consumer} + with a session for per-user state and store for shared state. + using the store of choice. + + Next, the application should call the 'C{L{begin}}' method on the + C{L{Consumer}} instance. This method takes the OpenID URL. The + C{L{begin}} method returns an C{L{AuthRequest}} + object. + + Next, the application should call the + C{L{redirectURL}} method on the + C{L{AuthRequest}} object. The parameter C{return_to} is the URL + that the OpenID server will send the user back to after attempting + to verify his or her identity. The C{realm} parameter is the + URL (or URL pattern) that identifies your web site to the user + when he or she is authorizing it. Send a redirect to the + resulting URL to the user's browser. + + That's the first half of the authentication process. The second + half of the process is done after the user's OpenID Provider sends the + user's browser a redirect back to your site to complete their + login. + + When that happens, the user will contact your site at the URL + given as the C{return_to} URL to the + C{L{redirectURL}} call made + above. The request will have several query parameters added to + the URL by the OpenID provider as the information necessary to + finish the request. + + Get a C{L{Consumer}} instance with the same session and store as + before and call its C{L{complete}} method, + passing in all the received query arguments. + + There are multiple possible return types possible from that + method. These indicate whether or not the login was successful, + and include any additional information appropriate for their type. + +@var SUCCESS: constant used as the status for + L{SuccessResponse} objects. + +@var FAILURE: constant used as the status for + L{FailureResponse} objects. + +@var CANCEL: constant used as the status for + L{CancelResponse} objects. + +@var SETUP_NEEDED: constant used as the status for + L{SetupNeededResponse} + objects. +""" + +import copy +import logging +from urllib.parse import urlparse, urldefrag, parse_qsl + +from openid import fetchers + +from openid.consumer.discover import discover, OpenIDServiceEndpoint, \ + DiscoveryFailure, OPENID_1_0_TYPE, OPENID_1_1_TYPE, OPENID_2_0_TYPE +from openid.message import Message, OPENID_NS, OPENID2_NS, OPENID1_NS, \ + IDENTIFIER_SELECT, no_default, BARE_NS +from openid import cryptutil +from openid import oidutil +from openid.association import Association, default_negotiator, \ + SessionNegotiator +from openid.dh import DiffieHellman +from openid.store.nonce import mkNonce, split as splitNonce +from openid.yadis.manager import Discovery +from openid import urinorm + +__all__ = [ + 'AuthRequest', + 'Consumer', + 'SuccessResponse', + 'SetupNeededResponse', + 'CancelResponse', + 'FailureResponse', + 'SUCCESS', + 'FAILURE', + 'CANCEL', + 'SETUP_NEEDED', +] + + +def makeKVPost(request_message, server_url): + """Make a Direct Request to an OpenID Provider and return the + result as a Message object. + + @raises openid.fetchers.HTTPFetchingError: if an error is + encountered in making the HTTP post. + + @rtype: L{openid.message.Message} + """ + # XXX: TESTME + resp = fetchers.fetch(server_url, body=request_message.toURLEncoded()) + + # Process response in separate function that can be shared by async code. + return _httpResponseToMessage(resp, server_url) + + +def _httpResponseToMessage(response, server_url): + """Adapt a POST response to a Message. + + @type response: L{openid.fetchers.HTTPResponse} + @param response: Result of a POST to an OpenID endpoint. + + @rtype: L{openid.message.Message} + + @raises openid.fetchers.HTTPFetchingError: if the server returned a + status of other than 200 or 400. + + @raises ServerError: if the server returned an OpenID error. + """ + # Should this function be named Message.fromHTTPResponse instead? + response_message = Message.fromKVForm(response.body) + if response.status == 400: + raise ServerError.fromMessage(response_message) + + elif response.status not in (200, 206): + fmt = 'bad status code from server %s: %s' + error_message = fmt % (server_url, response.status) + raise fetchers.HTTPFetchingError(error_message) + + return response_message + + +class Consumer(object): + """An OpenID consumer implementation that performs discovery and + does session management. + + @ivar consumer: an instance of an object implementing the OpenID + protocol, but doing no discovery or session management. + + @type consumer: GenericConsumer + + @ivar session: A dictionary-like object representing the user's + session data. This is used for keeping state of the OpenID + transaction when the user is redirected to the server. + + @cvar session_key_prefix: A string that is prepended to session + keys to ensure that they are unique. This variable may be + changed to suit your application. + """ + session_key_prefix = "_openid_consumer_" + + _token = 'last_token' + + _discover = staticmethod(discover) + + def __init__(self, session, store, consumer_class=None): + """Initialize a Consumer instance. + + You should create a new instance of the Consumer object with + every HTTP request that handles OpenID transactions. + + @param session: See L{the session instance variable} + + @param store: an object that implements the interface in + C{L{openid.store.interface.OpenIDStore}}. Several + implementations are provided, to cover common database + environments. + + @type store: C{L{openid.store.interface.OpenIDStore}} + + @see: L{openid.store.interface} + @see: L{openid.store} + """ + self.session = session + if consumer_class is None: + consumer_class = GenericConsumer + self.consumer = consumer_class(store) + self._token_key = self.session_key_prefix + self._token + + def begin(self, user_url, anonymous=False): + """Start the OpenID authentication process. See steps 1-2 in + the overview at the top of this file. + + @param user_url: Identity URL given by the user. This method + performs a textual transformation of the URL to try and + make sure it is normalized. For example, a user_url of + example.com will be normalized to http://example.com/ + normalizing and resolving any redirects the server might + issue. + + @type user_url: unicode + + @param anonymous: Whether to make an anonymous request of the OpenID + provider. Such a request does not ask for an authorization + assertion for an OpenID identifier, but may be used with + extensions to pass other data. e.g. "I don't care who you are, + but I'd like to know your time zone." + + @type anonymous: bool + + @returns: An object containing the discovered information will + be returned, with a method for building a redirect URL to + the server, as described in step 3 of the overview. This + object may also be used to add extension arguments to the + request, using its + L{addExtensionArg} + method. + + @returntype: L{AuthRequest} + + @raises openid.consumer.discover.DiscoveryFailure: when I fail to + find an OpenID server for this URL. If the C{yadis} package + is available, L{openid.consumer.discover.DiscoveryFailure} is + an alias for C{yadis.discover.DiscoveryFailure}. + """ + disco = Discovery(self.session, user_url, self.session_key_prefix) + try: + service = disco.getNextService(self._discover) + except fetchers.HTTPFetchingError as why: + raise DiscoveryFailure('Error fetching XRDS document: %s' % + (why.why, ), None) + + if service is None: + raise DiscoveryFailure('No usable OpenID services found for %s' % + (user_url, ), None) + else: + return self.beginWithoutDiscovery(service, anonymous) + + def beginWithoutDiscovery(self, service, anonymous=False): + """Start OpenID verification without doing OpenID server + discovery. This method is used internally by Consumer.begin + after discovery is performed, and exists to provide an + interface for library users needing to perform their own + discovery. + + @param service: an OpenID service endpoint descriptor. This + object and factories for it are found in the + L{openid.consumer.discover} module. + + @type service: + L{OpenIDServiceEndpoint} + + @returns: an OpenID authentication request object. + + @rtype: L{AuthRequest} + + @See: Openid.consumer.consumer.Consumer.begin + @see: openid.consumer.discover + """ + auth_req = self.consumer.begin(service) + self.session[self._token_key] = auth_req.endpoint + + try: + auth_req.setAnonymous(anonymous) + except ValueError as why: + raise ProtocolError(str(why)) + + return auth_req + + def complete(self, query, current_url): + """Called to interpret the server's response to an OpenID + request. It is called in step 4 of the flow described in the + consumer overview. + + @param query: A dictionary of the query parameters for this + HTTP request. + + @param current_url: The URL used to invoke the application. + Extract the URL from your application's web + request framework and specify it here to have it checked + against the openid.return_to value in the response. If + the return_to URL check fails, the status of the + completion will be FAILURE. + + @returns: a subclass of Response. The type of response is + indicated by the status attribute, which will be one of + SUCCESS, CANCEL, FAILURE, or SETUP_NEEDED. + + @see: L{SuccessResponse} + @see: L{CancelResponse} + @see: L{SetupNeededResponse} + @see: L{FailureResponse} + """ + + endpoint = self.session.get(self._token_key) + + message = Message.fromPostArgs(query) + response = self.consumer.complete(message, endpoint, current_url) + + try: + del self.session[self._token_key] + except KeyError: + pass + + if (response.status in ['success', 'cancel'] and + response.identity_url is not None): + + disco = Discovery(self.session, response.identity_url, + self.session_key_prefix) + # This is OK to do even if we did not do discovery in + # the first place. + disco.cleanup(force=True) + + return response + + def setAssociationPreference(self, association_preferences): + """Set the order in which association types/sessions should be + attempted. For instance, to only allow HMAC-SHA256 + associations created with a DH-SHA256 association session: + + >>> consumer.setAssociationPreference([('HMAC-SHA256', 'DH-SHA256')]) + + Any association type/association type pair that is not in this + list will not be attempted at all. + + @param association_preferences: The list of allowed + (association type, association session type) pairs that + should be allowed for this consumer to use, in order from + most preferred to least preferred. + @type association_preferences: [(str, str)] + + @returns: None + + @see: C{L{openid.association.SessionNegotiator}} + """ + self.consumer.negotiator = SessionNegotiator(association_preferences) + + +class DiffieHellmanSHA1ConsumerSession(object): + session_type = 'DH-SHA1' + hash_func = staticmethod(cryptutil.sha1) + secret_size = 20 + allowed_assoc_types = ['HMAC-SHA1'] + + def __init__(self, dh=None): + if dh is None: + dh = DiffieHellman.fromDefaults() + + self.dh = dh + + def getRequest(self): + cpub = cryptutil.longToBase64(self.dh.public) + + args = {'dh_consumer_public': cpub} + + if not self.dh.usingDefaultValues(): + args.update({ + 'dh_modulus': cryptutil.longToBase64(self.dh.modulus), + 'dh_gen': cryptutil.longToBase64(self.dh.generator), + }) + + return args + + def extractSecret(self, response): + dh_server_public64 = response.getArg(OPENID_NS, 'dh_server_public', + no_default) + enc_mac_key64 = response.getArg(OPENID_NS, 'enc_mac_key', no_default) + dh_server_public = cryptutil.base64ToLong(dh_server_public64) + enc_mac_key = oidutil.fromBase64(enc_mac_key64) + return self.dh.xorSecret(dh_server_public, enc_mac_key, self.hash_func) + + +class DiffieHellmanSHA256ConsumerSession(DiffieHellmanSHA1ConsumerSession): + session_type = 'DH-SHA256' + hash_func = staticmethod(cryptutil.sha256) + secret_size = 32 + allowed_assoc_types = ['HMAC-SHA256'] + + +class PlainTextConsumerSession(object): + session_type = 'no-encryption' + allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256'] + + def getRequest(self): + return {} + + def extractSecret(self, response): + mac_key64 = response.getArg(OPENID_NS, 'mac_key', no_default) + return oidutil.fromBase64(mac_key64) + + +class SetupNeededError(Exception): + """Internally-used exception that indicates that an immediate-mode + request cancelled.""" + + def __init__(self, user_setup_url=None): + Exception.__init__(self, user_setup_url) + self.user_setup_url = user_setup_url + + +class ProtocolError(ValueError): + """Exception that indicates that a message violated the + protocol. It is raised and caught internally to this file.""" + + +class TypeURIMismatch(ProtocolError): + """A protocol error arising from type URIs mismatching + """ + + def __init__(self, expected, endpoint): + ProtocolError.__init__(self, expected, endpoint) + self.expected = expected + self.endpoint = endpoint + + def __str__(self): + s = '<%s.%s: Required type %s not found in %s for endpoint %s>' % ( + self.__class__.__module__, self.__class__.__name__, self.expected, + self.endpoint.type_uris, self.endpoint) + return s + + +class ServerError(Exception): + """Exception that is raised when the server returns a 400 response + code to a direct request.""" + + def __init__(self, error_text, error_code, message): + Exception.__init__(self, error_text) + self.error_text = error_text + self.error_code = error_code + self.message = message + + def fromMessage(cls, message): + """Generate a ServerError instance, extracting the error text + and the error code from the message.""" + error_text = message.getArg(OPENID_NS, 'error', + '') + error_code = message.getArg(OPENID_NS, 'error_code') + return cls(error_text, error_code, message) + + fromMessage = classmethod(fromMessage) + + +class GenericConsumer(object): + """This is the implementation of the common logic for OpenID + consumers. It is unaware of the application in which it is + running. + + @ivar negotiator: An object that controls the kind of associations + that the consumer makes. It defaults to + C{L{openid.association.default_negotiator}}. Assign a + different negotiator to it if you have specific requirements + for how associations are made. + @type negotiator: C{L{openid.association.SessionNegotiator}} + """ + + # The name of the query parameter that gets added to the return_to + # URL when using OpenID1. You can change this value if you want or + # need a different name, but don't make it start with openid, + # because it's not a standard protocol thing for OpenID1. For + # OpenID2, the library will take care of the nonce using standard + # OpenID query parameter names. + openid1_nonce_query_arg_name = 'janrain_nonce' + + # Another query parameter that gets added to the return_to for + # OpenID 1; if the user's session state is lost, use this claimed + # identifier to do discovery when verifying the response. + openid1_return_to_identifier_name = 'openid1_claimed_id' + + session_types = { + 'DH-SHA1': DiffieHellmanSHA1ConsumerSession, + 'DH-SHA256': DiffieHellmanSHA256ConsumerSession, + 'no-encryption': PlainTextConsumerSession, + } + + _discover = staticmethod(discover) + + def __init__(self, store): + self.store = store + self.negotiator = default_negotiator.copy() + + def begin(self, service_endpoint): + """Create an AuthRequest object for the specified + service_endpoint. This method will create an association if + necessary.""" + if self.store is None: + assoc = None + else: + assoc = self._getAssociation(service_endpoint) + + request = AuthRequest(service_endpoint, assoc) + request.return_to_args[self.openid1_nonce_query_arg_name] = mkNonce() + + if request.message.isOpenID1(): + request.return_to_args[self.openid1_return_to_identifier_name] = \ + request.endpoint.claimed_id + + return request + + def complete(self, message, endpoint, return_to): + """Process the OpenID message, using the specified endpoint + and return_to URL as context. This method will handle any + OpenID message that is sent to the return_to URL. + """ + mode = message.getArg(OPENID_NS, 'mode', '') + + modeMethod = getattr(self, '_complete_' + mode, self._completeInvalid) + + return modeMethod(message, endpoint, return_to) + + def _complete_cancel(self, message, endpoint, _): + return CancelResponse(endpoint) + + def _complete_error(self, message, endpoint, _): + error = message.getArg(OPENID_NS, 'error') + contact = message.getArg(OPENID_NS, 'contact') + reference = message.getArg(OPENID_NS, 'reference') + + return FailureResponse( + endpoint, error, contact=contact, reference=reference) + + def _complete_setup_needed(self, message, endpoint, _): + if not message.isOpenID2(): + return self._completeInvalid(message, endpoint, _) + + user_setup_url = message.getArg(OPENID2_NS, 'user_setup_url') + return SetupNeededResponse(endpoint, user_setup_url) + + def _complete_id_res(self, message, endpoint, return_to): + try: + self._checkSetupNeeded(message) + except SetupNeededError as why: + return SetupNeededResponse(endpoint, why.user_setup_url) + else: + try: + return self._doIdRes(message, endpoint, return_to) + except (ProtocolError, DiscoveryFailure) as why: + return FailureResponse(endpoint, why) + + def _completeInvalid(self, message, endpoint, _): + mode = message.getArg(OPENID_NS, 'mode', '') + return FailureResponse(endpoint, 'Invalid openid.mode: %r' % (mode, )) + + def _checkReturnTo(self, message, return_to): + """Check an OpenID message and its openid.return_to value + against a return_to URL from an application. Return True on + success, False on failure. + """ + # Check the openid.return_to args against args in the original + # message. + try: + self._verifyReturnToArgs(message.toPostArgs()) + except ProtocolError as why: + logging.exception("Verifying return_to arguments: %s" % (why, )) + return False + + # Check the return_to base URL against the one in the message. + msg_return_to = message.getArg(OPENID_NS, 'return_to') + + # The URL scheme, authority, and path MUST be the same between + # the two URLs. + app_parts = urlparse(urinorm.urinorm(return_to)) + msg_parts = urlparse(urinorm.urinorm(msg_return_to)) + + # (addressing scheme, network location, path) must be equal in + # both URLs. + for part in range(0, 3): + if app_parts[part] != msg_parts[part]: + return False + + return True + + _makeKVPost = staticmethod(makeKVPost) + + def _checkSetupNeeded(self, message): + """Check an id_res message to see if it is a + checkid_immediate cancel response. + + @raises SetupNeededError: if it is a checkid_immediate cancellation + """ + # In OpenID 1, we check to see if this is a cancel from + # immediate mode by the presence of the user_setup_url + # parameter. + if message.isOpenID1(): + user_setup_url = message.getArg(OPENID1_NS, 'user_setup_url') + if user_setup_url is not None: + raise SetupNeededError(user_setup_url) + + def _doIdRes(self, message, endpoint, return_to): + """Handle id_res responses that are not cancellations of + immediate mode requests. + + @param message: the response paramaters. + @param endpoint: the discovered endpoint object. May be None. + + @raises ProtocolError: If the message contents are not + well-formed according to the OpenID specification. This + includes missing fields or not signing fields that should + be signed. + + @raises DiscoveryFailure: If the subject of the id_res message + does not match the supplied endpoint, and discovery on the + identifier in the message fails (this should only happen + when using OpenID 2) + + @returntype: L{Response} + """ + # Checks for presence of appropriate fields (and checks + # signed list fields) + self._idResCheckForFields(message) + + if not self._checkReturnTo(message, return_to): + raise ProtocolError( + "return_to does not match return URL. Expected %r, got %r" % + (return_to, message.getArg(OPENID_NS, 'return_to'))) + + # Verify discovery information: + endpoint = self._verifyDiscoveryResults(message, endpoint) + logging.info("Received id_res response from %s using association %s" % + (endpoint.server_url, + message.getArg(OPENID_NS, 'assoc_handle'))) + + self._idResCheckSignature(message, endpoint.server_url) + + # Will raise a ProtocolError if the nonce is bad + self._idResCheckNonce(message, endpoint) + + signed_list_str = message.getArg(OPENID_NS, 'signed', no_default) + signed_list = signed_list_str.split(',') + signed_fields = ["openid." + s for s in signed_list] + return SuccessResponse(endpoint, message, signed_fields) + + def _idResGetNonceOpenID1(self, message, endpoint): + """Extract the nonce from an OpenID 1 response. Return the + nonce from the BARE_NS since we independently check the + return_to arguments are the same as those in the response + message. + + See the openid1_nonce_query_arg_name class variable + + @returns: The nonce as a string or None + """ + return message.getArg(BARE_NS, self.openid1_nonce_query_arg_name) + + def _idResCheckNonce(self, message, endpoint): + if message.isOpenID1(): + # This indicates that the nonce was generated by the consumer + nonce = self._idResGetNonceOpenID1(message, endpoint) + server_url = '' + else: + nonce = message.getArg(OPENID2_NS, 'response_nonce') + server_url = endpoint.server_url + + if nonce is None: + raise ProtocolError('Nonce missing from response') + + try: + timestamp, salt = splitNonce(nonce) + except ValueError as why: + raise ProtocolError('Malformed nonce: %s' % (why, )) + + if (self.store is not None and + not self.store.useNonce(server_url, timestamp, salt)): + raise ProtocolError('Nonce already used or out of range') + + def _idResCheckSignature(self, message, server_url): + assoc_handle = message.getArg(OPENID_NS, 'assoc_handle') + if self.store is None: + assoc = None + else: + assoc = self.store.getAssociation(server_url, assoc_handle) + + if assoc: + if assoc.expiresIn <= 0: + # XXX: It might be a good idea sometimes to re-start the + # authentication with a new association. Doing it + # automatically opens the possibility for + # denial-of-service by a server that just returns expired + # associations (or really short-lived associations) + raise ProtocolError('Association with %s expired' % + (server_url, )) + + if not assoc.checkMessageSignature(message): + raise ProtocolError('Bad signature') + + else: + # It's not an association we know about. Stateless mode is our + # only possible path for recovery. + # XXX - async framework will not want to block on this call to + # _checkAuth. + if not self._checkAuth(message, server_url): + raise ProtocolError('Server denied check_authentication') + + def _idResCheckForFields(self, message): + # XXX: this should be handled by the code that processes the + # response (that is, if a field is missing, we should not have + # to explicitly check that it's present, just make sure that + # the fields are actually being used by the rest of the code + # in tests). Although, which fields are signed does need to be + # checked somewhere. + basic_fields = ['return_to', 'assoc_handle', 'sig', 'signed'] + basic_sig_fields = ['return_to', 'identity'] + + require_fields = { + OPENID2_NS: basic_fields + ['op_endpoint'], + OPENID1_NS: basic_fields + ['identity'], + } + + require_sigs = { + OPENID2_NS: + basic_sig_fields + + ['response_nonce', 'claimed_id', 'assoc_handle', 'op_endpoint'], + OPENID1_NS: + basic_sig_fields, + } + + for field in require_fields[message.getOpenIDNamespace()]: + if not message.hasKey(OPENID_NS, field): + raise ProtocolError('Missing required field %r' % (field, )) + + signed_list_str = message.getArg(OPENID_NS, 'signed', no_default) + signed_list = signed_list_str.split(',') + + for field in require_sigs[message.getOpenIDNamespace()]: + # Field is present and not in signed list + if message.hasKey(OPENID_NS, field) and field not in signed_list: + raise ProtocolError('"%s" not signed' % (field, )) + + def _verifyReturnToArgs(query): + """Verify that the arguments in the return_to URL are present in this + response. + """ + # NOTE -- query came from Message.toPostArgs, which returns a dict of + # {str: str} + message = Message.fromPostArgs(query) + return_to = message.getArg(OPENID_NS, 'return_to') + + if return_to is None: + raise ProtocolError('Response has no return_to') + + parsed_url = urlparse(return_to) + rt_query = parsed_url[4] + parsed_args = parse_qsl(rt_query) + + # NOTE -- parsed_args will be a dict of {bytes: bytes}, however it + # will be checked against return values from Message methods which are + # {str: str}. We need to compare apples to apples. + for rt_key, rt_value in parsed_args: + try: + value = query[rt_key] + if rt_value != value: + format = ("parameter %s value %r does not match " + "return_to's value %r") + raise ProtocolError(format % (rt_key, value, rt_value)) + except KeyError: + format = "return_to parameter %s absent from query %r" + raise ProtocolError(format % (rt_key, query)) + + # Make sure all non-OpenID arguments in the response are also + # in the signed return_to. + bare_args = message.getArgs(BARE_NS) + for pair in bare_args.items(): + if pair not in parsed_args: + raise ProtocolError("Parameter %s not in return_to URL" % + (pair[0], )) + + _verifyReturnToArgs = staticmethod(_verifyReturnToArgs) + + def _verifyDiscoveryResults(self, resp_msg, endpoint=None): + """ + Extract the information from an OpenID assertion message and + verify it against the original + + @param endpoint: The endpoint that resulted from doing discovery + @param resp_msg: The id_res message object + + @returns: the verified endpoint + """ + if resp_msg.getOpenIDNamespace() == OPENID2_NS: + return self._verifyDiscoveryResultsOpenID2(resp_msg, endpoint) + else: + return self._verifyDiscoveryResultsOpenID1(resp_msg, endpoint) + + def _verifyDiscoveryResultsOpenID2(self, resp_msg, endpoint): + to_match = OpenIDServiceEndpoint() + to_match.type_uris = [OPENID_2_0_TYPE] + to_match.claimed_id = resp_msg.getArg(OPENID2_NS, 'claimed_id') + to_match.local_id = resp_msg.getArg(OPENID2_NS, 'identity') + + # Raises a KeyError when the op_endpoint is not present + to_match.server_url = resp_msg.getArg(OPENID2_NS, 'op_endpoint', + no_default) + + # claimed_id and identifier must both be present or both + # be absent + if (to_match.claimed_id is None and to_match.local_id is not None): + raise ProtocolError( + 'openid.identity is present without openid.claimed_id') + + elif (to_match.claimed_id is not None and to_match.local_id is None): + raise ProtocolError( + 'openid.claimed_id is present without openid.identity') + + # This is a response without identifiers, so there's really no + # checking that we can do, so return an endpoint that's for + # the specified `openid.op_endpoint' + elif to_match.claimed_id is None: + return OpenIDServiceEndpoint.fromOPEndpointURL(to_match.server_url) + + # The claimed ID doesn't match, so we have to do discovery + # again. This covers not using sessions, OP identifier + # endpoints and responses that didn't match the original + # request. + if not endpoint: + logging.info('No pre-discovered information supplied.') + endpoint = self._discoverAndVerify(to_match.claimed_id, [to_match]) + elif endpoint.isOPIdentifier(): + logging.info( + 'Pre-discovered information based on OP-ID; need to rediscover.' + ) + endpoint = self._discoverAndVerify(to_match.claimed_id, [to_match]) + else: + # The claimed ID matches, so we use the endpoint that we + # discovered in initiation. This should be the most common + # case. + try: + self._verifyDiscoverySingle(endpoint, to_match) + except ProtocolError as e: + logging.exception( + "Error attempting to use stored discovery information: " + + str(e)) + logging.info("Attempting discovery to verify endpoint") + endpoint = self._discoverAndVerify(to_match.claimed_id, + [to_match]) + + # The endpoint we return should have the claimed ID from the + # message we just verified, fragment and all. + if endpoint.claimed_id != to_match.claimed_id: + endpoint = copy.copy(endpoint) + endpoint.claimed_id = to_match.claimed_id + return endpoint + + def _verifyDiscoveryResultsOpenID1(self, resp_msg, endpoint): + claimed_id = resp_msg.getArg(BARE_NS, + self.openid1_return_to_identifier_name) + + if endpoint is None and claimed_id is None: + raise RuntimeError( + 'When using OpenID 1, the claimed ID must be supplied, ' + 'either by passing it through as a return_to parameter ' + 'or by using a session, and supplied to the GenericConsumer ' + 'as the argument to complete()') + elif endpoint is not None and claimed_id is None: + claimed_id = endpoint.claimed_id + + to_match = OpenIDServiceEndpoint() + to_match.type_uris = [OPENID_1_1_TYPE] + to_match.local_id = resp_msg.getArg(OPENID1_NS, 'identity') + # Restore delegate information from the initiation phase + to_match.claimed_id = claimed_id + + if to_match.local_id is None: + raise ProtocolError('Missing required field openid.identity') + + to_match_1_0 = copy.copy(to_match) + to_match_1_0.type_uris = [OPENID_1_0_TYPE] + + if endpoint is not None: + try: + try: + self._verifyDiscoverySingle(endpoint, to_match) + except TypeURIMismatch: + self._verifyDiscoverySingle(endpoint, to_match_1_0) + except ProtocolError as e: + logging.exception( + "Error attempting to use stored discovery information: " + + str(e)) + logging.info("Attempting discovery to verify endpoint") + else: + return endpoint + + # Endpoint is either bad (failed verification) or None + return self._discoverAndVerify(claimed_id, [to_match, to_match_1_0]) + + def _verifyDiscoverySingle(self, endpoint, to_match): + """Verify that the given endpoint matches the information + extracted from the OpenID assertion, and raise an exception if + there is a mismatch. + + @type endpoint: openid.consumer.discover.OpenIDServiceEndpoint + @type to_match: openid.consumer.discover.OpenIDServiceEndpoint + + @rtype: NoneType + + @raises ProtocolError: when the endpoint does not match the + discovered information. + """ + # Every type URI that's in the to_match endpoint has to be + # present in the discovered endpoint. + for type_uri in to_match.type_uris: + if not endpoint.usesExtension(type_uri): + raise TypeURIMismatch(type_uri, endpoint) + + # Fragments do not influence discovery, so we can't compare a + # claimed identifier with a fragment to discovered information. + defragged_claimed_id, _ = urldefrag(to_match.claimed_id) + if defragged_claimed_id != endpoint.claimed_id: + raise ProtocolError( + 'Claimed ID does not match (different subjects!), ' + 'Expected %s, got %s' % + (defragged_claimed_id, endpoint.claimed_id)) + + if to_match.getLocalID() != endpoint.getLocalID(): + raise ProtocolError('local_id mismatch. Expected %s, got %s' % + (to_match.getLocalID(), endpoint.getLocalID())) + + # If the server URL is None, this must be an OpenID 1 + # response, because op_endpoint is a required parameter in + # OpenID 2. In that case, we don't actually care what the + # discovered server_url is, because signature checking or + # check_auth should take care of that check for us. + if to_match.server_url is None: + assert to_match.preferredNamespace() == OPENID1_NS, ( + """The code calling this must ensure that OpenID 2 + responses have a non-none `openid.op_endpoint' and + that it is set as the `server_url' attribute of the + `to_match' endpoint.""") + + elif to_match.server_url != endpoint.server_url: + raise ProtocolError('OP Endpoint mismatch. Expected %s, got %s' % + (to_match.server_url, endpoint.server_url)) + + def _discoverAndVerify(self, claimed_id, to_match_endpoints): + """Given an endpoint object created from the information in an + OpenID response, perform discovery and verify the discovery + results, returning the matching endpoint that is the result of + doing that discovery. + + @type to_match: openid.consumer.discover.OpenIDServiceEndpoint + @param to_match: The endpoint whose information we're confirming + + @rtype: openid.consumer.discover.OpenIDServiceEndpoint + @returns: The result of performing discovery on the claimed + identifier in `to_match' + + @raises DiscoveryFailure: when discovery fails. + """ + logging.info('Performing discovery on %s' % (claimed_id, )) + _, services = self._discover(claimed_id) + if not services: + raise DiscoveryFailure('No OpenID information found at %s' % + (claimed_id, ), None) + return self._verifyDiscoveredServices(claimed_id, services, + to_match_endpoints) + + def _verifyDiscoveredServices(self, claimed_id, services, + to_match_endpoints): + """See @L{_discoverAndVerify}""" + + # Search the services resulting from discovery to find one + # that matches the information from the assertion + failure_messages = [] + for endpoint in services: + for to_match_endpoint in to_match_endpoints: + try: + self._verifyDiscoverySingle(endpoint, to_match_endpoint) + except ProtocolError as why: + failure_messages.append(str(why)) + else: + # It matches, so discover verification has + # succeeded. Return this endpoint. + return endpoint + else: + logging.error('Discovery verification failure for %s' % + (claimed_id, )) + for failure_message in failure_messages: + logging.error(' * Endpoint mismatch: ' + failure_message) + + raise DiscoveryFailure( + 'No matching endpoint found after discovering %s' % + (claimed_id, ), None) + + def _checkAuth(self, message, server_url): + """Make a check_authentication request to verify this message. + + @returns: True if the request is valid. + @rtype: bool + """ + logging.info('Using OpenID check_authentication') + request = self._createCheckAuthRequest(message) + if request is None: + return False + try: + response = self._makeKVPost(request, server_url) + except (fetchers.HTTPFetchingError, ServerError) as e: + e0 = e.args[0] + logging.exception('check_authentication failed: %s' % e0) + return False + else: + return self._processCheckAuthResponse(response, server_url) + + def _createCheckAuthRequest(self, message): + """Generate a check_authentication request message given an + id_res message. + """ + signed = message.getArg(OPENID_NS, 'signed') + if signed: + if isinstance(signed, bytes): + signed = str(signed, encoding="utf-8") + for k in signed.split(','): + logging.info(k) + val = message.getAliasedArg(k) + + # Signed value is missing + if val is None: + logging.info('Missing signed field %r' % (k, )) + return None + + check_auth_message = message.copy() + check_auth_message.setArg(OPENID_NS, 'mode', 'check_authentication') + return check_auth_message + + def _processCheckAuthResponse(self, response, server_url): + """Process the response message from a check_authentication + request, invalidating associations if requested. + """ + is_valid = response.getArg(OPENID_NS, 'is_valid', 'false') + + invalidate_handle = response.getArg(OPENID_NS, 'invalidate_handle') + if invalidate_handle is not None: + logging.info('Received "invalidate_handle" from server %s' % + (server_url, )) + if self.store is None: + logging.error('Unexpectedly got invalidate_handle without ' + 'a store!') + else: + self.store.removeAssociation(server_url, invalidate_handle) + + if is_valid == 'true': + return True + else: + logging.error('Server responds that checkAuth call is not valid') + return False + + def _getAssociation(self, endpoint): + """Get an association for the endpoint's server_url. + + First try seeing if we have a good association in the + store. If we do not, then attempt to negotiate an association + with the server. + + If we negotiate a good association, it will get stored. + + @returns: A valid association for the endpoint's server_url or None + @rtype: openid.association.Association or NoneType + """ + assoc = self.store.getAssociation(endpoint.server_url) + + if assoc is None or assoc.expiresIn <= 0: + assoc = self._negotiateAssociation(endpoint) + if assoc is not None: + self.store.storeAssociation(endpoint.server_url, assoc) + + return assoc + + def _negotiateAssociation(self, endpoint): + """Make association requests to the server, attempting to + create a new association. + + @returns: a new association object + + @rtype: L{openid.association.Association} + """ + # Get our preferred session/association type from the negotiatior. + assoc_type, session_type = self.negotiator.getAllowedType() + + try: + assoc = self._requestAssociation(endpoint, assoc_type, + session_type) + except ServerError as why: + supportedTypes = self._extractSupportedAssociationType( + why, endpoint, assoc_type) + if supportedTypes is not None: + assoc_type, session_type = supportedTypes + # Attempt to create an association from the assoc_type + # and session_type that the server told us it + # supported. + try: + assoc = self._requestAssociation(endpoint, assoc_type, + session_type) + except ServerError as why: + # Do not keep trying, since it rejected the + # association type that it told us to use. + logging.error( + 'Server %s refused its suggested association ' + 'type: session_type=%s, assoc_type=%s' % ( + endpoint.server_url, session_type, assoc_type)) + return None + else: + return assoc + else: + return assoc + + def _extractSupportedAssociationType(self, server_error, endpoint, + assoc_type): + """Handle ServerErrors resulting from association requests. + + @returns: If server replied with an C{unsupported-type} error, + return a tuple of supported C{association_type}, C{session_type}. + Otherwise logs the error and returns None. + @rtype: tuple or None + """ + # Any error message whose code is not 'unsupported-type' + # should be considered a total failure. + if server_error.error_code != 'unsupported-type' or \ + server_error.message.isOpenID1(): + logging.error( + 'Server error when requesting an association from %r: %s' % + (endpoint.server_url, server_error.error_text)) + return None + + # The server didn't like the association/session type + # that we sent, and it sent us back a message that + # might tell us how to handle it. + logging.error('Unsupported association type %s: %s' % + (assoc_type, server_error.error_text, )) + + # Extract the session_type and assoc_type from the + # error message + assoc_type = server_error.message.getArg(OPENID_NS, 'assoc_type') + session_type = server_error.message.getArg(OPENID_NS, 'session_type') + + if assoc_type is None or session_type is None: + logging.error('Server responded with unsupported association ' + 'session but did not supply a fallback.') + return None + elif not self.negotiator.isAllowed(assoc_type, session_type): + fmt = ('Server sent unsupported session/association type: ' + 'session_type=%s, assoc_type=%s') + logging.error(fmt % (session_type, assoc_type)) + return None + else: + return assoc_type, session_type + + def _requestAssociation(self, endpoint, assoc_type, session_type): + """Make and process one association request to this endpoint's + OP endpoint URL. + + @returns: An association object or None if the association + processing failed. + + @raises ServerError: when the remote OpenID server returns an error. + """ + assoc_session, args = self._createAssociateRequest( + endpoint, assoc_type, session_type) + + try: + response = self._makeKVPost(args, endpoint.server_url) + except fetchers.HTTPFetchingError as why: + logging.exception('openid.associate request failed: %s' % (why, )) + return None + + try: + assoc = self._extractAssociation(response, assoc_session) + except KeyError as why: + logging.exception( + 'Missing required parameter in response from %s: %s' % + (endpoint.server_url, why)) + return None + except ProtocolError as why: + logging.exception('Protocol error parsing response from %s: %s' % + (endpoint.server_url, why)) + return None + else: + return assoc + + def _createAssociateRequest(self, endpoint, assoc_type, session_type): + """Create an association request for the given assoc_type and + session_type. + + @param endpoint: The endpoint whose server_url will be + queried. The important bit about the endpoint is whether + it's in compatiblity mode (OpenID 1.1) + + @param assoc_type: The association type that the request + should ask for. + @type assoc_type: str + + @param session_type: The session type that should be used in + the association request. The session_type is used to + create an association session object, and that session + object is asked for any additional fields that it needs to + add to the request. + @type session_type: str + + @returns: a pair of the association session object and the + request message that will be sent to the server. + @rtype: (association session type (depends on session_type), + openid.message.Message) + """ + session_type_class = self.session_types[session_type] + assoc_session = session_type_class() + + args = { + 'mode': 'associate', + 'assoc_type': assoc_type, + } + + if not endpoint.compatibilityMode(): + args['ns'] = OPENID2_NS + + # Leave out the session type if we're in compatibility mode + # *and* it's no-encryption. + if (not endpoint.compatibilityMode() or + assoc_session.session_type != 'no-encryption'): + args['session_type'] = assoc_session.session_type + + args.update(assoc_session.getRequest()) + message = Message.fromOpenIDArgs(args) + return assoc_session, message + + def _getOpenID1SessionType(self, assoc_response): + """Given an association response message, extract the OpenID + 1.X session type. + + This function mostly takes care of the 'no-encryption' default + behavior in OpenID 1. + + If the association type is plain-text, this function will + return 'no-encryption' + + @returns: The association type for this message + @rtype: str + + @raises KeyError: when the session_type field is absent. + """ + # If it's an OpenID 1 message, allow session_type to default + # to None (which signifies "no-encryption") + session_type = assoc_response.getArg(OPENID1_NS, 'session_type') + + # Handle the differences between no-encryption association + # respones in OpenID 1 and 2: + + # no-encryption is not really a valid session type for + # OpenID 1, but we'll accept it anyway, while issuing a + # warning. + if session_type == 'no-encryption': + logging.warning('OpenID server sent "no-encryption"' + 'for OpenID 1.X') + + # Missing or empty session type is the way to flag a + # 'no-encryption' response. Change the session type to + # 'no-encryption' so that it can be handled in the same + # way as OpenID 2 'no-encryption' respones. + elif session_type == '' or session_type is None: + session_type = 'no-encryption' + + return session_type + + def _extractAssociation(self, assoc_response, assoc_session): + """Attempt to extract an association from the response, given + the association response message and the established + association session. + + @param assoc_response: The association response message from + the server + @type assoc_response: openid.message.Message + + @param assoc_session: The association session object that was + used when making the request + @type assoc_session: depends on the session type of the request + + @raises ProtocolError: when data is malformed + @raises KeyError: when a field is missing + + @rtype: openid.association.Association + """ + # Extract the common fields from the response, raising an + # exception if they are not found + assoc_type = assoc_response.getArg(OPENID_NS, 'assoc_type', no_default) + assoc_handle = assoc_response.getArg(OPENID_NS, 'assoc_handle', + no_default) + + # expires_in is a base-10 string. The Python parsing will + # accept literals that have whitespace around them and will + # accept negative values. Neither of these are really in-spec, + # but we think it's OK to accept them. + expires_in_str = assoc_response.getArg(OPENID_NS, 'expires_in', + no_default) + try: + expires_in = int(expires_in_str) + except ValueError as why: + raise ProtocolError('Invalid expires_in field: %s' % (why, )) + + # OpenID 1 has funny association session behaviour. + if assoc_response.isOpenID1(): + session_type = self._getOpenID1SessionType(assoc_response) + else: + session_type = assoc_response.getArg(OPENID2_NS, 'session_type', + no_default) + + # Session type mismatch + if assoc_session.session_type != session_type: + if (assoc_response.isOpenID1() and + session_type == 'no-encryption'): + # In OpenID 1, any association request can result in a + # 'no-encryption' association response. Setting + # assoc_session to a new no-encryption session should + # make the rest of this function work properly for + # that case. + assoc_session = PlainTextConsumerSession() + else: + # Any other mismatch, regardless of protocol version + # results in the failure of the association session + # altogether. + fmt = 'Session type mismatch. Expected %r, got %r' + message = fmt % (assoc_session.session_type, session_type) + raise ProtocolError(message) + + # Make sure assoc_type is valid for session_type + if assoc_type not in assoc_session.allowed_assoc_types: + fmt = 'Unsupported assoc_type for session %s returned: %s' + raise ProtocolError(fmt % (assoc_session.session_type, assoc_type)) + + # Delegate to the association session to extract the secret + # from the response, however is appropriate for that session + # type. + try: + secret = assoc_session.extractSecret(assoc_response) + except ValueError as why: + fmt = 'Malformed response for %s session: %s' + raise ProtocolError(fmt % (assoc_session.session_type, why)) + + return Association.fromExpiresIn(expires_in, assoc_handle, secret, + assoc_type) + + +class AuthRequest(object): + """An object that holds the state necessary for generating an + OpenID authentication request. This object holds the association + with the server and the discovered information with which the + request will be made. + + It is separate from the consumer because you may wish to add + things to the request before sending it on its way to the + server. It also has serialization options that let you encode the + authentication request as a URL or as a form POST. + """ + + def __init__(self, endpoint, assoc): + """ + Creates a new AuthRequest object. This just stores each + argument in an appropriately named field. + + Users of this library should not create instances of this + class. Instances of this class are created by the library + when needed. + """ + self.assoc = assoc + self.endpoint = endpoint + self.return_to_args = {} + self.message = Message(endpoint.preferredNamespace()) + self._anonymous = False + + def setAnonymous(self, is_anonymous): + """Set whether this request should be made anonymously. If a + request is anonymous, the identifier will not be sent in the + request. This is only useful if you are making another kind of + request with an extension in this request. + + Anonymous requests are not allowed when the request is made + with OpenID 1. + + @raises ValueError: when attempting to set an OpenID1 request + as anonymous + """ + if is_anonymous and self.message.isOpenID1(): + raise ValueError('OpenID 1 requests MUST include the ' + 'identifier in the request') + else: + self._anonymous = is_anonymous + + def addExtension(self, extension_request): + """Add an extension to this checkid request. + + @param extension_request: An object that implements the + extension interface for adding arguments to an OpenID + message. + """ + extension_request.toMessage(self.message) + + def addExtensionArg(self, namespace, key, value): + """Add an extension argument to this OpenID authentication + request. + + Use caution when adding arguments, because they will be + URL-escaped and appended to the redirect URL, which can easily + get quite long. + + @param namespace: The namespace for the extension. For + example, the simple registration extension uses the + namespace C{sreg}. + + @type namespace: str + + @param key: The key within the extension namespace. For + example, the nickname field in the simple registration + extension's key is C{nickname}. + + @type key: str + + @param value: The value to provide to the server for this + argument. + + @type value: str + """ + self.message.setArg(namespace, key, value) + + def getMessage(self, realm, return_to=None, immediate=False): + """Produce a L{openid.message.Message} representing this request. + + @param realm: The URL (or URL pattern) that identifies your + web site to the user when she is authorizing it. + + @type realm: str + + @param return_to: The URL that the OpenID provider will send the + user back to after attempting to verify her identity. + + Not specifying a return_to URL means that the user will not + be returned to the site issuing the request upon its + completion. + + @type return_to: str + + @param immediate: If True, the OpenID provider is to send back + a response immediately, useful for behind-the-scenes + authentication attempts. Otherwise the OpenID provider + may engage the user before providing a response. This is + the default case, as the user may need to provide + credentials or approve the request before a positive + response can be sent. + + @type immediate: bool + + @returntype: L{openid.message.Message} + """ + if return_to: + return_to = oidutil.appendArgs(return_to, self.return_to_args) + elif immediate: + raise ValueError( + '"return_to" is mandatory when using "checkid_immediate"') + elif self.message.isOpenID1(): + raise ValueError('"return_to" is mandatory for OpenID 1 requests') + elif self.return_to_args: + raise ValueError('extra "return_to" arguments were specified, ' + 'but no return_to was specified') + + if immediate: + mode = 'checkid_immediate' + else: + mode = 'checkid_setup' + + message = self.message.copy() + if message.isOpenID1(): + realm_key = 'trust_root' + else: + realm_key = 'realm' + + message.updateArgs(OPENID_NS, { + realm_key: realm, + 'mode': mode, + 'return_to': return_to, + }) + + if not self._anonymous: + if self.endpoint.isOPIdentifier(): + # This will never happen when we're in compatibility + # mode, as long as isOPIdentifier() returns False + # whenever preferredNamespace() returns OPENID1_NS. + claimed_id = request_identity = IDENTIFIER_SELECT + else: + request_identity = self.endpoint.getLocalID() + claimed_id = self.endpoint.claimed_id + + # This is true for both OpenID 1 and 2 + message.setArg(OPENID_NS, 'identity', request_identity) + + if message.isOpenID2(): + message.setArg(OPENID2_NS, 'claimed_id', claimed_id) + + if self.assoc: + message.setArg(OPENID_NS, 'assoc_handle', self.assoc.handle) + assoc_log_msg = 'with association %s' % (self.assoc.handle, ) + else: + assoc_log_msg = 'using stateless mode.' + + logging.info("Generated %s request to %s %s" % + (mode, self.endpoint.server_url, assoc_log_msg)) + + return message + + def redirectURL(self, realm, return_to=None, immediate=False): + """Returns a URL with an encoded OpenID request. + + The resulting URL is the OpenID provider's endpoint URL with + parameters appended as query arguments. You should redirect + the user agent to this URL. + + OpenID 2.0 endpoints also accept POST requests, see + C{L{shouldSendRedirect}} and C{L{formMarkup}}. + + @param realm: The URL (or URL pattern) that identifies your + web site to the user when she is authorizing it. + + @type realm: str + + @param return_to: The URL that the OpenID provider will send the + user back to after attempting to verify her identity. + + Not specifying a return_to URL means that the user will not + be returned to the site issuing the request upon its + completion. + + @type return_to: str + + @param immediate: If True, the OpenID provider is to send back + a response immediately, useful for behind-the-scenes + authentication attempts. Otherwise the OpenID provider + may engage the user before providing a response. This is + the default case, as the user may need to provide + credentials or approve the request before a positive + response can be sent. + + @type immediate: bool + + @returns: The URL to redirect the user agent to. + + @returntype: str + """ + message = self.getMessage(realm, return_to, immediate) + return message.toURL(self.endpoint.server_url) + + def formMarkup(self, + realm, + return_to=None, + immediate=False, + form_tag_attrs=None): + """Get html for a form to submit this request to the IDP. + + @param form_tag_attrs: Dictionary of attributes to be added to + the form tag. 'accept-charset' and 'enctype' have defaults + that can be overridden. If a value is supplied for + 'action' or 'method', it will be replaced. + @type form_tag_attrs: {unicode: unicode} + """ + message = self.getMessage(realm, return_to, immediate) + return message.toFormMarkup(self.endpoint.server_url, form_tag_attrs) + + def htmlMarkup(self, + realm, + return_to=None, + immediate=False, + form_tag_attrs=None): + """Get an autosubmitting HTML page that submits this request to the + IDP. This is just a wrapper for formMarkup. + + @see: formMarkup + + @returns: str + """ + return oidutil.autoSubmitHTML( + self.formMarkup(realm, return_to, immediate, form_tag_attrs)) + + def shouldSendRedirect(self): + """Should this OpenID authentication request be sent as a HTTP + redirect or as a POST (form submission)? + + @rtype: bool + """ + return self.endpoint.compatibilityMode() + + +FAILURE = 'failure' +SUCCESS = 'success' +CANCEL = 'cancel' +SETUP_NEEDED = 'setup_needed' + + +class Response(object): + status = None + + def setEndpoint(self, endpoint): + self.endpoint = endpoint + if endpoint is None: + self.identity_url = None + else: + self.identity_url = endpoint.claimed_id + + def getDisplayIdentifier(self): + """Return the display identifier for this response. + + The display identifier is related to the Claimed Identifier, but the + two are not always identical. The display identifier is something the + user should recognize as what they entered, whereas the response's + claimed identifier (in the L{identity_url} attribute) may have extra + information for better persistence. + + URLs will be stripped of their fragments for display. XRIs will + display the human-readable identifier (i-name) instead of the + persistent identifier (i-number). + + Use the display identifier in your user interface. Use + L{identity_url} for querying your database or authorization server. + """ + if self.endpoint is not None: + return self.endpoint.getDisplayIdentifier() + return None + + +class SuccessResponse(Response): + """A response with a status of SUCCESS. Indicates that this request is a + successful acknowledgement from the OpenID server that the + supplied URL is, indeed controlled by the requesting agent. + + @ivar identity_url: The identity URL that has been authenticated; + the Claimed Identifier. + See also L{getDisplayIdentifier}. + + @ivar endpoint: The endpoint that authenticated the identifier. You + may access other discovered information related to this endpoint, + such as the CanonicalID of an XRI, through this object. + @type endpoint: + L{OpenIDServiceEndpoint} + + @ivar signed_fields: The arguments in the server's response that + were signed and verified. + + @cvar status: SUCCESS + """ + + status = SUCCESS + + def __init__(self, endpoint, message, signed_fields=None): + # Don't use setEndpoint, because endpoint should never be None + # for a successfull transaction. + self.endpoint = endpoint + self.identity_url = endpoint.claimed_id + + self.message = message + + if signed_fields is None: + signed_fields = [] + self.signed_fields = signed_fields + + def isOpenID1(self): + """Was this authentication response an OpenID 1 authentication + response? + """ + return self.message.isOpenID1() + + def isSigned(self, ns_uri, ns_key): + """Return whether a particular key is signed, regardless of + its namespace alias + """ + return self.message.getKey(ns_uri, ns_key) in self.signed_fields + + def getSigned(self, ns_uri, ns_key, default=None): + """Return the specified signed field if available, + otherwise return default + """ + if self.isSigned(ns_uri, ns_key): + return self.message.getArg(ns_uri, ns_key, default) + else: + return default + + def getSignedNS(self, ns_uri): + """Get signed arguments from the response message. Return a + dict of all arguments in the specified namespace. If any of + the arguments are not signed, return None. + """ + msg_args = self.message.getArgs(ns_uri) + + for key in msg_args.keys(): + if not self.isSigned(ns_uri, key): + logging.info( + "SuccessResponse.getSignedNS: (%s, %s) not signed." % + (ns_uri, key)) + return None + + return msg_args + + def extensionResponse(self, namespace_uri, require_signed): + """Return response arguments in the specified namespace. + + @param namespace_uri: The namespace URI of the arguments to be + returned. + + @param require_signed: True if the arguments should be among + those signed in the response, False if you don't care. + + If require_signed is True and the arguments are not signed, + return None. + """ + if require_signed: + return self.getSignedNS(namespace_uri) + else: + return self.message.getArgs(namespace_uri) + + def getReturnTo(self): + """Get the openid.return_to argument from this response. + + This is useful for verifying that this request was initiated + by this consumer. + + @returns: The return_to URL supplied to the server on the + initial request, or C{None} if the response did not contain + an C{openid.return_to} argument. + + @returntype: str + """ + return self.getSigned(OPENID_NS, 'return_to') + + def __eq__(self, other): + return ((self.endpoint == other.endpoint) and + (self.identity_url == other.identity_url) and + (self.message == other.message) and + (self.signed_fields == other.signed_fields) and + (self.status == other.status)) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return '<%s.%s id=%r signed=%r>' % ( + self.__class__.__module__, self.__class__.__name__, + self.identity_url, self.signed_fields) + + +class FailureResponse(Response): + """A response with a status of FAILURE. Indicates that the OpenID + protocol has failed. This could be locally or remotely triggered. + + @ivar identity_url: The identity URL for which authenitcation was + attempted, if it can be determined. Otherwise, None. + + @ivar message: A message indicating why the request failed, if one + is supplied. otherwise, None. + + @cvar status: FAILURE + """ + + status = FAILURE + + def __init__(self, endpoint, message=None, contact=None, reference=None): + self.setEndpoint(endpoint) + self.message = message + self.contact = contact + self.reference = reference + + def __repr__(self): + return "<%s.%s id=%r message=%r>" % (self.__class__.__module__, + self.__class__.__name__, + self.identity_url, self.message) + + +class CancelResponse(Response): + """A response with a status of CANCEL. Indicates that the user + cancelled the OpenID authentication request. + + @ivar identity_url: The identity URL for which authenitcation was + attempted, if it can be determined. Otherwise, None. + + @cvar status: CANCEL + """ + + status = CANCEL + + def __init__(self, endpoint): + self.setEndpoint(endpoint) + + +class SetupNeededResponse(Response): + """A response with a status of SETUP_NEEDED. Indicates that the + request was in immediate mode, and the server is unable to + authenticate the user without further interaction. + + @ivar identity_url: The identity URL for which authenitcation was + attempted. + + @ivar setup_url: A URL that can be used to send the user to the + server to set up for authentication. The user should be + redirected in to the setup_url, either in the current window + or in a new browser window. C{None} in OpenID 2.0. + + @cvar status: SETUP_NEEDED + """ + + status = SETUP_NEEDED + + def __init__(self, endpoint, setup_url=None): + self.setEndpoint(endpoint) + self.setup_url = setup_url diff --git a/flex/lib/python3.6/site-packages/openid/consumer/discover.py b/flex/lib/python3.6/site-packages/openid/consumer/discover.py new file mode 100644 index 0000000..07f4a94 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/consumer/discover.py @@ -0,0 +1,465 @@ +# -*- test-case-name: openid.test.test_discover -*- +"""Functions to discover OpenID endpoints from identifiers. +""" + +__all__ = [ + 'DiscoveryFailure', + 'OPENID_1_0_NS', + 'OPENID_1_0_TYPE', + 'OPENID_1_1_TYPE', + 'OPENID_2_0_TYPE', + 'OPENID_IDP_2_0_TYPE', + 'OpenIDServiceEndpoint', + 'discover', +] + +import urllib.parse +import logging + +from openid import fetchers, urinorm + +from openid import yadis +from openid.yadis.etxrd import nsTag, XRDSError, XRD_NS_2_0 +from openid.yadis.services import applyFilter as extractServices +from openid.yadis.discover import discover as yadisDiscover +from openid.yadis.discover import DiscoveryFailure +from openid.yadis import xrires, filters +from openid.yadis import xri + +from openid.consumer import html_parse + +OPENID_1_0_NS = 'http://openid.net/xmlns/1.0' +OPENID_IDP_2_0_TYPE = 'http://specs.openid.net/auth/2.0/server' +OPENID_2_0_TYPE = 'http://specs.openid.net/auth/2.0/signon' +OPENID_1_1_TYPE = 'http://openid.net/signon/1.1' +OPENID_1_0_TYPE = 'http://openid.net/signon/1.0' + +from openid.message import OPENID1_NS as OPENID_1_0_MESSAGE_NS +from openid.message import OPENID2_NS as OPENID_2_0_MESSAGE_NS + + +class OpenIDServiceEndpoint(object): + """Object representing an OpenID service endpoint. + + @ivar identity_url: the verified identifier. + @ivar canonicalID: For XRI, the persistent identifier. + """ + + # OpenID service type URIs, listed in order of preference. The + # ordering of this list affects yadis and XRI service discovery. + openid_type_uris = [ + OPENID_IDP_2_0_TYPE, + OPENID_2_0_TYPE, + OPENID_1_1_TYPE, + OPENID_1_0_TYPE, + ] + + def __init__(self): + self.claimed_id = None + self.server_url = None + self.type_uris = [] + self.local_id = None + self.canonicalID = None + self.used_yadis = False # whether this came from an XRDS + self.display_identifier = None + + def usesExtension(self, extension_uri): + return extension_uri in self.type_uris + + def preferredNamespace(self): + if (OPENID_IDP_2_0_TYPE in self.type_uris or + OPENID_2_0_TYPE in self.type_uris): + return OPENID_2_0_MESSAGE_NS + else: + return OPENID_1_0_MESSAGE_NS + + def supportsType(self, type_uri): + """Does this endpoint support this type? + + I consider C{/server} endpoints to implicitly support C{/signon}. + """ + return ((type_uri in self.type_uris) or + (type_uri == OPENID_2_0_TYPE and self.isOPIdentifier())) + + def getDisplayIdentifier(self): + """Return the display_identifier if set, else return the claimed_id. + """ + if self.display_identifier is not None: + return self.display_identifier + if self.claimed_id is None: + return None + else: + return urllib.parse.urldefrag(self.claimed_id)[0] + + def compatibilityMode(self): + return self.preferredNamespace() != OPENID_2_0_MESSAGE_NS + + def isOPIdentifier(self): + return OPENID_IDP_2_0_TYPE in self.type_uris + + def parseService(self, yadis_url, uri, type_uris, service_element): + """Set the state of this object based on the contents of the + service element.""" + self.type_uris = type_uris + self.server_url = uri + self.used_yadis = True + + if not self.isOPIdentifier(): + # XXX: This has crappy implications for Service elements + # that contain both 'server' and 'signon' Types. But + # that's a pathological configuration anyway, so I don't + # think I care. + self.local_id = findOPLocalIdentifier(service_element, + self.type_uris) + self.claimed_id = yadis_url + + def getLocalID(self): + """Return the identifier that should be sent as the + openid.identity parameter to the server.""" + # I looked at this conditional and thought "ah-hah! there's the bug!" + # but Python actually makes that one big expression somehow, i.e. + # "x is x is x" is not the same thing as "(x is x) is x". + # That's pretty weird, dude. -- kmt, 1/07 + if (self.local_id is self.canonicalID is None): + return self.claimed_id + else: + return self.local_id or self.canonicalID + + def fromBasicServiceEndpoint(cls, endpoint): + """Create a new instance of this class from the endpoint + object passed in. + + @return: None or OpenIDServiceEndpoint for this endpoint object""" + type_uris = endpoint.matchTypes(cls.openid_type_uris) + + # If any Type URIs match and there is an endpoint URI + # specified, then this is an OpenID endpoint + if type_uris and endpoint.uri is not None: + openid_endpoint = cls() + openid_endpoint.parseService(endpoint.yadis_url, endpoint.uri, + endpoint.type_uris, + endpoint.service_element) + else: + openid_endpoint = None + + return openid_endpoint + + fromBasicServiceEndpoint = classmethod(fromBasicServiceEndpoint) + + def fromHTML(cls, uri, html): + """Parse the given document as HTML looking for an OpenID + + @rtype: [OpenIDServiceEndpoint] + """ + discovery_types = [ + (OPENID_2_0_TYPE, 'openid2.provider', 'openid2.local_id'), + (OPENID_1_1_TYPE, 'openid.server', 'openid.delegate'), + ] + + link_attrs = html_parse.parseLinkAttrs(html) + services = [] + for type_uri, op_endpoint_rel, local_id_rel in discovery_types: + op_endpoint_url = html_parse.findFirstHref(link_attrs, + op_endpoint_rel) + if op_endpoint_url is None: + continue + + service = cls() + service.claimed_id = uri + service.local_id = html_parse.findFirstHref(link_attrs, + local_id_rel) + service.server_url = op_endpoint_url + service.type_uris = [type_uri] + + services.append(service) + + return services + + fromHTML = classmethod(fromHTML) + + def fromXRDS(cls, uri, xrds): + """Parse the given document as XRDS looking for OpenID services. + + @rtype: [OpenIDServiceEndpoint] + + @raises XRDSError: When the XRDS does not parse. + + @since: 2.1.0 + """ + return extractServices(uri, xrds, cls) + + fromXRDS = classmethod(fromXRDS) + + def fromDiscoveryResult(cls, discoveryResult): + """Create endpoints from a DiscoveryResult. + + @type discoveryResult: L{DiscoveryResult} + + @rtype: list of L{OpenIDServiceEndpoint} + + @raises XRDSError: When the XRDS does not parse. + + @since: 2.1.0 + """ + if discoveryResult.isXRDS(): + method = cls.fromXRDS + else: + method = cls.fromHTML + return method(discoveryResult.normalized_uri, + discoveryResult.response_text) + + fromDiscoveryResult = classmethod(fromDiscoveryResult) + + def fromOPEndpointURL(cls, op_endpoint_url): + """Construct an OP-Identifier OpenIDServiceEndpoint object for + a given OP Endpoint URL + + @param op_endpoint_url: The URL of the endpoint + @rtype: OpenIDServiceEndpoint + """ + service = cls() + service.server_url = op_endpoint_url + service.type_uris = [OPENID_IDP_2_0_TYPE] + return service + + fromOPEndpointURL = classmethod(fromOPEndpointURL) + + def __str__(self): + return ("<%s.%s " + "server_url=%r " + "claimed_id=%r " + "local_id=%r " + "canonicalID=%r " + "used_yadis=%s " + ">" % (self.__class__.__module__, self.__class__.__name__, + self.server_url, self.claimed_id, self.local_id, + self.canonicalID, self.used_yadis)) + + +def findOPLocalIdentifier(service_element, type_uris): + """Find the OP-Local Identifier for this xrd:Service element. + + This considers openid:Delegate to be a synonym for xrd:LocalID if + both OpenID 1.X and OpenID 2.0 types are present. If only OpenID + 1.X is present, it returns the value of openid:Delegate. If only + OpenID 2.0 is present, it returns the value of xrd:LocalID. If + there is more than one LocalID tag and the values are different, + it raises a DiscoveryFailure. This is also triggered when the + xrd:LocalID and openid:Delegate tags are different. + + @param service_element: The xrd:Service element + @type service_element: ElementTree.Node + + @param type_uris: The xrd:Type values present in this service + element. This function could extract them, but higher level + code needs to do that anyway. + @type type_uris: [str] + + @raises DiscoveryFailure: when discovery fails. + + @returns: The OP-Local Identifier for this service element, if one + is present, or None otherwise. + @rtype: str or unicode or NoneType + """ + # XXX: Test this function on its own! + + # Build the list of tags that could contain the OP-Local Identifier + local_id_tags = [] + if (OPENID_1_1_TYPE in type_uris or OPENID_1_0_TYPE in type_uris): + local_id_tags.append(nsTag(OPENID_1_0_NS, 'Delegate')) + + if OPENID_2_0_TYPE in type_uris: + local_id_tags.append(nsTag(XRD_NS_2_0, 'LocalID')) + + # Walk through all the matching tags and make sure that they all + # have the same value + local_id = None + for local_id_tag in local_id_tags: + for local_id_element in service_element.findall(local_id_tag): + if local_id is None: + local_id = local_id_element.text + elif local_id != local_id_element.text: + format = 'More than one %r tag found in one service element' + message = format % (local_id_tag, ) + raise DiscoveryFailure(message, None) + + return local_id + + +def normalizeURL(url): + """Normalize a URL, converting normalization failures to + DiscoveryFailure""" + try: + normalized = urinorm.urinorm(url) + except ValueError as why: + raise DiscoveryFailure('Normalizing identifier: %s' % (why, ), None) + else: + return urllib.parse.urldefrag(normalized)[0] + + +def normalizeXRI(xri): + """Normalize an XRI, stripping its scheme if present""" + if xri.startswith("xri://"): + xri = xri[6:] + return xri + + +def arrangeByType(service_list, preferred_types): + """Rearrange service_list in a new list so services are ordered by + types listed in preferred_types. Return the new list.""" + + def enumerate(elts): + """Return an iterable that pairs the index of an element with + that element. + + For Python 2.2 compatibility""" + return list(zip(list(range(len(elts))), elts)) + + def bestMatchingService(service): + """Return the index of the first matching type, or something + higher if no type matches. + + This provides an ordering in which service elements that + contain a type that comes earlier in the preferred types list + come before service elements that come later. If a service + element has more than one type, the most preferred one wins. + """ + for i, t in enumerate(preferred_types): + if preferred_types[i] in service.type_uris: + return i + + return len(preferred_types) + + # Build a list with the service elements in tuples whose + # comparison will prefer the one with the best matching service + prio_services = [(bestMatchingService(s), orig_index, s) + for (orig_index, s) in enumerate(service_list)] + prio_services.sort() + + # Now that the services are sorted by priority, remove the sort + # keys from the list. + for i in range(len(prio_services)): + prio_services[i] = prio_services[i][2] + + return prio_services + + +def getOPOrUserServices(openid_services): + """Extract OP Identifier services. If none found, return the + rest, sorted with most preferred first according to + OpenIDServiceEndpoint.openid_type_uris. + + openid_services is a list of OpenIDServiceEndpoint objects. + + Returns a list of OpenIDServiceEndpoint objects.""" + + op_services = arrangeByType(openid_services, [OPENID_IDP_2_0_TYPE]) + + openid_services = arrangeByType(openid_services, + OpenIDServiceEndpoint.openid_type_uris) + + return op_services or openid_services + + +def discoverYadis(uri): + """Discover OpenID services for a URI. Tries Yadis and falls back + on old-style discovery if Yadis fails. + + @param uri: normalized identity URL + @type uri: str + + @return: (claimed_id, services) + @rtype: (str, list(OpenIDServiceEndpoint)) + + @raises DiscoveryFailure: when discovery fails. + """ + # Might raise a yadis.discover.DiscoveryFailure if no document + # came back for that URI at all. I don't think falling back + # to OpenID 1.0 discovery on the same URL will help, so don't + # bother to catch it. + response = yadisDiscover(uri) + + yadis_url = response.normalized_uri + body = response.response_text + try: + openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body) + except XRDSError: + # Does not parse as a Yadis XRDS file + openid_services = [] + + if not openid_services: + # Either not an XRDS or there are no OpenID services. + + if response.isXRDS(): + # if we got the Yadis content-type or followed the Yadis + # header, re-fetch the document without following the Yadis + # header, with no Accept header. + return discoverNoYadis(uri) + + # Try to parse the response as HTML. + # + openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body) + + return (yadis_url, getOPOrUserServices(openid_services)) + + +def discoverXRI(iname): + endpoints = [] + iname = normalizeXRI(iname) + try: + canonicalID, services = xrires.ProxyResolver().query( + iname, OpenIDServiceEndpoint.openid_type_uris) + + if canonicalID is None: + raise XRDSError('No CanonicalID found for XRI %r' % (iname, )) + + flt = filters.mkFilter(OpenIDServiceEndpoint) + for service_element in services: + endpoints.extend(flt.getServiceEndpoints(iname, service_element)) + except XRDSError: + logging.exception('xrds error on ' + iname) + + for endpoint in endpoints: + # Is there a way to pass this through the filter to the endpoint + # constructor instead of tacking it on after? + endpoint.canonicalID = canonicalID + endpoint.claimed_id = canonicalID + endpoint.display_identifier = iname + + # FIXME: returned xri should probably be in some normal form + return iname, getOPOrUserServices(endpoints) + + +def discoverNoYadis(uri): + http_resp = fetchers.fetch(uri) + if http_resp.status not in (200, 206): + raise DiscoveryFailure( + 'HTTP Response status from identity URL host is not 200. ' + 'Got status %r' % (http_resp.status, ), http_resp) + + claimed_id = http_resp.final_url + openid_services = OpenIDServiceEndpoint.fromHTML(claimed_id, + http_resp.body) + return claimed_id, openid_services + + +def discoverURI(uri): + parsed = urllib.parse.urlparse(uri) + if parsed[0] and parsed[1]: + if parsed[0] not in ['http', 'https']: + raise DiscoveryFailure('URI scheme is not HTTP or HTTPS', None) + else: + uri = 'http://' + uri + + uri = normalizeURL(uri) + claimed_id, openid_services = discoverYadis(uri) + claimed_id = normalizeURL(claimed_id) + return claimed_id, openid_services + + +def discover(identifier): + if xri.identifierScheme(identifier) == "XRI": + return discoverXRI(identifier) + else: + return discoverURI(identifier) diff --git a/flex/lib/python3.6/site-packages/openid/consumer/html_parse.py b/flex/lib/python3.6/site-packages/openid/consumer/html_parse.py new file mode 100644 index 0000000..cc154eb --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/consumer/html_parse.py @@ -0,0 +1,278 @@ +""" +This module implements a VERY limited parser that finds tags in +the head of HTML or XHTML documents and parses out their attributes +according to the OpenID spec. It is a liberal parser, but it requires +these things from the data in order to work: + + - There must be an open tag + + - There must be an open tag inside of the tag + + - Only s that are found inside of the tag are parsed + (this is by design) + + - The parser follows the OpenID specification in resolving the + attributes of the link tags. This means that the attributes DO NOT + get resolved as they would by an XML or HTML parser. In particular, + only certain entities get replaced, and href attributes do not get + resolved relative to a base URL. + +From http://openid.net/specs.bml#linkrel: + + - The openid.server URL MUST be an absolute URL. OpenID consumers + MUST NOT attempt to resolve relative URLs. + + - The openid.server URL MUST NOT include entities other than &, + <, >, and ". + +The parser ignores SGML comments and . Both kinds of +quoting are allowed for attributes. + +The parser deals with invalid markup in these ways: + + - Tag names are not case-sensitive + + - The tag is accepted even when it is not at the top level + + - The tag is accepted even when it is not a direct child of + the tag, but a tag must be an ancestor of the + tag + + - tags are accepted even when they are not direct children of + the tag, but a tag must be an ancestor of the + tag + + - If there is no closing tag for an open or tag, the + remainder of the document is viewed as being inside of the tag. If + there is no closing tag for a tag, the link tag is treated + as a short tag. Exceptions to this rule are that closes + and or closes + + - Attributes of the tag are not required to be quoted. + + - In the case of duplicated attribute names, the attribute coming + last in the tag will be the value returned. + + - Any text that does not parse as an attribute within a link tag will + be ignored. (e.g. will ignore + pumpkin) + + - If there are more than one or tag, the parser only + looks inside of the first one. + + - The contents of + +''', flags) + +tag_expr = r''' +# Starts with the tag name at a word boundary, where the tag name is +# not a namespace +<%(tag_name)s\b(?!:) + +# All of the stuff up to a ">", hopefully attributes. +(?P[^>]*?) + +(?: # Match a short tag + /> + +| # Match a full tag + > + + (?P.*?) + + # Closed by + (?: # One of the specified close tags + + + # End of the string + | \Z + + ) + +) +''' + + +def tagMatcher(tag_name, *close_tags): + if close_tags: + options = '|'.join((tag_name, ) + close_tags) + closers = '(?:%s)' % (options, ) + else: + closers = tag_name + + expr = tag_expr % locals() + return re.compile(expr, flags) + + +# Must contain at least an open html and an open head tag +html_find = tagMatcher('html') +head_find = tagMatcher('head', 'body') +link_find = re.compile(r'\w+)= + +# Then either a quoted or unquoted attribute +(?: + + # Match everything that\'s between matching quote marks + (?P["\'])(?P.*?)(?P=qopen) +| + + # If the value is not quoted, match up to whitespace + (?P(?:[^\s<>/]|/(?!>))+) +) + +| + +(?P[<>]) +''', flags) + +# Entity replacement: +replacements = { + 'amp': '&', + 'lt': '<', + 'gt': '>', + 'quot': '"', +} + +ent_replace = re.compile(r'&(%s);' % '|'.join(list(replacements.keys()))) + + +def replaceEnt(mo): + "Replace the entities that are specified by OpenID" + return replacements.get(mo.group(1), mo.group()) + + +def parseLinkAttrs(html, ignore_errors=False): + """Find all link tags in a string representing a HTML document and + return a list of their attributes. + + @param html: the text to parse + @type html: str or unicode + + @param ignore_errors: whether to return despite e.g. parsing errors + @type ignore_errors: bool + + @return: A list of dictionaries of attributes, one for each link tag + @rtype: [[(type(html), type(html))]] + """ + if isinstance(html, bytes): + # Attempt to decode as UTF-8, since that's the most modern -- also + # try Latin-1, since that's suggested by HTTP/1.1. If neither of + # those works, fall over. + try: + html = html.decode("utf-8") + except UnicodeDecodeError: + try: + html = html.decode("latin1") + except UnicodeDecodeError: + if ignore_errors: + # Optionally ignore the errors and act as if no link attrs + # were found here + return [] + else: + raise AssertionError("Unreadable HTML!") + + stripped = removed_re.sub('', html) + html_mo = html_find.search(stripped) + if html_mo is None or html_mo.start('contents') == -1: + return [] + + start, end = html_mo.span('contents') + head_mo = head_find.search(stripped, start, end) + if head_mo is None or head_mo.start('contents') == -1: + return [] + + start, end = head_mo.span('contents') + link_mos = link_find.finditer(stripped, head_mo.start(), head_mo.end()) + + matches = [] + for link_mo in link_mos: + start = link_mo.start() + 5 + link_attrs = {} + for attr_mo in attr_find.finditer(stripped, start): + if attr_mo.lastgroup == 'end_link': + break + + # Either q_val or unq_val must be present, but not both + # unq_val is a True (non-empty) value if it is present + attr_name, q_val, unq_val = attr_mo.group('attr_name', 'q_val', + 'unq_val') + attr_val = ent_replace.sub(replaceEnt, unq_val or q_val) + + link_attrs[attr_name] = attr_val + + matches.append(link_attrs) + + return matches + + +def relMatches(rel_attr, target_rel): + """Does this target_rel appear in the rel_str?""" + # XXX: TESTME + rels = rel_attr.strip().split() + for rel in rels: + rel = rel.lower() + if rel == target_rel: + return 1 + + return 0 + + +def linkHasRel(link_attrs, target_rel): + """Does this link have target_rel as a relationship?""" + # XXX: TESTME + rel_attr = link_attrs.get('rel') + return rel_attr and relMatches(rel_attr, target_rel) + + +def findLinksRel(link_attrs_list, target_rel): + """Filter the list of link attributes on whether it has target_rel + as a relationship.""" + # XXX: TESTME + matchesTarget = lambda attrs: linkHasRel(attrs, target_rel) + return list(filter(matchesTarget, link_attrs_list)) + + +def findFirstHref(link_attrs_list, target_rel): + """Return the value of the href attribute for the first link tag + in the list that has target_rel as a relationship.""" + # XXX: TESTME + matches = findLinksRel(link_attrs_list, target_rel) + if not matches: + return None + first = matches[0] + return first.get('href') diff --git a/flex/lib/python3.6/site-packages/openid/cryptutil.py b/flex/lib/python3.6/site-packages/openid/cryptutil.py new file mode 100644 index 0000000..7fdd628 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/cryptutil.py @@ -0,0 +1,154 @@ +"""Module containing a cryptographic-quality source of randomness and +other cryptographically useful functionality + +Python 2.4 needs no external support for this module, nor does Python +2.3 on a system with /dev/urandom. + +Other configurations will need a quality source of random bytes and +access to a function that will convert binary strings to long +integers. This module will work with the Python Cryptography Toolkit +(pycrypto) if it is present. pycrypto can be found with a search +engine, but is currently found at: + +http://www.amk.ca/python/code/crypto +""" + +__all__ = [ + 'base64ToLong', + 'binaryToLong', + 'hmacSha1', + 'hmacSha256', + 'longToBase64', + 'longToBinary', + 'randomString', + 'randrange', + 'sha1', + 'sha256', +] + +import hmac +import os +import random + +from openid.oidutil import toBase64, fromBase64 + +import hashlib + + +class HashContainer(object): + def __init__(self, hash_constructor): + self.new = hash_constructor + self.digest_size = hash_constructor().digest_size + + +sha1_module = HashContainer(hashlib.sha1) +sha256_module = HashContainer(hashlib.sha256) + + +def hmacSha1(key, text): + if isinstance(key, str): + key = bytes(key, encoding="utf-8") + if isinstance(text, str): + text = bytes(text, encoding="utf-8") + return hmac.new(key, text, sha1_module).digest() + + +def sha1(s): + if isinstance(s, str): + s = bytes(s, encoding="utf-8") + return sha1_module.new(s).digest() + + +def hmacSha256(key, text): + if isinstance(key, str): + key = bytes(key, encoding="utf-8") + if isinstance(text, str): + text = bytes(text, encoding="utf-8") + return hmac.new(key, text, sha256_module).digest() + + +def sha256(s): + if isinstance(s, str): + s = bytes(s, encoding="utf-8") + return sha256_module.new(s).digest() + + +SHA256_AVAILABLE = True + +try: + from Crypto.Util.number import long_to_bytes, bytes_to_long +except ImportError: + # In the case where we don't have pycrypto installed, define substitute + # functionality. + + import pickle + + def longToBinary(l): + if l == 0: + return b'\x00' + b = bytearray(pickle.encode_long(l)) + b.reverse() + return bytes(b) + + def binaryToLong(s): + if isinstance(s, str): + s = s.encode("utf-8") + b = bytearray(s) + b.reverse() + return pickle.decode_long(bytes(b)) +else: + # We have pycrypto, so wrap its functions instead. + + def longToBinary(l): + if l < 0: + raise ValueError('This function only supports positive integers') + + bytestring = long_to_bytes(l) + if bytestring[0] > 127: + return b'\x00' + bytestring + else: + return bytestring + + def binaryToLong(bytestring): + if not bytestring: + raise ValueError('Empty string passed to strToLong') + + if bytestring[0] > 127: + raise ValueError('This function only supports positive integers') + + return bytes_to_long(bytestring) + + +# A cryptographically safe source of random bytes +getBytes = os.urandom + +# A randrange function that works for longs +randrange = random.randrange + + +def longToBase64(l): + return toBase64(longToBinary(l)) + + +def base64ToLong(s): + return binaryToLong(fromBase64(s)) + + +def randomString(length, chrs=None): + """Produce a string of length random bytes, chosen from chrs.""" + if chrs is None: + return getBytes(length) + else: + n = len(chrs) + return ''.join([chrs[randrange(n)] for _ in range(length)]) + + +def const_eq(s1, s2): + if len(s1) != len(s2): + return False + + result = True + for i in range(len(s1)): + result = result and (s1[i] == s2[i]) + + return result diff --git a/flex/lib/python3.6/site-packages/openid/dh.py b/flex/lib/python3.6/site-packages/openid/dh.py new file mode 100644 index 0000000..5de7e4e --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/dh.py @@ -0,0 +1,47 @@ +from openid import cryptutil + + +def strxor(x, y): + if len(x) != len(y): + raise ValueError('Inputs to strxor must have the same length') + + if isinstance(x, str): + x = x.encode("utf-8") + if isinstance(y, str): + y = y.encode("utf-8") + + return bytes([a ^ b for a, b in zip(x, y)]) + + +class DiffieHellman(object): + DEFAULT_MOD = 155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443 + + DEFAULT_GEN = 2 + + def fromDefaults(cls): + return cls(cls.DEFAULT_MOD, cls.DEFAULT_GEN) + + fromDefaults = classmethod(fromDefaults) + + def __init__(self, modulus, generator): + self.modulus = int(modulus) + self.generator = int(generator) + + self._setPrivate(cryptutil.randrange(1, modulus - 1)) + + def _setPrivate(self, private): + """This is here to make testing easier""" + self.private = private + self.public = pow(self.generator, self.private, self.modulus) + + def usingDefaultValues(self): + return (self.modulus == self.DEFAULT_MOD and + self.generator == self.DEFAULT_GEN) + + def getSharedSecret(self, composite): + return pow(composite, self.private, self.modulus) + + def xorSecret(self, composite, secret, hash_func): + dh_shared = self.getSharedSecret(composite) + hashed_dh_shared = hash_func(cryptutil.longToBinary(dh_shared)) + return strxor(secret, hashed_dh_shared) diff --git a/flex/lib/python3.6/site-packages/openid/extension.py b/flex/lib/python3.6/site-packages/openid/extension.py new file mode 100644 index 0000000..0052f00 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/extension.py @@ -0,0 +1,51 @@ +import warnings + +from openid import message as message_module + + +class Extension(object): + """An interface for OpenID extensions. + + @ivar ns_uri: The namespace to which to add the arguments for this + extension + """ + ns_uri = None + ns_alias = None + + def getExtensionArgs(self): + """Get the string arguments that should be added to an OpenID + message for this extension. + + @returns: A dictionary of completely non-namespaced arguments + to be added. For example, if the extension's alias is + 'uncle', and this method returns {'meat':'Hot Rats'}, the + final message will contain {'openid.uncle.meat':'Hot Rats'} + """ + raise NotImplementedError() + + def toMessage(self, message=None): + """Add the arguments from this extension to the provided + message, or create a new message containing only those + arguments. + + @returns: The message with the extension arguments added + """ + if message is None: + warnings.warn( + 'Passing None to Extension.toMessage is deprecated. ' + 'Creating a message assuming you want OpenID 2.', + DeprecationWarning, + stacklevel=2) + message = message_module.Message(message_module.OPENID2_NS) + + implicit = message.isOpenID1() + + try: + message.namespaces.addAlias( + self.ns_uri, self.ns_alias, implicit=implicit) + except KeyError: + if message.namespaces.getAlias(self.ns_uri) != self.ns_alias: + raise + + message.updateArgs(self.ns_uri, self.getExtensionArgs()) + return message diff --git a/flex/lib/python3.6/site-packages/openid/extensions/__init__.py b/flex/lib/python3.6/site-packages/openid/extensions/__init__.py new file mode 100644 index 0000000..710b200 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/extensions/__init__.py @@ -0,0 +1,5 @@ +"""OpenID Extension modules.""" + +__all__ = ['ax', 'pape', 'sreg'] + +from openid.extensions.draft import pape5 as pape diff --git a/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52831de7242bd3f809ca3009ac532fb18945ab93 GIT binary patch literal 340 zcmYL^y-ve06os9?7FFtuSWwptiGz}&MnZ@I1{N4#@?wQvCs<8v*G{UmZ^Wzc2w9nU z1tuJopDUenkM3~v-E=;mh3`N*gwTi8-*`6NSebKcfCvHyT<|K7IN(E2yNySD2f^eC zh10{?qhjLzc5&CYOc*Z3qLI+m%*4bk`oUL)V#GI^RiCyFh0YL+r{zN!3{y%$t|=Wu zQP-4`Q)$~c#O$}t#Gs3^)gzR4ti2&U8S`jgJyx3Uik9JBp>Zi|VA|kP<8*T&^ft>A zIKUw{*=m#JO@5grb}E6NvEnLv>39v%Qmpl=O}1F=+gB;l=!k%S);|fRlndTc8ms+q Qa&>(3dSo)bHQ$}N-(SIDf&c&j literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/ax.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/ax.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7c30ed69b62b49e699c2cce581f1a25bf9c2a317 GIT binary patch literal 22920 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 M;`=tf#f1z1A1KNXGXMYp literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/sreg.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/sreg.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..137d9f04b5c9b795a4b4b8102257c23b0eb0b227 GIT binary patch literal 16438 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. + @type ax_args: dict + + @raises KeyError: if the message is not consistent in its use + of namespace aliases. + + @raises NotAXMessage: If ax_args does not include an Attribute Exchange + mode. + + @raises AXError: If the data to be parsed does not follow the + attribute exchange specification. At least when + 'if_available' or 'required' is not specified for a + particular attribute type. + """ + # Raises an exception if the mode is not the expected value + self._checkMode(ax_args) + + aliases = NamespaceMap() + + for key, value in ax_args.items(): + if key.startswith('type.'): + alias = key[5:] + type_uri = value + aliases.addAlias(type_uri, alias) + + count_key = 'count.' + alias + count_s = ax_args.get(count_key) + if count_s: + try: + count = int(count_s) + if count <= 0: + raise AXError( + "Count %r must be greater than zero, got %r" % + (count_key, count_s, )) + except ValueError: + if count_s != UNLIMITED_VALUES: + raise AXError("Invalid count value for %r: %r" % + (count_key, count_s, )) + count = count_s + else: + count = 1 + + self.add(AttrInfo(type_uri, alias=alias, count=count)) + + required = toTypeURIs(aliases, ax_args.get('required')) + + for type_uri in required: + self.requested_attributes[type_uri].required = True + + if_available = toTypeURIs(aliases, ax_args.get('if_available')) + + all_type_uris = required + if_available + + for type_uri in aliases.iterNamespaceURIs(): + if type_uri not in all_type_uris: + raise AXError('Type URI %r was in the request but not ' + 'present in "required" or "if_available"' % + (type_uri, )) + + self.update_url = ax_args.get('update_url') + + def iterAttrs(self): + """Iterate over the AttrInfo objects that are + contained in this fetch_request. + """ + return iter(self.requested_attributes.values()) + + def __iter__(self): + """Iterate over the attribute type URIs in this fetch_request + """ + return iter(self.requested_attributes) + + def has_key(self, type_uri): + """Is the given type URI present in this fetch_request? + """ + return type_uri in self.requested_attributes + + __contains__ = has_key + + +class AXKeyValueMessage(AXMessage): + """An abstract class that implements a message that has attribute + keys and values. It contains the common code between + fetch_response and store_request. + """ + + # This class is abstract, so it's OK that it doesn't override the + # abstract method in Extension: + # + #pylint:disable-msg=W0223 + + def __init__(self): + AXMessage.__init__(self) + self.data = {} + + def addValue(self, type_uri, value): + """Add a single value for the given attribute type to the + message. If there are already values specified for this type, + this value will be sent in addition to the values already + specified. + + @param type_uri: The URI for the attribute + + @param value: The value to add to the response to the relying + party for this attribute + @type value: unicode + + @returns: None + """ + try: + values = self.data[type_uri] + except KeyError: + values = self.data[type_uri] = [] + + values.append(value) + + def setValues(self, type_uri, values): + """Set the values for the given attribute type. This replaces + any values that have already been set for this attribute. + + @param type_uri: The URI for the attribute + + @param values: A list of values to send for this attribute. + @type values: [unicode] + """ + + self.data[type_uri] = values + + def _getExtensionKVArgs(self, aliases=None): + """Get the extension arguments for the key/value pairs + contained in this message. + + @param aliases: An alias mapping. Set to None if you don't + care about the aliases for this request. + """ + if aliases is None: + aliases = NamespaceMap() + + ax_args = {} + + for type_uri, values in self.data.items(): + alias = aliases.add(type_uri) + + ax_args['type.' + alias] = type_uri + ax_args['count.' + alias] = str(len(values)) + + for i, value in enumerate(values): + key = 'value.%s.%d' % (alias, i + 1) + ax_args[key] = value + + return ax_args + + def parseExtensionArgs(self, ax_args): + """Parse attribute exchange key/value arguments into this + object. + + @param ax_args: The attribute exchange fetch_response + arguments, with namespacing removed. + @type ax_args: {unicode:unicode} + + @returns: None + + @raises ValueError: If the message has bad values for + particular fields + + @raises KeyError: If the namespace mapping is bad or required + arguments are missing + """ + self._checkMode(ax_args) + + aliases = NamespaceMap() + + for key, value in ax_args.items(): + if key.startswith('type.'): + type_uri = value + alias = key[5:] + checkAlias(alias) + aliases.addAlias(type_uri, alias) + + for type_uri, alias in aliases.items(): + try: + count_s = ax_args['count.' + alias] + except KeyError: + value = ax_args['value.' + alias] + + if value == '': + values = [] + else: + values = [value] + else: + count = int(count_s) + values = [] + for i in range(1, count + 1): + value_key = 'value.%s.%d' % (alias, i) + value = ax_args[value_key] + values.append(value) + + self.data[type_uri] = values + + def getSingle(self, type_uri, default=None): + """Get a single value for an attribute. If no value was sent + for this attribute, use the supplied default. If there is more + than one value for this attribute, this method will fail. + + @type type_uri: str + @param type_uri: The URI for the attribute + + @param default: The value to return if the attribute was not + sent in the fetch_response. + + @returns: The value of the attribute in the fetch_response + message, or the default supplied + @rtype: unicode or NoneType + + @raises ValueError: If there is more than one value for this + parameter in the fetch_response message. + @raises KeyError: If the attribute was not sent in this response + """ + values = self.data.get(type_uri) + if not values: + return default + elif len(values) == 1: + return values[0] + else: + raise AXError('More than one value present for %r' % (type_uri, )) + + def get(self, type_uri): + """Get the list of values for this attribute in the + fetch_response. + + XXX: what to do if the values are not present? default + parameter? this is funny because it's always supposed to + return a list, so the default may break that, though it's + provided by the user's code, so it might be okay. If no + default is supplied, should the return be None or []? + + @param type_uri: The URI of the attribute + + @returns: The list of values for this attribute in the + response. May be an empty list. + @rtype: [unicode] + + @raises KeyError: If the attribute was not sent in the response + """ + return self.data[type_uri] + + def count(self, type_uri): + """Get the number of responses for a particular attribute in + this fetch_response message. + + @param type_uri: The URI of the attribute + + @returns: The number of values sent for this attribute + + @raises KeyError: If the attribute was not sent in the + response. KeyError will not be raised if the number of + values was zero. + """ + return len(self.get(type_uri)) + + +class FetchResponse(AXKeyValueMessage): + """A fetch_response attribute exchange message + """ + mode = 'fetch_response' + + def __init__(self, request=None, update_url=None): + """ + @param request: When supplied, I will use namespace aliases + that match those in this request. I will also check to + make sure I do not respond with attributes that were not + requested. + + @type request: L{FetchRequest} + + @param update_url: By default, C{update_url} is taken from the + request. But if you do not supply the request, you may set + the C{update_url} here. + + @type update_url: str + """ + AXKeyValueMessage.__init__(self) + self.update_url = update_url + self.request = request + + def getExtensionArgs(self): + """Serialize this object into arguments in the attribute + exchange namespace + + @returns: The dictionary of unqualified attribute exchange + arguments that represent this fetch_response. + @rtype: {unicode;unicode} + """ + + aliases = NamespaceMap() + + zero_value_types = [] + + if self.request is not None: + # Validate the data in the context of the request (the + # same attributes should be present in each, and the + # counts in the response must be no more than the counts + # in the request) + + for type_uri in self.data: + if type_uri not in self.request: + raise KeyError( + 'Response attribute not present in request: %r' % + (type_uri, )) + + for attr_info in self.request.iterAttrs(): + # Copy the aliases from the request so that reading + # the response in light of the request is easier + if attr_info.alias is None: + aliases.add(attr_info.type_uri) + else: + aliases.addAlias(attr_info.type_uri, attr_info.alias) + + try: + values = self.data[attr_info.type_uri] + except KeyError: + values = [] + zero_value_types.append(attr_info) + + if (attr_info.count != UNLIMITED_VALUES) and \ + (attr_info.count < len(values)): + raise AXError( + 'More than the number of requested values were ' + 'specified for %r' % (attr_info.type_uri, )) + + kv_args = self._getExtensionKVArgs(aliases) + + # Add the KV args into the response with the args that are + # unique to the fetch_response + ax_args = self._newArgs() + + # For each requested attribute, put its type/alias and count + # into the response even if no data were returned. + for attr_info in zero_value_types: + alias = aliases.getAlias(attr_info.type_uri) + kv_args['type.' + alias] = attr_info.type_uri + kv_args['count.' + alias] = '0' + + update_url = ((self.request and self.request.update_url) or + self.update_url) + + if update_url: + ax_args['update_url'] = update_url + + ax_args.update(kv_args) + + return ax_args + + def parseExtensionArgs(self, ax_args): + """@see: {Extension.parseExtensionArgs}""" + super(FetchResponse, self).parseExtensionArgs(ax_args) + self.update_url = ax_args.get('update_url') + + def fromSuccessResponse(cls, success_response, signed=True): + """Construct a FetchResponse object from an OpenID library + SuccessResponse object. + + @param success_response: A successful id_res response object + @type success_response: openid.consumer.consumer.SuccessResponse + + @param signed: Whether non-signed args should be + processsed. If True (the default), only signed arguments + will be processsed. + @type signed: bool + + @returns: A FetchResponse containing the data from the OpenID + message, or None if the SuccessResponse did not contain AX + extension data. + + @raises AXError: when the AX data cannot be parsed. + """ + self = cls() + ax_args = success_response.extensionResponse(self.ns_uri, signed) + + try: + self.parseExtensionArgs(ax_args) + except NotAXMessage as err: + return None + else: + return self + + fromSuccessResponse = classmethod(fromSuccessResponse) + + +class StoreRequest(AXKeyValueMessage): + """A store request attribute exchange message representation + """ + mode = 'store_request' + + def __init__(self, aliases=None): + """ + @param aliases: The namespace aliases to use when making this + store request. Leave as None to use defaults. + """ + super(StoreRequest, self).__init__() + self.aliases = aliases + + def getExtensionArgs(self): + """ + @see: L{Extension.getExtensionArgs} + """ + ax_args = self._newArgs() + kv_args = self._getExtensionKVArgs(self.aliases) + ax_args.update(kv_args) + return ax_args + + +class StoreResponse(AXMessage): + """An indication that the store request was processed along with + this OpenID transaction. + """ + + SUCCESS_MODE = 'store_response_success' + FAILURE_MODE = 'store_response_failure' + + def __init__(self, succeeded=True, error_message=None): + AXMessage.__init__(self) + + if succeeded and error_message is not None: + raise AXError('An error message may only be included in a ' + 'failing fetch response') + if succeeded: + self.mode = self.SUCCESS_MODE + else: + self.mode = self.FAILURE_MODE + + self.error_message = error_message + + def succeeded(self): + """Was this response a success response?""" + return self.mode == self.SUCCESS_MODE + + def getExtensionArgs(self): + """@see: {Extension.getExtensionArgs}""" + ax_args = self._newArgs() + if not self.succeeded() and self.error_message: + ax_args['error'] = self.error_message + + return ax_args diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/__init__.py b/flex/lib/python3.6/site-packages/openid/extensions/draft/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..274a9335e5abf292542c521a2235c2f7541ca124 GIT binary patch literal 209 zcmYL@K?=e!5JgjPAwm!0qE4k%8W8aWZgl0QR43KYHc6AT*e<+}SMdmLy@e}NP<-&; z41e__-=9n$cp#3~pdX%lHZEr5gk(pi-u6!PQ2+J5fzrIqEQ2=FkjoO8HjrzGin-8r zOhfFkrzTE{m{xQdhmNy=Jt(e%W5Y`f7UHB68`hvYdyygvq+)_2d=I-?CJdLt0O~Ba YkfvO4o2E0>J;$p`VpFp9u*pF literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape2.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape2.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa88a189f92ef7248e84c70c92cb91daa7e27792 GIT binary patch literal 8114 zcmds6TXP&o6`tE(v|7n>;y8{2)Pw}$O)RZ^iDQ{SStxcQ%GszaB_W&3)@ZvWjWj#6 z?w+;1>s3)87ojMq;)UXg;w3M#oc1lWF=M(td8BZO#}6r zz-cOTDe) zZM9WkGn+>3zi+{H{NR9zOlK!xwYEZ zN=~W|@4Rs_zQ>saI9M%K8!$2*=JZ?4*ONhCg(^^42Puh*Ba)H}T(_7``&b{vVt?p|L)c){R} z|7MuMPrq&N?w&9A{BUqu|>6OdC`2SP2$RaPFj(lqQVU* zrqQ&R*|eDjg?#EWW5*<=eK-TvEV9x=1L|33Gq{&nh0Wq#W>q$a`wXw}87M1MmYu-! zER^#k?$yr>$7s$$DW4W5Ch@*Rx%ve4bA?u#^R9$yx;rB3xE@{xeQdbm;@9;=+;`&$ zue+%xSJa2={#{St9pB+XaMtpm-mUBeuen>W0D3j1@!3aMhZn0cXaX{obi#BpJ*L#N z#QiRR{45{ISbQ?Ro{o24uf*GhY0x6@#_#kxZtQpXdDjoI({>axd0s7JeENJzde8T8 z(o*d_2%>vb+4Jslmj_-~a^`OHok(!izb9edot_+RO7O+8&GWIXOfJxe&?DhCqL6Q4 zqpDrvkTEpccB(RT&o22xJj6z94viyg-#Ri(V`$3LyT+k8#JiR~v`{+CfEt*MS|KUO z9-M|!h@{+V`Jo@TTFJAIIku!azPy^9IFbiDqKZky911f2R;wL&Qnp%uHNJjO?+U0v z%c{ zTG$p>6ngHGzSZ* zBV5Y1+a`0MAKgikYq;bo6tOWh_Ra1BN{bl>Fe@!LY z=H1xa4jye)efPcF>!hMFoKQyb~ine&6wY<1|VQDY!1TRi_wS2xt%~HCN z6umBDG{YmQM^YK>a9UNW5afmm`8cT`qp?<(O2r(U$`p3INv^b{F1ga4#{)r}eWkcZ zrf}%6) zXp46eXFu}8ktrlLd~UYk5)g)m{?of%n4aKgwBs@g!&$H^=DamHlkIxCX>KfJPr0!y z;Q>z1IJAbQG8A%u=Ak{bedE9qE}6{G+Q%~)(9j|S8hGoW3fMkelCAnh5DMEl{csnT1bD0>Awa?{PKo@eB z%IW+8m0d`(1^w7J8QcvdYKg5FWo#c=;+5DLS_GnhZyj3q&q1aI<{X)C8lRjcX{YY$ zigH&)E$us3KG35TFq*@P7`-%f#P9m^ae0Wo^!$Mlo^p(M=Aa^eYr+$o#)n2|{oG(h zoK(rH!qsi^Sor`P8IDV#=#eu^eHB3mm*0n*kmR*+UN}HGI**OjjT@u$Jm!H&(%Sb- zHgxa#aE`qgN)mcF&aT(yW1Rtkx_}cDrpMoUzJ%9%-wS&Dh7ggs=6(RYqxLW!N6F=e zQB3GYa_z!3xFMHDKlpcY$-7+SdZp}bQ0T=wi&qhK%RFpH446zji}O>K*{D^t53-?k zNyP^nAYE_sq_EwOxfIW#CE|ZK@MA&2PW4J`%#>6{YpxY^WOJ!?RrxO<@D^-5vEitj z3*$SX*Hfl0zKf0=m;4?IW3C9Lbj*rbH5X8ltrsoFv=E->&B57BS&y4WuB~M8Wcf;4 zpF%~$oqe)x^9VK%eKEbipxImgT}az=1E6wJm&xdmMjHx@h)kq5V+dBu-F1PEx{-(_ zfkB*78X^Ucdm^M>94)pb87l;;B=B@&q zj^=vge)3sq3o1sAZ4hh7$E1ipw&T3t^>laI46wb$@j3ODl~4#D=&-JYCq_m_Q$Jy^ z8gi(Ux#tG~!Hh@%9r6*Hpi#8_>QDNRXaRa!3awxmX_I_kT2}dXXq0RN=V3_*su#tZuych6^=Rw}i$@srfjq}SJ6%|>WN^n^U1Hj?Y`fH{*L464>D;7e7DC}LXI+kP2m&;Chu3W`sm0xJ% zWB$u2^9EiWqs%A%BV}G-v#k2iL~dt>mq3=0=V9|8$rVMCXW>Fl3UV}qkG=sz*4dOv zikvBwQXqoKlN?sP7BiTk(a!ckcRRi0GAh$NPMp;oE-b15opLz}xyUx9jIJn%?Wnb@ zGfL(r5Na}`9*OHFCqgKnCc^<`k7|zg^jBzr*QvOQB0C_(Y}6?cRQMcMiZzH=m8~K% ztCLw~X+*6^i1ed7U2C}H6N$~AGGa4rUK&4-5g9G}QPBP7OeQ-a8VtaC4A*6okXs{n zledInf`@c8F}g>geB1?xyQ(#j{c#8Ie@JbVh7e>({gZeG^^r|$V;RLGp z5C_4SrQGQSDRj?Wec*2kPxN81sd8y|?Yie^z%+#kRgmBWVkxg|>H$X+){c*mv4ecB zl=!BLX=PJVq@Nm3=ic(!+7acHV}7nS8jFHVwy&|FzTK)vrgS|;wb_ocMAT@)94V}h zY-HRbkhD&1DchLd1&k^mgoMky*wfMdo+lMT349nCVQytoYY)J&FvMXl(PL|&VWwlb zN?C@HEB)ON!zo0cFC^!5qbxzAhLk?Q@}_Fv*i4O?!<04&0Fb(TK-ND+x zaS;?06DDqU=zwiK>{r46>8Sv&bq-w10oP)hYk}c4W8{~Q%wL&dQ8Bav_?HX*RT>r? z#H9l>y3fi7M(9}JX5!(nKw(F%0co+r;;<;bh>QEBVe!ZmpAU)SmBT8={Q~1wXk5kQ zX1!m+w{H;Bne?rX)|>p4hyYRcYNR;Ch(zcG?j#-5+d>sWWRlL z?b4-7E0ovkcyVn;yht4=d##oxUZ&bPDrjZN{MPzAH(Kwn-dcZimHrD=K&SSenxIx1 z8DUb?2O?_d)SOq_QP_rvX|pnV*7!zYnh~%erut7@lIAt$h@(~&TeVOlm}8k`yK0>Q zW7QlMDHe0^d~Uw#U?%c86G=J}iGn}BK}BblkC0$Na_S7FQz%sz;{S-uL2N^&H&5Q# ztbmyJG2Yp+6Bm%)D-z8`PPh@{UqNJAaf!aF#9kSRJ!8MD`aFzh_AB&sWXb{B!cc!j z>A*4)fR!`>IQ#7rfONq6l&(>YG7b&#Bb5IW1yTq8jkHD||7>ChJk;wsL+c@h5wD_1 z3c?GKG*N0q*$#c`Z%~agtQmSCI6CFN6``@Y@&PC|ebal4}|x z97~wi>;~wSd9zj(bsC=VjJQC>MJg^)LEc=`rxmJ^`iaX_C^q#P)p8BZV7Nlygp&S@ zOYG{gbWve!)rb$5DJmOo;g%q~%?7yy}jQ)uwxDImke{!L0mQYwy zB6#5kTqs#V3@M|?m!lU(i%H6FXx8`>QVaPK3dgo!D*6v`4x(~sjrgnB3;m+K@C(EI EH*tczX8-^I literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape5.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape5.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..794ec3689cfeaaf62cf6b755399b119b04627c54 GIT binary patch literal 13489 zcmdU0OLH69b?z5{MiYccj$R{AIqk7$BFG{@Jw0M6V{# zZZ|~|16C@-aoLMF<+#epR4P@eN@bH>7XAULY*Lk<(3>o>$?URpd6RtS+7Hp<-MO~qm3C*``4hEWa~ImP z?n~|ZJBoYOJ@=X7p7Zn*y?qAv=Uo-|s&@wWb6(k->y+{If~(=F#!u(*biPw|XEv0^ z#nCTT1KaNpdtToQA}8_(fj!u^qbHvI-p~uy?%R*V;F<4w!d~6S(;)J@vectN&+i^t z4-O+Q28f| zqJGdt;pg^+J=boc*K&FHN!`}OdNA3pJeokih=ei%7HG`jg^ z>j(ycH~Pkpe&EV~i}b^%O}bjnPVH}u?k9D3L(y#pXt236c-9*3Z$UP!Tw#lz;z8JY zV`*ve_KnuYBYE+`wa!xKdUNP*kFF%O92^`pcm2?54n%Wz$XeBMpsSr8sao?%)bDj^ zTGKxr7b`fzH*oS4*rC$aJl!)~_3t!T9_;ip1=cr1w)&Y08-%^$iAnZ~->PfCa%%DH zqt!D8Vw}zhMF>#$VYmrh<*h(f_2 zn*6vw90>9R&Ft~|XmK@)y#6qPtDsH@4_fP^GFi)O!rm5xzKvhJd`&b;akaDKMXS{5 z*!-S%^gxJ#i0gP5cmjILZ$ur_9_uLBFvJ`$Q3`D?5QFet*AjlVx6|fDn4!LEwg0xf>K7(&jT|mEAaiFJ{;8^m>B> z62TManP+=FXGo4@%R@Nu_C=tB?gnX-pMTSyGDUdYIr2Q%1km^AS`u}(B0B})M_HV8Ku1Rw>#JK%{+JIXP~{tjY4u$Qqz2&@FGpa3g8JUlecUlA)DV{L@Uh@zGq8P-XBd zi)>_=B)5AwLh?2dAuDxw9%rD_y((WJf=MYDv9UE6^rpJlP-Vv_9VIzED%|OuP~~Nu z6m)E|JF-9)M<|}qk#&c`nu35z9YV(_Vjx%Mh~fOI0NlD=Zx}gSJ&C-gjr@q=94w{? zwAE^KxiRs5*e)vb;bukOB|-R;B#`zOdJDwAvcdH&I>!iWgja4KrJ3}P?E^ZmtS@{SHv5e6hS;8YG6yOwo z1{Da|ye+89*;IRrMY8;Xw3iN688R4B6sV1MqLJ;BHbZ>~j;Ol~IG{dh)+OUBeI&k# zQv#6+@qbdI#UG>4uW%&FTq9*J_HH#rpP7A;Z=!>H_Y4xh8-PC^!zt8{E}1tgAxR@!HBm&lg!LW#$7(>)|QEj*8tV$A85T8Eo60O@gs>$*0& zm@3QkhnbH_!v#;tGAcr-An2~Lf1#j3DJ0$Lcn(4P3Q+46@;MMQ3Ep7U0>B4qb=Cr= zOME0Vwqqx)+k>rLubbc(vUv=9pZGO$F1C{i`Q9gBiMo5f+qrU_(rrnymX%pY7F9_e zwksGGO_b2l@wn=T?=eSrEna{N3B8HQ#CLkP7seGK6IW@22F*Zh`C&)e{k3UJoF~vF zt4&7p&uA+ou&fwm$k{?rA(@X}PGx=Si?_4jOz@PjHA}k*hs|LZO1TT%&x&k_O=JQ_ zq@O^_kuf%ifBCs~Y#d%58@nYJp%}`4>9{m5LH#Kf+@@bBe2Ihf?GI&bxH~SpWd!xg zxGa8oRFBMY8Rmrh50&5&=z}WggI^@{fiC{yQ{`hN&_0r1SMYUZT=|76u8b?Y)(H#^ zvu_@b#wJ=9$5q!HS2@YEj%si#*2~Iq3H4qo~t2Ro$wrld_*=onJ<8B9%UMJ~-0&g}9Z)r>8Pl_f zQ^~w0VTwSk?H$6jLV|;mX$W(!ffAHl5A)!ck{D162~Z~s+6O-3;(i3>&Vbvgk74tE zH(Itoe1af~aD_<62^rLX+}{a+jNQ`#H#dSp*I}*l^AN*E z?u`l};X&?5KSK`u6O9tu4HDbEmQG1QK*&~xK>mcNi%(^``hnBim+3iofXN{YBTpG< zq?G)#bxU|NDL1rDa0MxU)+=_#qtbq~z4!(Yqv-|Rfs6D>G+~GA&DYsMpZ4ja+nHl7 zrrMx$l5Qkfv{WuUOWabUE&~AhWpXV(`^2snm$r@~FBD7kyc7<5e#C*6I}z*Xcx>e& zKncw5kW+D)15>pYtMRG_! zppY1oRnQ+&7b5dG$K+{as4JL@qOZWQfNG~Ofpr+^Uk0!}4$hfg$;y#p=1THBRD9aZ zlGc%vbA`+HDstYutx43Hj3;ZAx=)`K2Z%mob=v4dVZ_;YE6M1jPs8vQi*3pwvrq`< zQx=;DPvjnxn*Kdwm8k=#y{Weu$Xqz<2-PB;F&rDxWZ4dNKsHMd8N@|WBpa8M)kH1n z0ItI0xNsB9EY{FS&Zuo{sASJ3Gs_`iK-YIIXp7}qmtZ_^KK*ay-eRS6Gyr9k8p4vZO$8-q0>@Deby|QvsTGG zZ`MuC`gRvZvOgXj?n259PEm3fCMda!Ov+M0^BEU&j&Bv`w={fN2CC^`OjqzegaWQsR@(^WQwn&9hSCakY*d)C)Nz*3MqU0Pw|CBvS-B^+@iVJj`~IT*PU4g$t~ z$?`A_XrcoLV2OH1i_>dKB-b!!fQXT~L@GjhlCH8rXfa7@%FE2TIOkDSah)eugp}cW-eONS;G7XFOl216S5t*yq>{k* zzB5dTH&Pj?cBj{aNMq72SEI6)F@{ch*qy^45X;MhgidC4BVu_4|LdkrIo#GX8W11E<0?Z_!i5yl( z4*ROiVTm65C?m-X@ojt|-lr2UQSnOI^BLcHTI{7QHCZpeOTDB;EhRzEgpFksX`U)P z`qo=cckjRv?qYY)AHv$Ve54G(cOX{75NTmLL)1n==*V((#QEpzAjSHz8WD*zhn)Zz zaIyzL1C{VP#2^v33SN*X9o_jG^m&!`15Epv1ZgvkiwJ~Z!<&~de<58MceonhnS?B)`V)@$dB9=!tH+h{U5U-FbbL3=lGBLQT^mNkT7$I(w29qC?dk82q zEZExwCC%1|g1>`d1cL|#WPXvhuYgVDtt!{?9NSiMW|7!NIvSdD2i4wV0s!hJ26-oBuGF2#vz$O$d1W76Kiw#*}-e4nFdU9m+m-?}+ z$`u(}lg@O~CMoNis!uNO7Mi79h>>@>k=I3GEFD*&sxQ~;+(1_nHYOcWy4IODSO_MW z<&k$UK#jyg(}BrRphQ*)iN$py2NnqhI;Q4kiMwmIFsn9zECv@ zh{(FatK!go0uQK;sk*Eq8OdCX`$l^PPI-gNuWvAKEN+sn6Xk&=$J_X6&rQ$P6z4K! z81YNQ_)g8HM~1>IalNe9RMRj)jTqQLk_x-|oR4y%n6@v>;$Ymci@Jz{&nV(cpn|b- z!j$U5SjTEfvacqkMZ^~Xynpf8NV9VNJ>Q8@Vb9!LHwy2)T0vUWX`L84m`_S9h7nDKE9H)bEk6nkLZudDF1Vy{K~Njnz-sBqY|Hw zEAFgY|A_`l7=$jKp25?9efs&)0@i5eB*lw&*bNk1MX0s*EYQD{8sgtr6Td+WngN<7 z!q@^~TtBuF!Wh{c6UMJHVZ4OV%ohmb&+%PtY>h!opXkUmK4&6aaTid}%y&2;RB|li8bf3#ca|qpXTxqIPbTo0M z*gQp>8Vc2C&Vkrm3EJ7t0R}K2$+}B{kYuDFuzcHdLl7<#oo5IrrIH(~3`)e(j?TU7 zhoDh+IL3FtU zlrjJCEbX^&kyq~!(lW;CUujPbXqzg&k0SW_h<>zKYc9p={*;UPt#y}3CBlJk{sVay zYNM=-TA4RtiBBweHShKY*gGA*iHr2xQ6ZCVenG2I>mwZtn9m`O^ z$SjZq$9eWhu6R?9&Kqoac_FYLzkTnxbMHUVz~ zOe{9_(v_TYPVh;vplB&V6ew`YzvBAl+Pe=rAFMuHyT3|1+e>Jq`-D5xs7<@-x2RR} zqFi^Z_ro3WHg^IUvCQ#^Cy=zk6yTwlK&~Kshzn(wNO1UYtoRw)MgGOgAOF_%x?a{6 zh$06Wu3_8R0&lA<(%=hBh$9k!79V}(cg$O+N2Eh^a18isT*x#9o?Qiczvya_Mr-E5 z94!$7Fd>DJtLTR-xW-~3`{(P)c0&a|ogxNy1=E^(x>g=lXa&8vdj9k#Jg2g2VtKAA zei>DFExJ2V!=FVnE*1unFW??un8|8-^~wl1vA`W8IvIQKI-T}h0-3$^KY$3z5JVTJSQDN^S;R03QfEQji8TKTIW)iL z$ywlhnzCcwt0}jZQK$0$$}8$^Y~Ga{N8hBcLV|FHPTkD^G3-LO4V{KU|6sVPq0il# N`!nTsS;zU|{{lB%HY5N5 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/pape2.py b/flex/lib/python3.6/site-packages/openid/extensions/draft/pape2.py new file mode 100644 index 0000000..d0587e3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/extensions/draft/pape2.py @@ -0,0 +1,285 @@ +"""An implementation of the OpenID Provider Authentication Policy +Extension 1.0 + +@see: http://openid.net/developers/specs/ + +@since: 2.1.0 +""" + +__all__ = [ + 'Request', + 'Response', + 'ns_uri', + 'AUTH_PHISHING_RESISTANT', + 'AUTH_MULTI_FACTOR', + 'AUTH_MULTI_FACTOR_PHYSICAL', +] + +from openid.extension import Extension +import re + +ns_uri = "http://specs.openid.net/extensions/pape/1.0" + +AUTH_MULTI_FACTOR_PHYSICAL = \ + 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical' +AUTH_MULTI_FACTOR = \ + 'http://schemas.openid.net/pape/policies/2007/06/multi-factor' +AUTH_PHISHING_RESISTANT = \ + 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant' + +TIME_VALIDATOR = re.compile('^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$') + + +class Request(Extension): + """A Provider Authentication Policy request, sent from a relying + party to a provider + + @ivar preferred_auth_policies: The authentication policies that + the relying party prefers + @type preferred_auth_policies: [str] + + @ivar max_auth_age: The maximum time, in seconds, that the relying + party wants to allow to have elapsed before the user must + re-authenticate + @type max_auth_age: int or NoneType + """ + + ns_alias = 'pape' + + def __init__(self, preferred_auth_policies=None, max_auth_age=None): + super(Request, self).__init__() + if not preferred_auth_policies: + preferred_auth_policies = [] + + self.preferred_auth_policies = preferred_auth_policies + self.max_auth_age = max_auth_age + + def __bool__(self): + return bool(self.preferred_auth_policies or + self.max_auth_age is not None) + + def addPolicyURI(self, policy_uri): + """Add an acceptable authentication policy URI to this request + + This method is intended to be used by the relying party to add + acceptable authentication types to the request. + + @param policy_uri: The identifier for the preferred type of + authentication. + @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies + """ + if policy_uri not in self.preferred_auth_policies: + self.preferred_auth_policies.append(policy_uri) + + def getExtensionArgs(self): + """@see: C{L{Extension.getExtensionArgs}} + """ + ns_args = { + 'preferred_auth_policies': ' '.join(self.preferred_auth_policies) + } + + if self.max_auth_age is not None: + ns_args['max_auth_age'] = str(self.max_auth_age) + + return ns_args + + def fromOpenIDRequest(cls, request): + """Instantiate a Request object from the arguments in a + C{checkid_*} OpenID message + """ + self = cls() + args = request.message.getArgs(self.ns_uri) + + if args == {}: + return None + + self.parseExtensionArgs(args) + return self + + fromOpenIDRequest = classmethod(fromOpenIDRequest) + + def parseExtensionArgs(self, args): + """Set the state of this request to be that expressed in these + PAPE arguments + + @param args: The PAPE arguments without a namespace + + @rtype: None + + @raises ValueError: When the max_auth_age is not parseable as + an integer + """ + + # preferred_auth_policies is a space-separated list of policy URIs + self.preferred_auth_policies = [] + + policies_str = args.get('preferred_auth_policies') + if policies_str: + if isinstance(policies_str, bytes): + policies_str = str(policies_str, encoding="utf-8") + for uri in policies_str.split(' '): + if uri not in self.preferred_auth_policies: + self.preferred_auth_policies.append(uri) + + # max_auth_age is base-10 integer number of seconds + max_auth_age_str = args.get('max_auth_age') + self.max_auth_age = None + + if max_auth_age_str: + try: + self.max_auth_age = int(max_auth_age_str) + except ValueError: + pass + + def preferredTypes(self, supported_types): + """Given a list of authentication policy URIs that a provider + supports, this method returns the subsequence of those types + that are preferred by the relying party. + + @param supported_types: A sequence of authentication policy + type URIs that are supported by a provider + + @returns: The sub-sequence of the supported types that are + preferred by the relying party. This list will be ordered + in the order that the types appear in the supported_types + sequence, and may be empty if the provider does not prefer + any of the supported authentication types. + + @returntype: [str] + """ + return list( + filter(self.preferred_auth_policies.__contains__, supported_types)) + + +Request.ns_uri = ns_uri + + +class Response(Extension): + """A Provider Authentication Policy response, sent from a provider + to a relying party + """ + + ns_alias = 'pape' + + def __init__(self, + auth_policies=None, + auth_time=None, + nist_auth_level=None): + super(Response, self).__init__() + if auth_policies: + self.auth_policies = auth_policies + else: + self.auth_policies = [] + + self.auth_time = auth_time + self.nist_auth_level = nist_auth_level + + def addPolicyURI(self, policy_uri): + """Add a authentication policy to this response + + This method is intended to be used by the provider to add a + policy that the provider conformed to when authenticating the user. + + @param policy_uri: The identifier for the preferred type of + authentication. + @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies + """ + if policy_uri not in self.auth_policies: + self.auth_policies.append(policy_uri) + + def fromSuccessResponse(cls, success_response): + """Create a C{L{Response}} object from a successful OpenID + library response + (C{L{openid.consumer.consumer.SuccessResponse}}) response + message + + @param success_response: A SuccessResponse from consumer.complete() + @type success_response: C{L{openid.consumer.consumer.SuccessResponse}} + + @rtype: Response or None + @returns: A provider authentication policy response from the + data that was supplied with the C{id_res} response or None + if the provider sent no signed PAPE response arguments. + """ + self = cls() + + # PAPE requires that the args be signed. + args = success_response.getSignedNS(self.ns_uri) + + # Only try to construct a PAPE response if the arguments were + # signed in the OpenID response. If not, return None. + if args is not None: + self.parseExtensionArgs(args) + return self + else: + return None + + def parseExtensionArgs(self, args, strict=False): + """Parse the provider authentication policy arguments into the + internal state of this object + + @param args: unqualified provider authentication policy + arguments + + @param strict: Whether to raise an exception when bad data is + encountered + + @returns: None. The data is parsed into the internal fields of + this object. + """ + policies_str = args.get('auth_policies') + if policies_str and policies_str != 'none': + self.auth_policies = policies_str.split(' ') + + nist_level_str = args.get('nist_auth_level') + if nist_level_str: + try: + nist_level = int(nist_level_str) + except ValueError: + if strict: + raise ValueError( + 'nist_auth_level must be an integer between ' + 'zero and four, inclusive') + else: + self.nist_auth_level = None + else: + if 0 <= nist_level < 5: + self.nist_auth_level = nist_level + + auth_time = args.get('auth_time') + if auth_time: + if TIME_VALIDATOR.match(auth_time): + self.auth_time = auth_time + elif strict: + raise ValueError("auth_time must be in RFC3339 format") + + fromSuccessResponse = classmethod(fromSuccessResponse) + + def getExtensionArgs(self): + """@see: C{L{Extension.getExtensionArgs}} + """ + if len(self.auth_policies) == 0: + ns_args = { + 'auth_policies': 'none', + } + else: + ns_args = { + 'auth_policies': ' '.join(self.auth_policies), + } + + if self.nist_auth_level is not None: + if self.nist_auth_level not in list(range(0, 5)): + raise ValueError('nist_auth_level must be an integer between ' + 'zero and four, inclusive') + ns_args['nist_auth_level'] = str(self.nist_auth_level) + + if self.auth_time is not None: + if not TIME_VALIDATOR.match(self.auth_time): + raise ValueError('auth_time must be in RFC3339 format') + + ns_args['auth_time'] = self.auth_time + + return ns_args + + +Response.ns_uri = ns_uri diff --git a/flex/lib/python3.6/site-packages/openid/extensions/draft/pape5.py b/flex/lib/python3.6/site-packages/openid/extensions/draft/pape5.py new file mode 100644 index 0000000..1441dd3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/extensions/draft/pape5.py @@ -0,0 +1,481 @@ +"""An implementation of the OpenID Provider Authentication Policy +Extension 1.0, Draft 5 + +@see: http://openid.net/developers/specs/ + +@since: 2.1.0 +""" + +__all__ = [ + 'Request', + 'Response', + 'ns_uri', + 'AUTH_PHISHING_RESISTANT', + 'AUTH_MULTI_FACTOR', + 'AUTH_MULTI_FACTOR_PHYSICAL', + 'LEVELS_NIST', + 'LEVELS_JISA', +] + +from openid.extension import Extension +import warnings +import re + +ns_uri = "http://specs.openid.net/extensions/pape/1.0" + +AUTH_MULTI_FACTOR_PHYSICAL = \ + 'http://schemas.openid.net/pape/policies/2007/06/multi-factor-physical' +AUTH_MULTI_FACTOR = \ + 'http://schemas.openid.net/pape/policies/2007/06/multi-factor' +AUTH_PHISHING_RESISTANT = \ + 'http://schemas.openid.net/pape/policies/2007/06/phishing-resistant' +AUTH_NONE = \ + 'http://schemas.openid.net/pape/policies/2007/06/none' + +TIME_VALIDATOR = re.compile('^\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ$') + +LEVELS_NIST = 'http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf' +LEVELS_JISA = 'http://www.jisa.or.jp/spec/auth_level.html' + + +class PAPEExtension(Extension): + _default_auth_level_aliases = { + 'nist': LEVELS_NIST, + 'jisa': LEVELS_JISA, + } + + def __init__(self): + self.auth_level_aliases = self._default_auth_level_aliases.copy() + + def _addAuthLevelAlias(self, auth_level_uri, alias=None): + """Add an auth level URI alias to this request. + + @param auth_level_uri: The auth level URI to send in the + request. + + @param alias: The namespace alias to use for this auth level + in this message. May be None if the alias is not + important. + """ + if alias is None: + try: + alias = self._getAlias(auth_level_uri) + except KeyError: + alias = self._generateAlias() + else: + existing_uri = self.auth_level_aliases.get(alias) + if existing_uri is not None and existing_uri != auth_level_uri: + raise KeyError('Attempting to redefine alias %r from %r to %r', + alias, existing_uri, auth_level_uri) + + self.auth_level_aliases[alias] = auth_level_uri + + def _generateAlias(self): + """Return an unused auth level alias""" + for i in range(1000): + alias = 'cust%d' % (i, ) + if alias not in self.auth_level_aliases: + return alias + + raise RuntimeError('Could not find an unused alias (tried 1000!)') + + def _getAlias(self, auth_level_uri): + """Return the alias for the specified auth level URI. + + @raises KeyError: if no alias is defined + """ + for (alias, existing_uri) in self.auth_level_aliases.items(): + if auth_level_uri == existing_uri: + return alias + + raise KeyError(auth_level_uri) + + +class Request(PAPEExtension): + """A Provider Authentication Policy request, sent from a relying + party to a provider + + @ivar preferred_auth_policies: The authentication policies that + the relying party prefers + @type preferred_auth_policies: [str] + + @ivar max_auth_age: The maximum time, in seconds, that the relying + party wants to allow to have elapsed before the user must + re-authenticate + @type max_auth_age: int or NoneType + + @ivar preferred_auth_level_types: Ordered list of authentication + level namespace URIs + + @type preferred_auth_level_types: [str] + """ + + ns_alias = 'pape' + + def __init__(self, + preferred_auth_policies=None, + max_auth_age=None, + preferred_auth_level_types=None): + super(Request, self).__init__() + if preferred_auth_policies is None: + preferred_auth_policies = [] + + self.preferred_auth_policies = preferred_auth_policies + self.max_auth_age = max_auth_age + self.preferred_auth_level_types = [] + + if preferred_auth_level_types is not None: + for auth_level in preferred_auth_level_types: + self.addAuthLevel(auth_level) + + def __bool__(self): + return bool(self.preferred_auth_policies or + self.max_auth_age is not None or + self.preferred_auth_level_types) + + def addPolicyURI(self, policy_uri): + """Add an acceptable authentication policy URI to this request + + This method is intended to be used by the relying party to add + acceptable authentication types to the request. + + @param policy_uri: The identifier for the preferred type of + authentication. + @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-05.html#auth_policies + """ + if policy_uri not in self.preferred_auth_policies: + self.preferred_auth_policies.append(policy_uri) + + def addAuthLevel(self, auth_level_uri, alias=None): + self._addAuthLevelAlias(auth_level_uri, alias) + if auth_level_uri not in self.preferred_auth_level_types: + self.preferred_auth_level_types.append(auth_level_uri) + + def getExtensionArgs(self): + """@see: C{L{Extension.getExtensionArgs}} + """ + ns_args = { + 'preferred_auth_policies': ' '.join(self.preferred_auth_policies), + } + + if self.max_auth_age is not None: + ns_args['max_auth_age'] = str(self.max_auth_age) + + if self.preferred_auth_level_types: + preferred_types = [] + + for auth_level_uri in self.preferred_auth_level_types: + alias = self._getAlias(auth_level_uri) + ns_args['auth_level.ns.%s' % (alias, )] = auth_level_uri + preferred_types.append(alias) + + ns_args['preferred_auth_level_types'] = ' '.join(preferred_types) + + return ns_args + + def fromOpenIDRequest(cls, request): + """Instantiate a Request object from the arguments in a + C{checkid_*} OpenID message + """ + self = cls() + args = request.message.getArgs(self.ns_uri) + is_openid1 = request.message.isOpenID1() + + if args == {}: + return None + + self.parseExtensionArgs(args, is_openid1) + return self + + fromOpenIDRequest = classmethod(fromOpenIDRequest) + + def parseExtensionArgs(self, args, is_openid1, strict=False): + """Set the state of this request to be that expressed in these + PAPE arguments + + @param args: The PAPE arguments without a namespace + + @param strict: Whether to raise an exception if the input is + out of spec or otherwise malformed. If strict is false, + malformed input will be ignored. + + @param is_openid1: Whether the input should be treated as part + of an OpenID1 request + + @rtype: None + + @raises ValueError: When the max_auth_age is not parseable as + an integer + """ + + # preferred_auth_policies is a space-separated list of policy URIs + self.preferred_auth_policies = [] + + policies_str = args.get('preferred_auth_policies') + if policies_str: + if isinstance(policies_str, bytes): + policies_str = str(policies_str, encoding="utf-8") + for uri in policies_str.split(' '): + if uri not in self.preferred_auth_policies: + self.preferred_auth_policies.append(uri) + + # max_auth_age is base-10 integer number of seconds + max_auth_age_str = args.get('max_auth_age') + self.max_auth_age = None + + if max_auth_age_str: + try: + self.max_auth_age = int(max_auth_age_str) + except ValueError: + if strict: + raise + + # Parse auth level information + preferred_auth_level_types = args.get('preferred_auth_level_types') + if preferred_auth_level_types: + aliases = preferred_auth_level_types.strip().split() + + for alias in aliases: + key = 'auth_level.ns.%s' % (alias, ) + try: + uri = args[key] + except KeyError: + if is_openid1: + uri = self._default_auth_level_aliases.get(alias) + else: + uri = None + + if uri is None: + if strict: + raise ValueError('preferred auth level %r is not ' + 'defined in this message' % (alias, )) + else: + self.addAuthLevel(uri, alias) + + def preferredTypes(self, supported_types): + """Given a list of authentication policy URIs that a provider + supports, this method returns the subsequence of those types + that are preferred by the relying party. + + @param supported_types: A sequence of authentication policy + type URIs that are supported by a provider + + @returns: The sub-sequence of the supported types that are + preferred by the relying party. This list will be ordered + in the order that the types appear in the supported_types + sequence, and may be empty if the provider does not prefer + any of the supported authentication types. + + @returntype: [str] + """ + return list( + filter(self.preferred_auth_policies.__contains__, supported_types)) + + +Request.ns_uri = ns_uri + + +class Response(PAPEExtension): + """A Provider Authentication Policy response, sent from a provider + to a relying party + + @ivar auth_policies: List of authentication policies conformed to + by this OpenID assertion, represented as policy URIs + """ + + ns_alias = 'pape' + + def __init__(self, auth_policies=None, auth_time=None, auth_levels=None): + super(Response, self).__init__() + if auth_policies: + self.auth_policies = auth_policies + else: + self.auth_policies = [] + + self.auth_time = auth_time + self.auth_levels = {} + + if auth_levels is None: + auth_levels = {} + + for uri, level in auth_levels.items(): + self.setAuthLevel(uri, level) + + def setAuthLevel(self, level_uri, level, alias=None): + """Set the value for the given auth level type. + + @param level: string representation of an authentication level + valid for level_uri + + @param alias: An optional namespace alias for the given auth + level URI. May be omitted if the alias is not + significant. The library will use a reasonable default for + widely-used auth level types. + """ + self._addAuthLevelAlias(level_uri, alias) + self.auth_levels[level_uri] = level + + def getAuthLevel(self, level_uri): + """Return the auth level for the specified auth level + identifier + + @returns: A string that should map to the auth levels defined + for the auth level type + + @raises KeyError: If the auth level type is not present in + this message + """ + return self.auth_levels[level_uri] + + def _getNISTAuthLevel(self): + try: + return int(self.getAuthLevel(LEVELS_NIST)) + except KeyError: + return None + + nist_auth_level = property( + _getNISTAuthLevel, + doc="Backward-compatibility accessor for the NIST auth level") + + def addPolicyURI(self, policy_uri): + """Add a authentication policy to this response + + This method is intended to be used by the provider to add a + policy that the provider conformed to when authenticating the user. + + @param policy_uri: The identifier for the preferred type of + authentication. + @see: http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#auth_policies + """ + if policy_uri == AUTH_NONE: + raise RuntimeError( + 'To send no policies, do not set any on the response.') + + if policy_uri not in self.auth_policies: + self.auth_policies.append(policy_uri) + + def fromSuccessResponse(cls, success_response): + """Create a C{L{Response}} object from a successful OpenID + library response + (C{L{openid.consumer.consumer.SuccessResponse}}) response + message + + @param success_response: A SuccessResponse from consumer.complete() + @type success_response: C{L{openid.consumer.consumer.SuccessResponse}} + + @rtype: Response or None + @returns: A provider authentication policy response from the + data that was supplied with the C{id_res} response or None + if the provider sent no signed PAPE response arguments. + """ + self = cls() + + # PAPE requires that the args be signed. + args = success_response.getSignedNS(self.ns_uri) + is_openid1 = success_response.isOpenID1() + + # Only try to construct a PAPE response if the arguments were + # signed in the OpenID response. If not, return None. + if args is not None: + self.parseExtensionArgs(args, is_openid1) + return self + else: + return None + + def parseExtensionArgs(self, args, is_openid1, strict=False): + """Parse the provider authentication policy arguments into the + internal state of this object + + @param args: unqualified provider authentication policy + arguments + + @param strict: Whether to raise an exception when bad data is + encountered + + @returns: None. The data is parsed into the internal fields of + this object. + """ + policies_str = args.get('auth_policies') + if policies_str: + auth_policies = policies_str.split(' ') + elif strict: + raise ValueError('Missing auth_policies') + else: + auth_policies = [] + + if (len(auth_policies) > 1 and strict and AUTH_NONE in auth_policies): + raise ValueError('Got some auth policies, as well as the special ' + '"none" URI: %r' % (auth_policies, )) + + if 'none' in auth_policies: + msg = '"none" used as a policy URI (see PAPE draft < 5)' + if strict: + raise ValueError(msg) + else: + warnings.warn(msg, stacklevel=2) + + auth_policies = [ + u for u in auth_policies if u not in ['none', AUTH_NONE] + ] + + self.auth_policies = auth_policies + + for (key, val) in args.items(): + if key.startswith('auth_level.'): + alias = key[11:] + + # skip the already-processed namespace declarations + if alias.startswith('ns.'): + continue + + try: + uri = args['auth_level.ns.%s' % (alias, )] + except KeyError: + if is_openid1: + uri = self._default_auth_level_aliases.get(alias) + else: + uri = None + + if uri is None: + if strict: + raise ValueError('Undefined auth level alias: %r' % + (alias, )) + else: + self.setAuthLevel(uri, val, alias) + + auth_time = args.get('auth_time') + if auth_time: + if TIME_VALIDATOR.match(auth_time): + self.auth_time = auth_time + elif strict: + raise ValueError("auth_time must be in RFC3339 format") + + fromSuccessResponse = classmethod(fromSuccessResponse) + + def getExtensionArgs(self): + """@see: C{L{Extension.getExtensionArgs}} + """ + if len(self.auth_policies) == 0: + ns_args = { + 'auth_policies': AUTH_NONE, + } + else: + ns_args = { + 'auth_policies': ' '.join(self.auth_policies), + } + + for level_type, level in self.auth_levels.items(): + alias = self._getAlias(level_type) + ns_args['auth_level.ns.%s' % (alias, )] = level_type + ns_args['auth_level.%s' % (alias, )] = str(level) + + if self.auth_time is not None: + if not TIME_VALIDATOR.match(self.auth_time): + raise ValueError('auth_time must be in RFC3339 format') + + ns_args['auth_time'] = self.auth_time + + return ns_args + + +Response.ns_uri = ns_uri diff --git a/flex/lib/python3.6/site-packages/openid/extensions/sreg.py b/flex/lib/python3.6/site-packages/openid/extensions/sreg.py new file mode 100644 index 0000000..c0b2090 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/extensions/sreg.py @@ -0,0 +1,527 @@ +"""Simple registration request and response parsing and object representation + +This module contains objects representing simple registration requests +and responses that can be used with both OpenID relying parties and +OpenID providers. + + 1. The relying party creates a request object and adds it to the + C{L{AuthRequest}} object + before making the C{checkid_} request to the OpenID provider:: + + auth_request.addExtension(SRegRequest(required=['email'])) + + 2. The OpenID provider extracts the simple registration request from + the OpenID request using C{L{SRegRequest.fromOpenIDRequest}}, + gets the user's approval and data, creates a C{L{SRegResponse}} + object and adds it to the C{id_res} response:: + + sreg_req = SRegRequest.fromOpenIDRequest(checkid_request) + # [ get the user's approval and data, informing the user that + # the fields in sreg_response were requested ] + sreg_resp = SRegResponse.extractResponse(sreg_req, user_data) + sreg_resp.toMessage(openid_response.fields) + + 3. The relying party uses C{L{SRegResponse.fromSuccessResponse}} to + extract the data from the OpenID response:: + + sreg_resp = SRegResponse.fromSuccessResponse(success_response) + +@since: 2.0 + +@var sreg_data_fields: The names of the data fields that are listed in + the sreg spec, and a description of them in English + +@var sreg_uri: The preferred URI to use for the simple registration + namespace and XRD Type value +""" + +from openid.message import registerNamespaceAlias, \ + NamespaceAliasRegistrationError +from openid.extension import Extension +import logging + +try: + str #pylint:disable-msg=W0104 +except NameError: + # For Python 2.2 + str = (str, str) #pylint:disable-msg=W0622 + +__all__ = [ + 'SRegRequest', + 'SRegResponse', + 'data_fields', + 'ns_uri', + 'ns_uri_1_0', + 'ns_uri_1_1', + 'supportsSReg', +] + +# The data fields that are listed in the sreg spec +data_fields = { + 'fullname': 'Full Name', + 'nickname': 'Nickname', + 'dob': 'Date of Birth', + 'email': 'E-mail Address', + 'gender': 'Gender', + 'postcode': 'Postal Code', + 'country': 'Country', + 'language': 'Language', + 'timezone': 'Time Zone', +} + + +def checkFieldName(field_name): + """Check to see that the given value is a valid simple + registration data field name. + + @raise ValueError: if the field name is not a valid simple + registration data field name + """ + if field_name not in data_fields: + raise ValueError('%r is not a defined simple registration field' % + (field_name, )) + + +# URI used in the wild for Yadis documents advertising simple +# registration support +ns_uri_1_0 = 'http://openid.net/sreg/1.0' + +# URI in the draft specification for simple registration 1.1 +# +ns_uri_1_1 = 'http://openid.net/extensions/sreg/1.1' + +# This attribute will always hold the preferred URI to use when adding +# sreg support to an XRDS file or in an OpenID namespace declaration. +ns_uri = ns_uri_1_1 + +try: + registerNamespaceAlias(ns_uri_1_1, 'sreg') +except NamespaceAliasRegistrationError as e: + logging.exception('registerNamespaceAlias(%r, %r) failed: %s' % + (ns_uri_1_1, 'sreg', str(e), )) + + +def supportsSReg(endpoint): + """Does the given endpoint advertise support for simple + registration? + + @param endpoint: The endpoint object as returned by OpenID discovery + @type endpoint: openid.consumer.discover.OpenIDEndpoint + + @returns: Whether an sreg type was advertised by the endpoint + @rtype: bool + """ + return (endpoint.usesExtension(ns_uri_1_1) or + endpoint.usesExtension(ns_uri_1_0)) + + +class SRegNamespaceError(ValueError): + """The simple registration namespace was not found and could not + be created using the expected name (there's another extension + using the name 'sreg') + + This is not I{illegal}, for OpenID 2, although it probably + indicates a problem, since it's not expected that other extensions + will re-use the alias that is in use for OpenID 1. + + If this is an OpenID 1 request, then there is no recourse. This + should not happen unless some code has modified the namespaces for + the message that is being processed. + """ + + +def getSRegNS(message): + """Extract the simple registration namespace URI from the given + OpenID message. Handles OpenID 1 and 2, as well as both sreg + namespace URIs found in the wild, as well as missing namespace + definitions (for OpenID 1) + + @param message: The OpenID message from which to parse simple + registration fields. This may be a request or response message. + @type message: C{L{openid.message.Message}} + + @returns: the sreg namespace URI for the supplied message. The + message may be modified to define a simple registration + namespace. + @rtype: C{str} + + @raise ValueError: when using OpenID 1 if the message defines + the 'sreg' alias to be something other than a simple + registration type. + """ + # See if there exists an alias for one of the two defined simple + # registration types. + for sreg_ns_uri in [ns_uri_1_1, ns_uri_1_0]: + alias = message.namespaces.getAlias(sreg_ns_uri) + if alias is not None: + break + else: + # There is no alias for either of the types, so try to add + # one. We default to using the modern value (1.1) + sreg_ns_uri = ns_uri_1_1 + try: + message.namespaces.addAlias(ns_uri_1_1, 'sreg') + except KeyError as why: + # An alias for the string 'sreg' already exists, but it's + # defined for something other than simple registration + raise SRegNamespaceError(why) + + # we know that sreg_ns_uri defined, because it's defined in the + # else clause of the loop as well, so disable the warning + return sreg_ns_uri #pylint:disable-msg=W0631 + + +class SRegRequest(Extension): + """An object to hold the state of a simple registration request. + + @ivar required: A list of the required fields in this simple + registration request + @type required: [str] + + @ivar optional: A list of the optional fields in this simple + registration request + @type optional: [str] + + @ivar policy_url: The policy URL that was provided with the request + @type policy_url: str or NoneType + + @group Consumer: requestField, requestFields, getExtensionArgs, addToOpenIDRequest + @group Server: fromOpenIDRequest, parseExtensionArgs + """ + + ns_alias = 'sreg' + + def __init__(self, + required=None, + optional=None, + policy_url=None, + sreg_ns_uri=ns_uri): + """Initialize an empty simple registration request""" + Extension.__init__(self) + self.required = [] + self.optional = [] + self.policy_url = policy_url + self.ns_uri = sreg_ns_uri + + if required: + self.requestFields(required, required=True, strict=True) + + if optional: + self.requestFields(optional, required=False, strict=True) + + # Assign getSRegNS to a static method so that it can be + # overridden for testing. + _getSRegNS = staticmethod(getSRegNS) + + def fromOpenIDRequest(cls, request): + """Create a simple registration request that contains the + fields that were requested in the OpenID request with the + given arguments + + @param request: The OpenID request + @type request: openid.server.CheckIDRequest + + @returns: The newly created simple registration request + @rtype: C{L{SRegRequest}} + """ + self = cls() + + # Since we're going to mess with namespace URI mapping, don't + # mutate the object that was passed in. + message = request.message.copy() + + self.ns_uri = self._getSRegNS(message) + args = message.getArgs(self.ns_uri) + self.parseExtensionArgs(args) + + return self + + fromOpenIDRequest = classmethod(fromOpenIDRequest) + + def parseExtensionArgs(self, args, strict=False): + """Parse the unqualified simple registration request + parameters and add them to this object. + + This method is essentially the inverse of + C{L{getExtensionArgs}}. This method restores the serialized simple + registration request fields. + + If you are extracting arguments from a standard OpenID + checkid_* request, you probably want to use C{L{fromOpenIDRequest}}, + which will extract the sreg namespace and arguments from the + OpenID request. This method is intended for cases where the + OpenID server needs more control over how the arguments are + parsed than that method provides. + + >>> args = message.getArgs(ns_uri) + >>> request.parseExtensionArgs(args) + + @param args: The unqualified simple registration arguments + @type args: {str:str} + + @param strict: Whether requests with fields that are not + defined in the simple registration specification should be + tolerated (and ignored) + @type strict: bool + + @returns: None; updates this object + """ + for list_name in ['required', 'optional']: + required = (list_name == 'required') + items = args.get(list_name) + if items: + for field_name in items.split(','): + try: + self.requestField(field_name, required, strict) + except ValueError: + if strict: + raise + + self.policy_url = args.get('policy_url') + + def allRequestedFields(self): + """A list of all of the simple registration fields that were + requested, whether they were required or optional. + + @rtype: [str] + """ + return self.required + self.optional + + def wereFieldsRequested(self): + """Have any simple registration fields been requested? + + @rtype: bool + """ + return bool(self.allRequestedFields()) + + def __contains__(self, field_name): + """Was this field in the request?""" + return (field_name in self.required or field_name in self.optional) + + def requestField(self, field_name, required=False, strict=False): + """Request the specified field from the OpenID user + + @param field_name: the unqualified simple registration field name + @type field_name: str + + @param required: whether the given field should be presented + to the user as being a required to successfully complete + the request + + @param strict: whether to raise an exception when a field is + added to a request more than once + + @raise ValueError: when the field requested is not a simple + registration field or strict is set and the field was + requested more than once + """ + checkFieldName(field_name) + + if strict: + if field_name in self.required or field_name in self.optional: + raise ValueError('That field has already been requested') + else: + if field_name in self.required: + return + + if field_name in self.optional: + if required: + self.optional.remove(field_name) + else: + return + + if required: + self.required.append(field_name) + else: + self.optional.append(field_name) + + def requestFields(self, field_names, required=False, strict=False): + """Add the given list of fields to the request + + @param field_names: The simple registration data fields to request + @type field_names: [str] + + @param required: Whether these values should be presented to + the user as required + + @param strict: whether to raise an exception when a field is + added to a request more than once + + @raise ValueError: when a field requested is not a simple + registration field or strict is set and a field was + requested more than once + """ + if isinstance(field_names, str): + raise TypeError('Fields should be passed as a list of ' + 'strings (not %r)' % (type(field_names), )) + + for field_name in field_names: + self.requestField(field_name, required, strict=strict) + + def getExtensionArgs(self): + """Get a dictionary of unqualified simple registration + arguments representing this request. + + This method is essentially the inverse of + C{L{parseExtensionArgs}}. This method serializes the simple + registration request fields. + + @rtype: {str:str} + """ + args = {} + + if self.required: + args['required'] = ','.join(self.required) + + if self.optional: + args['optional'] = ','.join(self.optional) + + if self.policy_url: + args['policy_url'] = self.policy_url + + return args + + +class SRegResponse(Extension): + """Represents the data returned in a simple registration response + inside of an OpenID C{id_res} response. This object will be + created by the OpenID server, added to the C{id_res} response + object, and then extracted from the C{id_res} message by the + Consumer. + + @ivar data: The simple registration data, keyed by the unqualified + simple registration name of the field (i.e. nickname is keyed + by C{'nickname'}) + + @ivar ns_uri: The URI under which the simple registration data was + stored in the response message. + + @group Server: extractResponse + @group Consumer: fromSuccessResponse + @group Read-only dictionary interface: keys, iterkeys, items, iteritems, + __iter__, get, __getitem__, keys, has_key + """ + + ns_alias = 'sreg' + + def __init__(self, data=None, sreg_ns_uri=ns_uri): + Extension.__init__(self) + if data is None: + self.data = {} + else: + self.data = data + + self.ns_uri = sreg_ns_uri + + def extractResponse(cls, request, data): + """Take a C{L{SRegRequest}} and a dictionary of simple + registration values and create a C{L{SRegResponse}} + object containing that data. + + @param request: The simple registration request object + @type request: SRegRequest + + @param data: The simple registration data for this + response, as a dictionary from unqualified simple + registration field name to string (unicode) value. For + instance, the nickname should be stored under the key + 'nickname'. + @type data: {str:str} + + @returns: a simple registration response object + @rtype: SRegResponse + """ + self = cls() + self.ns_uri = request.ns_uri + for field in request.allRequestedFields(): + value = data.get(field) + if value is not None: + self.data[field] = value + return self + + extractResponse = classmethod(extractResponse) + + # Assign getSRegArgs to a static method so that it can be + # overridden for testing + _getSRegNS = staticmethod(getSRegNS) + + def fromSuccessResponse(cls, success_response, signed_only=True): + """Create a C{L{SRegResponse}} object from a successful OpenID + library response + (C{L{openid.consumer.consumer.SuccessResponse}}) response + message + + @param success_response: A SuccessResponse from consumer.complete() + @type success_response: C{L{openid.consumer.consumer.SuccessResponse}} + + @param signed_only: Whether to process only data that was + signed in the id_res message from the server. + @type signed_only: bool + + @rtype: SRegResponse + @returns: A simple registration response containing the data + that was supplied with the C{id_res} response. + """ + self = cls() + self.ns_uri = self._getSRegNS(success_response.message) + if signed_only: + args = success_response.getSignedNS(self.ns_uri) + else: + args = success_response.message.getArgs(self.ns_uri) + + if not args: + return None + + for field_name in data_fields: + if field_name in args: + self.data[field_name] = args[field_name] + + return self + + fromSuccessResponse = classmethod(fromSuccessResponse) + + def getExtensionArgs(self): + """Get the fields to put in the simple registration namespace + when adding them to an id_res message. + + @see: openid.extension + """ + return self.data + + # Read-only dictionary interface + def get(self, field_name, default=None): + """Like dict.get, except that it checks that the field name is + defined by the simple registration specification""" + checkFieldName(field_name) + return self.data.get(field_name, default) + + def items(self): + """All of the data values in this simple registration response + """ + return list(self.data.items()) + + def iteritems(self): + return iter(self.data.items()) + + def keys(self): + return list(self.data.keys()) + + def iterkeys(self): + return iter(self.data.keys()) + + def has_key(self, key): + return key in self + + def __contains__(self, field_name): + checkFieldName(field_name) + return field_name in self.data + + def __iter__(self): + return iter(self.data) + + def __getitem__(self, field_name): + checkFieldName(field_name) + return self.data[field_name] + + def __bool__(self): + return bool(self.data) diff --git a/flex/lib/python3.6/site-packages/openid/fetchers.py b/flex/lib/python3.6/site-packages/openid/fetchers.py new file mode 100644 index 0000000..c109cd2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/fetchers.py @@ -0,0 +1,493 @@ +# -*- test-case-name: openid.test.test_fetchers -*- +""" +This module contains the HTTP fetcher interface and several implementations. +""" + +__all__ = [ + 'fetch', 'getDefaultFetcher', 'setDefaultFetcher', 'HTTPResponse', + 'HTTPFetcher', 'createHTTPFetcher', 'HTTPFetchingError', 'HTTPError' +] + +import urllib.request +import urllib.error +import urllib.parse +import http.client + +import time +import io +import sys +import contextlib + +import openid +import openid.urinorm + +# Try to import httplib2 for caching support +# http://bitworking.org/projects/httplib2/ +try: + import httplib2 +except ImportError: + # httplib2 not available + httplib2 = None + +# try to import pycurl, which will let us use CurlHTTPFetcher +try: + import pycurl +except ImportError: + pycurl = None + +USER_AGENT = "python-openid/%s (%s)" % (openid.__version__, sys.platform) +MAX_RESPONSE_KB = 1024 + + +def fetch(url, body=None, headers=None): + """Invoke the fetch method on the default fetcher. Most users + should need only this method. + + @raises Exception: any exceptions that may be raised by the default fetcher + """ + fetcher = getDefaultFetcher() + return fetcher.fetch(url, body, headers) + + +def createHTTPFetcher(): + """Create a default HTTP fetcher instance + + prefers Curl to urllib2.""" + if pycurl is None: + fetcher = Urllib2Fetcher() + else: + fetcher = CurlHTTPFetcher() + + return fetcher + + +# Contains the currently set HTTP fetcher. If it is set to None, the +# library will call createHTTPFetcher() to set it. Do not access this +# variable outside of this module. +_default_fetcher = None + + +def getDefaultFetcher(): + """Return the default fetcher instance + if no fetcher has been set, it will create a default fetcher. + + @return: the default fetcher + @rtype: HTTPFetcher + """ + global _default_fetcher + + if _default_fetcher is None: + setDefaultFetcher(createHTTPFetcher()) + + return _default_fetcher + + +def setDefaultFetcher(fetcher, wrap_exceptions=True): + """Set the default fetcher + + @param fetcher: The fetcher to use as the default HTTP fetcher + @type fetcher: HTTPFetcher + + @param wrap_exceptions: Whether to wrap exceptions thrown by the + fetcher wil HTTPFetchingError so that they may be caught + easier. By default, exceptions will be wrapped. In general, + unwrapped fetchers are useful for debugging of fetching errors + or if your fetcher raises well-known exceptions that you would + like to catch. + @type wrap_exceptions: bool + """ + global _default_fetcher + if fetcher is None or not wrap_exceptions: + _default_fetcher = fetcher + else: + _default_fetcher = ExceptionWrappingFetcher(fetcher) + + +def usingCurl(): + """Whether the currently set HTTP fetcher is a Curl HTTP fetcher.""" + fetcher = getDefaultFetcher() + if isinstance(fetcher, ExceptionWrappingFetcher): + fetcher = fetcher.fetcher + return isinstance(fetcher, CurlHTTPFetcher) + + +class HTTPResponse(object): + """XXX document attributes""" + headers = None + status = None + body = None + final_url = None + + def __init__(self, final_url=None, status=None, headers=None, body=None): + self.final_url = final_url + self.status = status + self.headers = headers + self.body = body + + def __repr__(self): + return "<%s status %s for %s>" % (self.__class__.__name__, self.status, + self.final_url) + + +class HTTPFetcher(object): + """ + This class is the interface for openid HTTP fetchers. This + interface is only important if you need to write a new fetcher for + some reason. + """ + + def fetch(self, url, body=None, headers=None): + """ + This performs an HTTP POST or GET, following redirects along + the way. If a body is specified, then the request will be a + POST. Otherwise, it will be a GET. + + + @param headers: HTTP headers to include with the request + @type headers: {str:str} + + @return: An object representing the server's HTTP response. If + there are network or protocol errors, an exception will be + raised. HTTP error responses, like 404 or 500, do not + cause exceptions. + + @rtype: L{HTTPResponse} + + @raise Exception: Different implementations will raise + different errors based on the underlying HTTP library. + """ + raise NotImplementedError + + +def _allowedURL(url): + parsed = urllib.parse.urlparse(url) + # scheme is the first item in the tuple + return parsed[0] in ('http', 'https') + + +class HTTPFetchingError(Exception): + """Exception that is wrapped around all exceptions that are raised + by the underlying fetcher when using the ExceptionWrappingFetcher + + @ivar why: The exception that caused this exception + """ + + def __init__(self, why=None): + Exception.__init__(self, why) + self.why = why + + +class ExceptionWrappingFetcher(HTTPFetcher): + """Fetcher that wraps another fetcher, causing all exceptions + + @cvar uncaught_exceptions: Exceptions that should be exposed to the + user if they are raised by the fetch call + """ + + uncaught_exceptions = (SystemExit, KeyboardInterrupt, MemoryError) + + def __init__(self, fetcher): + self.fetcher = fetcher + + def fetch(self, *args, **kwargs): + try: + return self.fetcher.fetch(*args, **kwargs) + except self.uncaught_exceptions: + raise + except: + exc_cls, exc_inst = sys.exc_info()[:2] + if exc_inst is None: + # string exceptions + exc_inst = exc_cls + + raise HTTPFetchingError(why=exc_inst) + + +class Urllib2Fetcher(HTTPFetcher): + """An C{L{HTTPFetcher}} that uses urllib2. + """ + + # Parameterized for the benefit of testing frameworks, see + # http://trac.openidenabled.com/trac/ticket/85 + urlopen = staticmethod(urllib.request.urlopen) + + def fetch(self, url, body=None, headers=None): + if not _allowedURL(url): + raise ValueError('Bad URL scheme: %r' % (url, )) + + if headers is None: + headers = {} + + headers.setdefault('User-Agent', "%s Python-urllib/%s" % + (USER_AGENT, urllib.request.__version__)) + + if isinstance(body, str): + body = bytes(body, encoding="utf-8") + + req = urllib.request.Request(url, data=body, headers=headers) + + url_resource = None + try: + url_resource = self.urlopen(req) + with contextlib.closing(url_resource): + return self._makeResponse(url_resource) + except urllib.error.HTTPError as why: + with contextlib.closing(why): + resp = self._makeResponse(why) + return resp + except (urllib.error.URLError, http.client.BadStatusLine) as why: + raise + except Exception as why: + raise AssertionError(why) + + def _makeResponse(self, urllib2_response): + ''' + Construct an HTTPResponse from the the urllib response. Attempt to + decode the response body from bytes to str if the necessary information + is available. + ''' + resp = HTTPResponse() + resp.body = urllib2_response.read(MAX_RESPONSE_KB * 1024) + resp.final_url = urllib2_response.geturl() + resp.headers = self._lowerCaseKeys( + dict(list(urllib2_response.info().items()))) + + if hasattr(urllib2_response, 'code'): + resp.status = urllib2_response.code + else: + resp.status = 200 + + _, extra_dict = self._parseHeaderValue( + resp.headers.get("content-type", "")) + # Try to decode the response body to a string, if there's a + # charset known; fall back to ISO-8859-1 otherwise, since that's + # what's suggested in HTTP/1.1 + charset = extra_dict.get('charset', 'latin1') + try: + resp.body = resp.body.decode(charset) + except Exception: + pass + + return resp + + def _lowerCaseKeys(self, headers_dict): + new_dict = {} + for k, v in headers_dict.items(): + new_dict[k.lower()] = v + return new_dict + + def _parseHeaderValue(self, header_value): + """ + Parse out a complex header value (such as Content-Type, with a value + like "text/html; charset=utf-8") into a main value and a dictionary of + extra information (in this case, 'text/html' and {'charset': 'utf8'}). + """ + values = header_value.split(';', 1) + if len(values) == 1: + # There's no extra info -- return the main value and an empty dict + return values[0], {} + main_value, extra_values = values[0], values[1].split(';') + extra_dict = {} + for value_string in extra_values: + try: + key, value = value_string.split('=', 1) + extra_dict[key.strip()] = value.strip() + except ValueError: + # Can't unpack it -- must be malformed. Ignore + pass + return main_value, extra_dict + + +class HTTPError(HTTPFetchingError): + """ + This exception is raised by the C{L{CurlHTTPFetcher}} when it + encounters an exceptional situation fetching a URL. + """ + pass + + +# XXX: define what we mean by paranoid, and make sure it is. +class CurlHTTPFetcher(HTTPFetcher): + """ + An C{L{HTTPFetcher}} that uses pycurl for fetching. + See U{http://pycurl.sourceforge.net/}. + """ + ALLOWED_TIME = 20 # seconds + + def __init__(self): + HTTPFetcher.__init__(self) + if pycurl is None: + raise RuntimeError('Cannot find pycurl library') + + def _parseHeaders(self, header_file): + header_file.seek(0) + + # Remove all non "name: value" header lines from the input + lines = [line.decode().strip() for line in header_file if b':' in line] + + headers = {} + for line in lines: + try: + name, value = line.split(':', 1) + except ValueError: + raise HTTPError("Malformed HTTP header line in response: %r" % + (line, )) + + value = value.strip() + + # HTTP headers are case-insensitive + name = name.lower() + headers[name] = value + + return headers + + def _checkURL(self, url): + # XXX: document that this can be overridden to match desired policy + # XXX: make sure url is well-formed and routeable + return _allowedURL(url) + + def fetch(self, url, body=None, headers=None): + stop = int(time.time()) + self.ALLOWED_TIME + off = self.ALLOWED_TIME + + if headers is None: + headers = {} + + headers.setdefault('User-Agent', + "%s %s" % (USER_AGENT, pycurl.version, )) + + header_list = [] + if headers is not None: + for header_name, header_value in headers.items(): + header = '%s: %s' % (header_name, header_value) + header_list.append(header.encode()) + + c = pycurl.Curl() + try: + c.setopt(pycurl.NOSIGNAL, 1) + + if header_list: + c.setopt(pycurl.HTTPHEADER, header_list) + + # Presence of a body indicates that we should do a POST + if body is not None: + c.setopt(pycurl.POST, 1) + c.setopt(pycurl.POSTFIELDS, body) + + while off > 0: + if not self._checkURL(url): + raise HTTPError("Fetching URL not allowed: %r" % (url, )) + + data = io.BytesIO() + + def write_data(chunk): + if data.tell() > (1024 * MAX_RESPONSE_KB): + return 0 + else: + return data.write(chunk) + + response_header_data = io.BytesIO() + c.setopt(pycurl.WRITEFUNCTION, write_data) + c.setopt(pycurl.HEADERFUNCTION, response_header_data.write) + c.setopt(pycurl.TIMEOUT, off) + c.setopt(pycurl.URL, openid.urinorm.urinorm(url)) + + c.perform() + + response_headers = self._parseHeaders(response_header_data) + code = c.getinfo(pycurl.RESPONSE_CODE) + if code in [301, 302, 303, 307]: + url = response_headers.get('location') + if url is None: + raise HTTPError( + 'Redirect (%s) returned without a location' % code) + + # Redirects are always GETs + c.setopt(pycurl.POST, 0) + + # There is no way to reset POSTFIELDS to empty and + # reuse the connection, but we only use it once. + else: + resp = HTTPResponse() + resp.headers = response_headers + resp.status = code + resp.final_url = url + resp.body = data.getvalue().decode() + return resp + + off = stop - int(time.time()) + + raise HTTPError("Timed out fetching: %r" % (url, )) + finally: + c.close() + + +class HTTPLib2Fetcher(HTTPFetcher): + """A fetcher that uses C{httplib2} for performing HTTP + requests. This implementation supports HTTP caching. + + @see: http://bitworking.org/projects/httplib2/ + """ + + def __init__(self, cache=None): + """@param cache: An object suitable for use as an C{httplib2} + cache. If a string is passed, it is assumed to be a + directory name. + """ + if httplib2 is None: + raise RuntimeError('Cannot find httplib2 library. ' + 'See http://bitworking.org/projects/httplib2/') + + super(HTTPLib2Fetcher, self).__init__() + + # An instance of the httplib2 object that performs HTTP requests + self.httplib2 = httplib2.Http(cache) + + # We want httplib2 to raise exceptions for errors, just like + # the other fetchers. + self.httplib2.force_exception_to_status_code = False + + def fetch(self, url, body=None, headers=None): + """Perform an HTTP request + + @raises Exception: Any exception that can be raised by httplib2 + + @see: C{L{HTTPFetcher.fetch}} + """ + if body: + method = 'POST' + else: + method = 'GET' + + if headers is None: + headers = {} + + # httplib2 doesn't check to make sure that the URL's scheme is + # 'http' so we do it here. + if not (url.startswith('http://') or url.startswith('https://')): + raise ValueError('URL is not a HTTP URL: %r' % (url, )) + + httplib2_response, content = self.httplib2.request( + url, method, body=body, headers=headers) + + # Translate the httplib2 response to our HTTP response abstraction + + # When a 400 is returned, there is no "content-location" + # header set. This seems like a bug to me. I can't think of a + # case where we really care about the final URL when it is an + # error response, but being careful about it can't hurt. + try: + final_url = httplib2_response['content-location'] + except KeyError: + # We're assuming that no redirects occurred + assert not httplib2_response.previous + + # And this should never happen for a successful response + assert httplib2_response.status != 200 + final_url = url + + return HTTPResponse( + body=content.decode(), # TODO Don't assume ASCII + final_url=final_url, + headers=dict(list(httplib2_response.items())), + status=httplib2_response.status, ) diff --git a/flex/lib/python3.6/site-packages/openid/kvform.py b/flex/lib/python3.6/site-packages/openid/kvform.py new file mode 100644 index 0000000..571bd9e --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/kvform.py @@ -0,0 +1,132 @@ +import logging + +__all__ = ['seqToKV', 'kvToSeq', 'dictToKV', 'kvToDict'] + + +class KVFormError(ValueError): + pass + + +def seqToKV(seq, strict=False): + """Represent a sequence of pairs of strings as newline-terminated + key:value pairs. The pairs are generated in the order given. + + @param seq: The pairs + @type seq: [(str, (unicode|str))] + + @return: A string representation of the sequence + @rtype: bytes + """ + + def err(msg): + formatted = 'seqToKV warning: %s: %r' % (msg, seq) + if strict: + raise KVFormError(formatted) + else: + logging.warning(formatted) + + lines = [] + for k, v in seq: + if isinstance(k, bytes): + k = k.decode('utf-8') + elif not isinstance(k, str): + err('Converting key to string: %r' % k) + k = str(k) + + if '\n' in k: + raise KVFormError( + 'Invalid input for seqToKV: key contains newline: %r' % (k, )) + + if ':' in k: + raise KVFormError( + 'Invalid input for seqToKV: key contains colon: %r' % (k, )) + + if k.strip() != k: + err('Key has whitespace at beginning or end: %r' % (k, )) + + if isinstance(v, bytes): + v = v.decode('utf-8') + elif not isinstance(v, str): + err('Converting value to string: %r' % (v, )) + v = str(v) + + if '\n' in v: + raise KVFormError( + 'Invalid input for seqToKV: value contains newline: %r' % + (v, )) + + if v.strip() != v: + err('Value has whitespace at beginning or end: %r' % (v, )) + + lines.append(k + ':' + v + '\n') + + return ''.join(lines).encode('utf-8') + + +def kvToSeq(data, strict=False): + """ + + After one parse, seqToKV and kvToSeq are inverses, with no warnings:: + + seq = kvToSeq(s) + seqToKV(kvToSeq(seq)) == seq + + @return str + """ + + def err(msg): + formatted = 'kvToSeq warning: %s: %r' % (msg, data) + if strict: + raise KVFormError(formatted) + else: + logging.warning(formatted) + + if isinstance(data, bytes): + data = data.decode("utf-8") + + lines = data.split('\n') + if lines[-1]: + err('Does not end in a newline') + else: + del lines[-1] + + pairs = [] + line_num = 0 + for line in lines: + line_num += 1 + + # Ignore blank lines + if not line.strip(): + continue + + pair = line.split(':', 1) + if len(pair) == 2: + k, v = pair + k_s = k.strip() + if k_s != k: + fmt = ('In line %d, ignoring leading or trailing ' + 'whitespace in key %r') + err(fmt % (line_num, k)) + + if not k_s: + err('In line %d, got empty key' % (line_num, )) + + v_s = v.strip() + if v_s != v: + fmt = ('In line %d, ignoring leading or trailing ' + 'whitespace in value %r') + err(fmt % (line_num, v)) + + pairs.append((k_s, v_s)) + else: + err('Line %d does not contain a colon' % line_num) + + return pairs + + +def dictToKV(d): + return seqToKV(sorted(d.items())) + + +def kvToDict(s): + return dict(kvToSeq(s)) diff --git a/flex/lib/python3.6/site-packages/openid/message.py b/flex/lib/python3.6/site-packages/openid/message.py new file mode 100644 index 0000000..325c14d --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/message.py @@ -0,0 +1,678 @@ +"""Extension argument processing code +""" +__all__ = [ + 'Message', 'NamespaceMap', 'no_default', 'registerNamespaceAlias', + 'OPENID_NS', 'BARE_NS', 'OPENID1_NS', 'OPENID2_NS', 'SREG_URI', + 'IDENTIFIER_SELECT' +] + +import copy +import warnings +import urllib.request +import urllib.error + +from openid import oidutil +from openid import kvform +try: + ElementTree = oidutil.importElementTree() +except ImportError: + # No elementtree found, so give up, but don't fail to import, + # since we have fallbacks. + ElementTree = None + +# This doesn't REALLY belong here, but where is better? +IDENTIFIER_SELECT = 'http://specs.openid.net/auth/2.0/identifier_select' + +# URI for Simple Registration extension, the only commonly deployed +# OpenID 1.x extension, and so a special case +SREG_URI = 'http://openid.net/sreg/1.0' + +# The OpenID 1.X namespace URI +OPENID1_NS = 'http://openid.net/signon/1.0' +THE_OTHER_OPENID1_NS = 'http://openid.net/signon/1.1' + +OPENID1_NAMESPACES = OPENID1_NS, THE_OTHER_OPENID1_NS + +# The OpenID 2.0 namespace URI +OPENID2_NS = 'http://specs.openid.net/auth/2.0' + +# The namespace consisting of pairs with keys that are prefixed with +# "openid." but not in another namespace. +NULL_NAMESPACE = oidutil.Symbol('Null namespace') + +# The null namespace, when it is an allowed OpenID namespace +OPENID_NS = oidutil.Symbol('OpenID namespace') + +# The top-level namespace, excluding all pairs with keys that start +# with "openid." +BARE_NS = oidutil.Symbol('Bare namespace') + +# Limit, in bytes, of identity provider and return_to URLs, including +# response payload. See OpenID 1.1 specification, Appendix D. +OPENID1_URL_LIMIT = 2047 + +# All OpenID protocol fields. Used to check namespace aliases. +OPENID_PROTOCOL_FIELDS = [ + 'ns', + 'mode', + 'error', + 'return_to', + 'contact', + 'reference', + 'signed', + 'assoc_type', + 'session_type', + 'dh_modulus', + 'dh_gen', + 'dh_consumer_public', + 'claimed_id', + 'identity', + 'realm', + 'invalidate_handle', + 'op_endpoint', + 'response_nonce', + 'sig', + 'assoc_handle', + 'trust_root', + 'openid', +] + + +class UndefinedOpenIDNamespace(ValueError): + """Raised if the generic OpenID namespace is accessed when there + is no OpenID namespace set for this message.""" + + +class InvalidOpenIDNamespace(ValueError): + """Raised if openid.ns is not a recognized value. + + For recognized values, see L{Message.allowed_openid_namespaces} + """ + + def __str__(self): + s = "Invalid OpenID Namespace" + if self.args: + s += " %r" % (self.args[0], ) + return s + + +# Sentinel used for Message implementation to indicate that getArg +# should raise an exception instead of returning a default. +no_default = object() + +# Global namespace / alias registration map. See +# registerNamespaceAlias. +registered_aliases = {} + + +class NamespaceAliasRegistrationError(Exception): + """ + Raised when an alias or namespace URI has already been registered. + """ + pass + + +def registerNamespaceAlias(namespace_uri, alias): + """ + Registers a (namespace URI, alias) mapping in a global namespace + alias map. Raises NamespaceAliasRegistrationError if either the + namespace URI or alias has already been registered with a + different value. This function is required if you want to use a + namespace with an OpenID 1 message. + """ + global registered_aliases + + if registered_aliases.get(alias) == namespace_uri: + return + + if namespace_uri in list(registered_aliases.values()): + raise NamespaceAliasRegistrationError( + 'Namespace uri %r already registered' % (namespace_uri, )) + + if alias in registered_aliases: + raise NamespaceAliasRegistrationError('Alias %r already registered' % + (alias, )) + + registered_aliases[alias] = namespace_uri + + +class Message(object): + """ + In the implementation of this object, None represents the global + namespace as well as a namespace with no key. + + @cvar namespaces: A dictionary specifying specific + namespace-URI to alias mappings that should be used when + generating namespace aliases. + + @ivar ns_args: two-level dictionary of the values in this message, + grouped by namespace URI. The first level is the namespace + URI. + """ + + allowed_openid_namespaces = [OPENID1_NS, THE_OTHER_OPENID1_NS, OPENID2_NS] + + def __init__(self, openid_namespace=None): + """Create an empty Message. + + @raises InvalidOpenIDNamespace: if openid_namespace is not in + L{Message.allowed_openid_namespaces} + """ + self.args = {} + self.namespaces = NamespaceMap() + if openid_namespace is None: + self._openid_ns_uri = None + else: + implicit = openid_namespace in OPENID1_NAMESPACES + self.setOpenIDNamespace(openid_namespace, implicit) + + @classmethod + def fromPostArgs(cls, args): + """Construct a Message containing a set of POST arguments. + + """ + self = cls() + + # Partition into "openid." args and bare args + openid_args = {} + for key, value in args.items(): + if isinstance(value, list): + raise TypeError("query dict must have one value for each key, " + "not lists of values. Query is %r" % (args, )) + + try: + prefix, rest = key.split('.', 1) + except ValueError: + prefix = None + + if prefix != 'openid': + self.args[(BARE_NS, key)] = value + else: + openid_args[rest] = value + + self._fromOpenIDArgs(openid_args) + + return self + + @classmethod + def fromOpenIDArgs(cls, openid_args): + """Construct a Message from a parsed KVForm message. + + @raises InvalidOpenIDNamespace: if openid.ns is not in + L{Message.allowed_openid_namespaces} + """ + self = cls() + self._fromOpenIDArgs(openid_args) + return self + + def _fromOpenIDArgs(self, openid_args): + ns_args = [] + + # Resolve namespaces + for rest, value in openid_args.items(): + try: + ns_alias, ns_key = rest.split('.', 1) + except ValueError: + ns_alias = NULL_NAMESPACE + ns_key = rest + + if ns_alias == 'ns': + self.namespaces.addAlias(value, ns_key) + elif ns_alias == NULL_NAMESPACE and ns_key == 'ns': + # null namespace + self.setOpenIDNamespace(value, False) + else: + ns_args.append((ns_alias, ns_key, value)) + + # Implicitly set an OpenID namespace definition (OpenID 1) + if not self.getOpenIDNamespace(): + self.setOpenIDNamespace(OPENID1_NS, True) + + # Actually put the pairs into the appropriate namespaces + for (ns_alias, ns_key, value) in ns_args: + ns_uri = self.namespaces.getNamespaceURI(ns_alias) + if ns_uri is None: + # we found a namespaced arg without a namespace URI defined + ns_uri = self._getDefaultNamespace(ns_alias) + if ns_uri is None: + ns_uri = self.getOpenIDNamespace() + ns_key = '%s.%s' % (ns_alias, ns_key) + else: + self.namespaces.addAlias(ns_uri, ns_alias, implicit=True) + + self.setArg(ns_uri, ns_key, value) + + def _getDefaultNamespace(self, mystery_alias): + """OpenID 1 compatibility: look for a default namespace URI to + use for this alias.""" + global registered_aliases + # Only try to map an alias to a default if it's an + # OpenID 1.x message. + if self.isOpenID1(): + return registered_aliases.get(mystery_alias) + else: + return None + + def setOpenIDNamespace(self, openid_ns_uri, implicit): + """Set the OpenID namespace URI used in this message. + + @raises InvalidOpenIDNamespace: if the namespace is not in + L{Message.allowed_openid_namespaces} + """ + if isinstance(openid_ns_uri, bytes): + openid_ns_uri = str(openid_ns_uri, encoding="utf-8") + if openid_ns_uri not in self.allowed_openid_namespaces: + raise InvalidOpenIDNamespace(openid_ns_uri) + + self.namespaces.addAlias(openid_ns_uri, NULL_NAMESPACE, implicit) + self._openid_ns_uri = openid_ns_uri + + def getOpenIDNamespace(self): + return self._openid_ns_uri + + def isOpenID1(self): + return self.getOpenIDNamespace() in OPENID1_NAMESPACES + + def isOpenID2(self): + return self.getOpenIDNamespace() == OPENID2_NS + + def fromKVForm(cls, kvform_string): + """Create a Message from a KVForm string""" + return cls.fromOpenIDArgs(kvform.kvToDict(kvform_string)) + + fromKVForm = classmethod(fromKVForm) + + def copy(self): + return copy.deepcopy(self) + + def toPostArgs(self): + """ + Return all arguments with openid. in front of namespaced arguments. + @return bytes + """ + args = {} + + # Add namespace definitions to the output + for ns_uri, alias in self.namespaces.items(): + if self.namespaces.isImplicit(ns_uri): + continue + if alias == NULL_NAMESPACE: + ns_key = 'openid.ns' + else: + ns_key = 'openid.ns.' + alias + args[ns_key] = oidutil.toUnicode(ns_uri) + + for (ns_uri, ns_key), value in self.args.items(): + key = self.getKey(ns_uri, ns_key) + # Ensure the resulting value is an UTF-8 encoded *bytestring*. + args[key] = oidutil.toUnicode(value) + + return args + + def toArgs(self): + """Return all namespaced arguments, failing if any + non-namespaced arguments exist.""" + # FIXME - undocumented exception + post_args = self.toPostArgs() + kvargs = {} + for k, v in post_args.items(): + if not k.startswith('openid.'): + raise ValueError( + 'This message can only be encoded as a POST, because it ' + 'contains arguments that are not prefixed with "openid."') + else: + kvargs[k[7:]] = v + + return kvargs + + def toFormMarkup(self, + action_url, + form_tag_attrs=None, + submit_text="Continue"): + """Generate HTML form markup that contains the values in this + message, to be HTTP POSTed as x-www-form-urlencoded UTF-8. + + @param action_url: The URL to which the form will be POSTed + @type action_url: str + + @param form_tag_attrs: Dictionary of attributes to be added to + the form tag. 'accept-charset' and 'enctype' have defaults + that can be overridden. If a value is supplied for + 'action' or 'method', it will be replaced. + @type form_tag_attrs: {unicode: unicode} + + @param submit_text: The text that will appear on the submit + button for this form. + @type submit_text: unicode + + @returns: A string containing (X)HTML markup for a form that + encodes the values in this Message object. + @rtype: str + """ + if ElementTree is None: + raise RuntimeError('This function requires ElementTree.') + + assert action_url is not None + + form = ElementTree.Element('form') + + if form_tag_attrs: + for name, attr in form_tag_attrs.items(): + form.attrib[name] = attr + + form.attrib['action'] = oidutil.toUnicode(action_url) + form.attrib['method'] = 'post' + form.attrib['accept-charset'] = 'UTF-8' + form.attrib['enctype'] = 'application/x-www-form-urlencoded' + + for name, value in self.toPostArgs().items(): + attrs = { + 'type': 'hidden', + 'name': oidutil.toUnicode(name), + 'value': oidutil.toUnicode(value) + } + form.append(ElementTree.Element('input', attrs)) + + submit = ElementTree.Element( + 'input', + {'type': 'submit', + 'value': oidutil.toUnicode(submit_text)}) + form.append(submit) + + return str(ElementTree.tostring(form, encoding='utf-8'), + encoding="utf-8") + + def toURL(self, base_url): + """Generate a GET URL with the parameters in this message + attached as query parameters.""" + return oidutil.appendArgs(base_url, self.toPostArgs()) + + def toKVForm(self): + """Generate a KVForm string that contains the parameters in + this message. This will fail if the message contains arguments + outside of the 'openid.' prefix. + """ + return kvform.dictToKV(self.toArgs()) + + def toURLEncoded(self): + """Generate an x-www-urlencoded string""" + args = sorted(self.toPostArgs().items()) + return urllib.parse.urlencode(args) + + def _fixNS(self, namespace): + """Convert an input value into the internally used values of + this object + + @param namespace: The string or constant to convert + @type namespace: str or unicode or BARE_NS or OPENID_NS + """ + if isinstance(namespace, bytes): + namespace = str(namespace, encoding="utf-8") + + if namespace == OPENID_NS: + if self._openid_ns_uri is None: + raise UndefinedOpenIDNamespace('OpenID namespace not set') + else: + namespace = self._openid_ns_uri + + if namespace != BARE_NS and not isinstance(namespace, str): + raise TypeError( + "Namespace must be BARE_NS, OPENID_NS or a string. got %r" % + (namespace, )) + + if namespace != BARE_NS and ':' not in namespace: + fmt = 'OpenID 2.0 namespace identifiers SHOULD be URIs. Got %r' + warnings.warn(fmt % (namespace, ), DeprecationWarning) + + if namespace == 'sreg': + fmt = 'Using %r instead of "sreg" as namespace' + warnings.warn( + fmt % (SREG_URI, ), + DeprecationWarning, ) + return SREG_URI + + return namespace + + def hasKey(self, namespace, ns_key): + namespace = self._fixNS(namespace) + return (namespace, ns_key) in self.args + + def getKey(self, namespace, ns_key): + """Get the key for a particular namespaced argument""" + namespace = self._fixNS(namespace) + if namespace == BARE_NS: + return ns_key + + ns_alias = self.namespaces.getAlias(namespace) + + # No alias is defined, so no key can exist + if ns_alias is None: + return None + + if ns_alias == NULL_NAMESPACE: + tail = ns_key + else: + tail = '%s.%s' % (ns_alias, ns_key) + + return 'openid.' + tail + + def getArg(self, namespace, key, default=None): + """Get a value for a namespaced key. + + @param namespace: The namespace in the message for this key + @type namespace: str + + @param key: The key to get within this namespace + @type key: str + + @param default: The value to use if this key is absent from + this message. Using the special value + openid.message.no_default will result in this method + raising a KeyError instead of returning the default. + + @rtype: str or the type of default + @raises KeyError: if default is no_default + @raises UndefinedOpenIDNamespace: if the message has not yet + had an OpenID namespace set + """ + namespace = self._fixNS(namespace) + args_key = (namespace, key) + try: + return self.args[args_key] + except KeyError: + if default is no_default: + raise KeyError((namespace, key)) + else: + return default + + def getArgs(self, namespace): + """Get the arguments that are defined for this namespace URI + + @returns: mapping from namespaced keys to values + @returntype: dict of {str:bytes} + """ + namespace = self._fixNS(namespace) + args = [] + for ((pair_ns, ns_key), value) in self.args.items(): + if pair_ns == namespace: + if isinstance(ns_key, bytes): + k = str(ns_key, encoding="utf-8") + else: + k = ns_key + if isinstance(value, bytes): + v = str(value, encoding="utf-8") + else: + v = value + args.append((k, v)) + return dict(args) + + def updateArgs(self, namespace, updates): + """Set multiple key/value pairs in one call + + @param updates: The values to set + @type updates: {unicode:unicode} + """ + namespace = self._fixNS(namespace) + for k, v in updates.items(): + self.setArg(namespace, k, v) + + def setArg(self, namespace, key, value): + """Set a single argument in this namespace""" + assert key is not None + assert value is not None + namespace = self._fixNS(namespace) + # try to ensure that internally it's consistent, at least: str -> str + if isinstance(value, bytes): + value = str(value, encoding="utf-8") + self.args[(namespace, key)] = value + if not (namespace is BARE_NS): + self.namespaces.add(namespace) + + def delArg(self, namespace, key): + namespace = self._fixNS(namespace) + del self.args[(namespace, key)] + + def __repr__(self): + return "<%s.%s %r>" % (self.__class__.__module__, + self.__class__.__name__, self.args) + + def __eq__(self, other): + return self.args == other.args + + def __ne__(self, other): + return not (self == other) + + def getAliasedArg(self, aliased_key, default=None): + if aliased_key == 'ns': + return self.getOpenIDNamespace() + + if aliased_key.startswith('ns.'): + uri = self.namespaces.getNamespaceURI(aliased_key[3:]) + if uri is None: + if default == no_default: + raise KeyError + else: + return default + else: + return uri + + try: + alias, key = aliased_key.split('.', 1) + except ValueError: + # need more than x values to unpack + ns = None + else: + ns = self.namespaces.getNamespaceURI(alias) + + if ns is None: + key = aliased_key + ns = self.getOpenIDNamespace() + + return self.getArg(ns, key, default) + + +class NamespaceMap(object): + """Maintains a bijective map between namespace uris and aliases. + """ + + def __init__(self): + self.alias_to_namespace = {} + self.namespace_to_alias = {} + self.implicit_namespaces = [] + + def getAlias(self, namespace_uri): + return self.namespace_to_alias.get(namespace_uri) + + def getNamespaceURI(self, alias): + return self.alias_to_namespace.get(alias) + + def iterNamespaceURIs(self): + """Return an iterator over the namespace URIs""" + return iter(self.namespace_to_alias) + + def iterAliases(self): + """Return an iterator over the aliases""" + return iter(self.alias_to_namespace) + + def items(self): + """Iterate over the mapping + + @returns: iterator of (namespace_uri, alias) + """ + return self.namespace_to_alias.items() + + def addAlias(self, namespace_uri, desired_alias, implicit=False): + """Add an alias from this namespace URI to the desired alias + """ + if isinstance(namespace_uri, bytes): + namespace_uri = str(namespace_uri, encoding="utf-8") + # Check that desired_alias is not an openid protocol field as + # per the spec. + assert desired_alias not in OPENID_PROTOCOL_FIELDS, \ + "%r is not an allowed namespace alias" % (desired_alias,) + + # Check that desired_alias does not contain a period as per + # the spec. + if isinstance(desired_alias, str): + assert '.' not in desired_alias, \ + "%r must not contain a dot" % (desired_alias,) + + # Check that there is not a namespace already defined for + # the desired alias + current_namespace_uri = self.alias_to_namespace.get(desired_alias) + if (current_namespace_uri is not None and + current_namespace_uri != namespace_uri): + + fmt = ('Cannot map %r to alias %r. ' + '%r is already mapped to alias %r') + + msg = fmt % (namespace_uri, desired_alias, current_namespace_uri, + desired_alias) + raise KeyError(msg) + + # Check that there is not already a (different) alias for + # this namespace URI + alias = self.namespace_to_alias.get(namespace_uri) + if alias is not None and alias != desired_alias: + fmt = ('Cannot map %r to alias %r. ' + 'It is already mapped to alias %r') + raise KeyError(fmt % (namespace_uri, desired_alias, alias)) + + assert (desired_alias == NULL_NAMESPACE or + type(desired_alias) in [str, str]), repr(desired_alias) + assert namespace_uri not in self.implicit_namespaces + self.alias_to_namespace[desired_alias] = namespace_uri + self.namespace_to_alias[namespace_uri] = desired_alias + if implicit: + self.implicit_namespaces.append(namespace_uri) + return desired_alias + + def add(self, namespace_uri): + """Add this namespace URI to the mapping, without caring what + alias it ends up with""" + # See if this namespace is already mapped to an alias + alias = self.namespace_to_alias.get(namespace_uri) + if alias is not None: + return alias + + # Fall back to generating a numerical alias + i = 0 + while True: + alias = 'ext' + str(i) + try: + self.addAlias(namespace_uri, alias) + except KeyError: + i += 1 + else: + return alias + + assert False, "Not reached" + + def isDefined(self, namespace_uri): + return namespace_uri in self.namespace_to_alias + + def __contains__(self, namespace_uri): + return self.isDefined(namespace_uri) + + def isImplicit(self, namespace_uri): + return namespace_uri in self.implicit_namespaces diff --git a/flex/lib/python3.6/site-packages/openid/oidutil.py b/flex/lib/python3.6/site-packages/openid/oidutil.py new file mode 100644 index 0000000..754277f --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/oidutil.py @@ -0,0 +1,236 @@ +"""This module contains general utility code that is used throughout +the library. + +For users of this library, the C{L{log}} function is probably the most +interesting. +""" + +__all__ = [ + 'log', 'appendArgs', 'toBase64', 'fromBase64', 'autoSubmitHTML', + 'toUnicode' +] + +import binascii +import logging + +# import urllib.parse as urlparse +from urllib.parse import urlencode + +xxe_safe_elementtree_modules = [ + 'defusedxml.cElementTree', + 'defusedxml.ElementTree', +] + +elementtree_modules = [ + 'xml.etree.cElementTree', + 'xml.etree.ElementTree', + 'cElementTree', + 'elementtree.ElementTree', +] + + +def toUnicode(value): + """Returns the given argument as a unicode object. + + @param value: A UTF-8 encoded string or a unicode (coercable) object + @type message: str or unicode + + @returns: Unicode object representing the input value. + """ + if isinstance(value, bytes): + return value.decode('utf-8') + return str(value) + + +def autoSubmitHTML(form, title='OpenID transaction in progress'): + if isinstance(form, bytes): + form = str(form, encoding="utf-8") + if isinstance(title, bytes): + title = str(title, encoding="utf-8") + html = """ + + + %s + + +%s + + + +""" % (title, form) + return html + + +def importSafeElementTree(module_names=None): + """Find a working ElementTree implementation that is not vulnerable + to XXE, using `defusedxml`. + + >>> XXESafeElementTree = importSafeElementTree() + + @param module_names: The names of modules to try to use as + a safe ElementTree. Defaults to C{L{xxe_safe_elementtree_modules}} + + @returns: An ElementTree module that is not vulnerable to XXE. + """ + if module_names is None: + module_names = xxe_safe_elementtree_modules + try: + return importElementTree(module_names) + except ImportError: + raise ImportError('Unable to find a ElementTree module ' + 'that is not vulnerable to XXE. ' + 'Tried importing %r' % (module_names, )) + + +def importElementTree(module_names=None): + """Find a working ElementTree implementation, trying the standard + places that such a thing might show up. + + >>> ElementTree = importElementTree() + + @param module_names: The names of modules to try to use as + ElementTree. Defaults to C{L{elementtree_modules}} + + @returns: An ElementTree module + """ + if module_names is None: + module_names = elementtree_modules + + for mod_name in module_names: + try: + ElementTree = __import__(mod_name, None, None, ['unused']) + except ImportError: + pass + else: + # Make sure it can actually parse XML + try: + ElementTree.XML('') + except (SystemExit, MemoryError, AssertionError): + raise + except: + logging.exception( + 'Not using ElementTree library %r because it failed to ' + 'parse a trivial document: %s' % mod_name) + else: + return ElementTree + else: + raise ImportError('No ElementTree library found. ' + 'You may need to install one. ' + 'Tried importing %r' % (module_names, )) + + +def log(message, level=0): + """Handle a log message from the OpenID library. + + This is a legacy function which redirects to logging.error. + The logging module should be used instead of this + + @param message: A string containing a debugging message from the + OpenID library + @type message: str + + @param level: The severity of the log message. This parameter is + currently unused, but in the future, the library may indicate + more important information with a higher level value. + @type level: int or None + + @returns: Nothing. + """ + + logging.error("This is a legacy log message, please use the " + "logging module. Message: %s", message) + + +def appendArgs(url, args): + """Append query arguments to a HTTP(s) URL. If the URL already has + query arguemtns, these arguments will be added, and the existing + arguments will be preserved. Duplicate arguments will not be + detected or collapsed (both will appear in the output). + + @param url: The url to which the arguments will be appended + @type url: str + + @param args: The query arguments to add to the URL. If a + dictionary is passed, the items will be sorted before + appending them to the URL. If a sequence of pairs is passed, + the order of the sequence will be preserved. + @type args: A dictionary from string to string, or a sequence of + pairs of strings. + + @returns: The URL with the parameters added + @rtype: str + """ + if hasattr(args, 'items'): + args = sorted(args.items()) + else: + args = list(args) + + if not isinstance(url, str): + url = str(url, encoding="utf-8") + + if not args: + return url + + if '?' in url: + sep = '&' + else: + sep = '?' + + # Map unicode to UTF-8 if present. Do not make any assumptions + # about the encodings of plain bytes (str). + i = 0 + for k, v in args: + if not isinstance(k, bytes): + k = k.encode('utf-8') + + if not isinstance(v, bytes): + v = v.encode('utf-8') + + args[i] = (k, v) + i += 1 + + return '%s%s%s' % (url, sep, urlencode(args)) + + +def toBase64(s): + """Represent string / bytes s as base64, omitting newlines""" + if isinstance(s, str): + s = s.encode("utf-8") + return binascii.b2a_base64(s)[:-1] + + +def fromBase64(s): + if isinstance(s, str): + s = s.encode("utf-8") + try: + return binascii.a2b_base64(s) + except binascii.Error as why: + # Convert to a common exception type + raise ValueError(str(why)) + + +class Symbol(object): + """This class implements an object that compares equal to others + of the same type that have the same name. These are distict from + str or unicode objects. + """ + + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return type(self) is type(other) and self.name == other.name + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash((self.__class__, self.name)) + + def __repr__(self): + return '' % (self.name, ) diff --git a/flex/lib/python3.6/site-packages/openid/server/__init__.py b/flex/lib/python3.6/site-packages/openid/server/__init__.py new file mode 100644 index 0000000..c8fde25 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/server/__init__.py @@ -0,0 +1,6 @@ +""" +This package contains the portions of the library used only when +implementing an OpenID server. See L{openid.server.server}. +""" + +__all__ = ['server', 'trustroot'] diff --git a/flex/lib/python3.6/site-packages/openid/server/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/server/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5da49cbc5eec307127c6fecded08c57fbaf1d427 GIT binary patch literal 376 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�KR1pXeb=N|KE@SBD;B(~SfooyCz4iKO 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 a8zwg80l<}<8Rvv^&Mmn!KeX~VPX8~?9$y6j literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/odnoklassniki.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/odnoklassniki.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c292d223dd8f8187c151420886c096580aec4290 GIT binary patch literal 6718 zcmbVR-E!N;6~-Sy5TqnpcKnm14&o%OSxcn+lf-rv$BLxHN*u`}$w`Yz1_N!Pj) z`KDnTsOOmx6q=@O%F<^ND=_n!#7xgPRP7?3i>!p_lBeUj#3tC}Gs&J{Q>={pBzuib zv(wLHdy1XekSb?8|1j5B80`ga9Ebj%Z>=q~lbvgp8!~I9!QG~3EwoyJUw0Ef3au@- zzUPH3p0QX|k1aR3ZCd#4BuOj2U8}X8Bs)1rm97w-{PSOm_pCKEmBByGTy^mYI9qzZI#QRy!nvGA~*rS!=ok{g<**`1U zYm;7m>iU7l>NL^dr=oHfS4@Q`*)qhhFxgg_Vrxvbb4;^!mb3Frw+)uJ3#`meZ7a5! z3Dqw4#aUug5Z-ipdQ5iN8anTeHCiMEFHDX}uybZ?+p`gMyOyeLpQYM^wT;ae^6xtD z4dhyn!qBTH)o8EE+cnX*Mgt=5cf8I}{BFx1>ykuJR+UBR1os-;i+3E+lA7M*mXBx0 z57T_VUMJDfFNaIVwTLVJ97R{!l@hrtGx>saD2tjhtf^FE+7IeCYES8@Jq`VG7o@Jd zt1zAA50$RcRl6E9p64L3LZ`a!C2byB?bzcM^AhMNwi-NYTH{jV-hLY>Nwp9VOwESR zQVeq&csNsZ*(vOAY##=yQf%pQd8i8*@x{c8Z9ibM?}v4MGnw13jTp~tJ>p)! z!4dV7ji%aql9q?N=u7niRu+>$Bt?@={GC;GlnVZiK{Uf{_4ousL>QPOk82HAd<_L* zfho@wXdBo#l)h1Vxt@+X$m>w<d?I2nHkf(o46;beSLXj(^`12 z(m6v4iD^xXZO&mo5Aj@4(vn-RdvV+cvflJy1S~B+c=*Zvl|^TD;mhUD^uTJ4%puJQ zn}M>YX_tH669)w6#9p0yNt%znB=N)T^wgM5Iv8IuILVP;M3HJP-;Ps#??Bu&hCQSz z=CbwvL}shK1*)X^1u(MWO&VJe0HYz7@fZ_ECB>9W(1;;-el-UEzCMN{Rx1jhKz~u7 zK1uZ{6qSk8a0F@{Cp8_X8L@WYpU_Dpd2emWp4wPkd$8`#$(#h4O(PG)UlqZ3*C z6w5IkP7RF147j;`<`lum!0z@djSR-~@h$-&W&Scs+|#);oW zu?-^r_Dz_Y+*7*p_lO)WNP9X5;dYgV@;7Clwj`%h-s6P34k676{5TOB0PvOS94F|d zxkql$_9`-`wHzVzg*i{%>ru0H=R^7?ty!0J`Itx`r0UTps`EiqcZ2v2DudCmQjPib zB+dDW*NiK9VM3`!cown|UrzIVp5pJ(I(|J^%wS={YT&2_oB4p&Bz%l&)4Gi-V|AF$fYpi9!Rl|nB$k&beSoeKrWsQDfANqJxlDHevZu21s3|PhkPek| zQcojizN>asW(bF0*wwlk9KZQo?d8xjC!B!Z%ZpJ(H~}MpL+Iu(qJ(h3=;qnPq24Pb z`ECK@CifKn6O+3IHYHH}Q&#I0#P~A1aH#am$LG7|lJsr4S40R=#4LqwabDX0@oPAj zu6Yj6?6rBRtLz&1Qh}X)t{lp;6q*TUVdoB&?_|to?iR^H{z~Tc`f8@0q4yWsw9jPp z^>AZ(U2vFle`RB{GeLM29%v

    =|%TrZsrqU~*uWzmAqUdS^QmA?h?*%|^j_=bk> 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id_connect.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id_connect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38dc0d5b46c15884de3d0fbed212f5357649de3f GIT binary patch literal 6866 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openshift.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openshift.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8fada90d232995b7d961941addeb16f2749648b0 GIT binary patch literal 1761 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orcid.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orcid.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9d4f3895be035ce4a471f26d7d43f91ad63aa07 GIT binary patch literal 2587 zcmah~Uvt|;5ZCFSW!X;L66lnc!Tg;u;3$;9&`t+x5*IR*I1{^2G=N6+of9Rpk#!UG@R9{R#l-+AJR-LspdDGaB%y}iA?)$Q)@Za=8i+)KYx|FNrS ze`=?i3H%M{VigG0e2o&S$7H1YI?+DYs6ovK8a0`IWcUWmEo#HuW(LenTIy*{C%^A@ zx7Rk-8{PIK+r83gkH>K|2(u_n8h69NK1=9(4Vn%_Bh0S34QRVrHWt@BZ+w{Trb$br zgD8w!A?#k}ETq{kL-pk-^;qJ?Va7z}%`FKp<%5VW?`EUeeL~>SO(NaTLcwN#AAsbz zp>x=T4}@tx0S9$Te1qz6c-^oOVsimYVQ!gOiR8?U!Y6$#T~=1(3c)+LqcnG z;X&E2(nVT-p!qd=j-H2c;h|<~{vw3nf~=?r%#M)1@k}_-EtX_wA|fs2NB-K=Sj^l? z`Hq+34i8Dnqnzch{Fh3WLTpc#$CJCuCsq!wEU$P!P4k+vG(P~7=2o}YmwH5{%kGaO z&O{I;vNY#qjk6)hb^~R5bD*pEX_^7!(=L`^l4%(!v?F~_FUS#@>V*z*Ae&7?T4FM0 zoTIj^pclb7YivL&@xCWTx6xf?ZDGfDH$<9hjAi zGJg!|C?648-Zx>TOv@KFTvfKL`I-xBf84)bl-ITIU!S^#3mO&Fpw)eyKT`AgjxFup zqi?jNe#TNoc~Lteg;nT-31D3Q3c?DP~#%t@S+Adf_FYbozgEx51zTq+;{mMqD?KL8PnuBAx^uU4ynIA(c0JgF((9^eL&SK5VA0U+WFuxYbt`6a=! zEJ}8K2V;U-vUa=I*$&p*{dRDB`U~vqGIL3RE@I_QY*sC&t9tylh4q*J`vx%uAqHSZrbY4}O15i3{c*}1< zS6iQ}-Ud`}6RI~pKejx7D%`y=p?A~!c}*3=mY7eQPs`|BaH>|Gt+;RfpX#jg9;;#H zectN40&g};{9SN^zlWrSM7<#%a(Mq$GETxb<5cjj!0PAF1xD~`@Hk2V?|d0X-%@Ai dSf^D4)#|?)lg{-Sj6T8VQXm3NQZ?$I{sT^zrr-bo literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/patreon.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/patreon.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..211fe9e3bcc4472551b7aece7fc34e3e8ba586ec GIT binary patch literal 1763 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¥zSN2DH_BBd+tga3YRX~FaM>$vf!hxU*XO@MbW>qqx2FNw>=A5TQDQa_BSipm=EqORA9KmH0N3gr0 zOxAXP8sY&6{=M=N5nRxedlyuAvp8g2NN*;*oyF5gocDIOS+a#f+}n( z=CY_R-mAZEMu}ANMP2t(`F#ScRBclQfiiq{uMBI&Bi9rvZH?Sk;SfC!gcJ=zmNCmh z4s?@{^Ni{t0p>~`^OdSP11B~aR_XGk1qV44oMPy&=Po6@8V_(kJbO4fN)nNNq3k*9v#c_k#x>?zH@Y|+ihR{n@4|og#1HJ zH3!CDKv#<(IEe_S+{)z&ncK0P~(3YD}g6Ix0;FbgE;4zV0aP}QWjDr@aJW- zCnNPE3$J$j9aZGUd9b#=5m?3>3Wzf| ztpNmANEJB78LG%VN`z3K_UQORlLQ#| zg=42)!jAAG{q90nxSG?7a(acN2&vVVxRE>$BCuo^UdER~l|>Hm!`Vh$z#l!_9OKHk z&~cinOE9bI_|oy!DI-qn^%F!KTMIfS(HeAxY^ZFog7zB&T8#k?u7zl%TwVkf)0!M7 zkvbB+pSG+me$_H&5aakV{64nn(kYuxG!irb8=jB7hUPmMR0Nnv4=KEXo6oER903=R ziQIl_apwv-z;9>A2-DE^e*rnv`kA0tReSATf4y1v0S*6y$N*`tTw8(#rVT5Rt}XOvJ1a)-e>GrRI=c7Dz6`)|NvPTI z^;7_MEDq&>+Yko34gtVk!IkjTf|6nFxRq1ErO=cNgdf5H#iA}0USGbBD7*~Oq;P`!AEfMFw
    ^aIbd7=zmt40JZ((v(;&j62d$J(@F-11EOJv0nfu$s!a0 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/podio.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/podio.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f49cd7de4f229d7ecbf30a7dce29db0a68e9321 GIT binary patch literal 1608 zcmZ`(&2Aev5a#|#>$T+ANo-gBLDj#5fLiM|ZV?AT5!E)@psf_x3Q#Nx5St}sZM@oD zlT=zS;BYt`a_9SI$UDtu@XjAB`qL%kFS62L zP98v)AA?{dBFtiTW=-tKw#YYxIm~@Qn9J?wPUJA}jC5*M7Cb6gTC@kfsTzOYeiA1a zJZGP_S&_(gtnLMEXk(>Hc`pphD>W|iJy|4aobAQ1qbqpK)R?1tH!VV*hgqz+RN>+z z8J2i{cdRB^@V5nlbu1lx9ZNp{V*-|C^FVt5T^@trB(ea6%`6Dkj$GzM9&@7_^P)Pt z$!@XRZ2R1ee74RSFG#e;nrs8cfZbr5Fs`#L0Jx*q4Ziu$st;GdwlRL5t2BvKTI4UG zN1h3|`d|s##Ge$Cu)rN5ibA9nuipDV7gYd>+I=)Q8|p@!BwR{5w*_XP>Z;^A;J=iq z;F6}ft}k@m6nq4>@!UUgfCy|j+R%@n%La%U@X{j8ZV^D~^tA)~w4JiwVWwTlvyrYd zuHrP4+J^(nB9~m?P9y%Q!cwHqAXVYBScIb@V_e8^B*HrvpXK86;BcQ+tU8njcP|bO zCx>4i>_a=x!(mva<=&HNnz6kv@$7_)t^gzAV z0@h~E%w_f8u=xFcR4+v_N;9tAJf85XZi4!RX&04fK22dd0UkS^_R)-<+T&=_{%D$I z#t@8BAr&?HS{5(VjumYzf#90#c%lh`iyJ6*QM`qsg<_c0J_`(f#l%}fn@EH6iE8j23oxStJ;stv}>+I7waF< zhf>9ADs{8BxCZ9(RQ1LC0hhZzh9?1v39$Q!;HfO~jyp$<|JQCT&ZVwh{EXx4pw~!|x6Kwm>0MW>;CYO+MB4k(=zY?^V< z8MaEoPPluSgK(F literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/professionali.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/professionali.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6984ec727edd873c31a6bd96cca38d2804df6a61 GIT binary patch literal 2234 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 H8RYXX{Iy8d literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/quizlet.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/quizlet.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dea14bc7b17d513942948d66ec56ab4222e0cc33 GIT binary patch literal 1076 zcmZWn&2G~`5cXew>LzWW-1w0tI1q_sLmN<2A%sZ-L@iB7(gS;0x!z4;>e$Zix}sKZ z?aLtXBs_wzoOp$v*mV+=ma%p{`et@_zHer?Ts9y6!tkf2D8H2zGlc(6EcQ%nSb+*w zvF58&4Qi_LQNcPkt`%$$?MjC_&JC1GKKX8biM_-p-0IpfpX^uzZyZ#;z&dr@GZNqz z77krzIeg!=giSb)*ggQ!1)qdLm4&Y7_*F-otWn}%J|Qx_=7oR+z;`%d94u~PU^aiL zHsMp>{G*B@D{5-KcNm$!?F!_d3378X`@$g#R0SaotD?^~n|S?NfhAlL6w7IO!EpXavT>U*EG)`O3o#=Uc{q8rWa&g+ZCSJ%PQgyC z4wA>q=xRhN5K=E8$tn(zT52%j zkJ2I~ED8fgekimg;y0HdqTab^7&vz*7==D2lz|ZiyJs&0I%_oRIKfGiHTKRL&1v&> zqb_V5fFX#ysCpWEKCZqBT)M~WGq5riRX3z$0iewPZychFbW7BRGWi#rwxt4@EPC=} zL2cf@+TGM+(c&UPk}5)}iO@8}u`m4+LbKTM7m)%&Vm1YANC4(L2k5Z<6&CDX2c7hf z;63|f_^I3PKzrEf9%s;&-Dqitvh7T6hFa490=Bg-p8x;= literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/rdio.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/rdio.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cfcd4802902118ba0e4c600ab0bd570397e95fd4 GIT binary patch literal 2702 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/salesforce.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/salesforce.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a709a233245bb15dd1e3b10ab3c7802e766d3391 GIT binary patch literal 2131 zcmb7F-EP}96c+Vk%Z{BoOPXxmu(|u`YG97iBrTeu*y5({+O3NN+bs&+2#iQON@U3) z<*X4>>>_=Py}({&kHFhq^%ZipLrVE+)(wpWemtbkA*u6yI$Eh#%b)&5+@+$X{jFWN zOt9CX@dp4zBPi5H$VEsaIx#|hWVi;>e$t3ZtT!65sBvt#CX5TD2;(9(VQdk*r!`7% zVFj&$L}|i88u>AyZ&5Z=euz2kv?j^UvM-%ZuL2nB(D=^)RC5v0T%91-Ai8T3xv=RL zh~*YZ!L^~4NYO2mkH{>UBiG6N&~m3pl}x|U+zPoyYS3573>;-a)Ot9id=N7~-#5K| zK2y(*Qpa#08YO{`lOT@j+t}Zu5lLtGS!^?wKFjI7og^9a?;R(Mhe5P>?y%^`BZpIV z5crfknYSZjViu${UA|IW62t!K%9UamJ%A`-ZgqP76I2&^Ktv^oc!DFJdccGz1)NVP z@o*waJQzkenJ_AO@Ykc+o-s;+D?cVAVT*Jue zJL6#7+@1s>Y2J-|c0XC#cX;dvIBZJpn|{n_KH(`lmlk&}C}(^qYH&PHzPy(w>0>w+ zF9T?XjVj27JH2_Hu}rs%m!rpiG`~rs)+GZ?fjRKpK9yb7liP?~g!rpJPcc)Z#0-}^` zfSeU`87xTmQkFhA(3dde6#z}Ip*f_ZGMYx|jelkDEMT*tt0OA_y9A*Mz?K{+09$5M z0oZbV1&wK8drF3$C(52TipeCTU{^eEe}a_&VN~=y68m77O3E{>=lv(0?o+qbf7;pf zo_C*#T5DsY-RpV%&M)nJrrUl2gYy}8_8p&J+pxwXcdpxh+->)sO-{DzlzDA;~KghKGgQS>C LfmHO0QM>gIwwy^| literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/saml.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/saml.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0f37d04a221d8e67033ab2707bbf0669d009ab3 GIT binary patch literal 11756 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shimmering.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shimmering.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cf254792e052960cf670a4e4b94b1377389ebe7 GIT binary patch literal 1422 zcma)5&2HQ_5SFN))hcVm`9Th8Q7CA8*n>@{-UM+`1aWMmF_L-({~A277c;+1cn-b;1;prD#UCEiHOS_;xU)_%p)D>AW_-qTG2v3JFHg*xi0g?cu)rpukXg-fjX&F|5^yWl?$5)~I58zKtpme{u>(Wx zf|%PUQ1uJ@95osO&1p0TG`G=Q&^+dy*mE>@F3`C@_vXIQ=PvVqH>h;#uMc@Om1Q(l zTtN11D(NuIq^jbQ7F>68`mgHo0*>pz_?o@idv_kaJMVk43km8Vo#wf* zOOv=XFOUIaBP9@$vyZ`x{;_#>d|5J+mbwkXLZRLE9zu58>wC*u zwoF_9um1vP4?>vVHH4w=f-pa8Zr`yogQ^9X8}!`Y5TNJPU%n8OsY~!^MO)+`uUVFG zy<&cppieHb#^#AxnFh{jg-e?q$htKy~%dJl=Kkb3XuDk*H$vZ|Eq^}6`8tH&A^j2mSz$+FV1=pM1gT5;b hn#2=^XwrP^X5&hzCUR4q;D`2Ik4<1@=-}Et=OctJh+_Z% literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shopify.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shopify.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d46fb87738cb45344ccd2bd576be8e24252b23f1 GIT binary patch literal 3217 zcmZuzU31&U6~zJ|2tp*S$e*p-6rDDmvb99Vc2Ya_q)~0TKkXY!Q%0#D!jSNaq1+Nb=5JoVfKWXW#8*^A|3KhHV$-n~5-1lRw=qs@V3 z{ntA8xTt@EqMpETE3!D_cFrPZ-gabjhr78mb0e2oyu-b`GxH)3b%%HJ?#z$;Sug4_ z>n|4fdGDpgd%{~eQNZmzYZNTYVDGRfv&nJz>Gnb&-U^>3>5-W88zC=J6(;(lAjEU1 zb*VlYkIQ3ySj;z7k!DH0nP9}06baXdg1T>I#aPV8d7_2V<8~%BR?P}qhkBL=-{TzC z?)NtAh}9d1$(-jRrukzbWg*q~toaoDIZ>k7)X1&_+TEih%LT9dRDY1nQ-Oae&3F0` zTJ)$x7-0b=3kb2u;dbP5C+hG^{4&3C;6xtxqOS0Hm-{cR6^nYj$Agy^aJl-8HDSEZ z2QPck8XxitXzhyuU+3?bop<~ZJ!nB}w!3`qEldd~G1pm|=&YEZ zLR@!SU}~2tb@warOl4ZsUGbvKq)?Aif`T48R|n+*in;}(tqMw5+3JR7Q=8i>_Jwo8 zPV5s0|8d%iRd(g9+{%hkrY?5r?AHCbT`^9uKo9ebI(7FjQHj>FcCuMnuT8Q!k7os6 zi@l0`QrnZV_RB;c&XXDXaiLO~m2|p)Tk;Be{!ooenSBW*jK56e zcv9qCNHw0w@$IAc=kocTdpG%#FYl>4caQGeo89~H&P_ZA^YQ+;%*xGYi!A4xAIwvE zSKoX-KBu?Mw2-20h)wcYjkN1kYhRTc|X48WKQZqRf|t64C(X zpVlO8thScJt=VxqZYwQjz}ZpRi@K-93mxm@Qq<0Y&~;aeGEY)5vgHO1^r4cnm}6SP zyfIhl!#aA$v`EZ_hS#VNt;)EEbBQhAee?Wpciv6u==9L`sD1%O?ZaqGv&vf8(^g`Y zJ8`SdzwFBUj-7NNHD^kK>r6e!lTEuTIM<0^`BT5@a{DLruUGY`e&5n~uLwM6`Q)k4 z3po#yG!;t4x7Ia^9I=d`T0gc z-BF7YQO3tX>fmgMB#qSFJuD3c*Dg&Xe@<;3rJ?I+AjR_qVp_8?bjVUY3K|Yvg1L4U z8JE8>)3k`!ZXypT zvC@bKh8cYCsI-8t`%-WOyA*Pj*=SgAkQgy62K#X{($FJ+jk917bW%n=>SV@8Yi81x z7$0?t308=f6(O1_VN z@^-TmbC^5Suu0}E5A-SM(|GEwF2Pu=&o)4!&n_VfU1Kh@+49Ppo_IRwHJv0?Qo*Mv z>J<#Yrvm3fj%S8A+tBNtbdK*Df)M{fmWR4aj#$A#xpPYPv{TU+OJr4EpDAc(IK0H$l8+r#+&1Y&~w+ut=&@YaK3 zR^|d1fG{r(vUym_f?WDWI9n(kLI>pL+-aHvO270-MY0Oc{ZQFEef*JCVwS;flBJ*MqeSVNfBE9k&Hv;Ph{v^^`MRCxSQsuF`dpoVscEK zC_9_{L)R(wQy3oz;0FwB=Gm^@_kDl3<`4Q8O~28ad_-d?1Iz0$b!VZG(9Ob8rywRI qrD#fMt-h7fcB66%Am_wsxG+})|9*4J`r*?mZJ-T5DuIz%M^ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/sketchfab.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/sketchfab.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1aeaa4f96313464a3ace8fca9f0a4fb75b8d052 GIT binary patch literal 1599 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 KShK;RHS-^Fi1fw) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stripe.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stripe.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43388b54d3bc527aa94c811b40b4aad8f1a0cd97 GIT binary patch literal 1991 zcmah~?@k*>5Wjym=4t~0LP|+0w~ZRP3LI@{6{4z&18$4b+L7%@?W)r1xLxp>^WCw# zrs4vh;4$(7eU(1Ke(k5eLO*rp?t*|u>Y|$+&(7}7Z{|14lSac``;(Dz)iC}tt_=tD z=kUpgAecc6W-==>Cl;|x;|GJ;%(*m}!>tRO*sL-zTGcFe2TFt~Z}!?#HTtT#?*~Ub zW{;aJ31rh(&)g=Aky5FA=6UI<8YS_%OoGsl)_vHqA-K=fh@<;Pn0P$)B42T-yz(UJ z$^3$ik(xyA-zJ3CGPV1{m%O+#0L#^w0qr?_l7Qd_F#(3fObFK^4zo#xIi$)eWQJ8q zjm?l*w!-eQ57{dFh^-w~iOU*n?$RJ1u={L*-MKVLoh`B@ur$~*y9@Iin+MsXj?gs&vk`~UjC_LelgmLZW2C7a| zt{Z|M2rfs3Q(!?#ao}vjw*#MC0C8rHfhre37G}S-AUjU>sL$0@#7#ulWL)`SB%22! znKTClUEum;8Y_XE(?-kEHFS;r3D*@q0e2{}eaIE8IFd&Px(1P?Ni6vvhPVTp{(0l2 zB0K@EcqhK_4w8s*A-x0PJvsU^7ROuLn=E75w%mGpw6#6i{$^_v#$oIYy);bM_ordR z*1wJe@lh@%+-ByP1;5QPO8dgNFkyC>wF;g1*1KX=Ah;s)gPq>14jpt}wfpU1 zudkhP62@c}UGvS_VZQQPj+lo-bVVwtf>=SlQQlLWlozTHi@3Us(L#`uG~$ZW)EE9l zehe$p1!0(V%Q7u<&dff!k-zut3h*JoflRz&<9+yXl|u6zI-gl%NF#8wWM(&8@Y6IZ zJJTD>B+lFy+!w&pH=E}`w`vbI7oPH5h4K0R{smH^t{0ggw?~Xn4L2d!Q|~_cLiD?B%H)u6xb4FL2|2h zd~qms_2_q;Z%4O$qqECGApZ&qV~jUT@IHUIA2HG9N$%9R;P;r|4f9W;>Y-Y$LF`IX#hzaIYB z>+g~FaIe>;ulqldh4#))XE30{-pfv5S-N5QxdS_P$y~p)yVvjR4C!Fl9(IV^`DNH| z)7|#4EtW7oB$Q~P_zVSR`I-N9l7#7q3 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/suse.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/suse.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..540e3c1a741dab490aa4851abaa6999ceb209076 GIT binary patch literal 872 zcmY*XOK%e~5cWRWhR|~7aqs~Sk;uBJDQYT&iV&bmsFfOe*^9MZyNQ*Jy|KNi5^;k+ z!(ZXR57;Xw{sJdvHV@K~Jf5A0=bLZ#=-{CJ^ruLF1%&)2TM~nP2FE=IB}hs{Ai^S; zglQO%Peeo_{!T@H0EvXq(*Dkmvg=Yrm3`B-Z4k_wYMn)%Z<1sQo? zx>Lr=xgQzbbtYF?(akD9O03L;A4yD4lwne{BJ-Z1SiR#$N;4pvZL`UZKu8dp;(AT-}O$$z5b;C zx_1aN)a(l@RoT6%R3W;@I=3hOaLTrJ+RcrXD+h1v`@1aX^~0g`9LeBH>?3i=AlX?w zxxBZPWGn5Wvsi?lFZph-!&~UqG v1aT0Djqjop*L$49&&YYYhf4r_7O^$qt!Ek*@rk-KLtvHLuxpjneQ7?4>vYC_dWv4TtYsPYt6&; z6C5T)0R(7DGRi0=zYuW1{XoE#^u5a*@TR1<@-IcR9<4i1g^`nCS)A;~sJUzouAbm9 z0~C@l3IvZ*=7PgKaM=oY%!d`W3O);96@m|ht-%^}aPGi5hUnIv*>Li7*f_85&xAS> zDr~+&f$?NsB(W$Gm4}&(Peh)WjQK@l#x~oTmqu!yK<)iDna-+jPl{q`eu^Sx9rRn9 zeyp;{ek+|+vijzKZ3?xJd2E5%X8GE{F~MPWQM|izPTnGKmM!qkDXjeMf&Z5?Mbm;V zI{G_GY=_k;dDF;VP~AP0MXB@9<{5%0M7Y%IECC5u$;wz=PgSe3kUG-R6xFQ@2=;jY zSQfSS)~LL4cl$d%ul6&EsDSx%m5D;u^sMfRIF`on1`C$YUkWWUGwwMo!1LT5uRE_r zTFQ~uN~0Ig$aJm)L|A)5ADh}={9)&Rh~7ly=Rc#RPR`JObS88(S1CwsqPdP9F22w8 z>0oaMDya6%;L&2Rm+k#H*ui$3N3&>|EVqx!B!%rC@>oAAc1|Os;zXp|wjA5B((*#V zoAr^1u>xSC3)IVZ^;%P4OI+91VG9Lu9O_f2>0M`&R=2ONQUB8XvW~W%_!h`M4r2iU zJ*QYl=RHj*UPHHfaHB@D5J_snxmH;?Ywsj-s>-~m1M58(nPgjShVAvslX!ttyU1_P z(d!tZ4zPMlm7}?h*os?rW6KtyO|lJi2eqcTb53>d8j%0mjeF~Lz`2didEMbWQ&6UM z-R1nW6zRpzD(9eLRC`UL{=qZ;eDpi}dbqzonojv_@_aPrFGsUqC(qbd!&f*T9f4qvdX8+NGCmU4aj62(nC+A?cjN>iDDdRN^Ey3Ovmhd+bnJ!%WH(OqMu@z+oj P{Md;d&Q zcC?Uz0?xH>&==^7^w3AxYfpWJo;u_z!lEeYf^%M)BI8&Lc4rFPGnwq&*%i zci^OZ78l)?$v6mt|&S+)!TAa2`rUSNMw|yZ>vY! zwVVq`TINvv&CN}(rtgJW5@aesz}eLTO2#q+?20enYpW$`qp_aHu!=_@R^|mz;Lg0YR>p<7vfyOh!TD$V*1mOaU9GY2OzN!cm9uiG z`&&hd2NNb2JZ&!o<88_$iDJ>7@hoZA(Ia7)EmDaZIpZQrOK-M_jh##f1Oo!r4 zh{t^pzZ?7mSpNAX$a!=P>%_p3DIkLAHtP~&(v%s5*su-^YKxASGWyUBkI-7e%MlrSvcbj%NJ z&$RSYd{)D=B53cw&YSisEVrhy03qTJP`{B!OB$gAF&qg`bPE zae1Rw-H=MC>dU5XDutXrRW+z$469)l`|qN#u{`N)mmbzkz_|1=PBOZP8SGmaUoD6x z9Gtc=hQANS+=ZZMZ!-MpYli~3cTt@iE!DR4qI2}'Nh%BJ+=xP(-T-EQ1O6t>6SjpO_@X;c0sR)7RXO`OnxnhGJ7bSY6`6LvSKjF2qHGn+W^&t_&E zH72>*-tq>00bYei@GVzJJONjnGu|#sRG876IUb*xIp;gynfpsi?%TgH`e(y1em7>C z4f>Zb#55io%wqO)gV~&nEo!kw-|(7c<_-i;lDxIwF4W0Z>nM!Q zc*gFvSRTn%s2;j4XeUaI&-^4*Tq=KZ zlJu3nVBJa4&aozS z*a}-^w~sA4&*qu?+@LO7V2dy>uqCz(^CG(iLEfnr>KOHX<@>V%w=f2tsW=K%oM$h? zr_I{w$|)olS(x(jqn8*=!ink`HpguJr!f@-bIEeqI1PwdwyH3a0HS z;!& z6Sn?o7KsOH^W2}I@OqRBK3NE~ev`VR%W7rn`2?NU!6ms2!XUPJn>eOp66obSHDH<; z8#gyivu44*1sKz;G4q;SuIbznkdUV_6z?yjZ`g(+XRf$s8rYfDH=b(g+2x!6%37$x z)=1CXW_foOo2g}lZvr-)ky;v*w?IKS z@C_RyB>Mdt6V`YEpm@zLb=n69LAU)~r-I7wJU!U!wFi6qUE0X_1s5KvY$PCc`Lj_h zba{Iwos(RtI6L-C9nwwz*L3{+nG#_TisK@Mw?Tdku1IvuATB8<6;UVnRpwof)K!67 zK*vIK2+oTzLD%7HE(qXd4FYi&_R!|uqu@#B zN4nZR9DK9i+oL+*;IQ{CUF~(gKkW1e!C?PMr>mD%+Pk}*et%{KP#W~w!K3z|EpDM# zcTl{B0xN7rU@`@g6su6afeNH?+AXAd({dIZM_YA0TVTRyLla2g6Bym2k Oqx}K_(!kp0qV+dECDkVY literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tripit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tripit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b3128f0f531e5057c38c741762efe91f4024cbe GIT binary patch literal 1757 zcmaJ?-EQ1O6!zb)z1~epNLi$dxwJu5_ZWYjgb<^V+yJuPxXc>ym2% zHLlDni&(E(kd0RL*&!7r%e<0P<`F7zoN#Z<^3+>o23*L?Ow^XVmdUH?7EM9SZ`F>} zQhZ19opG5Ya$CFOm_dkY-AV9b>Fem;go;pVsWT2XEKZYg?J&xVJc9!lEU~U6Js`hp z>>YUh{aaA5_#ChloMRS@^Mp{wgE0#ppKfLBYZSuhHU zxY#%;4kcm@M%&gY{;tguA?w4tv!S-e;-EJF26rTOF*K4OVRSQ^vWFaA8342qTq9%%>hSY+n;FQ?8U~We1opHR)de@m5 zC(7bnrMEl?ufikjEmwU7t~h7c7!y?)YtH=m%$e`|=A3=DvEhC8C#NSiA%Bsz25a&X zy4nK62_@X%W^7DNY8r%F+>WiOO>KkxK)Ayz?+C95`_`fkuMSAt%@S`opN?YLIzE`| z${g37b~9L#w<(U0&txJ3?!`#yuE4Ugr%M>wE)d)@oBZRNHS@i?z?p@S$^ z(uE*@5V--v-@o}Y8C}Bb_?Ln7$7#%kRQ_1{&(5DF@?v{ui)TFBQQI%hw|AyH-)(O} zJ4^hbKZ|Ca(L9QI=Xny!7kcZ$U(10`m`btSV8#6I+t04^`kBxS(XxndB0c0ujO>%O zcj>PAgvI~h;y}kNFErVQ$-?+olN*y;ppe}pk#S?`Wro8}wMlNx8+&2P-G$9vP|xDE zJ+Ppk*DpkoH3*fvhzOHFR$v)oK4QZW?;k){DB%L?s!-qT(<-Pq4ditpDrj$y;`FRo zl;#2W=k8t{Oh-J}{}e+&Ct3G{OqUPU-HX#Oh}Aw!%7fIwxGHyaq+^l0<9Qq-(zYQH zTHBFz7z!@5RtlSp)7<8P4kRjk7t+I(y30EsgOh52A!fs97>^7aZ1(wmFjudF5LT~( zfiOS00oGU;x9|=6-7Io7`*K+X!7N%YuFG$I#Oz0a88yT*Yn#+XjBiz%P_HOIR_+&6 zX=A-+?y@T-K=NMz8im-1Q8BV7>vRg4(5K&S1}hIPPN45%xL%qzSi24GRe&!?R2O=nOhQY@)6k!=<5%RwCXhb{jJlbK=GRQhKe(m+Q8qs}o z#s!xNsm)O`BE>PQ=uSdqp%Y>rb`vN^#?17?G@Z@Ok*o$<7MzHI`(daUq2`FWk)KYwGylq|8mm#>bPf-?7Kf^Sj_??%9G^7#qa7P+L-=SM@zphMXYVv?ydmGsDv^@xX?SvnnhWOc`{S-lvAdY z%|0)mCq%bYIyz@Wk&+pv^iXwRC+qb?8`qkt3dU{7N`SQSxE8_u!gHg=|9eh<4>cH%tq*o$7D5PTRfxPvvM|w85Hz&;G?n3#R zpPP==E*K9-$ZuiWMe-lpHqEuWia( z>^kcgqvp$v2F}jThO5{Q3t6Tc6qcUvUVL-@;G*BjZn@hWvw2P`nrl`lW^AmDf?!PM zS&xjg6t!V_t$5bkZ0x?2OF|WZvC&wnl9+y|Zn)0?;Ie3gRS+Q@>oz>8nVSC>b>(=r zu{#vtiO=C{#k$`^H&731^XV1VH|s9hc1*kWqx4Ut;Ew1c=rE5tx)!!$#Nzj&2=K=gjW+Mi-Kv;N?B_IjJG;+S(5Nd8&Val zl|ys;mAq6Ar|X4`_I>p+9LaXp3D=J-ro<2%0N=LzGZ8g=p_EiMs%J34l#?;=4Q#N= z@B)VVc*Q3sYNBm)eCGP>BeHrAa?R;=GPhfjAk^($?mJ@IJO)NFiqS39+_}!^X7Q_j zY~#GuZJyh1o|lr=f`Q-5^OKsi>V_}esyur6Jpca1TlEQSq3$~Kg)^6mUU@FS%Dq!P zXzfX2I0yFQZXCDcZqrVl!-EXSzG7`oWw$l|ZCJ`vriYLhf{$`|gn;=r*YF4)UFDS4 YBK+T$Hh#1^{9~6MItGJL&xacP3#--Js{jB1 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitch.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitch.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..46e15b866a1adc8f10c4cfa246074b4af695851f GIT binary patch literal 1307 zcma)6L2uhO6eeX^Q4~8%yJ8qN6fnA749sTMW<^sBMLkDdgLZZiD~Ityup$$ymL-Lx zQ&h_>z3#Ak|3LrBeuAz$?JxASM=9;x9){6)j}-Oz-uu3f@||us`1(&l|7aof7g`%G ztRI5uZ@~#fk-)-9@zS9VMn5Cb67FjxTJ{{d(Pv9 z%*1y+k;l5n&HbPUV{S~L?-NqIGV?qe=sZq%I^b|(s3aF=E^YWQ$%)KJ%8k^9TxHV4 z)CGofvrL1(F|^jlRqzwnvMCwE&WGW^>LHkZ08S!`0fr+mwChk;v}jwn)DvyGAw24f zjZ;iFg--*qDT3FCZiy|?fw?2P0B*bLG}xLtK{wOAs` z!F$}iNQPH1%sE*o4jhr(rnpV4&s9>&vj2a=O%7)(_t|t7(an{XidhC9#TL08c5Z`> z!Sq+)F7OKw@)FmxbBRGl5b})+2-}m&&G=GQZMo!0TA{vM`8KEBR2y)Z1}BNqhSgD< zDZg5R0lDfzjmZh!jaNc)m(fG58^i)jhL&M%J#&AkLM%+FEVXVzE0?QNU3rU_cCN{7 z+-DG4H5Wi|!>!WV1Bb9{0eGn~0aCj7s;W-*bq1u5e>V|dp_N+OJQo;Wevg&CY;RC*Ze;#%n n->)Mkz~7e;K-)Fb%6m~smQLj*C*tP~F|VqwH==A`z}Ar3}gAXZ`S4Pq|0u5D6bOMTSz#)Tg~O_buI z(QS{^$#&yO6rb^oeb8WeEE|z}>^ESXC{@VEK~P+(lRVp$d7MP)W&{x}!6T+lxOQ(P zdBC$EjTDzEn5`rOHQk|gqK2ve9K%7Ixb#1dB%kj2+?twd`UHDX7W0&|7;N)l&otQs zg(HF?Q;T6@GmAKE^%z4w4sl^Fu?q2+OR8*%_{<~A&(Hv~D)VnVQe(@kc7sTr)!7QX zSDqmUkyXH9t*p&pFee(ny#Pu>=gBjb#F0w!>=q)jG)t_s5>`6jcKhM@y<4E7B5B>` z9p}R!*J}iK7UJXg|Jzsa^AW&ERSx&@zYE7PmvZVG#u`sO^jkxxr+fzl!xMbwikB3c zplkdS&ejxG7UkzQyS;AS<5t?CTMUDzC1h5a4^+M!kH!$R)X_b!T z>7aC2q$0T3Dy4B6!C~mH=+8+|h~xtJ5L`qe800DALIwj7JUrXZ#QDzd78|qiuH1Qa zwzE6j{b*+k#&H&eL6H=jPew_~Hb2Z_@kni*2X|Pv8Rvq}4AkiVU|Vr1s&L$On9>D- zu!ntX{NV0Iw-!>Fa^TzA0P88%Wp-^%tcg8w zSml=rAn1jpY?`B2#CR}h?SA6xK@Uta+8#h5vsnnD`WE=;Lc(s=4d z#q0!w4y?S!%=y^{}HK4Y;L?gmE_h_}@xqyR{w6@C+`vNCuY_-~~)JsL_=9Rx;OzoPp4r5d{ea4*d2o(slg`mLW2--EKB$7 zseZo*VbxrLE{eBw+8zjGIe?mr$FD5lHg{}VLetI&=t&+mH%MveQ##DqDCMBnC_NuV zh6C`mL@9J$O2sM!62J3(*lW}McGxECz0P+>oqkBe?pK{d`b{VNvb#_2xA*os{r;ly zUi%1s_6|rpJm?6lNt@QbiF)wsm emZnn~&%d{tYKGF@CsPPN)oe5!!NE0Lv;P9(?hdN} literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/uber.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/uber.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e95230c742f8b40481f0873ee5b6a2c34e24e55 GIT binary patch literal 1611 zcmaJ?UvJws5Eu2wRum^o+q`VqgEn*nJSR9s;2THYlQIg2pB`k@2HJEo#3psLjlog)QpzjApGIc^8+A`<+%HhmZWrFuGYf5^?VpZDRc2O2=Y5Q%+g(vMRAzyhHzt#vyjRmQ|fzh7O*r(Ldk>-7M4U% zfcd?l94Fr22;6F-%KIS{Z2rOsB+Fp}cMMZJ1Huf9AOw>laMi>%wXj2NT%!)I(;9YZ zow{!fyhhik2dhWdX#>`Ex&dKrRywNrJFu->sO+MM>09KNU%m?`$*$sbPx}{T;~-N3 z1&U-mE?N2c|LVyMfU4~CPOl%SxYSUqnkO)Y3uI!9jF|xrp|d8!_pg{F*X)jjDJ6nM zoJl~#7AzV3VQ{7Y_AbbIdM(Swa~X!GH&kuMbjwb9l5*OIjX@!zb5=N8_V! z5BFhQr9nT)<9zqBh!eW|O&akha{ne+$akdZ;&A*?Y{I2>~MGj}Q5{*i6t!7v;>Hng9-zrBQ~&kBwDNRk5P6p~sP6g@J`hmDO}n!4 zEXS@2W+bw-stqze4kf<_FKRj(QQUy+F(JsJxk7#=s@nChYSj*X2Y zGE5KE&?YLkR}fsPoi!VkOLQ6usXRi)87&e9e1njiBGd%{J6jb3uAM$7FWSH0k9sGa z^ETYsMb$VK-h-fEqkw0k|$@4RTwB@f<{{M?2c&$$O4 za+P|%qlof&g{Vs577!&DDg`c-#_};;%iYxHXMks&5cup8f*t+k;;QNN;fg_RU)P`e QY#!?O>L(!-u~EbN2Pq-4djJ3c literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ubuntu.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ubuntu.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91dd3b71fd6fb0903ed6b49a2e04d9024e2c41f2 GIT binary patch literal 780 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/upwork.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/upwork.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8eea9ad0e5773abcf2f5ba34f412037d143004db GIT binary patch literal 1612 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vend.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vend.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..139dd60fefd24eb8bc77973a079feaa5a78c8f3c GIT binary patch literal 1558 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 i1T8spZsir^Do#mFCyrv0tW zH4DZMq07&~FpX$TXGW+`3}Wcoj~X+Xb*wRq8?Q`avQl5G+i6(&g$H}F+ilL0{oC%I zKRnFZ2^G z6Ypdv>Ak@H8~e#5to)-xVs$;QeD6zMd~pb#vsQx9L+G*xhHFHJA`GTOvIeo3NlMHj zHY<@bvx&pXWPv%P!Y;8@_6b`XTjU(8vGd0osj?-u49`V&nXRyk$2zI83sCNr{G6(` z_;h-+DE-TLjeld*2SYF82U*NN2idGk}4!OX8}vEpCaNAGQ7(oCfQrqAj!17qmyf|@+B2ZaD_yK*`ZJ&mGhZ1;MpRWZz#V6> z#!&MT&YAsM|J^(?kF29oW@Kh&WhGXAV*$oadfem5Ohhi`gfmz2+(QHlVjX+(deEKYr18CgaHa4&cAPwcFl#aD5(<`&)zVqgLmEqATu4 zF6?xlG}|58?9<(TtJi5hY2`MHCw>s+nkYlrrwgHfy1*o5$&)0A#<@A>38{ghn5>GIbvw5t4@bEpxSYljyub}b4Gr!k&V%_= z6D#oeH}$3>cn-Spp8LWZ#UUJ*^hUzFbMSQ}4mUP$v6Q8oa^vp7#^z-6+l^b$#*sJh zronW5Zx)1X{hMeg?k2Ymy*YKR4`abk98`_pQRVbyzI2xO$x&T_jN}C{npM$jMpd`; zRm0HnmtIpRSjeaGZr~ZgOo%-r*fp3G1VdtFdZ07wwF!hWSn0B+?w~8DgbA3t_tKp| z*YvB8ssd9`>Y+h2iUoc&ug=HUjohAakR!|OsW1GA6nIO*X*(E;l&YIz8C+r&gDvJK zzB-h`TtsJCRT{7xFp(87nqetCsz$o<9wGtj3=sirNHa{T3tip?lNq9x8HL=YtALB1 zf$%^?ATI_20~wib3_!>@va?cVGkr_TY(S}$E}f#JWQA#BCFvI*2IxKI)D2bPN%>+8 zVxjUSc!;ZLE~BITKNJEfkXybO%iKPAfln2iFDRCO04MP&MBITct6(&14VYs9gB(4* zG>_6*Jfwtokuoqwu#`$*yntggFh<0csq3Bk`P`vOVwC0;N+&U!!DUDDDy4@rUuhL) zWlG^6iPA#nc6*0DYW+r*o4bRby1h1O4%*!gg)4|G_gX*iw)z9At`Fr}YHn}0`u#c2 z%3Gc%E!bp-)OxL*cCWQPp#4E}&?1%AuY+Eb?lcEYaS1b4s3IhDbV9WY+R-{13}3E#LqE literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weixin.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weixin.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cdae117d6599744a16c4f4bc6b218c8644630c2 GIT binary patch literal 5066 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/withings.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/withings.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9cefa4a5f476c91081558d47a6c2ee83e6d692f9 GIT binary patch literal 931 zcmb7D&2AGh5Vm(W=@L?@<%W=|9LXVZtA^4bga{>qs;Ci3DugfAcE?Fp*_9&T^f9()J7=OFY0-5}5C&kd`F5r6>4 zD8UqC^a_Ck?ll6J<1a3Cz#E}vVH^oi_T2zOg$k4_!W;u#$|iP zgILenshCGWI}`~C!cgR?CV?qO$f)4O@hzexU*ueArp276#p7EKZ;VtnL}{2?M@wKsD@w_tmj*7R@2oxl9{2xB*-TQLGvhDU!wpkjA<@TEBUn zv~w#_(^)AMo|wpQW?y=W$CJ{RTxB9PS+|ViQvCW%G8tV3nv<(Ql8K0cOGPG<9L^6? zx#)EFp@5>RI>+-)H|ZXA_6%s7p_Qt1!b9OO&LwAS$w-^1>0c literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/wunderlist.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/wunderlist.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbdb135cb7ee6d699206cd1576176f49998d0c97 GIT binary patch literal 1366 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yammer.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yammer.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbbc4e67aae58700aee8b53657a8101842fe95e6 GIT binary patch literal 1794 zcma)6-EQMV6!yOzC+W5zmWE|fByPw;;?f4RT_LogM6d#-qDdvzLK=BJlf=}q-5F;~ zLvqE6JKlg7;8l2px#enKfh*38owT5=*s_jMSX*n+V|bQl}h9x5$o@vHkZn9T@n zp7!SX^st%H6wkvvN|R=g;3mt1Nt8^Q&w}uRB$$2F#A(QyLH@{XLYwA!#vXZIcAZbt zq|MSW3gUJEkvfzFIG+;X-icC=Bwifkgyr5kly@yR?o9Jp?7mb1LQCcD7Xc&1Q9`h6 z+6L+&n26%509V7RuVW3M=)Q?-xQ=%wx^H0{J1-R9#uehKltq*$sX+V(jJd%`Jmxkgc@V{nI}kWa6L4|B zLyEtD_A(kh2YS8d0rkddj0t7ln0iMSA1Cy(dwhTwcyY|S4==jMv*S;@2hb*oH}bM5 zYd@PuF>Zg7g!Ey4aOvGjVmnMJDGf;K|4m+Y&8rgv0O33+!fyyn0-T~Q_HPs3x#bi} z2k;R9Z0M4S43wq1W@4o+l^@q*y|LDejb;L4)^GX``bu9JE3;f_gVljvf$lbxl~vlS zpxaArX)X1ou{4)9uKjKS+w}nz2jE5$%m}ZH=W&dr>W(AI@1oRq0m-9ffi4fg^ zwS2F4KKlCf?1|qSJvkkq^RsXHZtvuzKOCab={J38@!kISqq83RvN!6{y4Z1Fh|O~- zGy$24wt{kQDm1XAef1J0Jo5j)Vt)b*{4z;(aQG=FE8vu}s)YE%H2=1u>P49#7 z_s;s?o%e^M8_u_~`yTjy0G$bnOLkQ%tb>-xZG@osBjlU%;8eVNNxAgc7LYri6?fxv PQT&*=JBC`*cboqJR6EV! literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yandex.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yandex.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8d5b0f68784881a9fc6f909988487e18b3a9970 GIT binary patch literal 3160 zcmcgu-Ez}L6xK?zEXxiJg#p4(M+N$0Aa)3W5{BuJCV^&3lTKnTs$oW>XxE7xSrWT) zN?hm4%y88=;04-8=_Ba2S3E&yxav78%Q0!V>9n5I%v@6Vjwh3*ER-s*$?Fy}JDfOxTpVp6FMA=JwBVp0niVY2WW2u+!&fB7wBr#7L zmetw~gzd*s%7g7*8pM(9Q7Q@ryB%{oU!}o&#X^Gd{lI5-5ZUQ2L!GV_!yW-`9LOC( zx7h=&hW!j=(G|x8x0_il_gB7G|N5~Q zymwZ6+w=EWMEg^@-)3_!_RsI8Y4V`iJnmG#I2(RdD9cd9DG&of$&8ZJ25LuvQ>g1% ziFLgo1UqYmlU?vfUCVUA!gf}sEQN8AnVg9vjs*Kv;Wb$4-OtU02m4;in)@Daw&Rd8 zE}CuLyt8*Z;x85!=V+hy7sbN;y@kc@;=P4A`0Yf^Rx=5b+3j8s(%HL_&+n&mFPb9u z123G#5Y75AXQK{R)<@Qh<_I;(K~~*isf!qJ19}#XLn{^1zcA_4#)OhD6+j1FQWUWQ zjZ{g=KpCnXbwGyXPzBd$6l%$hR3DH|X-GOor%cr$89;a+DsmhcI0Q|qVH+G->R)@p zBF4cTN6?h;Exd|Y1gMQ*`;Ihu88*)l2_>Kzv)br^0j8 z3BQ0!@`PyrM^J+wuH^Q3@-v-}?#U?xHQ+ z6GUdnkKi&#mN*6~N3hxMg%Sms)eg8w^93saz`R;lvr}VGBRB*{#@0(m*yW$&YG0uE z5(Prz7I4Bqj5y}5q!r!#Tymlh$q}sLDY^jy(8iXOsdLgyLtEwyxP1JsAnV|_qs?z6K$5tx->Ma zdA`qpl%t4yd`D!)-cR^FW^h)KnT>j>hX@x41%()B?70zFb7RM9>-8$10VRGB#U&J% zQCtC$onCs@`gUV;%~@)#ZLGV%%xrpTd3kkf%WZ9Zx0?5yd9UaDRhY4oo!VSoS=(G) zZn;~nrPiwCGM~qVk0K;%lD{?#dgu!F7W#+^DBK{g51@cbUNlmShnl}CM G^vqu_?G4ia literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/zotero.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/zotero.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25887cb49cba883a75fab632352e3aabb1deb4ae GIT binary patch literal 1330 zcmaJ=%}ygn5bo(2|1g*Yl*4MZduS;L$x7^D1&J0Et?&|(phP$U`!bi&FxB7=<7v~~ z17emv!DH+TTvgp&Uwu_uD=Yr1zaae6CFF0iusqcF z@YCxkfP@4bps6#aA$7=)1YGd$33!~|xuFZS6Vk31(hsHKO0s@0H={S~G)^vf2Ks9T zGSMtHyFSA;GG?N8gJ5!FMl$PYnTR;;#CW8ucnoI5ZFpD6fM-D(8?H_8a8*!LFLXy{ zocjMbm|NQ^{r9ov(>D{e%ojy%4?n$$f|Jle926YPmxdm=VGX>n4z;iWb!gm^unA3A z!hH#R#Isy3PvKNI7we1In9WuyQN%{bjE#9RiZh|dAr`?_pxAf-d3}A|o#iamc_8h< z;Ndhc)}M`2{4z)M#h1@U#Yw`oPLRQB+1(=F$4@s=+&bXAB#4L>rXs+SaLi4vGL~zu zSe^-dhY4U4C9ecJy#;(UG{@iY7USSFH-EFN>FM>0?G7-v|O zbCJbqcOF>Q?JI}h|3TVr>1m#xl?~t~7O5_qitCBY&|kw$EAj7#V4}no@*Z5pDmarV zaHWGY6>MF+$<*cc&L$L4?C9;c7u!4Ioo}}{ah+$uAee|r=QI~7biT?%hL2$HLdpc_YY1^9xckptkmaLJff&H3q@HDEKdEIRjB98eze)Bd`E3h feSxm9R?VTZ+*OXJZY!JLO|iVQmDN^wPRsodRf%wz literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/amazon.py b/flex/lib/python3.6/site-packages/social_core/backends/amazon.py new file mode 100644 index 0000000..eb7a347 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/amazon.py @@ -0,0 +1,45 @@ +""" +Amazon OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/amazon.html +""" +import ssl + +from .oauth import BaseOAuth2 + + +class AmazonOAuth2(BaseOAuth2): + name = 'amazon' + ID_KEY = 'user_id' + AUTHORIZATION_URL = 'https://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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/angel.py b/flex/lib/python3.6/site-packages/social_core/backends/angel.py new file mode 100644 index 0000000..4d7f51b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/angel.py @@ -0,0 +1,30 @@ +""" +Angel OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/angel.html +""" +from .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 + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/aol.py b/flex/lib/python3.6/site-packages/social_core/backends/aol.py new file mode 100644 index 0000000..fb427a4 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/aol.py @@ -0,0 +1,10 @@ +""" +AOL OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/aol.html +""" +from .open_id import OpenIdAuth + + +class AOLOpenId(OpenIdAuth): + name = 'aol' + URL = 'http://openid.aol.com' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/appsfuel.py b/flex/lib/python3.6/site-packages/social_core/backends/appsfuel.py new file mode 100644 index 0000000..fe630e8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/appsfuel.py @@ -0,0 +1,42 @@ +""" +Appsfueld OAuth2 backend (with sandbox mode support), docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/appsfuel.html +""" +from .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' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/arcgis.py b/flex/lib/python3.6/site-packages/social_core/backends/arcgis.py new file mode 100644 index 0000000..fdd3876 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/arcgis.py @@ -0,0 +1,33 @@ +""" +ArcGIS OAuth2 backend +""" +from .oauth import BaseOAuth2 + + +class ArcGISOAuth2(BaseOAuth2): + name = 'arcgis' + ID_KEY = 'username' + AUTHORIZATION_URL = 'https://www.arcgis.com/sharing/rest/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://www.arcgis.com/sharing/rest/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + EXTRA_DATA = [ + ('expires_in', 'expires_in') + ] + + def get_user_details(self, response): + """Return user details from ArcGIS account""" + return {'username': response.get('username'), + 'email': response.get('email'), + 'fullname': response.get('fullName'), + 'first_name': response.get('firstName'), + 'last_name': response.get('lastName')} + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + return self.get_json( + 'https://www.arcgis.com/sharing/rest/community/self', + params={ + 'token': access_token, + 'f': 'json' + } + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/asana.py b/flex/lib/python3.6/site-packages/social_core/backends/asana.py new file mode 100644 index 0000000..02efaad --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/asana.py @@ -0,0 +1,42 @@ +import datetime + +from .oauth import BaseOAuth2 + + +class AsanaOAuth2(BaseOAuth2): + name = 'asana' + AUTHORIZATION_URL = 'https://app.asana.com/-/oauth_authorize' + ACCESS_TOKEN_METHOD = 'POST' + ACCESS_TOKEN_URL = 'https://app.asana.com/-/oauth_token' + REFRESH_TOKEN_URL = 'https://app.asana.com/-/oauth_token' + REDIRECT_STATE = False + USER_DATA_URL = 'https://app.asana.com/api/1.0/users/me' + EXTRA_DATA = [ + ('expires_in', 'expires'), + ('refresh_token', 'refresh_token'), + ('name', 'name'), + ] + + def get_user_details(self, response): + data = response['data'] + fullname, first_name, last_name = self.get_user_names(data['name']) + return {'email': data['email'], + 'username': data['email'], + 'fullname': fullname, + 'last_name': last_name, + 'first_name': first_name} + + def user_data(self, access_token, *args, **kwargs): + return self.get_json(self.USER_DATA_URL, headers={ + 'Authorization': 'Bearer {}'.format(access_token) + }) + + def extra_data(self, user, uid, response, details=None, *args, **kwargs): + data = super(AsanaOAuth2, self).extra_data( + user, uid, response, details + ) + if self.setting('ESTIMATE_EXPIRES_ON'): + expires_on = datetime.datetime.utcnow() + \ + datetime.timedelta(seconds=data['expires']) + data['expires_on'] = expires_on.isoformat() + return data diff --git a/flex/lib/python3.6/site-packages/social_core/backends/azuread.py b/flex/lib/python3.6/site-packages/social_core/backends/azuread.py new file mode 100644 index 0000000..94589e0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/azuread.py @@ -0,0 +1,122 @@ +import time + +from jwt import DecodeError, ExpiredSignature, decode as jwt_decode + +from ..exceptions import AuthTokenError +from .oauth import BaseOAuth2 + +""" +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: + https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html +""" + + +class AzureADOAuth2(BaseOAuth2): + name = 'azuread-oauth2' + SCOPE_SEPARATOR = ' ' + AUTHORIZATION_URL = \ + 'https://login.microsoftonline.com/common/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/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 = super(AzureADOAuth2, self).auth_extra_arguments() + resource = self.setting('RESOURCE') + if resource: + extra_arguments.update({'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 { + 'client_id': self.setting('KEY'), + 'client_secret': self.setting('SECRET'), + '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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/azuread_tenant.py b/flex/lib/python3.6/site-packages/social_core/backends/azuread_tenant.py new file mode 100644 index 0000000..6c68320 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/azuread_tenant.py @@ -0,0 +1,115 @@ +import base64 +import json + +from cryptography.x509 import load_pem_x509_certificate +from cryptography.hazmat.backends import default_backend +from jwt import DecodeError, ExpiredSignature, decode as jwt_decode + +from ..exceptions import AuthTokenError +from .azuread import AzureADOAuth2 + +""" +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: + https://python-social-auth.readthedocs.io/en/latest/backends/azuread.html + +See https://nicksnettravels.builttoroam.com/post/2017/01/24/Verifying-Azure-Active-Directory-JWT-Tokens.aspx +for verifying JWT tokens. +""" + + +class AzureADTenantOAuth2(AzureADOAuth2): + name = 'azuread-tenant-oauth2' + OPENID_CONFIGURATION_URL = \ + 'https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration' + AUTHORIZATION_URL = \ + 'https://login.microsoftonline.com/{tenant_id}/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://login.microsoftonline.com/{tenant_id}/oauth2/token' + JWKS_URL = 'https://login.microsoftonline.com/{tenant_id}/discovery/keys' + + @property + def tenant_id(self): + return self.setting('TENANT_ID', 'common') + + def openid_configuration_url(self): + return self.OPENID_CONFIGURATION_URL.format(tenant_id=self.tenant_id) + + def authorization_url(self): + return self.AUTHORIZATION_URL.format(tenant_id=self.tenant_id) + + def access_token_url(self): + return self.ACCESS_TOKEN_URL.format(tenant_id=self.tenant_id) + + def jwks_url(self): + return self.JWKS_URL.format(tenant_id=self.tenant_id) + + def get_certificate(self, kid): + # retrieve keys from jwks_url + resp = self.request(self.jwks_url(), method='GET') + resp.raise_for_status() + + # find the proper key for the kid + for key in resp.json()['keys']: + if key['kid'] == kid: + x5c = key['x5c'][0] + break + else: + raise DecodeError('Cannot find kid={}'.format(kid)) + + certificate = '-----BEGIN CERTIFICATE-----\n' \ + '{}\n' \ + '-----END CERTIFICATE-----'.format(x5c) + + return load_pem_x509_certificate(certificate.encode(), + default_backend()) + + def user_data(self, access_token, *args, **kwargs): + response = kwargs.get('response') + id_token = response.get('id_token') + + # decode the JWT header as JSON dict + jwt_header = json.loads( + base64.b64decode(id_token.split('.', 1)[0]).decode() + ) + + # get key id and algorithm + key_id = jwt_header['kid'] + algorithm = jwt_header['alg'] + + try: + # retrieve certificate for key_id + certificate = self.get_certificate(key_id) + + return jwt_decode( + id_token, + key=certificate.public_key(), + algorithms=algorithm, + audience=self.setting('SOCIAL_AUTH_AZUREAD_OAUTH2_KEY') + ) + except (DecodeError, ExpiredSignature) as error: + raise AuthTokenError(self, error) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/base.py b/flex/lib/python3.6/site-packages/social_core/backends/base.py new file mode 100644 index 0000000..2ee7a6c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/base.py @@ -0,0 +1,247 @@ +import time + +from requests import request, ConnectionError + +from ..utils import SSLHttpAdapter, module_member, parse_qs, user_agent +from ..exceptions import AuthFailed + + +class BaseAuth(object): + """A authentication backend that authenticates the user based on + the provider response""" + name = '' # provider name, it's stored in database + supports_inactive_user = False # Django auth + ID_KEY = None + EXTRA_DATA = None + GET_ALL_EXTRA_DATA = False + REQUIRES_EMAIL_VALIDATION = False + SEND_USER_AGENT = False + SSL_PROTOCOL = None + + def __init__(self, strategy, redirect_uri=None): + self.strategy = strategy + self.redirect_uri = redirect_uri + 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): + 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 = kwargs.get('strategy') or self.strategy + self.redirect_uri = kwargs.get('redirect_uri') or self.redirect_uri + self.data = self.strategy.request_data() + kwargs.setdefault('is_new', False) + pipeline = self.strategy.get_pipeline(self) + args, kwargs = self.strategy.clean_authenticate_args(*args, **kwargs) + 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(self) + 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', {}) + + if not isinstance(pipeline_index, int) or \ + pipeline_index < 0 or \ + pipeline_index >= len(pipeline): + pipeline_index = 0 + + for idx, name in enumerate(pipeline[pipeline_index:]): + 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) + return out + + def extra_data(self, user, uid, response, details=None, *args, **kwargs): + """Return default extra data to store in extra_data field""" + data = { + # store the last time authentication toke place + 'auth_time': int(time.time()) + } + extra_data_entries = [] + if self.GET_ALL_EXTRA_DATA or self.setting('GET_ALL_EXTRA_DATA', False): + extra_data_entries = response.keys() + else: + extra_data_entries = (self.EXTRA_DATA or []) + self.setting('EXTRA_DATA', []) + for entry in extra_data_entries: + 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. + """ + return self.strategy.get_user(user_id) + + def continue_pipeline(self, partial): + """Continue previous halted pipeline""" + return self.strategy.authenticate(self, + pipeline_index=partial.next_step, + *partial.args, + **partial.kwargs) + + def auth_extra_arguments(self): + """Return extra arguments needed on auth process. The defaults can be + overridden 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'] = self.setting('USER_AGENT') or \ + 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') diff --git a/flex/lib/python3.6/site-packages/social_core/backends/battlenet.py b/flex/lib/python3.6/site-packages/social_core/backends/battlenet.py new file mode 100644 index 0000000..64511ef --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/battlenet.py @@ -0,0 +1,50 @@ +from .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', + params={'access_token': access_token} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/beats.py b/flex/lib/python3.6/site-packages/social_core/backends/beats.py new file mode 100644 index 0000000..b2ee590 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/beats.py @@ -0,0 +1,65 @@ +""" +Beats backend, docs at: + https://developer.beatsmusic.com/docs +""" +import base64 + +from ..utils import handle_http_errors +from .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)} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/behance.py b/flex/lib/python3.6/site-packages/social_core/backends/behance.py new file mode 100644 index 0000000..d05f622 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/behance.py @@ -0,0 +1,40 @@ +""" +Behance OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/behance.html +""" +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/belgiumeid.py b/flex/lib/python3.6/site-packages/social_core/backends/belgiumeid.py new file mode 100644 index 0000000..b85a17f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/belgiumeid.py @@ -0,0 +1,11 @@ +""" +Belgium EID OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/belgium_eid.html +""" +from .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' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/bitbucket.py b/flex/lib/python3.6/site-packages/social_core/backends/bitbucket.py new file mode 100644 index 0000000..1ffd876 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/bitbucket.py @@ -0,0 +1,101 @@ +""" +Bitbucket OAuth2 and OAuth1 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/bitbucket.html +""" +from ..exceptions import AuthForbidden +from .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}) + + +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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/box.py b/flex/lib/python3.6/site-packages/social_core/backends/box.py new file mode 100644 index 0000000..2ccbd45 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/box.py @@ -0,0 +1,55 @@ +""" +Box.net OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/box.html +""" +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/bungie.py b/flex/lib/python3.6/site-packages/social_core/backends/bungie.py new file mode 100644 index 0000000..49f6d42 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/bungie.py @@ -0,0 +1,90 @@ +""" +Bungie OAuth2 backend +""" +from social_core.backends.oauth import BaseOAuth2 +from django.conf import settings + + +class BungieOAuth2(BaseOAuth2): + + name = 'bungie' + ID_KEY = 'membership_id' + AUTHORIZATION_URL = 'https://www.bungie.net/en/oauth/authorize/' + ACCESS_TOKEN_URL = 'https://www.bungie.net/platform/app/oauth/token/' + REFRESH_TOKEN_URL = 'https://www.bungie.net/platform/app/oauth/token/' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + EXTRA_DATA = [ + ('refresh_token', 'refresh_token', True), + ('access_token', 'access_token', True), + ('expires_in', 'expires'), + ('membership_id', 'membership_id'), + ('refresh_expires_in', 'refresh_expires_in') + ] + + def auth_html(self): + """Abstract Method Inclusion""" + pass + + def auth_headers(self): + """Adds X-API-KEY and Origin""" + return {'X-API-KEY': settings.SOCIAL_AUTH_BUNGIE_API_KEY, + 'Content-Type': 'application/x-www-form-urlencoded', + 'Origin': settings.SOCIAL_AUTH_BUNGIE_ORIGIN, + 'Accept': 'application/json' + } + + def make_bungie_request(self, url, access_token, kwargs): + """Helper function to get username data keyed off displayName""" + print('ENTERING MAKE BUNGIE REQUEST') + headers = self.auth_headers() + print(repr(headers)) + auth_header = {'Authorization': 'Bearer ' + access_token} + headers.update(auth_header) + import requests as python_requests + r = python_requests.get(url, headers=headers) + this_json = r.json() + return this_json + + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + self.process_error(self.data) + state = self.validate_state() + 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) + + 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 {}) + if 'access_token' not in response: + response['Response']['access_token']['value'] = access_token + kwargs.update({'response': response, 'backend': self}) + return self.strategy.authenticate(*args, **kwargs) + + def user_data(self, access_token, *args, **kwargs): + """Grab user profile information from Bunige""" + membership_id = kwargs['response']['membership_id'] + url = 'https://www.bungie.net/Platform/User/GetBungieNetUser/' + this_json = self.make_bungie_request(url, access_token, kwargs) + username = this_json['Response']['user']['displayName'] + return {'username': username, 'uid': membership_id} + + def get_user_details(self, response, *args, **kwargs): + """Return user details from Bungie account""" + username = response['username'] + uid = response['uid'] + bnId = response['bnId'] + return { + 'first_name': username, + 'username': username, + 'uid': uid, + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/changetip.py b/flex/lib/python3.6/site-packages/social_core/backends/changetip.py new file mode 100644 index 0000000..beb134f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/changetip.py @@ -0,0 +1,27 @@ +from .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 + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/chatwork.py b/flex/lib/python3.6/site-packages/social_core/backends/chatwork.py new file mode 100644 index 0000000..24ba030 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/chatwork.py @@ -0,0 +1,67 @@ +""" +Chatwork OAuth2 backend +""" +import base64 + +from .oauth import BaseOAuth2 + + +class ChatworkOAuth2(BaseOAuth2): + """Chatwork OAuth authentication backend""" + name = 'chatwork' + API_URL = 'https://api.chatwork.com/v2' + AUTHORIZATION_URL = 'https://www.chatwork.com/packages/oauth2/login.php' + ACCESS_TOKEN_URL = 'https://oauth.chatwork.com/token' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = True + DEFAULT_SCOPE = ['users.profile.me:read'] + ID_KEY = 'account_id' + EXTRA_DATA = [ + ('expires_in', 'expires'), + ('refresh_token', 'refresh_token') + ] + + def api_url(self, path): + api_url = self.setting('API_URL') or self.API_URL + return '{0}{1}'.format(api_url.rstrip('/'), path) + + def auth_headers(self): + return { + 'Authorization': b'Basic ' + base64.b64encode( + '{0}:{1}'.format(*self.get_key_and_secret()).encode() + ) + } + + def auth_complete_params(self, state=None): + return { + 'grant_type': 'authorization_code', + 'code': self.data.get('code', ''), + 'redirect_uri': self.get_redirect_uri(state) + } + + def get_user_details(self, response): + """Return user details from Chatwork account""" + fullname, first_name, last_name = self.get_user_names( + response.get('name') + ) + username = response.get('chatwork_id') or \ + response.get('login_mail') or \ + response.get('account_id') + email = response.get('mail') or \ + response.get('login_mail') or \ + '' + return { + 'username': username, + '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""" + headers = {'Authorization': 'Bearer ' + access_token} + return self.get_json(self.api_url('/me'), headers=headers) + + def refresh_token_params(self, token, *args, **kwargs): + return {'refresh_token': token, 'grant_type': 'refresh_token'} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/classlink.py b/flex/lib/python3.6/site-packages/social_core/backends/classlink.py new file mode 100644 index 0000000..cec0d7a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/classlink.py @@ -0,0 +1,44 @@ +from .oauth import BaseOAuth2 + + +class ClasslinkOAuth(BaseOAuth2): + """ + Classlink OAuth authentication backend. + + Docs: https://developer.classlink.com/docs/oauth2-workflow + """ + name = 'classlink' + AUTHORIZATION_URL = 'https://launchpad.classlink.com/oauth2/v2/auth' + ACCESS_TOKEN_URL = 'https://launchpad.classlink.com/oauth2/v2/token' + ACCESS_TOKEN_METHOD = 'POST' + DEFAULT_SCOPE = ['profile'] + REDIRECT_STATE = False + SCOPE_SEPARATOR = ' ' + + def get_user_id(self, details, response): + """Return user unique id provided by service""" + return response['UserId'] + + def get_user_details(self, response): + """Return user details from Classlink account""" + fullname, first_name, last_name = self.get_user_names( + first_name=response.get('FirstName'), + last_name=response.get('LastName') + ) + + return { + 'username': response.get('Email') or response.get('LoginId'), + 'email': response.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://nodeapi.classlink.com/v2/my/info' + auth_header = {"Authorization": "Bearer %s" % token} + try: + return self.get_json(url, headers=auth_header) + except ValueError: + return None diff --git a/flex/lib/python3.6/site-packages/social_core/backends/clef.py b/flex/lib/python3.6/site-packages/social_core/backends/clef.py new file mode 100644 index 0000000..9f4cbdd --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/clef.py @@ -0,0 +1,54 @@ +""" +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 .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/coding.py b/flex/lib/python3.6/site-packages/social_core/backends/coding.py new file mode 100644 index 0000000..78f244c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/coding.py @@ -0,0 +1,48 @@ +""" +Coding OAuth2 backend, docs at: +""" +from six.moves.urllib.parse import urljoin + +from .oauth import BaseOAuth2 + + +class CodingOAuth2(BaseOAuth2): + """Coding OAuth authentication backend""" + + name = 'coding' + API_URL = 'https://coding.net/api/' + AUTHORIZATION_URL = 'https://coding.net/oauth_authorize.html' + ACCESS_TOKEN_URL = 'https://coding.net/api/oauth/access_token' + ACCESS_TOKEN_METHOD = 'POST' + SCOPE_SEPARATOR = ',' + DEFAULT_SCOPE = ['user'] + REDIRECT_STATE = False + + 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('name'), + '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 data.get('code') != 0: + # 获取失败 + pass + return data.get('data') + + def _user_data(self, access_token, path=None): + url = urljoin( + self.api_url(), + 'account/current_user{0}'.format(path or '') + ) + return self.get_json(url, params={'access_token': access_token}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/coinbase.py b/flex/lib/python3.6/site-packages/social_core/backends/coinbase.py new file mode 100644 index 0000000..3e05ac8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/coinbase.py @@ -0,0 +1,37 @@ +""" +Coinbase OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/coinbase.html +""" +from .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' + REVOKE_TOKEN_URL = 'https://api.coinbase.com/oauth/revoke' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + + def get_user_id(self, details, response): + return response['data']['id'] + + def get_user_details(self, response): + """Return user details from Coinbase account""" + user_data = response['data'] + email = user_data.get('email', '') + name = user_data['name'] + username = user_data.get('username') + fullname, first_name, last_name = self.get_user_names(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.coinbase.com/v2/user', + headers={'Authorization': 'Bearer ' + access_token}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/coursera.py b/flex/lib/python3.6/site-packages/social_core/backends/coursera.py new file mode 100644 index 0000000..ff5f287 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/coursera.py @@ -0,0 +1,43 @@ +""" +Coursera OAuth2 backend, docs at: + https://tech.coursera.org/app-platform/oauth2/ +""" +from .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 get_user_id(self, details, response): + """Return a username prepared in get_user_details as uid""" + return details.get(self.ID_KEY) + + 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)} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/dailymotion.py b/flex/lib/python3.6/site-packages/social_core/backends/dailymotion.py new file mode 100644 index 0000000..8f908be --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/dailymotion.py @@ -0,0 +1,24 @@ +""" +DailyMotion OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/dailymotion.html +""" +from .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/auth/', + params={'access_token': access_token}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/deezer.py b/flex/lib/python3.6/site-packages/social_core/backends/deezer.py new file mode 100644 index 0000000..e480366 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/deezer.py @@ -0,0 +1,49 @@ +""" +Deezer backend, docs at: + https://developers.deezer.com/api/oauth + https://developers.deezer.com/api/permissions +""" +from six.moves.urllib.parse import parse_qsl + +from .oauth import BaseOAuth2 + + +class DeezerOAuth2(BaseOAuth2): + """Deezer OAuth2 authentication backend""" + name = 'deezer' + ID_KEY = 'name' + AUTHORIZATION_URL = 'https://connect.deezer.com/oauth/auth.php' + ACCESS_TOKEN_URL = 'https://connect.deezer.com/oauth/access_token.php' + ACCESS_TOKEN_METHOD = 'POST' + SCOPE_SEPARATOR = ',' + REDIRECT_STATE = False + + def auth_complete_params(self, state=None): + client_id, client_secret = self.get_key_and_secret() + return { + 'app_id': client_id, + 'secret': client_secret, + 'code': self.data.get('code') + } + + def request_access_token(self, *args, **kwargs): + response = self.request(*args, **kwargs) + return dict(parse_qsl(response.text)) + + def get_user_details(self, response): + """Return user details from Deezer account""" + fullname, first_name, last_name = self.get_user_names( + first_name=response.get('firstname'), + last_name=response.get('lastname') + ) + return {'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""" + return self.get_json('http://api.deezer.com/user/me', params={ + 'access_token': access_token + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/digitalocean.py b/flex/lib/python3.6/site-packages/social_core/backends/digitalocean.py new file mode 100644 index 0000000..ff654a9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/digitalocean.py @@ -0,0 +1,41 @@ +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/disqus.py b/flex/lib/python3.6/site-packages/social_core/backends/disqus.py new file mode 100644 index 0000000..eceeb44 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/disqus.py @@ -0,0 +1,52 @@ +""" +Disqus OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/disqus.html +""" +from .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' + REDIRECT_STATE = False + 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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/docker.py b/flex/lib/python3.6/site-packages/social_core/backends/docker.py new file mode 100644 index 0000000..58c18fc --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/docker.py @@ -0,0 +1,46 @@ +""" +Docker Hub OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/docker.html +""" +from .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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/douban.py b/flex/lib/python3.6/site-packages/social_core/backends/douban.py new file mode 100644 index 0000000..cfc7cba --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/douban.py @@ -0,0 +1,59 @@ +""" +Douban OAuth1 and OAuth2 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/douban.html +""" +from .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)} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/dribbble.py b/flex/lib/python3.6/site-packages/social_core/backends/dribbble.py new file mode 100644 index 0000000..7381e6d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/dribbble.py @@ -0,0 +1,62 @@ +""" +Dribbble OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/dribbble.html + http://developer.dribbble.com/v1/oauth/ +""" + +from .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) + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/drip.py b/flex/lib/python3.6/site-packages/social_core/backends/drip.py new file mode 100644 index 0000000..e3b62c0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/drip.py @@ -0,0 +1,25 @@ +""" +Drip OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/drip.html +""" +from .oauth import BaseOAuth2 + + +class DripOAuth(BaseOAuth2): + name = 'drip' + AUTHORIZATION_URL = 'https://www.getdrip.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://www.getdrip.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + + def get_user_id(self, details, response): + return details['email'] + + def get_user_details(self, response): + return {'email': response['users'][0]['email'], + 'fullname': response['users'][0]['name'], + 'username': response['users'][0]['email']} + + def user_data(self, access_token, *args, **kwargs): + return self.get_json('https://api.getdrip.com/v2/user', headers={ + 'Authorization': 'Bearer %s' % access_token + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/dropbox.py b/flex/lib/python3.6/site-packages/social_core/backends/dropbox.py new file mode 100644 index 0000000..5e90244 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/dropbox.py @@ -0,0 +1,113 @@ +""" +Dropbox OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/dropbox.html +""" +import warnings + +from .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 __init__(self, *args, **kwargs): + warnings.warn( + 'Dropbox V1 api is deprecated and will be shute down 2017-06-28 ' + 'https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/', + DeprecationWarning, + stacklevel=2 + ) + super(DropboxOAuth, self).__init__(*args, **kwargs) + + 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 __init__(self, *args, **kwargs): + warnings.warn( + 'Dropbox V1 api is deprecated and will be shute down 2017-06-28 ' + 'https://blogs.dropbox.com/developers/2016/06/api-v1-deprecated/', + DeprecationWarning, + stacklevel=2 + ) + super(DropboxOAuth2, self).__init__(*args, **kwargs) + + 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)} + ) + + +class DropboxOAuth2V2(BaseOAuth2): + name = 'dropbox-oauth2' + ID_KEY = 'uid' + AUTHORIZATION_URL = 'https://www.dropbox.com/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://api.dropboxapi.com/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + + def get_user_details(self, response): + """Return user details from Dropbox account""" + name = response.get('name') + return {'username': str(response.get('account_id')), + 'email': response.get('email'), + 'fullname': name.get('display_name'), + 'first_name': name.get('given_name'), + 'last_name': name.get('surname')} + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + return self.get_json( + 'https://api.dropboxapi.com/2/users/get_current_account', + headers={'Authorization': 'Bearer {0}'.format(access_token)}, + method='POST' + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/echosign.py b/flex/lib/python3.6/site-packages/social_core/backends/echosign.py new file mode 100644 index 0000000..e75fad9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/echosign.py @@ -0,0 +1,24 @@ +from .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/edmodo.py b/flex/lib/python3.6/site-packages/social_core/backends/edmodo.py new file mode 100644 index 0000000..4ac7964 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/edmodo.py @@ -0,0 +1,34 @@ +""" +Edmodo OAuth2 Sign-in backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/edmodo.html +""" +from .oauth import BaseOAuth2 + + +class EdmodoOAuth2(BaseOAuth2): + """Edmodo OAuth2""" + name = 'edmodo' + AUTHORIZATION_URL = 'https://api.edmodo.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://api.edmodo.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + + def get_user_details(self, response): + """Return user details from Edmodo 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('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 Edmodo""" + return self.get_json( + 'https://api.edmodo.com/users/me', + params={'access_token': access_token} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/email.py b/flex/lib/python3.6/site-packages/social_core/backends/email.py new file mode 100644 index 0000000..417b498 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/email.py @@ -0,0 +1,12 @@ +""" +Legacy Email backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/email.html +""" +from .legacy import LegacyAuth + + +class EmailAuth(LegacyAuth): + name = 'email' + ID_KEY = 'email' + REQUIRES_EMAIL_VALIDATION = True + EXTRA_DATA = ['email'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/eventbrite.py b/flex/lib/python3.6/site-packages/social_core/backends/eventbrite.py new file mode 100644 index 0000000..1af5bc6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/eventbrite.py @@ -0,0 +1,29 @@ +from .oauth import BaseOAuth2 + + +class EventbriteOAuth2(BaseOAuth2): + """Eventbrite OAuth2 authentication backend""" + name = 'eventbrite' + AUTHORIZATION_URL = 'https://www.eventbrite.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://www.eventbrite.com/oauth/token' + METADATA_URL = 'https://www.eventbriteapi.com/v3/users/me' + ACCESS_TOKEN_METHOD = 'POST' + STATE_PARAMETER = False + REDIRECT_STATE = False + + def get_user_details(self, response): + """Return user details from an Eventbrite metadata response""" + email = next(filter(lambda x: x['primary'], response['emails']))['email'] + + return { + 'username': email, + 'email': email, + 'first_name': response['first_name'], + 'last_name': response['last_name'] + } + + def user_data(self, access_token, *args, **kwargs): + """Loads user data and datacenter information from service""" + return self.get_json(self.METADATA_URL, headers={ + 'Authorization': 'Bearer ' + access_token + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/eveonline.py b/flex/lib/python3.6/site-packages/social_core/backends/eveonline.py new file mode 100644 index 0000000..2d71728 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/eveonline.py @@ -0,0 +1,42 @@ +""" +EVE Online Single Sign-On (SSO) OAuth2 backend +Documentation at https://eveonline-third-party-documentation.readthedocs.io/en/latest/sso/index.html +""" +from .oauth import BaseOAuth2 + + +class EVEOnlineOAuth2(BaseOAuth2): + """EVE Online OAuth authentication backend""" + name = 'eveonline' + BASE_URL = 'https://login.eveonline.com/oauth' + AUTHORIZATION_URL = BASE_URL + '/authorize' + ACCESS_TOKEN_URL = BASE_URL + '/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)} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/evernote.py b/flex/lib/python3.6/site-packages/social_core/backends/evernote.py new file mode 100644 index 0000000..a038503 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/evernote.py @@ -0,0 +1,75 @@ +""" +Evernote OAuth1 backend (with sandbox mode support), docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/evernote.html +""" +from requests import HTTPError + +from ..exceptions import AuthCanceled +from .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, response=err.response) + 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 milliseconds, 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' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/exacttarget.py b/flex/lib/python3.6/site-packages/social_core/backends/exacttarget.py new file mode 100644 index 0000000..a066cdd --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/exacttarget.py @@ -0,0 +1,104 @@ +""" +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 ..exceptions import AuthFailed, AuthCanceled +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/facebook.py b/flex/lib/python3.6/site-packages/social_core/backends/facebook.py new file mode 100644 index 0000000..e64145d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/facebook.py @@ -0,0 +1,233 @@ +""" +Facebook OAuth2 and Canvas Application backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/facebook.html +""" +import hmac +import time +import json +import base64 +import hashlib + +from ..utils import parse_qs, constant_time_compare, handle_http_errors +from .oauth import BaseOAuth2 +from ..exceptions import AuthException, AuthCanceled, AuthUnknownError, \ + AuthMissingParameter + + +API_VERSION = 2.9 + + +class FacebookOAuth2(BaseOAuth2): + """Facebook OAuth2 authentication backend""" + name = 'facebook' + REDIRECT_STATE = False + RESPONSE_TYPE = None + SCOPE_SEPARATOR = ',' + AUTHORIZATION_URL = 'https://www.facebook.com/v{version}/dialog/oauth' + ACCESS_TOKEN_URL = \ + 'https://graph.facebook.com/v{version}/oauth/access_token' + REVOKE_TOKEN_URL = \ + 'https://graph.facebook.com/v{version}/{uid}/permissions' + REVOKE_TOKEN_METHOD = 'DELETE' + USER_DATA_URL = 'https://graph.facebook.com/v{version}/me' + EXTRA_DATA = [ + ('id', 'id'), + ('expires', 'expires'), + ('granted_scopes', 'granted_scopes'), + ('denied_scopes', 'denied_scopes') + ] + + def auth_params(self, state=None): + params = super(FacebookOAuth2, self).auth_params(state) + params['return_scopes'] = 'true' + return params + + def authorization_url(self): + version = self.setting('API_VERSION', API_VERSION) + return self.AUTHORIZATION_URL.format(version=version) + + def access_token_url(self): + version = self.setting('API_VERSION', API_VERSION) + return self.ACCESS_TOKEN_URL.format(version=version) + + 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() + + version = self.setting('API_VERSION', API_VERSION) + return self.get_json(self.USER_DATA_URL.format(version=version), + 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): + try: + return response.json() + except ValueError: + 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' in response: + data['expires'] = response['expires_in'] + + if self.data.get('granted_scopes'): + data['granted_scopes'] = self.data['granted_scopes'].split(',') + + if self.data.get('denied_scopes'): + data['denied_scopes'] = self.data['denied_scopes'].split(',') + + kwargs.update({'backend': self, 'response': data}) + return self.strategy.authenticate(*args, **kwargs) + + def revoke_token_url(self, token, uid): + version = self.setting('API_VERSION', API_VERSION) + return self.REVOKE_TOKEN_URL.format(version=version, 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 += '='.encode('ascii') * (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) + payload_json_bytes = base64_url_decode(payload) + data = json.loads(payload_json_bytes.decode('utf-8', 'replace')) + expected_sig = hmac.new(secret.encode('ascii'), + msg=payload.encode('ascii'), + 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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/fedora.py b/flex/lib/python3.6/site-packages/social_core/backends/fedora.py new file mode 100644 index 0000000..cfd6d22 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/fedora.py @@ -0,0 +1,11 @@ +""" +Fedora OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/fedora.html +""" +from .open_id import OpenIdAuth + + +class FedoraOpenId(OpenIdAuth): + name = 'fedora' + URL = 'https://id.fedoraproject.org' + USERNAME_KEY = 'nickname' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/fitbit.py b/flex/lib/python3.6/site-packages/social_core/backends/fitbit.py new file mode 100644 index 0000000..c324b20 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/fitbit.py @@ -0,0 +1,67 @@ +""" +Fitbit OAuth backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/fitbit.html +""" +import base64 + +from .oauth import BaseOAuth1, BaseOAuth2 + + +class FitbitOAuth1(BaseOAuth1): + """Fitbit OAuth1 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'] + + +class FitbitOAuth2(BaseOAuth2): + """Fitbit OAuth2 authentication backend""" + name = 'fitbit' + AUTHORIZATION_URL = 'https://www.fitbit.com/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://api.fitbit.com/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + REFRESH_TOKEN_URL = 'https://api.fitbit.com/oauth2/token' + DEFAULT_SCOPE = ['profile'] + ID_KEY = 'encodedId' + REDIRECT_STATE = False + EXTRA_DATA = [('expires_in', 'expires'), + ('refresh_token', 'refresh_token', True), + ('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""" + auth_header = {"Authorization": "Bearer %s" % access_token} + return self.get_json( + 'https://api.fitbit.com/1/user/-/profile.json', + headers=auth_header + )['user'] + + def auth_headers(self): + tokens = '{0}:{1}'.format(*self.get_key_and_secret()) + tokens = base64.urlsafe_b64encode(tokens.encode()) + tokens = tokens.decode() + return { + 'Authorization': 'Basic {0}'.format(tokens) + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/five_hundred_px.py b/flex/lib/python3.6/site-packages/social_core/backends/five_hundred_px.py new file mode 100644 index 0000000..8c36bc5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/five_hundred_px.py @@ -0,0 +1,34 @@ +""" +500px OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/five_hundred_px.html +""" +from .oauth import BaseOAuth1 + + +class FiveHundredPxOAuth(BaseOAuth1): + """500px OAuth authentication backend""" + name = '500px' + AUTHORIZATION_URL = 'https://api.500px.com/v1/oauth/authorize' + REQUEST_TOKEN_URL = 'https://api.500px.com/v1/oauth/request_token' + ACCESS_TOKEN_URL = 'https://api.500px.com/v1/oauth/access_token' + + def get_user_details(self, user): + """Return user details from 500px account""" + fullname, first_name, last_name = self.get_user_names( + user.get('fullname') + ) + return { + 'username': user.get('username') or user.get('id'), + 'email': user.get('email'), + 'fullname': fullname, + 'first_name': first_name, + 'last_name': last_name + } + + def user_data(self, access_token, *args, **kwargs): + """Return user data provided""" + response = self.get_json( + 'https://api.500px.com/v1/users', + auth=self.oauth_auth(access_token) + ) + return response.get('user') diff --git a/flex/lib/python3.6/site-packages/social_core/backends/flickr.py b/flex/lib/python3.6/site-packages/social_core/backends/flickr.py new file mode 100644 index 0000000..546ab67 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/flickr.py @@ -0,0 +1,43 @@ +""" +Flickr OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/flickr.html +""" +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/foursquare.py b/flex/lib/python3.6/site-packages/social_core/backends/foursquare.py new file mode 100644 index 0000000..3ed4302 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/foursquare.py @@ -0,0 +1,36 @@ +""" +Foursquare OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/foursquare.html +""" +from .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/gae.py b/flex/lib/python3.6/site-packages/social_core/backends/gae.py new file mode 100644 index 0000000..a6aa8e3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/gae.py @@ -0,0 +1,40 @@ +""" +Google App Engine support using User API +""" +from __future__ import absolute_import + +from google.appengine.api import users + +from .base import BaseAuth +from ..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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/github.py b/flex/lib/python3.6/site-packages/social_core/backends/github.py new file mode 100644 index 0000000..f2574db --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/github.py @@ -0,0 +1,119 @@ +""" +Github OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/github.html +""" +from requests import HTTPError + +from six.moves.urllib.parse import urljoin + +from .oauth import BaseOAuth2 +from ..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') + ) + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/github_enterprise.py b/flex/lib/python3.6/site-packages/social_core/backends/github_enterprise.py new file mode 100644 index 0000000..3461214 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/github_enterprise.py @@ -0,0 +1,42 @@ +""" +Github Enterprise OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/github_enterprise.html +""" +from six.moves.urllib.parse import urljoin + +from ..utils import append_slash +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/gitlab.py b/flex/lib/python3.6/site-packages/social_core/backends/gitlab.py new file mode 100644 index 0000000..2615642 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/gitlab.py @@ -0,0 +1,58 @@ +""" +GitLab OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/gitlab.html + +Thanks to [@saily](https://github.com/saily) who published an +implementation for GitLab support on his blog post [Weblate with +GitLab as OAuth provider](http://widerin.net/blog/weblate-gitlab-oauth-login/). +His code was a great reference when working on this implementation. +""" +from requests import HTTPError + +from six.moves.urllib.parse import urljoin + +from .oauth import BaseOAuth2 +from ..exceptions import AuthFailed + + +class GitLabOAuth2(BaseOAuth2): + """GitLab OAuth authentication backend""" + name = 'gitlab' + API_URL = 'https://gitlab.com' + AUTHORIZATION_URL = 'https://gitlab.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://gitlab.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + DEFAULT_SCOPE = ['read_user'] + EXTRA_DATA = [ + ('id', 'id'), + ('expires_in', 'expires'), + ('refresh_token', 'refresh_token') + ] + + def api_url(self, path): + api_url = self.setting('API_URL') or self.API_URL + return '{0}{1}'.format(api_url.rstrip('/'), path) + + def authorization_url(self): + return self.api_url('/oauth/authorize') + + def access_token_url(self): + return self.api_url('/oauth/token') + + def get_user_details(self, response): + """Return user details from GitLab 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(self.api_url('/api/v4/user'), params={ + 'access_token': access_token + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/goclio.py b/flex/lib/python3.6/site-packages/social_core/backends/goclio.py new file mode 100644 index 0000000..4d75c1c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/goclio.py @@ -0,0 +1,35 @@ +from .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') diff --git a/flex/lib/python3.6/site-packages/social_core/backends/goclioeu.py b/flex/lib/python3.6/site-packages/social_core/backends/goclioeu.py new file mode 100644 index 0000000..0b25ddf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/goclioeu.py @@ -0,0 +1,14 @@ +from .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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/google.py b/flex/lib/python3.6/site-packages/social_core/backends/google.py new file mode 100644 index 0000000..62c5c90 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/google.py @@ -0,0 +1,186 @@ +""" +Google OpenId, OAuth2, OAuth1, Google+ Sign-in backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/google.html +""" +from ..utils import handle_http_errors +from .open_id import OpenIdAuth +from .oauth import BaseOAuth2, BaseOAuth1 +from ..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 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', + 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'] + 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', + ] + 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/v3/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) + elif 'id_token' in self.data: # Client-side workflow + token = self.data.get('id_token') + response = self.get_json( + 'https://www.googleapis.com/oauth2/v3/tokeninfo', + params={'id_token': token} + ) + self.process_error(response) + return self.do_auth(token, response=response, *args, **kwargs) + else: + raise AuthMissingParameter(self, 'access_token, id_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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/google_openidconnect.py b/flex/lib/python3.6/site-packages/social_core/backends/google_openidconnect.py new file mode 100644 index 0000000..55b8267 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/google_openidconnect.py @@ -0,0 +1,21 @@ +""" +Google OpenIdConnect: + https://python-social-auth.readthedocs.io/en/latest/backends/google.html +""" +from .open_id_connect import OpenIdConnectAuth +from .google import GoogleOAuth2 + + +class GoogleOpenIdConnect(GoogleOAuth2, OpenIdConnectAuth): + name = 'google-openidconnect' + OIDC_ENDPOINT = 'https://accounts.google.com' + # differs from value in discovery document + # http://openid.net/specs/openid-connect-core-1_0.html#rfc.section.15.6.2 + 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'} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/instagram.py b/flex/lib/python3.6/site-packages/social_core/backends/instagram.py new file mode 100644 index 0000000..cc138e5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/instagram.py @@ -0,0 +1,53 @@ +""" +Instagram OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/instagram.html +""" +import hmac + +from hashlib import sha256 + +from .oauth import BaseOAuth2 + + +class InstagramOAuth2(BaseOAuth2): + name = 'instagram' + AUTHORIZATION_URL = 'https://api.instagram.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://api.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""" + key, secret = self.get_key_and_secret() + params = {'access_token': access_token} + sig = self._generate_sig("/users/self", params, secret) + params['sig'] = sig + return self.get_json('https://api.instagram.com/v1/users/self', + params=params) + + def _generate_sig(self, endpoint, params, secret): + sig = endpoint + for key in sorted(params.keys()): + sig += '|%s=%s' % (key, params[key]) + return hmac.new(secret.encode(), sig.encode(), sha256).hexdigest() diff --git a/flex/lib/python3.6/site-packages/social_core/backends/itembase.py b/flex/lib/python3.6/site-packages/social_core/backends/itembase.py new file mode 100644 index 0000000..7938e93 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/itembase.py @@ -0,0 +1,86 @@ +import time + +from .oauth import BaseOAuth2 +from ..utils import handle_http_errors + + +class ItembaseOAuth2(BaseOAuth2): + name = 'itembase' + ID_KEY = 'uuid' + AUTHORIZATION_URL = 'https://accounts.itembase.com/oauth/v2/auth' + ACCESS_TOKEN_URL = 'https://accounts.itembase.com/oauth/v2/token' + USER_DETAILS_URL = 'https://users.itembase.com/v1/me' + ACTIVATION_ENDPOINT = 'https://solutionservice.itembase.com/activate' + DEFAULT_SCOPE = ['user.minimal'] + EXTRA_DATA = [ + ('access_token', 'access_token'), + ('token_type', 'token_type'), + ('refresh_token', 'refresh_token'), + ('expires_in', 'expires_in'), # seconds to expiration + ('expires', 'expires'), # expiration timestamp in UTC + ('uuid', 'uuid'), + ('username', 'username'), + ('email', 'email'), + ('first_name', 'first_name'), + ('middle_name', 'middle_name'), + ('last_name', 'last_name'), + ('name_format', 'name_format'), + ('locale', 'locale'), + ('preferred_currency', 'preferred_currency'), + ] + + def add_expires(self, data): + data['expires'] = int(time.time()) + data.get('expires_in', 0) + return data + + def extra_data(self, user, uid, response, details=None, *args, **kwargs): + data = BaseOAuth2.extra_data(self, user, uid, response, + details=details, + *args, **kwargs) + return self.add_expires(data) + + def process_refresh_token_response(self, response, *args, **kwargs): + data = BaseOAuth2.process_refresh_token_response(self, response, + *args, **kwargs) + return self.add_expires(data) + + def get_user_details(self, response): + """Return user details from Itembase account""" + return response + + def user_data(self, access_token, *args, **kwargs): + return self.get_json(self.USER_DETAILS_URL, headers={ + 'Authorization': 'Bearer {0}'.format(access_token) + }) + + def activation_data(self, response): + # returns activation_data dict with activation_url inside + # see http://developers.itembase.com/authentication/activation + return self.get_json(self.ACTIVATION_ENDPOINT, headers={ + 'Authorization': 'Bearer {0}'.format(response['access_token']) + }) + + @handle_http_errors + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + state = self.validate_state() + self.process_error(self.data) + # itembase needs GET request with params instead of just data + response = self.request_access_token( + self.access_token_url(), + params=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) + + +class ItembaseOAuth2Sandbox(ItembaseOAuth2): + name = 'itembase-sandbox' + AUTHORIZATION_URL = 'http://sandbox.accounts.itembase.io/oauth/v2/auth' + ACCESS_TOKEN_URL = 'http://sandbox.accounts.itembase.io/oauth/v2/token' + USER_DETAILS_URL = 'http://sandbox.users.itembase.io/v1/me' + ACTIVATION_ENDPOINT = 'http://sandbox.solutionservice.itembase.io/activate' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/jawbone.py b/flex/lib/python3.6/site-packages/social_core/backends/jawbone.py new file mode 100644 index 0000000..47aaaba --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/jawbone.py @@ -0,0 +1,77 @@ +""" +Jawbone OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/jawbone.html +""" +from ..utils import handle_http_errors +from .oauth import BaseOAuth2 +from ..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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/justgiving.py b/flex/lib/python3.6/site-packages/social_core/backends/justgiving.py new file mode 100644 index 0000000..d0dbdac --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/justgiving.py @@ -0,0 +1,56 @@ +from requests.auth import HTTPBasicAuth +from ..utils import handle_http_errors +from .oauth import BaseOAuth2 + + +class JustGivingOAuth2(BaseOAuth2): + """Just Giving OAuth authentication backend""" + name = 'justgiving' + ID_KEY = 'userId' + AUTHORIZATION_URL = 'https://identity.justgiving.com/connect/authorize' + ACCESS_TOKEN_URL = 'https://identity.justgiving.com/connect/token' + ACCESS_TOKEN_METHOD = 'POST' + USER_DATA_URL = 'https://api.justgiving.com/v1/account' + DEFAULT_SCOPE = ['openid', 'account', 'profile', 'email', 'fundraise'] + + def get_user_details(self, response): + """Return user details from Just Giving account""" + fullname, first_name, last_name = self.get_user_names( + '', + response.get('firstName'), + response.get('lastName')) + return { + 'username': response.get('email'), + '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""" + key, secret = self.get_key_and_secret() + return self.get_json(self.USER_DATA_URL, headers={ + 'Authorization': 'Bearer {0}'.format(access_token), + 'Content-Type': 'application/json', + 'x-application-key': secret, + 'x-api-key': key + }) + + @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) + + key, secret = self.get_key_and_secret() + response = self.request_access_token( + self.access_token_url(), + data=self.auth_complete_params(state), + headers=self.auth_headers(), + auth=HTTPBasicAuth(key, secret), + method=self.ACCESS_TOKEN_METHOD + ) + self.process_error(response) + return self.do_auth(response['access_token'], response=response, + *args, **kwargs) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/kakao.py b/flex/lib/python3.6/site-packages/social_core/backends/kakao.py new file mode 100644 index 0000000..4ef89f6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/kakao.py @@ -0,0 +1,40 @@ +""" +Kakao OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/kakao.html +""" +from .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], + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/khanacademy.py b/flex/lib/python3.6/site-packages/social_core/backends/khanacademy.py new file mode 100644 index 0000000..d44e64e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/khanacademy.py @@ -0,0 +1,126 @@ +""" +Khan Academy OAuth backend, docs at: + https://github.com/Khan/khan-api/wiki/Khan-Academy-API-Authentication +""" +import six + +from six.moves.urllib_parse import urlencode + +from oauthlib.oauth1 import SIGNATURE_HMAC, SIGNATURE_TYPE_QUERY +from requests_oauthlib import OAuth1 + +from .oauth import BaseOAuth1 + + +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('email'), + 'email': response.get('email'), + 'fullname': response.get('nickname'), + '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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/lastfm.py b/flex/lib/python3.6/site-packages/social_core/backends/lastfm.py new file mode 100644 index 0000000..617fc33 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/lastfm.py @@ -0,0 +1,59 @@ +import hashlib + +from ..utils import handle_http_errors +from .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 + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/launchpad.py b/flex/lib/python3.6/site-packages/social_core/backends/launchpad.py new file mode 100644 index 0000000..ecde01e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/launchpad.py @@ -0,0 +1,11 @@ +""" +Launchpad OpenId backend +""" + +from .open_id import OpenIdAuth + + +class LaunchpadOpenId(OpenIdAuth): + name = 'launchpad' + URL = 'https://login.launchpad.net' + USERNAME_KEY = 'nickname' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/legacy.py b/flex/lib/python3.6/site-packages/social_core/backends/legacy.py new file mode 100644 index 0000000..6fc546e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/legacy.py @@ -0,0 +1,44 @@ +from .base import BaseAuth +from ..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 + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/line.py b/flex/lib/python3.6/site-packages/social_core/backends/line.py new file mode 100644 index 0000000..faf5059 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/line.py @@ -0,0 +1,94 @@ +# vim:fileencoding=utf-8 +import requests +import json + +from .oauth import BaseOAuth2 +from ..exceptions import AuthFailed +from ..utils import handle_http_errors + + +class LineOAuth2(BaseOAuth2): + name = 'line' + AUTHORIZATION_URL = 'https://access.line.me/dialog/oauth/weblogin' + ACCESS_TOKEN_URL = 'https://api.line.me/v1/oauth/accessToken' + BASE_API_URL = 'https://api.line.me' + USER_INFO_URL = BASE_API_URL + '/v1/profile' + ACCESS_TOKEN_METHOD = 'POST' + STATE_PARAMETER = True + REDIRECT_STATE = True + ID_KEY = 'mid' + EXTRA_DATA = [ + ('mid', 'id'), + ('expire', 'expire'), + ('refreshToken', 'refresh_token') + ] + + def auth_params(self, state=None): + client_id, client_secret = self.get_key_and_secret() + return { + 'client_id': client_id, + 'redirect_uri': self.get_redirect_uri(), + 'response_type': self.RESPONSE_TYPE + } + + def process_error(self, data): + error_code = data.get('errorCode') or \ + data.get('statusCode') or \ + data.get('error') + error_message = data.get('errorMessage') or \ + data.get('statusMessage') or \ + data.get('error_desciption') + if error_code is not None or error_message is not None: + raise AuthFailed(self, error_message or error_code) + + @handle_http_errors + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + client_id, client_secret = self.get_key_and_secret() + code = self.data.get('code') + + self.process_error(self.data) + + try: + response = self.request_access_token( + self.access_token_url(), + method=self.ACCESS_TOKEN_METHOD, + params={ + 'requestToken': code, + 'channelSecret': client_secret + } + ) + self.process_error(response) + + return self.do_auth(response['accessToken'], response=response, + *args, **kwargs) + except requests.HTTPError as err: + self.process_error(json.loads(err.response.content)) + + def get_user_details(self, response): + response.update({ + 'fullname': response.get('displayName'), + 'picture_url': response.get('pictureUrl') + }) + return response + + 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 user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + try: + response = self.get_json( + self.USER_INFO_URL, + headers={ + "Authorization": "Bearer {}".format(access_token) + } + ) + self.process_error(response) + return response + except requests.HTTPError as err: + self.process_error(err.response.json()) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/linkedin.py b/flex/lib/python3.6/site-packages/social_core/backends/linkedin.py new file mode 100644 index 0000000..77495dc --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/linkedin.py @@ -0,0 +1,98 @@ +""" +LinkedIn OAuth1 and OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/linkedin.html +""" +from .oauth import BaseOAuth1, BaseOAuth2 + + +class BaseLinkedinAuth(object): + EXTRA_DATA = [('id', 'id'), + ('expires_in', 'expires'), + ('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): + headers = self.user_data_headers() or {} + headers['oauth_token'] = access_token + return self.get_json( + self.user_details_url(), + params={'format': 'json'}, + headers=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 + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/live.py b/flex/lib/python3.6/site-packages/social_core/backends/live.py new file mode 100644 index 0000000..e18f3ae --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/live.py @@ -0,0 +1,44 @@ +""" +Live OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/live.html +""" +from .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 + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/livejournal.py b/flex/lib/python3.6/site-packages/social_core/backends/livejournal.py new file mode 100644 index 0000000..8fad03e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/livejournal.py @@ -0,0 +1,27 @@ +""" +LiveJournal OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/livejournal.html +""" +from six.moves.urllib_parse import urlsplit + +from .open_id import OpenIdAuth +from ..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']) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/loginradius.py b/flex/lib/python3.6/site-packages/social_core/backends/loginradius.py new file mode 100644 index 0000000..49bfa55 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/loginradius.py @@ -0,0 +1,69 @@ +""" +LoginRadius BaseOAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/loginradius.html +""" +from .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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/lyft.py b/flex/lib/python3.6/site-packages/social_core/backends/lyft.py new file mode 100644 index 0000000..4a3e26c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/lyft.py @@ -0,0 +1,58 @@ +""" +Lyft OAuth2 backend. Read more about the + API at https://developer.lyft.com/docs +""" +from .oauth import BaseOAuth2 + + +class LyftOAuth2(BaseOAuth2): + name = 'lyft' + ID_KEY = 'id' + SCOPE_SEPARATOR = ' ' + AUTHORIZATION_URL = 'https://api.lyft.com/oauth/authorize' + ACCESS_TOKEN_URL = 'https://api.lyft.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + REFRESH_TOKEN_URL = 'https://api.lyft.com/oauth/token' + USER_DATA_URL = 'https://api.lyft.com/v1/profile' + DEFAULT_SCOPE = ['public', 'profile', 'rides.read', 'rides.request'] + RESPONSE_TYPE = 'code' + STATE_PARAMETER = 'asdf' + EXTRA_DATA = [ + ('id', 'id'), + ('username', 'username'), + ('access_token', 'access_token'), + ('refresh_token', 'refresh_token'), + ('token_type', 'token_type'), + ('expires_in', 'expires_in'), + ('scope', 'scope'), + ] + + def get_user_details(self, response): + """Return user details from Lyft account""" + return { + 'id': response['id'], + 'username': response['id'] + } + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + response = kwargs.pop('response') + return self.get_json(self.USER_DATA_URL, headers={ + 'Authorization': 'Bearer {0}'.format(access_token) + }) + + def auth_complete_params(self, state=None): + client_id, client_secret = self.get_key_and_secret() + return { + 'grant_type': 'authorization_code', + 'code': self.data['code'] + } + + def auth_complete_credentials(self): + return self.get_key_and_secret() + + def refresh_token_params(self, refresh_token, *args, **kwargs): + return { + 'refresh_token': refresh_token, + 'grant_type': 'refresh_token' + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mailchimp.py b/flex/lib/python3.6/site-packages/social_core/backends/mailchimp.py new file mode 100644 index 0000000..e6b3222 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mailchimp.py @@ -0,0 +1,32 @@ +from .oauth import BaseOAuth2 + + +class MailChimpOAuth2(BaseOAuth2): + """MailChimp OAuth2 authentication backend""" + name = 'mailchimp' + AUTHORIZATION_URL = 'https://login.mailchimp.com/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://login.mailchimp.com/oauth2/token' + METADATA_URL = 'https://login.mailchimp.com/oauth2/metadata' + ACCESS_TOKEN_METHOD = 'POST' + STATE_PARAMETER = False + REDIRECT_STATE = False + ID_KEY = 'user_id' + EXTRA_DATA = [ + ('accountname', 'accountname'), + ('api_endpoint', 'api_endpoint'), + ('role', 'role'), + ('login', 'login') + ] + + def get_user_details(self, response): + """Return user details from a Mailchimp metadata response""" + return { + 'username': response['login']['login_name'], + 'email': response['login']['email'] + } + + def user_data(self, access_token, *args, **kwargs): + """Loads user data and datacenter information from service""" + return self.get_json(self.METADATA_URL, headers={ + 'Authorization': 'OAuth ' + access_token + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mailru.py b/flex/lib/python3.6/site-packages/social_core/backends/mailru.py new file mode 100644 index 0000000..aee19c1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mailru.py @@ -0,0 +1,46 @@ +""" +Mail.ru OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/mailru.html +""" +from hashlib import md5 + +from six.moves.urllib_parse import unquote + +from .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] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mapmyfitness.py b/flex/lib/python3.6/site-packages/social_core/backends/mapmyfitness.py new file mode 100644 index 0000000..7c202b9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mapmyfitness.py @@ -0,0 +1,49 @@ +""" +MapMyFitness OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/mapmyfitness.html +""" +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mediawiki.py b/flex/lib/python3.6/site-packages/social_core/backends/mediawiki.py new file mode 100644 index 0000000..c8115d7 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mediawiki.py @@ -0,0 +1,182 @@ +""" +MediaWiki OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/mediawiki.html +""" + +import re +import time +import six +import requests +import jwt + +from six import b +from six.moves.urllib.parse import parse_qs, urlencode, urlparse +from requests_oauthlib import OAuth1 + +from .oauth import BaseOAuth1 +from ..exceptions import AuthException + + +def force_unicode(value): + """ + Return string in unicode. + """ + if isinstance(value, six.text_type): + return value + else: + if six.PY3: + return str(value, "unicode-escape") + else: + return unicode(value, "unicode-escape") + + +class MediaWiki(BaseOAuth1): + """ + Handles the handshake with Mediawiki and fetching of user data. + """ + name = 'mediawiki' + MEDIAWIKI_URL = 'https://meta.wikimedia.org/w/index.php' + SOCIAL_AUTH_MEDIAWIKI_CALLBACK = 'oob' + LEEWAY = 10.0 + + def unauthorized_token(self): + """ + Return request for unauthorized token (first stage) + + Mediawiki request token is requested from e.g.: + * https://en.wikipedia.org/w/index.php?title=Special:OAuth/initiate + """ + params = self.request_token_extra_arguments() + params.update(self.get_scope_argument()) + params['title'] = 'Special:OAuth/initiate' + key, secret = self.get_key_and_secret() + decoding = None if six.PY3 else 'utf-8' + response = self.request( + self.setting('MEDIAWIKI_URL'), + params=params, + auth=OAuth1( + key, + secret, + callback_uri=self.setting('CALLBACK'), + decoding=decoding + ), + method=self.REQUEST_TOKEN_METHOD + ) + + if response.content.decode().startswith('Error'): + raise AuthException(self, response.content.decode()) + + return response.content.decode() + + def oauth_authorization_request(self, token): + """ + Generates the URL for the authorization link + """ + if not isinstance(token, dict): + token = parse_qs(token) + + oauth_token = token.get(self.OAUTH_TOKEN_PARAMETER_NAME)[0] + state = self.get_or_create_state() + base_url = self.setting('MEDIAWIKI_URL') + + return '{0}?{1}'.format(base_url, urlencode({ + 'title': 'Special:Oauth/authenticate', + self.OAUTH_TOKEN_PARAMETER_NAME: oauth_token, + self.REDIRECT_URI_PARAMETER_NAME: self.get_redirect_uri(state) + })) + + def access_token(self, token): + """ + Fetches the Mediawiki access token. + """ + auth_token = self.oauth_auth(token) + + response = requests.post( + url=self.setting('MEDIAWIKI_URL'), + params={'title': 'Special:Oauth/token'}, + auth=auth_token + ) + credentials = parse_qs(response.content) + oauth_token_key = credentials.get(b('oauth_token'))[0] + oauth_token_secret = credentials.get(b('oauth_token_secret'))[0] + oauth_token_key = oauth_token_key.decode() + oauth_token_secret = oauth_token_secret.decode() + + return { + 'oauth_token': oauth_token_key, + 'oauth_token_secret': oauth_token_secret + } + + def get_user_details(self, response): + """ + Gets the user details from Special:OAuth/identify + """ + key, secret = self.get_key_and_secret() + access_token = response['access_token'] + + auth = OAuth1(key, client_secret=secret, + resource_owner_key=access_token['oauth_token'], + resource_owner_secret=access_token['oauth_token_secret']) + + req_resp = requests.post(url=self.setting('MEDIAWIKI_URL'), + params={'title': 'Special:OAuth/identify'}, + auth=auth) + + try: + identity = jwt.decode(req_resp.content, secret, + audience=key, algorithms=['HS256'], + leeway=self.LEEWAY) + except jwt.InvalidTokenError as exception: + raise AuthException( + self, + 'An error occurred while trying to read json ' + + 'content: {0}'.format(exception) + ) + + issuer = urlparse(identity['iss']).netloc + expected_domain = urlparse(self.setting('MEDIAWIKI_URL')).netloc + + if not issuer == expected_domain: + raise AuthException( + self, + 'Unexpected issuer {0}, expected {1}'.format( + issuer, + expected_domain + ) + ) + + now = time.time() + issued_at = float(identity['iat']) + if not now >= (issued_at - self.LEEWAY): + raise AuthException( + self, + 'Identity issued {0} seconds in the future'.format( + issued_at - now + ) + ) + + authorization_header = force_unicode( + req_resp.request.headers['Authorization'] + ) + request_nonce = re.search(r'oauth_nonce="(.*?)"', + authorization_header).group(1) + + if identity['nonce'] != request_nonce: + raise AuthException( + self, + 'Replay attack detected: {0} != {1}'.format( + identity['nonce'], + request_nonce + ) + ) + + return { + 'username': identity['username'], + 'userID': identity['sub'] + } + + def get_user_id(self, details, response): + """ + Get the unique Mediawiki user ID. + """ + return details['userID'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/meetup.py b/flex/lib/python3.6/site-packages/social_core/backends/meetup.py new file mode 100644 index 0000000..4c9cd8f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/meetup.py @@ -0,0 +1,34 @@ +""" +Meetup OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/meetup.html +""" +from .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mendeley.py b/flex/lib/python3.6/site-packages/social_core/backends/mendeley.py new file mode 100644 index 0000000..d44601f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mendeley.py @@ -0,0 +1,67 @@ +""" +Mendeley OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/mendeley.html +""" +from .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)} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/microsoft.py b/flex/lib/python3.6/site-packages/social_core/backends/microsoft.py new file mode 100644 index 0000000..0b5a495 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/microsoft.py @@ -0,0 +1,78 @@ +import time + +from jwt import DecodeError, ExpiredSignature + +from ..exceptions import AuthTokenError +from .oauth import BaseOAuth2 + +""" +OAuth2 Backend to work with microsoft graph. +""" + + +class MicrosoftOAuth2(BaseOAuth2): + name = 'microsoft-graph' + SCOPE_SEPARATOR = ' ' + AUTHORIZATION_URL = \ + 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' + ACCESS_TOKEN_URL = \ + 'https://login.microsoftonline.com/common/oauth2/v2.0/token' + + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + DEFAULT_SCOPE = ['User.Read'] + + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + self.process_error(self.data) + state = self.validate_state() + + 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) + + def get_user_id(self, details, response): + """Use user account id as unique id""" + return response.get('id') + + def get_user_details(self, response): + """Return user details from Microsoft online account""" + return {'username': response.get('displayName', ''), + 'email': response.get('mail'), + 'fullname': response.get('displayName', ''), + 'first_name': response.get('givenName', ''), + 'last_name': response.get('surname', '')} + + def user_data(self, access_token, *args, **kwargs): + """Return user data by querying Microsoft service""" + try: + return self.get_json( + 'https://graph.microsoft.com/v1.0/me', + headers={ + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json', + 'Authorization': 'Bearer ' + access_token + }, + method='GET' + ) + except (DecodeError, ExpiredSignature) as error: + raise AuthTokenError(self, error) + + 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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mineid.py b/flex/lib/python3.6/site-packages/social_core/backends/mineid.py new file mode 100644 index 0000000..1ac415e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mineid.py @@ -0,0 +1,38 @@ +from .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'), + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/mixcloud.py b/flex/lib/python3.6/site-packages/social_core/backends/mixcloud.py new file mode 100644 index 0000000..8b1b77e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/mixcloud.py @@ -0,0 +1,26 @@ +""" +Mixcloud OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/mixcloud.html +""" +from .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'}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/monzo.py b/flex/lib/python3.6/site-packages/social_core/backends/monzo.py new file mode 100644 index 0000000..46b5ac6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/monzo.py @@ -0,0 +1,32 @@ +from .oauth import BaseOAuth2 + + +class MonzoOAuth2(BaseOAuth2): + """ + Monzo OAuth2 authentication backend. + """ + + name = 'monzo' + + AUTHORIZATION_URL = 'https://auth.getmondo.co.uk/' + ACCESS_TOKEN_URL = 'https://api.monzo.com/oauth2/token' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + + def get_user_details(self, response): + fullname, first_name, last_name = self.get_user_names( + response['accounts'][0]['description'], + ) + + return { + 'username': str(response.get('user_id')), + 'fullname': fullname, + 'first_name': first_name, + 'last_name': last_name, + } + + def user_data(self, access_token, *args, **kwargs): + return self.get_json( + 'https://api.monzo.com/accounts', + headers={'Authorization': 'Bearer {0}'.format(access_token)}, + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/moves.py b/flex/lib/python3.6/site-packages/social_core/backends/moves.py new file mode 100644 index 0000000..0ba1ddd --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/moves.py @@ -0,0 +1,30 @@ +""" +Moves OAuth2 backend, docs at: + https://dev.moves-app.com/docs/authentication + +Written by Avi Alkalay +Certified to work with Django 1.6 +""" +from .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': str(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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/nationbuilder.py b/flex/lib/python3.6/site-packages/social_core/backends/nationbuilder.py new file mode 100644 index 0000000..64dc28f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/nationbuilder.py @@ -0,0 +1,48 @@ +""" +NationBuilder OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/nationbuilder.html +""" +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/naver.py b/flex/lib/python3.6/site-packages/social_core/backends/naver.py new file mode 100644 index 0000000..6229ba5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/naver.py @@ -0,0 +1,60 @@ +from xml.dom import minidom + +from .oauth import BaseOAuth2 + + +class NaverOAuth2(BaseOAuth2): + """Naver OAuth authentication backend""" + name = 'naver' + AUTHORIZATION_URL = 'https://nid.naver.com/oauth2.0/authorize' + ACCESS_TOKEN_URL = 'https://nid.naver.com/oauth2.0/token' + ACCESS_TOKEN_METHOD = 'POST' + EXTRA_DATA = [ + ('id', 'id'), + ] + + def get_user_id(self, details, response): + return response.get('id') + + def get_user_details(self, response): + """Return user details from Naver account""" + return { + 'username': response.get('username'), + 'email': response.get('email'), + 'fullname': response.get('username'), + } + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + response = self.request( + 'https://openapi.naver.com/v1/nid/getUserProfile.xml', + headers={ + 'Authorization': 'Bearer {0}'.format(access_token), + 'Content_Type': 'text/xml' + } + ) + + dom = minidom.parseString(response.text.encode('utf-8').strip()) + + return { + 'id': self._dom_value(dom, 'id'), + 'email': self._dom_value(dom, 'email'), + 'username': self._dom_value(dom, 'name'), + 'nickname': self._dom_value(dom, 'nickname'), + 'gender': self._dom_value(dom, 'gender'), + 'age': self._dom_value(dom, 'age'), + 'birthday': self._dom_value(dom, 'birthday'), + 'profile_image': self._dom_value(dom, 'profile_image') + } + + def auth_headers(self): + client_id, client_secret = self.get_key_and_secret() + return { + 'grant_type': 'authorization_code', + 'code': self.data.get('code'), + 'client_id': client_id, + 'client_secret': client_secret, + } + + def _dom_value(self, dom, key): + return dom.getElementsByTagName(key)[0].childNodes[0].data diff --git a/flex/lib/python3.6/site-packages/social_core/backends/ngpvan.py b/flex/lib/python3.6/site-packages/social_core/backends/ngpvan.py new file mode 100644 index 0000000..8e25570 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/ngpvan.py @@ -0,0 +1,66 @@ +""" +NGP VAN's `ActionID` Provider + +http://developers.ngpvan.com/action-id +""" +from openid.extensions import ax + +from .open_id import OpenIdAuth + + +class ActionIDOpenID(OpenIdAuth): + """ + NGP VAN's ActionID OpenID 1.1 authentication backend + """ + name = 'actionid-openid' + URL = 'https://accounts.ngpvan.com/Home/Xrds' + USERNAME_KEY = 'email' + + def get_ax_attributes(self): + """ + Return the AX attributes that ActionID responds with, as well as the + user data result that it must map to. + """ + return [ + ('http://openid.net/schema/contact/internet/email', 'email'), + ('http://openid.net/schema/contact/phone/business', 'phone'), + ('http://openid.net/schema/namePerson/first', 'first_name'), + ('http://openid.net/schema/namePerson/last', 'last_name'), + ('http://openid.net/schema/namePerson', 'fullname'), + ] + + def setup_request(self, params=None): + """ + Setup the OpenID request + + Because ActionID does not advertise the availiability of AX attributes + nor use standard attribute aliases, we need to setup the attributes + manually instead of rely on the parent OpenIdAuth.setup_request() + """ + request = self.openid_request(params) + + fetch_request = ax.FetchRequest() + fetch_request.add(ax.AttrInfo( + 'http://openid.net/schema/contact/internet/email', + alias='ngpvanemail', + required=True + )) + + fetch_request.add(ax.AttrInfo( + 'http://openid.net/schema/contact/phone/business', + alias='ngpvanphone', + required=False + )) + fetch_request.add(ax.AttrInfo( + 'http://openid.net/schema/namePerson/first', + alias='ngpvanfirstname', + required=False + )) + fetch_request.add(ax.AttrInfo( + 'http://openid.net/schema/namePerson/last', + alias='ngpvanlastname', + required=False + )) + request.addExtension(fetch_request) + + return request diff --git a/flex/lib/python3.6/site-packages/social_core/backends/nk.py b/flex/lib/python3.6/site-packages/social_core/backends/nk.py new file mode 100644 index 0000000..30cd52f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/nk.py @@ -0,0 +1,76 @@ +from urllib import urlencode + +import six + +from requests_oauthlib import OAuth1 + +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/oauth.py b/flex/lib/python3.6/site-packages/social_core/backends/oauth.py new file mode 100644 index 0000000..9f39842 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/oauth.py @@ -0,0 +1,436 @@ +import six + +from requests_oauthlib import OAuth1 +from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER + +from six.moves.urllib_parse import urlencode, unquote + +from ..utils import url_add_parameters, parse_qs, handle_http_errors, \ + constant_time_compare +from ..exceptions import AuthFailed, AuthCanceled, AuthUnknownError, \ + AuthMissingParameter, AuthStateMissing, \ + AuthStateForbidden, AuthTokenError +from .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 constant_time_compare(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 extra_data(self, user, uid, response, details=None, *args, **kwargs): + """Return access_token, token_type, and extra defined names to store in + extra_data field""" + data = super(BaseOAuth2, self).extra_data(user, uid, response, + details=details, + *args, **kwargs) + data['token_type'] = response.get('token_type') or \ + kwargs.get('token_type') + return data + + 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 login process, must return user instance""" + self.process_error(self.data) + state = self.validate_state() + + 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 {}) + if 'access_token' not in response: + response['access_token'] = access_token + 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() diff --git a/flex/lib/python3.6/site-packages/social_core/backends/odnoklassniki.py b/flex/lib/python3.6/site-packages/social_core/backends/odnoklassniki.py new file mode 100644 index 0000000..b8f80e8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/odnoklassniki.py @@ -0,0 +1,172 @@ +""" +Odnoklassniki OAuth2 and Iframe Application backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/odnoklassnikiru.html +""" +from hashlib import md5 + +from six.moves.urllib_parse import unquote + +from .base import BaseAuth +from .oauth import BaseOAuth2 +from ..exceptions import AuthFailed + + +class OdnoklassnikiOAuth2(BaseOAuth2): + """Odnoklassniki authentication backend""" + name = 'odnoklassniki-oauth2' + ID_KEY = 'uid' + ACCESS_TOKEN_METHOD = 'POST' + SCOPE_SEPARATOR = ';' + AUTHORIZATION_URL = 'https://connect.ok.ru/oauth/authorize' + ACCESS_TOKEN_URL = 'https://api.ok.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': response.get('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, 'https://api.ok.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 + https://apiok.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: + https://apiok.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 + https://apiok.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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/open_id.py b/flex/lib/python3.6/site-packages/social_core/backends/open_id.py new file mode 100644 index 0000000..82aeb01 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/open_id.py @@ -0,0 +1,263 @@ +import datetime +from calendar import timegm + +import six + +from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE +from openid.consumer.discover import DiscoveryFailure +from openid.extensions import sreg, ax, pape + +from ..utils import url_add_parameters, cache +from .base import BaseAuth +from .oauth import BaseOAuth2 +from ..exceptions import AuthException, AuthFailed, AuthCanceled, \ + AuthUnknownError, AuthMissingParameter, \ + AuthTokenError + + +# 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, partial): + """Continue previous halted pipeline""" + response = self.consumer().complete(dict(self.data.items()), + self.strategy.absolute_uri( + self.redirect_uri + )) + return self.strategy.authenticate(self, + response=response, + pipeline_index=partial.next_step, + *partial.args, + **partial.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) + return self.strategy.authenticate(self, response=response, + *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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/open_id_connect.py b/flex/lib/python3.6/site-packages/social_core/backends/open_id_connect.py new file mode 100644 index 0000000..4ba8baf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/open_id_connect.py @@ -0,0 +1,197 @@ +import datetime +from calendar import timegm + +import six + +from jwkest import JWKESTException +from jwkest.jwk import KEYS +from jwkest.jws import JWS + +from .oauth import BaseOAuth2 +from ..utils import cache +from ..exceptions import AuthTokenError + + +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. + """ + # Override OIDC_ENDPOINT in your subclass to enable autoconfig of OIDC + OIDC_ENDPOINT = None + ID_TOKEN_MAX_AGE = 600 + DEFAULT_SCOPE = ['openid', 'profile', 'email'] + EXTRA_DATA = ['id_token', 'refresh_token', ('sub', 'id')] + REDIRECT_STATE = False + ACCESS_TOKEN_METHOD = 'POST' + REVOKE_TOKEN_METHOD = 'GET' + ID_KEY = 'sub' + USERNAME_KEY = 'preferred_username' + ID_TOKEN_ISSUER = '' + ACCESS_TOKEN_URL = '' + AUTHORIZATION_URL = '' + REVOKE_TOKEN_URL = '' + USERINFO_URL = '' + JWKS_URI = '' + + def __init__(self, *args, **kwargs): + self.id_token = None + super(OpenIdConnectAuth, self).__init__(*args, **kwargs) + + def authorization_url(self): + return self.AUTHORIZATION_URL or \ + self.oidc_config().get('authorization_endpoint') + + def access_token_url(self): + return self.ACCESS_TOKEN_URL or \ + self.oidc_config().get('token_endpoint') + + def revoke_token_url(self, token, uid): + return self.REVOKE_TOKEN_URL or \ + self.oidc_config().get('revocation_endpoint') + + def id_token_issuer(self): + return self.ID_TOKEN_ISSUER or \ + self.oidc_config().get('issuer') + + def userinfo_url(self): + return self.USERINFO_URL or \ + self.oidc_config().get('userinfo_endpoint') + + def jwks_uri(self): + return self.JWKS_URI or \ + self.oidc_config().get('jwks_uri') + + @cache(ttl=86400) + def oidc_config(self): + return self.get_json(self.OIDC_ENDPOINT + + '/.well-known/openid-configuration') + + @cache(ttl=86400) + def get_jwks_keys(self): + keys = KEYS() + keys.load_from_url(self.jwks_uri()) + + # Add client secret as oct key so it can be used for HMAC signatures + client_id, client_secret = self.get_key_and_secret() + keys.add({'key': client_secret, 'kty': 'oct'}) + return keys + + 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 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.authorization_url(), + handle=nonce + )[0] + except IndexError: + pass + + def remove_nonce(self, nonce_id): + self.strategy.storage.association.remove([nonce_id]) + + def validate_claims(self, id_token): + if id_token['iss'] != self.id_token_issuer(): + raise AuthTokenError(self, 'Invalid issuer') + + client_id, client_secret = self.get_key_and_secret() + + if isinstance(id_token['aud'], six.string_types): + id_token['aud'] = [id_token['aud']] + + if client_id not in id_token['aud']: + raise AuthTokenError(self, 'Invalid audience') + + if len(id_token['aud']) > 1 and 'azp' not in id_token: + raise AuthTokenError(self, 'Incorrect id_token: azp') + + if 'azp' in id_token and id_token['azp'] != client_id: + raise AuthTokenError(self, 'Incorrect id_token: azp') + + utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple()) + if utc_timestamp > id_token['exp']: + raise AuthTokenError(self, 'Signature has expired') + + if 'nbf' in id_token and utc_timestamp < id_token['nbf']: + raise AuthTokenError(self, 'Incorrect id_token: nbf') + + # Verify the token was issued in the last 10 minutes + iat_leeway = self.setting('ID_TOKEN_MAX_AGE', self.ID_TOKEN_MAX_AGE) + if utc_timestamp > id_token['iat'] + iat_leeway: + 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') + + def validate_and_return_id_token(self, jws): + """ + Validates the id_token according to the steps at + http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation. + """ + try: + # Decode the JWT and raise an error if the sig is invalid + id_token = JWS().verify_compact(jws.encode('utf-8'), + self.get_jwks_keys()) + except JWKESTException: + raise AuthTokenError(self, 'Signature verification failed') + + self.validate_claims(id_token) + + 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 + + def user_data(self, access_token, *args, **kwargs): + return self.get_json(self.userinfo_url(), headers={ + 'Authorization': 'Bearer {0}'.format(access_token) + }) + + def get_user_details(self, response): + username_key = self.setting('USERNAME_KEY', default=self.USERNAME_KEY) + return { + 'username': response.get(username_key), + 'email': response.get('email'), + 'fullname': response.get('name'), + 'first_name': response.get('given_name'), + 'last_name': response.get('family_name'), + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/openshift.py b/flex/lib/python3.6/site-packages/social_core/backends/openshift.py new file mode 100644 index 0000000..0417bc1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/openshift.py @@ -0,0 +1,39 @@ +""" +Openshift OAuth2 backend +""" +import requests + +from six.moves.urllib.parse import urljoin + +from ..utils import append_slash +from .oauth import BaseOAuth2 + + +class OpenshiftOAuth2(BaseOAuth2): + name = 'openshift' + ACCESS_TOKEN_METHOD = 'POST' + + def access_token_url(self): + return urljoin(append_slash(self.setting('URL')), 'oauth/token') + + def authorization_url(self): + return urljoin(append_slash(self.setting('URL')), 'oauth/authorize') + + def get_user_id(self, details, response): + return response['metadata']['uid'] + + def get_user_details(self, response): + """Return user details from openshift account""" + username = response['metadata']['name'] + email = response['metadata']['name'] + return {'username': username, + 'email': email} + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + headers = {'Authorization': 'Bearer ' + access_token} + + return requests.get( + urljoin(append_slash(self.setting('URL')), 'oapi/v1/users/~'), + headers=headers + ).json() diff --git a/flex/lib/python3.6/site-packages/social_core/backends/openstreetmap.py b/flex/lib/python3.6/site-packages/social_core/backends/openstreetmap.py new file mode 100644 index 0000000..c44431f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/openstreetmap.py @@ -0,0 +1,57 @@ +""" +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 .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 + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/orbi.py b/flex/lib/python3.6/site-packages/social_core/backends/orbi.py new file mode 100644 index 0000000..453581a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/orbi.py @@ -0,0 +1,42 @@ +""" +Orbi OAuth2 backend +""" +from .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 + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/orcid.py b/flex/lib/python3.6/site-packages/social_core/backends/orcid.py new file mode 100644 index 0000000..69f41ff --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/orcid.py @@ -0,0 +1,75 @@ +""" +ORCID OAuth2 Application backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/orcid.html +""" +from .oauth import BaseOAuth2 + + +class ORCIDOAuth2(BaseOAuth2): + """ORCID OAuth2 authentication backend""" + name = 'orcid' + ID_KEY = 'orcid' + AUTHORIZATION_URL = 'https://orcid.org/oauth/authorize' + ACCESS_TOKEN_URL = 'https://orcid.org/oauth/token' + USER_DATA_URL = 'https://pub.orcid.org/v2.0/{}' + DEFAULT_SCOPE = ['/authenticate'] + ACCESS_TOKEN_METHOD = 'POST' + EXTRA_DATA = [ + ('orcid', 'id'), + ('expires_in', 'expires'), + ('refresh_token', 'refresh_token') + ] + + def auth_params(self, state=None): + params = super(ORCIDOAuth2, self).auth_params(state) + return params + + def get_user_details(self, response): + """Return user details from ORCID account""" + fullname= response.get('name', '') + first_name = last_name = email = '' + person = response.get('person') + if person: + name = person.get('name') + if name: + first_name = name.get('given-names', {}).get('value', '') + last_name = name.get('family-name', {}).get('value', '') + + emails = person.get('emails') + if emails: + emails_list = emails.get('email') + if emails_list and len(emails_list) > 0: + email = emails_list[0].get('email', '') + + if len(emails_list) > 1: + for email_dict in emails_list: + if email_dict.get('primary','') == True: + email = email_dict.get('email', '') + break + else: + mail = emails_list[0].get('email', '') + + return {'username': response.get('orcid'), + '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""" + params = self.setting('PROFILE_EXTRA_PARAMS', {}) + params['access_token'] = access_token + try: + return self.get_json(self.USER_DATA_URL.format( + kwargs['response']['orcid']), + headers={'Content-Type': 'application/json'}, + params=params) + except ValueError as e: + return None + + +class ORCIDOAuth2Sandbox(ORCIDOAuth2): + name = 'orcid-sandbox' + AUTHORIZATION_URL = 'https://sandbox.orcid.org/oauth/authorize' + ACCESS_TOKEN_URL = 'https://sandbox.orcid.org/oauth/token' + USER_DATA_URL = 'https://pub.sandbox.orcid.org/v2.0/{}' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/patreon.py b/flex/lib/python3.6/site-packages/social_core/backends/patreon.py new file mode 100644 index 0000000..5cee35b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/patreon.py @@ -0,0 +1,41 @@ +""" +Patreon OAuth2 backend +https://www.patreon.com/platform/documentation/oauth +""" +from .oauth import BaseOAuth2 + + +class PatreonOAuth2(BaseOAuth2): + """Patreon OAuth2 authentication backend""" + name = 'patreon' + AUTHORIZATION_URL = 'https://www.patreon.com/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://api.patreon.com/oauth2/token' + REVOKE_TOKEN_URL = 'https://api.patreon.com/oauth2/revoke' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + ID_KEY = 'id' + EXTRA_DATA = [ + ('id', 'id'), + ] + + def get_user_details(self, response): + details = response['attributes'] + return { + 'username': details.get('full_name'), + 'email': details.get('email'), + 'fullname': details.get('full_name'), + 'first_name': details.get('first_name'), + 'last_name': details.get('last_name'), + } + + def user_data(self, access_token, *args, **kwargs): + return self.get_api(access_token, 'current_user')['data'] + + def get_api(self, access_token, suffix): + return self.get_json( + 'https://api.patreon.com/oauth2/api/{}'.format(suffix), + headers=self.get_auth_header(access_token) + ) + + def get_auth_header(self, access_token): + return {'Authorization': 'Bearer {0}'.format(access_token)} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/persona.py b/flex/lib/python3.6/site-packages/social_core/backends/persona.py new file mode 100644 index 0000000..9d150ac --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/persona.py @@ -0,0 +1,50 @@ +""" +Mozilla Persona authentication backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/persona.html +""" +from ..utils import handle_http_errors +from .base import BaseAuth +from ..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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/pinterest.py b/flex/lib/python3.6/site-packages/social_core/backends/pinterest.py new file mode 100644 index 0000000..eb5d2ce --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/pinterest.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +""" +Pinterest OAuth2 backend, docs at: + https://developers.pinterest.com/docs/api/authentication/ +""" + +from __future__ import unicode_literals + +import ssl + +from .oauth import BaseOAuth2 + + +class PinterestOAuth2(BaseOAuth2): + name = 'pinterest' + ID_KEY = 'user_id' + AUTHORIZATION_URL = 'https://api.pinterest.com/oauth/' + ACCESS_TOKEN_URL = 'https://api.pinterest.com/v1/oauth/token' + REDIRECT_STATE = False + ACCESS_TOKEN_METHOD = 'POST' + SSL_PROTOCOL = ssl.PROTOCOL_TLSv1 + + def user_data(self, access_token, *args, **kwargs): + response = self.get_json('https://api.pinterest.com/v1/me/', + params={'access_token': access_token}) + + if 'data' in response: + username = response['data']['url'].strip('/').split('/')[-1] + response = { + 'user_id': response['data']['id'], + 'first_name': response['data']['first_name'], + 'last_name': response['data']['last_name'], + 'username': username, + } + return response + + 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.get('username'), + 'email': None, + 'fullname': fullname, + 'first_name': first_name, + 'last_name': last_name} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/pixelpin.py b/flex/lib/python3.6/site-packages/social_core/backends/pixelpin.py new file mode 100644 index 0000000..fc7308c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/pixelpin.py @@ -0,0 +1,33 @@ +from .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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/pocket.py b/flex/lib/python3.6/site-packages/social_core/backends/pocket.py new file mode 100644 index 0000000..130eff8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/pocket.py @@ -0,0 +1,45 @@ +""" +Pocket OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/pocket.html +""" +from .base import BaseAuth +from ..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('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('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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/podio.py b/flex/lib/python3.6/site-packages/social_core/backends/podio.py new file mode 100644 index 0000000..c022236 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/podio.py @@ -0,0 +1,38 @@ +""" +Podio OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/podio.html +""" +from .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/professionali.py b/flex/lib/python3.6/site-packages/social_core/backends/professionali.py new file mode 100644 index 0000000..15f3977 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/professionali.py @@ -0,0 +1,55 @@ +# -*- 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 ..utils import parse_qs +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/pushbullet.py b/flex/lib/python3.6/site-packages/social_core/backends/pushbullet.py new file mode 100644 index 0000000..d821223 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/pushbullet.py @@ -0,0 +1,23 @@ +import base64 + +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/qiita.py b/flex/lib/python3.6/site-packages/social_core/backends/qiita.py new file mode 100644 index 0000000..adf820c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/qiita.py @@ -0,0 +1,66 @@ +""" +Qiita OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/qiita.html + http://qiita.com/api/v2/docs#get-apiv2oauthauthorize +""" +import json + +from .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) + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/qq.py b/flex/lib/python3.6/site-packages/social_core/backends/qq.py new file mode 100644 index 0000000..9bbe39e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/qq.py @@ -0,0 +1,71 @@ +""" +Created on May 13, 2014 + +@author: Yong Zhang (zyfyfe@gmail.com) +""" + +import json + +from ..utils import parse_qs +from .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 user detail from QQ account sometimes nickname will duplicate + with another qq account, to avoid this issue it's possible to use + openid as username. + """ + if self.setting('USE_OPENID_AS_USERNAME', False): + username = response.get('openid', '') + else: + username = response.get('nickname', '') + + fullname, first_name, last_name = self.get_user_names( + first_name=response.get('nickname', '') + ) + + return { + 'username': username, + 'fullname': fullname, + 'first_name': first_name, + 'last_name': last_name + } + + def get_openid(self, access_token): + response = self.request(self.OPENID_URL, params={ + 'access_token': access_token + }) + content = response.content.decode() + data = json.loads(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('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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/quizlet.py b/flex/lib/python3.6/site-packages/social_core/backends/quizlet.py new file mode 100644 index 0000000..54d6cba --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/quizlet.py @@ -0,0 +1,23 @@ +""" +Quizlet OAuth2 Sign-in backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/quizlet.html +""" +from .oauth import BaseOAuth2 + + +class QuizletOAuth2(BaseOAuth2): + """Quizlet OAuth2""" + name = 'quizlet' + ID_KEY = 'user_id' + API_URL = 'https://api.quizlet.com/2.0/' + AUTHORIZATION_URL = 'https://quizlet.com/authorize' + ACCESS_TOKEN_URL = 'https://api.quizlet.com/oauth/token' + ACCESS_TOKEN_METHOD = 'POST' + SCOPE_SEPARATOR = ' ' + DEFAULT_SCOPE = ['read'] + + def get_user_details(self, response): + """Return user details from Quizlet account""" + return { + 'username': response.get('user_id') + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/rdio.py b/flex/lib/python3.6/site-packages/social_core/backends/rdio.py new file mode 100644 index 0000000..60e9f60 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/rdio.py @@ -0,0 +1,72 @@ +""" +Rdio OAuth1 and OAuth2 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/rdio.html +""" +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/readability.py b/flex/lib/python3.6/site-packages/social_core/backends/readability.py new file mode 100644 index 0000000..593117f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/readability.py @@ -0,0 +1,35 @@ +""" +Readability OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/readability.html +""" +from .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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/reddit.py b/flex/lib/python3.6/site-packages/social_core/backends/reddit.py new file mode 100644 index 0000000..e2919bf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/reddit.py @@ -0,0 +1,56 @@ +""" +Reddit OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/reddit.html +""" +import base64 + +from .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': b'Basic ' + 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 + + def auth_complete_credentials(self): + return self.get_key_and_secret() diff --git a/flex/lib/python3.6/site-packages/social_core/backends/runkeeper.py b/flex/lib/python3.6/site-packages/social_core/backends/runkeeper.py new file mode 100644 index 0000000..18c2eec --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/runkeeper.py @@ -0,0 +1,47 @@ +""" +RunKeeper OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/runkeeper.html +""" +from .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}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/salesforce.py b/flex/lib/python3.6/site-packages/social_core/backends/salesforce.py new file mode 100644 index 0000000..c1e689d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/salesforce.py @@ -0,0 +1,49 @@ +from six.moves.urllib_parse import urlencode + +from .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' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/saml.py b/flex/lib/python3.6/site-packages/social_core/backends/saml.py new file mode 100644 index 0000000..ac43bf9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/saml.py @@ -0,0 +1,335 @@ +""" +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 .base import BaseAuth +from ..exceptions import AuthFailed, AuthMissingParameter + +# 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) + value = attributes[key] if key in attributes else None + if isinstance(value, list): + value = value[0] + return value + + @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" + EXTRA_DATA = [] + + 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=None): + """ + 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 if idp else {}, + '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. + + Returns (metadata XML string, list of errors) + + Example usage (Django): + from ..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)) + """ + config = self.generate_saml_config() + saml_settings = OneLogin_Saml2_Settings( + config, + sp_validation_only=True + ) + 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""" + try: + idp_name = self.strategy.request_data()['idp'] + except KeyError: + raise AuthMissingParameter(self, '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 '{0}:{1}'.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: {0} ({1})'.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 extra_data(self, user, uid, response, details=None, *args, **kwargs): + return super(SAMLAuth, self).extra_data(user, uid, + response['attributes'], + details=details, + *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_core.exceptions.AuthForbidden if the user should not + be authenticated, or do nothing to allow the login pipeline to + continue. + """ + pass diff --git a/flex/lib/python3.6/site-packages/social_core/backends/shimmering.py b/flex/lib/python3.6/site-packages/social_core/backends/shimmering.py new file mode 100644 index 0000000..bc9a18c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/shimmering.py @@ -0,0 +1,36 @@ +""" +Shimmering Oauth +""" +from .oauth import BaseOAuth2 + + +class ShimmeringOAuth2(BaseOAuth2): + """Shimmering Verify OAuth2 authentication backend""" + name = 'shimmering' + ID_KEY = 'id' + AUTHORIZATION_URL = 'http://developers.shimmeringverify.com/o/authorize/' + ACCESS_TOKEN_URL = 'http://developers.shimmeringverify.com/o/token/' + ACCESS_TOKEN_METHOD = 'POST' + + def get_user_details(self, response): + """Return user details from Shimmering""" + first_name = response.get('first_name') + last_name = response.get('last_name') + email = response.get('email') + username = response.get('username') + fullname = '{} {}'.format(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""" + headers = {'Authorization': 'Bearer %s' % access_token} + return self.get_json( + 'http://developers.shimmeringverify.com/user_info/', + headers=headers + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/shopify.py b/flex/lib/python3.6/site-packages/social_core/backends/shopify.py new file mode 100644 index 0000000..11031c6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/shopify.py @@ -0,0 +1,92 @@ +""" +Shopify OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/shopify.html +""" +import imp +import six + +from ..utils import handle_http_errors +from .oauth import BaseOAuth2 +from ..exceptions import AuthFailed, AuthCanceled + + +class ShopifyOAuth2(BaseOAuth2): + """Shopify OAuth2 authentication backend""" + name = 'shopify' + ID_KEY = 'shop' + EXTRA_DATA = [ + ('shop', 'shop'), + ('website', 'website'), + ('expires', 'expires') + ] + REDIRECT_STATE = False + + @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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/sketchfab.py b/flex/lib/python3.6/site-packages/social_core/backends/sketchfab.py new file mode 100644 index 0000000..5a8d0c6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/sketchfab.py @@ -0,0 +1,39 @@ +""" +Sketchfab OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/sketchfab.html + https://sketchfab.com/developers/oauth +""" +from .oauth import BaseOAuth2 + + +class SketchfabOAuth2(BaseOAuth2): + name = 'sketchfab' + ID_KEY = 'uid' + AUTHORIZATION_URL = 'https://sketchfab.com/oauth2/authorize/' + ACCESS_TOKEN_URL = 'https://sketchfab.com/oauth2/token/' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + REQUIRES_EMAIL_VALIDATION = False + EXTRA_DATA = [ + ('username', 'username'), + ('apiToken', 'apiToken') + ] + + def get_user_details(self, response): + """Return user details from Sketchfab account""" + user_data = response + email = user_data.get('email', '') + username = user_data['username'] + name = user_data.get('displayName', '') + fullname, first_name, last_name = self.get_user_names(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://sketchfab.com/v2/users/me', headers={ + 'Authorization': 'Bearer {0}'.format(access_token) + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/skyrock.py b/flex/lib/python3.6/site-packages/social_core/backends/skyrock.py new file mode 100644 index 0000000..98906b6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/skyrock.py @@ -0,0 +1,32 @@ +""" +Skyrock OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/skyrock.html +""" +from .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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/slack.py b/flex/lib/python3.6/site-packages/social_core/backends/slack.py new file mode 100644 index 0000000..d4034a3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/slack.py @@ -0,0 +1,59 @@ +""" +Slack OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/slack.html + https://api.slack.com/docs/oauth +""" +from .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' + DEFAULT_SCOPE = ['identity.basic', 'identity.email'] + SCOPE_SEPARATOR = ',' + REDIRECT_STATE = False + EXTRA_DATA = [ + ('id', 'id'), + ('name', 'name'), + ('real_name', 'real_name') + ] + + def auth_extra_arguments(self): + params = super(SlackOAuth2, self).auth_extra_arguments() or {} + if self.setting('TEAM'): + params['team'] = self.setting('TEAM') + return params + + 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 + user = response['user'] + team = response.get('team') + name = user['name'] + email = user.get('email') + username = email and email.split('@', 1)[0] or name + fullname, first_name, last_name = self.get_user_names(name) + + if self.setting('USERNAME_WITH_TEAM', True) and team and \ + 'name' in team: + name = '{0}@{1}'.format(name, response['team']['name']) + + return { + 'username': username, + '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""" + response = self.get_json('https://slack.com/api/users.identity', + params={'token': access_token}) + if not response.get('id', None): + response['id'] = response['user']['id'] + return response diff --git a/flex/lib/python3.6/site-packages/social_core/backends/soundcloud.py b/flex/lib/python3.6/site-packages/social_core/backends/soundcloud.py new file mode 100644 index 0000000..341eaaf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/soundcloud.py @@ -0,0 +1,56 @@ +""" +Soundcloud OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/soundcloud.html +""" +from six.moves.urllib_parse import urlencode + +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/spotify.py b/flex/lib/python3.6/site-packages/social_core/backends/spotify.py new file mode 100644 index 0000000..cc0e67e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/spotify.py @@ -0,0 +1,47 @@ +""" +Spotify backend, docs at: + https://developer.spotify.com/spotify-web-api/ + https://developer.spotify.com/spotify-web-api/authorization-guide/ +""" +import base64 + +from .oauth import BaseOAuth2 + + +class SpotifyOAuth2(BaseOAuth2): + """Spotify OAuth2 authentication backend""" + name = 'spotify' + ID_KEY = 'id' + AUTHORIZATION_URL = 'https://accounts.spotify.com/authorize' + ACCESS_TOKEN_URL = 'https://accounts.spotify.com/api/token' + ACCESS_TOKEN_METHOD = 'POST' + SCOPE_SEPARATOR = ' ' + 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)} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/stackoverflow.py b/flex/lib/python3.6/site-packages/social_core/backends/stackoverflow.py new file mode 100644 index 0000000..56e2421 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/stackoverflow.py @@ -0,0 +1,43 @@ +""" +Stackoverflow OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/stackoverflow.html +""" +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/steam.py b/flex/lib/python3.6/site-packages/social_core/backends/steam.py new file mode 100644 index 0000000..5627a7d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/steam.py @@ -0,0 +1,47 @@ +""" +Steam OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/steam.html +""" +from .open_id import OpenIdAuth +from ..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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/stocktwits.py b/flex/lib/python3.6/site-packages/social_core/backends/stocktwits.py new file mode 100644 index 0000000..c657573 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/stocktwits.py @@ -0,0 +1,37 @@ +""" +Stocktwits OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/stocktwits.html +""" +from .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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/strava.py b/flex/lib/python3.6/site-packages/social_core/backends/strava.py new file mode 100644 index 0000000..b4a8a59 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/strava.py @@ -0,0 +1,46 @@ +""" +Strava OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/strava.html +""" +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/stripe.py b/flex/lib/python3.6/site-packages/social_core/backends/stripe.py new file mode 100644 index 0000000..54d3cbd --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/stripe.py @@ -0,0 +1,46 @@ +""" +Stripe OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/stripe.html +""" +from .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_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'} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/suse.py b/flex/lib/python3.6/site-packages/social_core/backends/suse.py new file mode 100644 index 0000000..595cfc2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/suse.py @@ -0,0 +1,17 @@ +""" +Open Suse OpenId backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/suse.html +""" +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/taobao.py b/flex/lib/python3.6/site-packages/social_core/backends/taobao.py new file mode 100644 index 0000000..0db7f26 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/taobao.py @@ -0,0 +1,26 @@ +from .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')} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/thisismyjam.py b/flex/lib/python3.6/site-packages/social_core/backends/thisismyjam.py new file mode 100644 index 0000000..f3aa58e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/thisismyjam.py @@ -0,0 +1,33 @@ +""" +ThisIsMyJam OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/thisismyjam.html +""" +from .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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/trello.py b/flex/lib/python3.6/site-packages/social_core/backends/trello.py new file mode 100644 index 0000000..e998df3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/trello.py @@ -0,0 +1,47 @@ +""" +Trello OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/trello.html +""" +from .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') + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/tripit.py b/flex/lib/python3.6/site-packages/social_core/backends/tripit.py new file mode 100644 index 0000000..3225744 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/tripit.py @@ -0,0 +1,43 @@ +""" +Tripit OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/tripit.html +""" +from xml.dom import minidom + +from .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 + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/tumblr.py b/flex/lib/python3.6/site-packages/social_core/backends/tumblr.py new file mode 100644 index 0000000..7dad1ec --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/tumblr.py @@ -0,0 +1,31 @@ +""" +Tumblr OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/tumblr.html +""" +from ..utils import first +from .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)) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/twilio.py b/flex/lib/python3.6/site-packages/social_core/backends/twilio.py new file mode 100644 index 0000000..f38451a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/twilio.py @@ -0,0 +1,40 @@ +""" +Twilio auth backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/twilio.html +""" +from re import sub + +from six.moves.urllib_parse import urlencode + +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/twitch.py b/flex/lib/python3.6/site-packages/social_core/backends/twitch.py new file mode 100644 index 0000000..bd679ff --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/twitch.py @@ -0,0 +1,30 @@ +""" +Twitch OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/twitch.html +""" +from .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} + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/twitter.py b/flex/lib/python3.6/site-packages/social_core/backends/twitter.py new file mode 100644 index 0000000..38a3775 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/twitter.py @@ -0,0 +1,41 @@ +""" +Twitter OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/twitter.html +""" +from .oauth import BaseOAuth1 +from ..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': 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""" + return self.get_json( + 'https://api.twitter.com/1.1/account/verify_credentials.json', + params={'include_email': 'true'}, + auth=self.oauth_auth(access_token) + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/uber.py b/flex/lib/python3.6/site-packages/social_core/backends/uber.py new file mode 100644 index 0000000..21bd9c5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/uber.py @@ -0,0 +1,39 @@ +""" +Uber OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/uber.html +""" +from .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( + '', + response.get('first_name', ''), + response.get('last_name', '') + ) + 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""" + 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) + }) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/ubuntu.py b/flex/lib/python3.6/site-packages/social_core/backends/ubuntu.py new file mode 100644 index 0000000..3db9b87 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/ubuntu.py @@ -0,0 +1,16 @@ +""" +Ubuntu One OpenId backend +""" +from .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'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/udata.py b/flex/lib/python3.6/site-packages/social_core/backends/udata.py new file mode 100644 index 0000000..c7d0c41 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/udata.py @@ -0,0 +1,37 @@ +""" +Udata related backends. + +Docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/udata.html +""" +from .oauth import BaseOAuth2 + + +class UdataBaseOAuth2(BaseOAuth2): + """Udata base OAuth authentication backend.""" + SCOPE_SEPARATOR = ',' + REDIRECT_STATE = False + DEFAULT_SCOPE = ['default'] + ACCESS_TOKEN_METHOD = 'POST' + + def get_user_details(self, response): + """Return user details from Udata account.""" + return { + 'username': response.get('first_name'), + 'email': response.get('email') or '', + 'first_name': response.get('first_name') + } + + def user_data(self, access_token, *args, **kwargs): + """Load user data from service.""" + return self.get_json(self.USER_DATA_URL, params={ + 'access_token': access_token + }) + + +class DatagouvfrOAuth2(UdataBaseOAuth2): + """Datagouvfr OAuth authentication backend.""" + name = 'datagouv' + ACCESS_TOKEN_URL = 'https://www.data.gouv.fr/oauth/token' + AUTHORIZATION_URL = 'https://www.data.gouv.fr/oauth/authorize' + USER_DATA_URL = 'https://www.data.gouv.fr/api/1/me/' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/untappd.py b/flex/lib/python3.6/site-packages/social_core/backends/untappd.py new file mode 100644 index 0000000..9973bdb --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/untappd.py @@ -0,0 +1,111 @@ +import requests + +from .oauth import BaseOAuth2 +from ..exceptions import AuthFailed +from ..utils import handle_http_errors + + +class UntappdOAuth2(BaseOAuth2): + """Untappd OAuth2 authentication backend""" + name = 'untappd' + AUTHORIZATION_URL = 'https://untappd.com/oauth/authenticate/' + ACCESS_TOKEN_URL = 'https://untappd.com/oauth/authorize/' + BASE_API_URL = 'https://api.untappd.com' + USER_INFO_URL = BASE_API_URL + '/v4/user/info/' + ACCESS_TOKEN_METHOD = 'GET' + STATE_PARAMETER = False + REDIRECT_STATE = False + EXTRA_DATA = [ + ('id', 'id'), + ('bio', 'bio'), + ('date_joined', 'date_joined'), + ('location', 'location'), + ('url', 'url'), + ('user_avatar', 'user_avatar'), + ('user_avatar_hd', 'user_avatar_hd'), + ('user_cover_photo', 'user_cover_photo') + ] + + def auth_params(self, state=None): + client_id, client_secret = self.get_key_and_secret() + params = { + 'client_id': client_id, + 'redirect_url': self.get_redirect_uri(), + 'response_type': self.RESPONSE_TYPE + } + return params + + def process_error(self, data): + """ + All errors from Untappd are contained in the 'meta' key of the + response. + """ + response_code = data.get('meta', {}).get('http_code') + if response_code is not None and response_code != requests.codes.ok: + raise AuthFailed(self, data['meta']['error_detail']) + + @handle_http_errors + def auth_complete(self, *args, **kwargs): + """Completes login process, must return user instance""" + client_id, client_secret = self.get_key_and_secret() + code = self.data.get('code') + + self.process_error(self.data) + + # Untapped sends the access token request with URL parameters, + # not a body + response = self.request_access_token( + self.access_token_url(), + method=self.ACCESS_TOKEN_METHOD, + params={ + 'response_type': 'code', + 'code': code, + 'client_id': client_id, + 'client_secret': client_secret, + 'redirect_url': self.get_redirect_uri() + } + ) + + self.process_error(response) + + # Both the access_token and the rest of the response are + # buried in the 'response' key + return self.do_auth( + response['response']['access_token'], + response=response['response'], + *args, **kwargs + ) + + def get_user_details(self, response): + """Return user details from an Untappd account""" + # Start with the user data as it was returned + user_data = response['user'] + + # Make a few updates to match expected key names + user_data.update({ + 'username': user_data.get('user_name'), + 'email': user_data.get('settings', {}).get('email_address', ''), + 'first_name': user_data.get('first_name'), + 'last_name': user_data.get('last_name'), + 'fullname': user_data.get('first_name') + ' ' + + user_data.get('last_name') + }) + return user_data + + def get_user_id(self, details, response): + """ + Return a unique ID for the current user, by default from + server response. + """ + return response['user'].get(self.ID_KEY) + + def user_data(self, access_token, *args, **kwargs): + """Loads user data from service""" + response = self.get_json(self.USER_INFO_URL, params={ + 'access_token': access_token, + 'compact': 'true' + }) + self.process_error(response) + + # The response data is buried in the 'response' key + return response['response'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/upwork.py b/flex/lib/python3.6/site-packages/social_core/backends/upwork.py new file mode 100644 index 0000000..be68d0d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/upwork.py @@ -0,0 +1,40 @@ +""" +Upwork OAuth1 backend +""" +from .oauth import BaseOAuth1 + + +class UpworkOAuth(BaseOAuth1): + """Upwork OAuth authentication backend""" + name = 'upwork' + ID_KEY = 'id' + AUTHORIZATION_URL = 'https://www.upwork.com/services/api/auth' + REQUEST_TOKEN_URL = \ + 'https://www.upwork.com/api/auth/v1/oauth/token/request' + REQUEST_TOKEN_METHOD = 'POST' + ACCESS_TOKEN_URL = 'https://www.upwork.com/api/auth/v1/oauth/token/access' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_URI_PARAMETER_NAME = 'oauth_callback' + + def get_user_details(self, response): + """Return user details from Upwork account""" + info = response.get('info', {}) + auth_user = response.get('auth_user', {}) + first_name = auth_user.get('first_name') + last_name = auth_user.get('last_name') + fullname = '{} {}'.format(first_name, last_name) + profile_url = info.get('profile_url', '') + username = profile_url.rsplit('/')[-1].replace('~', '') + return { + 'username': username, + '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://www.upwork.com/api/auth/v1/info.json', + auth=self.oauth_auth(access_token) + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/username.py b/flex/lib/python3.6/site-packages/social_core/backends/username.py new file mode 100644 index 0000000..ef93750 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/username.py @@ -0,0 +1,11 @@ +""" +Legacy Username backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/username.html +""" +from .legacy import LegacyAuth + + +class UsernameAuth(LegacyAuth): + name = 'username' + ID_KEY = 'username' + EXTRA_DATA = ['username'] diff --git a/flex/lib/python3.6/site-packages/social_core/backends/utils.py b/flex/lib/python3.6/site-packages/social_core/backends/utils.py new file mode 100644 index 0000000..9fbe51e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/utils.py @@ -0,0 +1,82 @@ +from collections import OrderedDict + +from .base import BaseAuth +from ..exceptions import MissingBackend +from ..utils import module_member, user_is_authenticated + + +# Cache for discovered backends. +BACKENDSCACHE = OrderedDict() + + +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 = OrderedDict() + 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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/vend.py b/flex/lib/python3.6/site-packages/social_core/backends/vend.py new file mode 100644 index 0000000..99e4563 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/vend.py @@ -0,0 +1,39 @@ +""" +Vend OAuth2 backend: +""" +from .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 {} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/vimeo.py b/flex/lib/python3.6/site-packages/social_core/backends/vimeo.py new file mode 100644 index 0000000..cc34084 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/vimeo.py @@ -0,0 +1,79 @@ +from .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, + ) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/vk.py b/flex/lib/python3.6/site-packages/social_core/backends/vk.py new file mode 100644 index 0000000..c794632 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/vk.py @@ -0,0 +1,201 @@ +# -*- coding: utf-8 -*- +""" +VK.com OpenAPI, OAuth2 and Iframe application OAuth2 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/vk.html +""" +import json +from time import time +from hashlib import md5 + +from ..utils import parse_qs +from .base import BaseAuth +from .oauth import BaseOAuth2 +from ..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 = '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_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 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', + {'user_id': user_id} + ).get('response', 0) + if not int(is_user): + return None + + auth_data = { + 'auth': self, + 'backend': self, + 'request': self.strategy.request_data(), + 'response': { + self.ID_KEY: user_id, + } + } + auth_data['response'].update(json.loads(auth_data['request']['api_result'])['response'][0]) + 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', '5.53') + 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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/weibo.py b/flex/lib/python3.6/site-packages/social_core/backends/weibo.py new file mode 100644 index 0000000..cb5dc90 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/weibo.py @@ -0,0 +1,61 @@ +# coding:utf-8 +# author:hepochen@gmail.com https://github.com/hepochen +""" +Weibo OAuth2 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/weibo.html +""" +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/weixin.py b/flex/lib/python3.6/site-packages/social_core/backends/weixin.py new file mode 100644 index 0000000..1d62d84 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/weixin.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# author:duoduo3369@gmail.com https://github.com/duoduo369 +""" +Weixin OAuth2 backend +""" +import urllib +from requests import HTTPError + +from .oauth import BaseOAuth2 +from ..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' + DEFAULT_SCOPE = ['snsapi_login'] + 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, response=err.response) + 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) + + +class WeixinOAuth2APP(WeixinOAuth2): + """ + Weixin OAuth authentication backend + + Can't use in web, only in weixin app + """ + name = 'weixinapp' + ID_KEY = 'openid' + AUTHORIZATION_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize' + ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token' + ACCESS_TOKEN_METHOD = 'POST' + REDIRECT_STATE = False + + def auth_url(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 + + params = self.auth_params(state) + params.update(self.get_scope_argument()) + params.update(self.auth_extra_arguments()) + params = urllib.urlencode(sorted(params.items())) + return '{}#wechat_redirect'.format( + self.AUTHORIZATION_URL + '?' + 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, + } + + def validate_state(self): + return None + + 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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/withings.py b/flex/lib/python3.6/site-packages/social_core/backends/withings.py new file mode 100644 index 0000000..6453d08 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/withings.py @@ -0,0 +1,14 @@ +from .oauth import BaseOAuth1 + + +class WithingsOAuth(BaseOAuth1): + name = 'withings' + AUTHORIZATION_URL = 'https://developer.health.nokia.com/account/authorize' + REQUEST_TOKEN_URL = 'https://developer.health.nokia.com/account/request_token' + ACCESS_TOKEN_URL = 'https://developer.health.nokia.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': ''} diff --git a/flex/lib/python3.6/site-packages/social_core/backends/wunderlist.py b/flex/lib/python3.6/site-packages/social_core/backends/wunderlist.py new file mode 100644 index 0000000..e25775e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/wunderlist.py @@ -0,0 +1,29 @@ +from .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) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/xing.py b/flex/lib/python3.6/site-packages/social_core/backends/xing.py new file mode 100644 index 0000000..62cc85d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/xing.py @@ -0,0 +1,70 @@ +""" +XING OAuth1 backend, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/xing.html +""" +import six + +from requests_oauthlib import OAuth1 +from oauthlib.oauth1 import SIGNATURE_TYPE_AUTH_HEADER + +from .oauth import BaseOAuth1 +from ..exceptions import AuthTokenError + + +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 clean_oauth_auth(self, access_token): + """Override of oauth_auth since Xing doesn't like callback_uri + and oauth_verifier on authenticated API calls""" + key, secret = self.get_key_and_secret() + resource_owner_key = access_token.get('oauth_token') + resource_owner_secret = access_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') + # 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' + return OAuth1(key, secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + decoding=decoding) + + 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.clean_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'] + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/yahoo.py b/flex/lib/python3.6/site-packages/social_core/backends/yahoo.py new file mode 100644 index 0000000..750a6a2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/yahoo.py @@ -0,0 +1,159 @@ +""" +Yahoo OpenId, OAuth1 and OAuth2 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/yahoo.html +""" +from requests.auth import HTTPBasicAuth + +from ..utils import handle_http_errors +from .open_id import OpenIdAuth +from .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) + } diff --git a/flex/lib/python3.6/site-packages/social_core/backends/yammer.py b/flex/lib/python3.6/site-packages/social_core/backends/yammer.py new file mode 100644 index 0000000..859d513 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/yammer.py @@ -0,0 +1,44 @@ +""" +Yammer OAuth2 production and staging backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/yammer.html +""" +from .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' diff --git a/flex/lib/python3.6/site-packages/social_core/backends/yandex.py b/flex/lib/python3.6/site-packages/social_core/backends/yandex.py new file mode 100644 index 0000000..314246e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/yandex.py @@ -0,0 +1,79 @@ +""" +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 six.moves.urllib_parse import urlsplit + +from .open_id import OpenIdAuth +from .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, *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, *args, **kwargs): + return self.get_json('https://login.yandex.ru/info', + params={'oauth_token': access_token, + 'format': 'json'}) diff --git a/flex/lib/python3.6/site-packages/social_core/backends/zotero.py b/flex/lib/python3.6/site-packages/social_core/backends/zotero.py new file mode 100644 index 0000000..1ee2be3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/backends/zotero.py @@ -0,0 +1,29 @@ +""" +Zotero OAuth1 backends, docs at: + https://python-social-auth.readthedocs.io/en/latest/backends/zotero.html +""" +from .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', '') + } diff --git a/flex/lib/python3.6/site-packages/social_core/exceptions.py b/flex/lib/python3.6/site-packages/social_core/exceptions.py new file mode 100644 index 0000000..09440fa --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/exceptions.py @@ -0,0 +1,116 @@ +class 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 __init__(self, *args, **kwargs): + self.response = kwargs.pop('response', None) + super(AuthCanceled, self).__init__(*args, **kwargs) + + def __str__(self): + msg = super(AuthCanceled, self).__str__() + if msg: + return 'Authentication process canceled: {0}'.format(msg) + 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' diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__init__.py b/flex/lib/python3.6/site-packages/social_core/pipeline/__init__.py new file mode 100644 index 0000000..a8edd80 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/pipeline/__init__.py @@ -0,0 +1,59 @@ +DEFAULT_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_core.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_core.pipeline.social_auth.social_uid', + + # Verifies that the current auth process is valid within the current + # project, this is where emails and domains whitelists are applied (if + # defined). + 'social_core.pipeline.social_auth.auth_allowed', + + # Checks if the current social-account is already associated in the site. + 'social_core.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_core.pipeline.user.get_username', + + # Send a validation email to the user to verify its email address. + # 'social_core.pipeline.mail.mail_validation', + + # Associates the current social details with another user account with + # a similar email address. + # 'social_core.pipeline.social_auth.associate_by_email', + + # Create a user account if we haven't found one yet. + 'social_core.pipeline.user.create_user', + + # Create the record that associated the social account with this user. + 'social_core.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_core.pipeline.social_auth.load_extra_data', + + # Update the user record with any changed info from the auth service. + 'social_core.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_core.pipeline.disconnect.allowed_to_disconnect', + + # Collects the social associations to disconnect. + 'social_core.pipeline.disconnect.get_entries', + + # Revoke any access_token when possible. + 'social_core.pipeline.disconnect.revoke_tokens', + + # Removes the social associations. + 'social_core.pipeline.disconnect.disconnect' +) diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..938c84bb5097f8cfca2f144ff33e9590b9e44162 GIT binary patch literal 954 zcma)*!A=`75QYs21wuo6sZ@z`D}}P_RD_C1RaFG3h=fuk!euX(yB??&#}2kPZIO5t z?tPU$La%*`J=L??kQ@?%SNqvJ{yp~2c=qe{+Rh)5+;0-{kIc02O+JU$Jr5Au2@x-n z6;Zz+jh8ufCY4;V)L4i|as*0hh)W~S{P)RNfag-V{M}O*WK!hOQZa79xKic^h(Z2oqx8mHoG@C*l<4*KcVoLZ=KD41Rjcnc$v3 zc49m&o5c8Fu4f@#YP5#b$7R5nH*9KMkeaEW2n%R$CAbBtnGstUn*oF^2B2NOyX;8Q zmA%HMT?seMt|BUkDq;=s46%;bK;YGERBa8%TL`?OwvN`-6K9etfN$TFD05E!6??h6bVde`7L zR@4QbnXR&vX|9uctu~L~5-thqj+~|=(Ak4$#z3>DueixE+uP)ve>nFqhd@%?!J3_| zuIMV?F%7Fm)@ppuG^J2wkt<=gQRq0$l<{IYQD$rUP{&u0Et*^}Ijt>XE%+jqd|YHG zq~c@Ad()#_&cmpSOI$`O>`%jJ7M+A$vPsTwc$t>Ls7f;oj`KwJb$8BHk)&}J#Ff4e zl0u4a;@0LgFS0Zj9L1=b9F~u!VYB+%)Gj$-ZPwg?Y!f^19s4L-Skb@S4h@**>8!vi N6X%R7RewO+{RCzDZL9zQ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/disconnect.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/disconnect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9722833abe3e06777428041c3b2aa3a4d7d18f88 GIT binary patch literal 1141 zcmZ8g+iuf95Z(1Hshi{hXjD}AfII{THARht5U4E@AUP6^K(d4^*SihQ#ZGpe-sA|0 z^k?`LK4D*Z;um;gW)n!-wP$ubyPlmrXJ)@zb@zU<;J_l}7ugsl*snok8x$jqj>wn> zloGbhOlIBEz+e?-GY2e_m00Vq8e?TzLD#=c$;sJ1us@ZaT7kIBa*uofqvEjDG6%yh$=?p3RaG zYaXPrILup9PaijGMzj2DGtLBGIYMp2em+W4?yYb0Y;rA-0)$1PB1VmB&mGhdL5b{_3!x$K*?EZZos62XyF& zI?P5v78GUf6wW}eF!M2i6>DKoGOd4xvV<#Iez5oP5~^j$(}o?CB+v6C?JJjGL8*i+%A@6F&Y~E$u%IIN1#Djaq0Cvr{sB~l z{MU%6>gGnNY((Q)l~!@?0Va^6haeL1A+$m*>cUe4ORvR9I1Xe0ZO!ZxFs|wvqT1Xp z4CoAd0ev`iRscXxQ01;mr{OHg)VXRN*A(LM_tV+|-&2q!e~^$s54IY-4x$wE33iIq nEvAc5Q>>HLn>Rx0BGL6xemTzAY{Xwt434!_v0U5TD!YFHY$F8S literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/mail.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/mail.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..928a407f55d8c5a1d561602cc9a0afd09ad86446 GIT binary patch literal 989 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/user.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/user.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd9d142fb5a8d50cf61c6d71d1ca57403b46c3f8 GIT binary patch literal 2485 zcma)8&2HO95Z+xYzSG)ryZ zOj@Ejng`CJMgvvK?_e!d!D(hZ_UYO?A|;ZVQ84cK?W45NAJB2Y+&=5?F`ioEi1E(B9x^4|qg1swZS( zDn3IE;4NWvwXU9^Nmgl1WnnAcqFJ!Wi5&ky$##M#d6DM_aYp2UFN|KP*~X+0TI!f+ zQV=%(U0D~vq9}^|SFkPuUmE=po0Ae)7U4qBq$EuKR^YBhO9_@7EnP#Ca=4&$7<7Mv z4lA7%XA{)5CuNbPsF!_%ITB@xL{?Z=(U0UB3W+FB=0r&prh2JMV2#QAVN=ZK5!}ER z%GaEjr{&ieR`_1bO;?3=ENOoQcK@#x-r&*oCn(IT`FWwqRpGYheGCDet8)WFkmhz9 zwa4z(c5P>~;arwN{qBRByWMcNc6WB%+Jn2>J6ld}cXxZ!-Ko{@H@|sThI!6hV`umN z_SSdq>6C_3y0=riTR&4Yoh$#;z>@WQ&FzQv7Q|gW@-3+W6v-43z*cbmH<@bMffsNa zlxlc?R2_2vz>8V+z~j~SAfSv#)i$r*?0p{c(c1bdJ*LO&(b}!v+IoNetF={lI$^b0 z9s0xNy|EwAa1I|tquHwO-1%Ak?GK*J+N2zr?@cMhy z+gOl(0-@s@9TvN%S-x^R7___~+5l#1=#)?I=YOABow9;??)1ddv*)E)fZ?u=fbIrZr?j}0~U7TeJ<WU0 zyn%>KtQl_rUj#k;h^1YJ?-val=3c}LB#*gVa|VSnj4~ARX4ED^<9UiHkp&u~m*^#K z17cJCg#{3aX(lEK*_^_IA&h8ELM1f-w6r!7#!r}LHPt1gR|vlZDt=WC zsyyX&z0cn6u#g=N`Nk%Qk=#(n$2VG>L6WFTK365DrYdKfu;O*On9Q1rp6b+Z#T>dC znx=H_+!__=5b}fa;PUAtenoQ9q10&mETGY}n9h5uV)`MN+yRQ@RtTaaqcsn2T_c#w zuPxa<7T(b*ZH|(V&H8;R`f{9^u{561657Xg=^&ScwMFSbBm) zhQEOwjjq{+Y)E7`2VW}o@$&AF!VNFVQe8YLL=XTF7D4YVw)vpHIOKx^s2RRe-r5QhFeW?6> zFY@A;rzW7plmS?hj4J#oAegOvFYGW{vCjojM&c}f{4)@pEchFi6I`_X$lt=Vya|R6|53PO+DwstP-GGO#cjR$j%l?eV&vxOS8YWOMe4EjZPB) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d5bcb39e90c93e40158900a2fc670f096c9b6f4e GIT binary patch literal 2419 zcmbVO&2Jk;6rY*>@Oo`0Ap}}zq2^N~3P)oQizjLkZm-ObE6 zZKCzbmEcfS;)H~vp1AO@@F&cH3#VTA1A5}U-PnmrxvQxY$l_e0t4r7;&$wR^ICW#@7t_m)?d_#r=BQOj3%mRELHK3QIZub;HH+F6t>ZV#iF zFMgbKk820Uu5!2NtAM@^x+OPq zYrwKQoaDCL&h3E(#pd1-%Q%3jdgP5MB|nlL1z6M`dbzVs#&lGP$jEze4fb-zWS!hw zzRO0{k+0p{A5;08u3(+8+=o6d_jPs5 zJzhUzV++92*f5?HDoYb3Ooa;_LcokSR6^n{8fzGF<3jKfasQ^71sT9KW9)U5CM{;X zOs4x0+@!>iwLDpeN;Xi;puk=^hvFp=#>SnETi)Ij+6XSZkC&ko(C!1Y@6C)TSWa?? z{LgI9-Yj7Ru$t3ppoCIjK~NwJ>|mFcj0FuC_LP=@vi?BGMNvX+o9|$R1*C%^P4xmxpn>at4 zVr;yCR!yEQ=52@F9g%QTO~eBoC@nIH(UJ=&UPo~b=lR`Ogh_yQcIu^GS}otjIUWjx ze~T9T7Qr#I&qV=mN__}|Fq_ul=fMx_I0-SWvyX+#HXHs zKtQ6g2+9%5nfx^e!$%fK8q<6#G~oxFhNR?0kfbP8!)!g89m zCwKPm+^nBA_fz!aJP1N(AT?b~Md~ANp*7DIAVKG-yo~3u4ag!WbH-B9gThc_Bc~Zh z53?A!O96M;+i4mbyLkB 1: + raise AuthException( + backend, + 'The given email address is associated with another account' + ) + else: + return {'user': users[0], + 'is_new': False} + + +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) diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/user.py b/flex/lib/python3.6/site-packages/social_core/pipeline/user.py new file mode 100644 index 0000000..d0d67b9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/pipeline/user.py @@ -0,0 +1,105 @@ +from uuid import uuid4 + +from ..utils import slugify, module_member + + +USER_FIELDS = ['username', 'email'] + + +def get_username(strategy, details, backend, user=None, *args, **kwargs): + if 'username' not in backend.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: + override_clean = strategy.setting('CLEAN_USERNAME_FUNCTION') + if override_clean: + clean_func = module_member(override_clean) + else: + 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] + if max_length is not None + else username) + 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, backend, user=None, *args, **kwargs): + if user: + return {'is_new': False} + + fields = dict((name, kwargs.get(name, details.get(name))) + for name in backend.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 not user: + return + + 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 is None or not hasattr(user, name) or name in protected: + continue + + # Check https://github.com/omab/python-social-auth/issues/671 + current_value = getattr(user, name, None) + if current_value or current_value == value: + continue + + changed = True + setattr(user, name, value) + + if changed: + strategy.storage.user.changed(user) diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/utils.py b/flex/lib/python3.6/site-packages/social_core/pipeline/utils.py new file mode 100644 index 0000000..82d88d7 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/pipeline/utils.py @@ -0,0 +1,73 @@ +import six + + +SERIALIZABLE_TYPES = (dict, list, tuple, set, bool, type(None)) + \ + six.integer_types + six.string_types + \ + (six.text_type, six.binary_type,) + + +def is_dict_type(value): + """Treat any dict, MergeDict, MultiDict instance as dict type""" + # Check by class name to avoid importing Django MergeDict or + # Werkzeug MultiDict + return isinstance(value, dict) or \ + value.__class__.__name__ in ('MergeDict', 'MultiDict') + + +def partial_prepare(strategy, backend, next_step, user=None, social=None, + *args, **kwargs): + kwargs.update({ + '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 + }) + + clean_args = [strategy.to_session_value(val) for val in args] + + # Clean any MergeDict data type from the values + clean_kwargs = {} + for name, value in kwargs.items(): + value = dict(value) if is_dict_type(value) else value + if isinstance(value, SERIALIZABLE_TYPES): + clean_kwargs[name] = strategy.to_session_value(value) + + return strategy.storage.partial.prepare(backend.name, next_step, { + 'args': clean_args, + 'kwargs': clean_kwargs + }) + + +def partial_store(strategy, backend, next_step, user=None, social=None, + *args, **kwargs): + partial = partial_prepare(strategy, backend, next_step, user=user, + social=social, *args, **kwargs) + return strategy.storage.partial.store(partial) + + +def partial_load(strategy, token): + partial = strategy.storage.partial.load(token) + + if partial: + args = partial.args + kwargs = partial.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) + + partial.args = [strategy.from_session_value(val) for val in args] + partial.kwargs = dict((key, strategy.from_session_value(val)) + for key, val in kwargs.items()) + return partial diff --git a/flex/lib/python3.6/site-packages/social_core/storage.py b/flex/lib/python3.6/site-packages/social_core/storage.py new file mode 100644 index 0000000..bff1581 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/storage.py @@ -0,0 +1,339 @@ +"""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 .exceptions import MissingBackend +from .backends.utils import get_backend + + +NO_ASCII_REGEX = re.compile(r'[^\x00-\x7F]+') +NO_SPECIAL_REGEX = re.compile(r'[^\w.@+_-]+', re.UNICODE) + + +class UserMixin(object): + user = '' + provider = '' + uid = None + extra_data = None + + def get_backend(self, strategy): + return get_backend(strategy.get_backends(), self.provider) + + def get_backend_instance(self, strategy): + try: + backend_class = self.get_backend(strategy) + except MissingBackend: + return None + else: + return backend_class(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) + extra_data = backend.extra_data(self, + self.uid, + response, + self.extra_data) + if self.set_extra_data(extra_data): + self.save() + + def expiration_timedelta(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 the remaining difference + return datetime.utcfromtimestamp(expires) - now + else: + # expires is the time to live seconds since creation, + # check against auth_time if present, otherwise return + # the value + auth_time = self.extra_data.get('auth_time') + if auth_time: + reference = datetime.utcfromtimestamp(auth_time) + return (reference + timedelta(seconds=expires)) - now + else: + return timedelta(seconds=expires) + + def expiration_datetime(self): + # backward compatible alias + return self.expiration_timedelta() + + def access_token_expired(self): + expiration = self.expiration_timedelta() + return expiration and expiration.total_seconds() <= 0 + + def get_access_token(self, strategy): + """Returns a valid access token.""" + if self.access_token_expired(): + self.refresh_token(strategy) + return self.access_token + + def set_extra_data(self, extra_data=None): + if extra_data and self.extra_data != extra_data: + if self.extra_data and not isinstance( + self.extra_data, six.string_types): + 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""" + value = NO_ASCII_REGEX.sub('', value) + value = NO_SPECIAL_REGEX.sub('', value) + return 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 PartialMixin(object): + token = '' + data = '' + next_step = '' + backend = '' + + @property + def args(self): + return self.data.get('args', []) + + @args.setter + def args(self, value): + self.data['args'] = value + + @property + def kwargs(self): + return self.data.get('kwargs', {}) + + @kwargs.setter + def kwargs(self, value): + self.data['kwargs'] = value + + def extend_kwargs(self, values): + self.data['kwargs'].update(values) + + @classmethod + def generate_token(cls): + return uuid.uuid4().hex + + @classmethod + def load(cls, token): + raise NotImplementedError('Implement in subclass') + + @classmethod + def destroy(cls, token): + raise NotImplementedError('Implement in subclass') + + @classmethod + def prepare(cls, backend, next_step, data): + partial = cls() + partial.backend = backend + partial.next_step = next_step + partial.data = data + partial.token = cls.generate_token() + return partial + + @classmethod + def store(cls, partial): + partial.save() + return partial + + +class BaseStorage(object): + user = UserMixin + nonce = NonceMixin + association = AssociationMixin + code = CodeMixin + partial = PartialMixin + + @classmethod + def is_integrity_error(cls, exception): + """Check if given exception flags an integrity error in the DB""" + raise NotImplementedError('Implement in subclass') diff --git a/flex/lib/python3.6/site-packages/social_core/store.py b/flex/lib/python3.6/site-packages/social_core/store.py new file mode 100644 index 0000000..e3275a2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/store.py @@ -0,0 +1,84 @@ +import 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 diff --git a/flex/lib/python3.6/site-packages/social_core/strategy.py b/flex/lib/python3.6/site-packages/social_core/strategy.py new file mode 100644 index 0000000..3a492d5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/strategy.py @@ -0,0 +1,226 @@ +import time +import random +import hashlib + +from .utils import setting_name, module_member +from .store import OpenIdStore, OpenIdSessionWrapper +from .pipeline import DEFAULT_AUTH_PIPELINE, DEFAULT_DISCONNECT_PIPELINE +from .pipeline.utils import partial_load, partial_store, partial_prepare + + +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_save(self, next_step, backend, *args, **kwargs): + return partial_store(self, backend, next_step, *args, **kwargs) + + def partial_prepare(self, next_step, backend, *args, **kwargs): + return partial_prepare(self, backend, next_step, *args, **kwargs) + + def partial_load(self, token): + return partial_load(self, token) + + def clean_partial_pipeline(self, token): + self.storage.partial.destroy(token) + + def openid_store(self): + return OpenIdStore(self) + + def get_pipeline(self, backend=None): + return self.setting('PIPELINE', DEFAULT_AUTH_PIPELINE, backend) + + def get_disconnect_pipeline(self, backend=None): + return self.setting( + 'DISCONNECT_PIPELINE', + DEFAULT_DISCONNECT_PIPELINE, + backend + ) + + 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, partial_token=None): + 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, partial_token) + 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 + elif verification_code.email != email: + return False + elif verification_code.verified: + 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 + args, kwargs = self.clean_authenticate_args(*args, **kwargs) + return backend.authenticate(*args, **kwargs) + + def clean_authenticate_args(self, *args, **kwargs): + """Take authenticate arguments and return a "cleaned" version + of them""" + return 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') diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__init__.py b/flex/lib/python3.6/site-packages/social_core/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a666d501f90c7da2b2996fecf3e0a245a00bb651 GIT binary patch literal 205 zcmXr!<>it-l@i4O1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuPXh5qRg_yl2rY& z#3KE){G623qGJ8DB7Ng*!@QzG6H9}Xs+20rViSvO6U$snGZO&`m1M z%t_HT%}Xw_C^0D1FV0WSOw7?uEG@~*X@;9ge literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/models.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/models.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70a2ed7f6754676362ae62af585b64faf48285d4 GIT binary patch literal 8896 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 b{60BvK9V`tyyiypYU_us&DLA3daL=rq+Zdj literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/pipeline.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/pipeline.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c4515a9b8fc78e324b8af61d21cd00ea8b96369 GIT binary patch literal 1496 zcmb_c!EVz)5Z$$%#7Ues4W()Y2Tq6}f{PkZBO$~WghM%?Tr4-U32vM?opo9@%B}qw zE_?`|;43G7ffF;E#w1cJKw_=kof+@$yqSHoN3~kz(KqOQb%gjX)|yT7d+PcV4In_q zVk~=73MIh;`$k;Lo<*_)1tZ%~gwl=ZIp9K><^oipN^_Cccu+HIN+fT=wvk<^pNQtp z4~vd(%B(aEF^$5wFG=Ud{<(jku3ylw5NCwqTH-#)l^n?vwzF=5bxnUy2rHdycQbby zYJHg?%**~tY}#2>E3Kns5)4(Exu_sQ)laj==`4yN2v2nq&r%i4Frvk-PBBc?@G5gc z9O|q%y5#wbzzQMyIrpa+UC;vmB1C_X#Gp|71N4tZuO@ij?i|7#<{jNW9<@8;&YSij zwc*77=ue|*%Xn=a;Vk{!>L;jf4S|tUrBltjR2)sz%js2C5A`S* zBp9q>qYD(eNQ1B{a?3)Gr2m&qN5`|_yx{ZHw2_@*fd}?Th7!vz4`m)WJloP5^+E=@>l#^`63w*qh;=gE>ySu9AUW`TTDJ1KL-O5TIf)=a zFt?i&syUqVc^ls!yj4++lM5m+CE@X!L(b*hba+mGRt1H73o4`oDk&)@buKIFHrQ*W z<#e-hn5Gz=Qk@tu^NODv2qa%&W<~H1{Mh9bG2&o=$v9Y6*%LY^=ZRRmo{$V4+` tip(@aL}Xqd-<1j1Y+frSQ&y&;+_&QdX0dw5@1^Gbp6$7w>pJ{-?l0;aBys=% literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/strategy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/strategy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a630b701a861d5c02ee8a7615d0ed7f9ed92c598 GIT binary patch literal 5647 zcmb7IOLyDG6~>DsL{YXR%MbYpzhX17L^*a+D|J&liEOKNR8^uE<>@&%FlQuD1_^ow zlq())C7+XB|408ySKSudtnwGM>iq@)4N#Png2WvRaA)S5`NcC8`UKahZnN^HR-OFSpf0OMRQb-6d@~eY$hY^SIn?UB-Sw%SzU@!%gkkraTCKhB zb-RnrpxvJsJ~pYF;e|e*(JkkQaIK*;tzj^o86P!ZrE#su-r%eycr$E*8y8C4-_tG2 zLio<@bD_VO5wYKMp+E(B*E@W*>)E>Qi z*b(m^FE6n^>o3d4Pu@LVZZCiRc!{p;%do7#w(+XX80J9k0z@H!8bs zJ77H@&lT5w-}C(Vi$E`GQW;|79c%y(Y6x3b_3=W#N?b>U&MXo>83+lp7|qpb9;5ng zn$!>%N}X<{2#h<>l?oK3sws-}Dl)^aAC;OxC*=PKN5DpB1@Dj!+%PdAGU%af?u;9I zaiqcjhsD|n+rBtUgNy>1O8O;@wL04~85p-tf)TT?RSN=kC?-KFAk!eTATYcPQ97EU zcetfemTsE@wVaml1(*B}kyJ~Z+?Or>>|5Fk(pme>_`m0HfQ zYwS9`nPoSaL*+U4klkdr=*t|t&F;{f^XxABjLHk_9=lKFMTN1-KL2PpF0n7!7wiFX z&;P7hTH`X=$fKwNv8EAYEvH796>fQ-NK1onE2=AMgl1?Bv;$+eKz?r=AhIQ%UyP<) z!Qc0&XP0@Q7fra5hao9cl21u4Wh>}J1=;IzAKpVA!M?h@S+HBB$7U;lh|GDBA$AF$9cBu_breWeL@$S~#bjR_t7dkL~v_4>Laxa$gC(|rf6Y|7`awuB}I*cLCk3ifB zWEh(yysy5pS$AI%PQr(o9g7If9uh+;w(*F-bvz;lXEgfPoK@+BJA5duo}}R6z!*3G z=)6!6j)60iPQ4|`BS`A}s|=>=W2H1?{SCmn9=SlUrO=J)G>V0D46>o>(8Y2F)ObOG z>r_0~o%KM3POIbe$UPB2!kv~JitX3+RAHxLqHg|hIws;8k0}jpyZS+FRI|=TV(Dbt zv9*(B!@wh}8FcrL0Yp9+uNxGso@emnBUy&&H^MnLgri(ed0&D@+^2Dlf%8dcWVS0A zJjdt^#*HNtSGs(DPtOvO2Qgt+j~7c78*U|zm#m0KmVtMCpDCB}Ds~B0WQ=>sF!*l{(1BeIuQ%YczTi)2@A+1en`5p(B_WTNM4roCw zZlh6S2$Pn&A5VozOu0}C(H;>i(+{vt82uaTLHA^ssZhH?Hwm{&sK&xH z&<$z(NCqjuuJF-<6r>>IuMwV(kvbxNw)XGhV zc6*}r2J^KiHZ*XgE-6q(o9(RN5KP4qk&|rrlHl!NkGoqU zXy>psfh`f=-5j!GTcjHa%m@gPz)8p`g#98FFc97TssCh#=D;M})^1_9ctBsomsBe; zePHg|n8g5x3dUMw)4CNXJfhXX%V>u9-z9zM`QVtmuiZXzTuQ6 zk8YxvYFR+x z;s+4y1;h)Gmmse|u-uA2f&3W+XMa__Q6fi2>YF<0H~2OZcPkKQY8yT=$jd}DinfM9 zTlSHkVT>4Qwlz-P%(XRIifO}6RQ<1v9*f|ZqcQ)(%8Tc3Uf13F%8%=>pVwF1&H4rv zFaH*wY5{}5;Z&S5A?pJ;&TvWC9y*y8xYujizgtz2YCtt&l6bo z>7XD^?wj2A(*z&FNxfA$Dvk({1Iq_QY~3=>6m8SCY^zK^l<6<7l}qZYSt+V$vR0jF z*y#zNK*SRWUg8nRO(Ib-c)Lpno9c*8odCs$5}%Z$o&Y7XpT_5a-y#;OL(gf`(k*kk HFm3-27`#d) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_exceptions.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_exceptions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91e51afa8a34fd9f323030132fbfdaee2d401da2 GIT binary patch literal 5134 zcmbVQ-E!N;6$Zc`QIsfA|FR`nkmNsRB2$T#Oyucgq&QBRnOc*n#@^bv5`UgZ^f)$c4wkZ_U0Lwc~rcX&X2-`TTg&+2ibQD6HfGyXNB zDF0SQK4sh=;tKw*DhgAW>L`ZFG*)snw`7#uvQc&`M#ZffRd>ReaBD`*oirxhx>0wh zj45~8n06aR!<{i^+*xDRZ5mB?&X{xOjd^#$SWuO(6;@`IUldm1>W_;zC1VMeDw}|1 zf-eCt1J~Fj@T9~mz;!kSJSFie@HA@xHzd9VJi}&zXC=N3++=gWa}uus&$9*K1&P;z z7ugcB%kBZ+llUI+ef9wOfyDQLAF>v3OX3GiJyBX8{7RE_{HunsH5ad( z_?B%tN4@a%7iPep{bccOX!~9~{Zjbe#TTY^$vqa=$>A&0b~uY?=>F7){h1KHh!+Vz zw}Zg;F1|H|>GF_^xJlNN&0SK_5ih);PCK1IxrW5vQ6kstcCCa$Nrg6}c>to(@8b6FFsxKo*Wsfksl zoG5SW^v&PYmx{MvRzmHvF8-t{e^yl`BF|D-{#m_I>6sf%RbDFJ(Hg|}e^Fm4p7sj& z$OY|KEa4rkN<7KaiEr|sx|kNuIxY_a^MZ>Ke2ghG2)GEJz3G`wTna+b(&BQ!o%6WF zh4_ae*5LJ@zqY%=zC!TZSEgv6`woH`w9iHR@#ROJc(Z@7$08OT1pA*|?jN`ZpYHGB zbK$j5+g-c6d)BiZw)=@^iO<5lH*GnO&aR0$+O>Sa`-YCZ4-vhfO=BnJ9z5#4jhC~w zfr@$13#KtB>Y`d!HMOob)#$_A8F`fd6Rm1o>vTLMc&8KBI~~_&J%{e6I~@v3@{L%= zJH!eN8#GW{Mg~d~v4{Z$QwgdVYM@irq%A+QR^c?rEM8)mS7erktSVs+%W-pbrJf=n zarNBy&rA`uzVQ$%f!Wk4h};Wp%cRwJlVv`1)1}fkqTJmIU z!G-Jr`PS5$D#(%%LAi5?E4YneR0Pv*aXshp}dz>@Q^zL9=(+4g&Y9@-++=oj$ zqlPRQHM9d9l1ss=mx(sIzot5(^%LX~t#WK(J+23|3F~gMK|jhNUc>7Orr{x6_K8)x z(#et`PFvU^xlsKZuJjuP#Y)90*RaY<@$LL3K$54uD2Lu8%mwsXa5=!$r&m=4Su*rW zFdUN0Zxo_oJ)NU0N&AsBtnoyDKg^+6#}5}!q$K{F-k(GeRfa4Xitju1vL_mJNGZQ) z^EV30|0yO@#k@a+%hMrm)OfOFco&7Xl-78iC=Qsvv?i=_CNZHGlI48c;uNB1iBe=ZEkR;1kBxMGT9$iv? z(dG+mQ@W5R8a?SL^w8JQ8{t1}#5vq&i@ARcmmf0R$ubsqSwM5rV)Hw{Cv;0-OGPJY zI)QEq?ma+rnw$!Ah|^i9An1?ba+V=Zma&MhQD)>E2ZyoWJ4KXDc#ZU-Xx!wXQMde_ z!*tIN^)rt7!I_e?98Gh@nrN%N$Y>(VSenqL4NtN|A#3tvn^2}H0AuM!=jI&Fbb2b7 zhfm<*WH^&$EY9=U^ER=LC(e>TMMRr}-mGuXxBEBSIwnQD&0)V#%>GljyveX9OY5OA z+4Jl$1cQ%_+Tb>>509MkbetTP&R|GZq0Jv5>(h_i4;_V1g&L3>q1Z> q37Q!}0!I#}@_}YQE8| literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_partial.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_partial.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ff2dfce8866cf1d68f9ee48c3463edc102dffa3 GIT binary patch literal 3783 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_storage.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_storage.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b6899a09eb5355ce695b88e6d85d69eba94f6bf2 GIT binary patch literal 10333 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e0b9d854781a0fac063bd2ffb2e4a81db03d86a8 GIT binary patch literal 10076 zcmc&)TXWmS6~>Juc+p+9D_Xx+^w`K-D<1at+i_1sn!%)6j;%j?#{GkWXX~` z=>NRLN;^tx?lXz$to)tC%9hfZ-&b1;sHw0jYN}DqB5G=E3N=$v%^B28vl-OPL^Wqo zGt1^sGZ)pIL(M!}K+Qr_^AeNqOZCO4;8<$bSe}0`eB-H#x;BpFN0hDN@HS9bQcGr1OJOoo zzLQ!iNgQX5B#U!_m6(omk(F5m=Mt;38qPYKV$(R6*$kV-xx(hyJkC|NK(YuH??-HZ z8F5V%wTV3X(OJXsDGu)?6eFo4`7)EYLCd~E2cZEyDa~QIyKDCQD{Z%1SAwePL3sY1 zM+4L0)CI}gvz)DeNPHHz-;Wx7Za;=V8;?!i*m50aaj&t(8`pNO_V}agx2~`u8{YD+ z-`u%=t9$Fl^(**o_Zk}wNOD<7dinico8R=WJZgAu8!}!dJuSCgZbcP_xc0%3*PzlE z__pJ%^r0$o+jF6hSltW6BZ*v+hnMoCvU0f1i3Fu&1f`}06+m!&8bd}R;xlwj@(GJM zTm=haT&vsncN1dI;z3O8<;lcm8=Zt&`$)AOOKT`sX1D!>)blA)kC1DoO@`6}IuHPS z6&EQ0DonNIjBK@xY}GB-4b)AOpFqZEgpBRpW7Dx&LOKl?OZn}|Qa;!$L;WQ4IRX{K z^ACN$|3RbCbN$4o8hz7$*f;?zF9}wjaJRQ@P@9CC9}qW16q2mT6?wQknWLQ6CuRza zMLr<|_2Jowsb}ZtRl!lHn#8%rX8YCwuC7kzYC;ds%~|opx=C}wEytw!Bhk-0#7X3U zj^*M|>x2seTw*dea6>c3wz4OGdwC?UNxxd!gW+_fk+LuEsW2zmS4Q$k-B;+4rAO0W zhaMfqR1vzmN-jOReotJ_M5_GlUc|W?6t`U7HT{%>(1bHnkJ@9fc(8fGj|JgtN;wP2`c8!(K*Lu?r!oFz}q3#g5?lxoq-v zf#58GMrdRtkL$!H=}nT0^6=VZ1W)XtS+51UVf4(dWf(!lFuE=qK#E3CGYp!8!zX+m zWAH^P&QL*9IwuFsNiZBiqQuEf@Fgn7^N_fr$%s#(piVq0bXixUe^D>uswkf42QlxW z!O4RdgcOzF+*gPp*1b>C;n&8&_(Vm&VtOF9BHc>+h*1(hq%`lwev=HNF6U0Ob3#XP zY)7HN2Y5L)$od3Q!k{4CDg(TPIGV6S;(TTQAbA^gH8dO|FDSa39fY3~$P8zy@qoTT zr2aTX>hcLm4O=GUlIKY#tBbfn{48Qkrufpnx+llJLXa9-tp1kJ42%iHvzx8QwS@yDwTRq+CH58Oc~H z@lpbs(S~1~j#L9`am-D}%Bm>gfB-?qeH=nAVil$|H-udCQO+;p<gV(E6yi>R6*2q;F%rS*jf11U zSa(j82*)yk91{J#6gd}lj}>>~D6(=-Xg-zaT?V7A zSOxIB>n9b}1Zsq@6|CeqSsH925FW%-oC*j_6M&GA`3-fSc-O1X%;u5)lol?$F^N0! zk3XZ~X=#)AQ>xAPr<1`FRPTq$$3reN0qY3e`VzkphHnUq5!6v=9Nnw9*HZU|_`Y{L zZqZR$asN=eq&Cw!TPp&@QnOXO>$A`YQGSh59<%t*D`(}i@^C37 zwa}RzXqbc5Yv?5xt5@*Az!683bf2$B()r=w>$G`X1a0%d;%&+~e-fpfzxs0h5D>`a zd`je1P%uRCMv}3ei`Ma>Rz&7j=C9$k2{JBtmj6}Xq0XWS^yR$aSHx=>H4%N;a+Gc&G=W`g-~@*1c{>O+)qq6$<*3otUsqv)9yC=+Ql zT6XaiRc}t93Tfx${S0l1g^rUFL=`PuGt?!}ACcCZp;Z`^}e14)iif<`*fBFbkAqnX!fU-I!P4@#17& z(~X+->cPd<`{;*LK9>K8ikno>qKZ?RiGN51WpQ$+nCp13gF_@$Qdu`m$Dh;F%!ET; zZgrZYf*R-xR+%Qb*dBvW*shmiG#PeF-&%j=qDC zLj);hx*MtIrBNJcltqt~PL&ms9ABw9oPiOnTHl&oq>st}Aj@nsPKh^ujtUBgk=IEo zM9Os2Z0`W~L2-S}`25a;K=*u(P08)upvJ971KKpC4O_bN2fSxs4`E>OGP(*h+Oi5N z;YQ~MA6P+g=ZVR;JrT16XWNct_FzES{okNM`4a3*4tjpQD6&jJ!Rnf}6O>|N)E#R# zP%%_cikhcTA#zYxgw=*LdC3@0uuE-#X<@mdPlLGz((>(xe+0!MXG@XLv@)vzMe-s= zMDlPgN6}$a^b7+e$RJcC$k@aUbSg550u?|+N2ZNCoI_NUfC}IOsKB0bf(qx@cMMR1lQL6S&^wA7i9L5E#zDNv1I|);_0jC1+inrVJqb0iE2Apzxf(t7>zWxFYRGr5a|ZmN^}FeKA;;FHCjhYEVe** zBLx;KCNA^_-FAwdQb$isWrb!+8Ha4M94aFbJiki>G1euE3-(;;2R1-leXvaulh6Z|Fi z;ov*>VGDSoB2rf~GQ}N}AycuhA!Mo?lBw1Vv;h*+_}F6Fvpmmsdq(7wgoMjsntK&A zgqS{Bs^JzM|B_x8pw5Hxtbp>M4>yTVUNHDS@w!(-LAzPA*t8Q)=E6h)rkXa}3bn(J zWJYsLR%VDfM~VS?p`=|2X-{Q9nW5S!JDO4Oi~Dp{!j;|;^8{13A+!E%TVmQ@m3NISgK={qaO@Peomv_qv97-Y*4XE#dv&iMNW%U#*agg&c$GdbjW510l{`-qNT(z``G& z=ZH^XIL#oO0KjSO&&9fooIy~;&ZdR8gj{Qn@zD^0-0oq;e-+PL`UVF7)C3NvsNn@F y-lgIS6%;B7dh-|z^bf;r(~rnlJPO9arJ?}9)5shwsWo+4oxe1{fPZcN_5T6QJ|Wux literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__init__.py b/flex/lib/python3.6/site-packages/social_core/tests/actions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d5c3f31300afe2124cca1f095bd6614c8e6ba6a GIT binary patch literal 213 zcmXr!<>it-l@i4O1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuO|J1qRg_yl2rY& z#3KE){G623qGJ8DB7Ng*!@QzG6H9}Xs+20rViSvO6U$snGZO&`m1M z%t_HT%}Xw_C^0D1FV0WSOw7?uEG@~;CW;)OS literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/actions.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/actions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8df58f7e2d76327619be54729527cbff93f1b976 GIT binary patch literal 6187 zcmbtYNpl;=6`l=)!A5|)Xsxy^$*@g;mS{&(B-<24ON?bm5uqfWERz9xNDer_Ks^IV zM1hBtROMXc(htxvm;8v_Q$D70nrlwF_y_Qm@AV8oh^DtPQ1g1R= zoi(!}t$Vq4-pscPW&!CmGrWm*(JZ!0W~n`CPPV7asrIxvEy^<9OncUxm8Dw}%d*@v ziRB!%HTP6C=b5}AmGiGKC#hDJqs;Ak{T_G1a2E-q$Gxu21Lu{B;<6GMB4s@cqC(qe zJK!u^}cQBiL+zGcsXKS}f6CVCfC6VACh&U3d(^QxYbtvS}n~oz%uMq!N~A1vbHoti&dn&ZgKjn_;tTj?LqLj4iM?*l~7( zokYp0ml`|$asmBhzLGX&c80zA%rLX;Eq3;qWail0>>R>8d!N0-%Fkr8z!urN>^;OL zzLGS_EJERxXyS$)I4ezB8?CE(Baq7;_nb|S@PX4XcD%`Kc7Gq=T) zl*=L&IedYJOVi1)JG-VvYKRQaZwm8{Qcl};J=55Bxnnmx$5b1xZ|1wbhUYfp!A7Yq zHwXhWvx$lMj~z@ZiMpLFbP%>ZF@tOqJ%tXl>@d>E9?a>`4{gtSZ3X7E|G0xnuV;={ zDTa!&%nf#|UI44%$7vZ_*TdG)R?v^YfxaTX-~@6m0(#E?U2ODR zk1c-KY4XeA(!;7i8gG#xY_aKcC&>VmAs*s{231>})=^mL?(%b(_7z$-p}QpOa)18y zb6puOX{0TJM5?`tFEAlOsU<&^WGRHEM5rZUDhc%@Of&hZyeAK&meIWyX2R@K#4Q8b zMGjM#`W*VUazgugp=+(yeN z9^7e$Fe$fO;57M2cRFOE;!s67?gUO~HSH!2Z63wEKw^S~PU0*HG7V10D@rGGh%`dB zvck_J*G%7tXU5;9ibA|cwNvQ(LH9bM!4!lf8)N^HT$1}Uhx9EPjFe3#N^=&qTgxOXOUw)vm>>$l@&o$_Fn3s+jSL7916EwzN1|t^- znO16$eyYecS4x#wfl^7!Nt$PCiscbbcmESJ_H(#PNYA{Gxjs<3-y%NS{gxH?l+k!7 z9VlS;DH;*wQfqEtAk?UDV*6*(UN)Sk9DK(HSvLDz-OCMR^w~g85uemrU~@Qh>I=DB zL;8);EOVGM^eNIgfZVZKKV>iOIJ^D4y%;!v;*e7NKlwBQBj+~muCA}#u~r_`Z(Fz4 z>$e}=us&b=GFmtye`9U+{#rdcHC}Y*?ydEjb${*V`u(-ly7l1xo#^a%IrY0%tFN!F z)YtFUtQ#why2$FBCX-G-AL+ZLgz_XZ3Xb^G~z~A6mvS|QNHPSLZ=f74+ae>lYa`) zR3Yd`A$=S80&)q&#=xWpa=Zb_@r3sZfFUX`tKx`8xd0v%{4EP-ldqs93d8}Xvx`#U zh=S%oM~7fYw*<@Y(U5d}CAoj$5bnV16?H1th||5`<4T;y8U!>DFUEt){1W2jXco!@w#5MO8>CQR-pO;kz+T1H6g9 z9r8%rNWBFEF}Q;YAhSr_a>6JFDs_0c_ONGra1yXBk#_IyMm;L6tgfzYY*_WXunA#0 zljCltC3dnvsi;Pz=0~(T?R~T9~yr zY5GH6vcNIToq*HU^WV{OGcZ3+JZ6T^@mNs7;dnzJA!3N(PagHP06Zx!e(_sIU`RfdEhfXB`Z%LWuQbM#71la`4lfH zhB}4TC?^U#>@o~TY6?@sK*XrzAg$sHbmR_AK0)#G>;fyk1YQFr4{)3iUTW#A1Y}hO zGUM(ea9kUxY;vFi$7^f~I6es+p9YR=Y)0UCjQZ#=M*R_nDr^=EF!vgcr{eq32*)wT zUK%482WenpYJ`w!;P^30jd~*-pJar%Q{Df@_p1cQr-8ZS$UTiZGcTm>zNmF_gy6FS z9VIb>pJJzh;6^w{Ir!!W20H@;Cr%+Pyoq^b5s&#L``S#vIAg1b8R{7E5OUvou7V$q z?d3vZDY#r%L7}w(emFbGxiWkEIjt#;RlU(VjxmUrii12myC4m6$?B!n3Dg#OB8NET zn?daK9W~#WYfiFr+g@{vo-ar7mxfIL!8(p!5*L3j+8T z@`n_E{d*|hdW>rmHh_nCxUxov;0K7mpQ#S_3l6_5)O$?5?ds;Nw_3h5arYHZDfryJ|pqE_rPya-pF0x#ACu);Oi9o4T;Z5 z+#&G=i5iJ}B#CJ$|Bo4W2r-lrA^iKU9O@a`DF6=Rb zD=O}F-}(PkUeAj=nP7D=Jh8eV&nOylr;skm#PvGjl;2ovT~*2*P%9U+Xe ztSD<)$@8lfZ6x40zU9kK7iJ<`a@j#8}G?gsJpiJdz~I$I)fk;Gp}bReQa^1RY? zJkPQY)C!|5Qzg2Si>Dg$MglSgL)MbNB4@E=Qs>Y3D{K9g|tb`>mMkftunNDJe3p#XIydE=KVM RUc6i=dHm_!;)Ie{{|}CTZB_sP literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_associate.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_associate.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adbb966628a22eafa6e4a8bef0c0b05011dbdbd4 GIT binary patch literal 3668 zcmbVPUvJyi6(=c@qGd^QVkfDcCe^m0g9cdgzjRJe6iK@#eOL`7b+LgMz)`%{G8Kws z?xm8N$eROnFU9r&`UUn?_7U#up8OT|wBNauWNWcKbToK$|L~r3&%fU}-fguScmK)! z=gWriFXO^vWBeGe{0j&(n3)*9!7Nrw%(3NLron9HB(<^a+Zem7o;YLIcTMA&!92F~ z&R|R2J+1k5%o}VO^JQMg+{3)dT9~)={t`34HM;FHNHGr1YM@Q0 zOP(CT+DkzYC83l-@DJnPf9>ZYevL!@*P-YiWeMX#_K!q=_vLmfUhO^HVkIjd%Dw%U zdk@DCKi%8HGfex3{XEV$2a`Bqn-9`R>=#?F`r32JW(aR?Mw#H#g+PxBxJ&j!?K;_~ zS%6DazV78Gq6LMTc%=(sm~H%(>z8<>r}ba(2l?VlI{GOHENze{@M{+E!l*W~Pfcc$ z?J;wz6=tm)BbU|Y9~#!Fc1^KDOmEJ~DjjMsYJUT3Q>{I7%`AU)nbwO({%`dWs$>wY zzH^28VhtDSy8iu{?!gh&x2qL9WpH<3$xNXG-iY z+G78I+v0LY$Y(DID3^jjHG*KAu}MPXW)P5*svZ9Z|32pt&?cCro(@E@LCV|(QLSmZ zL_A4?;M|qak{l<6#agV*e!*7Q4R({Q;%|-JVz=2H_5oYR&c;tRyZh7nsR=~;kliChWA_0tAE~u( zCP@+J3D7diCTSrLk#eeD!P$-=3@f*fiz1hg`u!^235ELcI2>}>2i#-?2n2pR!Eu=9 zC%s{O1Z2s^{Fr|qWsH{}&(4Q=j9tnZ&4X+S6sZoEE8{E;lURnG>5A_@i;J%(gYwSo z{Drdvq+8kgHcUHDMVLmhjIsrod|KW(Jm!LTvNYnIH=*qHdOCH_C~S59mK&Gwo!w?_ zXKQPJb8CBZd+%^-@6qn=qumF-hSM_!24~Mf;+5ZEM85RhZ#au50~%Wp*BL~$P$$}UcF?ynYELFrx9gDKP1>`jV~78mC_YEi1eV)+vJgSZi0 zx~82c@Z80UIY($8`+%SsLTi*}=8vYvW~-=S&mK9zYio+vgy2|VHI3Kyf2}&_tGceb z2VK)|Q}_i8rVa)fJJF%8<3a)NMwk>-af|ph*9dUo*w2Kwdm7%x)1(Wk zx)6lZ6H=S`qW0>zrRzc?A!} z?~$P50nT23QR`J7s|`hEy0N1q!7wC9w-y z?q6#}YI3WazQ^vpOs+zY93_57UG@iwtPGpMIr+BqDJ+tXF9UZ;hhS2rc~Z353~T1~TATUlNC G?SBA1NQ-0u literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_disconnect.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_disconnect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b90d4c1eb628a1f9fb8744ff39c0b5821237806f GIT binary patch literal 2801 zcma)8&2HO95Z)yzilQjlR-B}1+N3{81Esa)Hck--K@rz65(9za#tK^SLeSc^O_>tq z-IWs?@}g<}U9r^qA!FR%+ptzlgX1+LJ$OT@mevir4l-QQJyl#)YhHiQ1jr?>k~|acO}a zvco00cyD)cslD{$;sRXTom!*Tjk>AcRUeecCDvAXpGG4U}0=AwH#inFuCy__?A4Mm1ytw=SlTTtb4d zh-n~cF~e+iuNTIt8S_qRXFjYNVg^T5kRVVQnZ-#{=X{4ki4m7M#zxoNc(-fVFsX*V zzzyM}eepXm@Ad_pJjaYH;Ey9iLC_Ar9cB*9BNJv8l#5dEkA?nGMUZ(g(ioT&Esyj$ z)T89gO5YrM)UIdFLTCC`FJeE4Jg@=}S}ncXxmQ4+8W9r(oal5#vL8WAi_19q6C^|T zSFMqg{W3s-;fvKGh?wt64pj;BD?AUt*XIBTUPfvsczga;SVs1j zU=2*5LO;h!S4dw6guL%ADlB_cRW)RSB987#RP z4ZJ%(aAkQ#UTdgECNtCN^tW z)P;iJ(P>%%>EZp4FAV1wtylA@69o7t5Co|k1nq?NV&uIb!1j`9#6>*Tr$|0SGKXYz zO3h(E5grh1V7lWv)UlnsQ`FLGS=V)tcAB5C8A$A=j^TmB;5MeIXZ{b$A=)$m literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_login.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_login.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ed579e194e1b343aea955f3e5637c723abd2be0 GIT binary patch literal 3377 zcmbVO+iu%N5ap7TM9cCe*P=;XBqwnT8wi59j*daDsQ35bc@9OqM#o6rxB6C}E z@t(wu`JWug*PEdm_*-0tP3RpyIQ}twvI>H0Hes5rGs5&^%{G|9OekR=mWDFLGR%T9 z&1P5@$_$%jIVdeQ$L67&VGC>#$}Bqrx}1#`szKZJM>a-%@WB$l4ftdZ#2al_JAy8z z!9-F611uI$52vFsl6$ScHDSTX{&8tvxQ9;2ONWjqwE~ZEAxkY$da!rT7Y8eA%dE$G zYjS0EZ)L5s_T$PjJZ-VvUOXucPcqGmS3%c)!5Y(h#0QX#< z=ZVO`b_$PJ%s?}`ak|SWM>eG>- zEd5T0mT{!3UK8C6?UWjkX8I9f=HOq_XFy*H`xG5AHTyUKhvj`a`j1r zHnukFnJCANZ|`z zzb&IhYzcnQMgKAmn%7~kc)x}x~C9z7gQTUp|kJtkkjT1-rSS9)++ZDVSPO|iz!YBDl4;`<|akj*DG(aOqde^7oF+S%5 z6c7n{3B(^-OJQ(qy(NDFH{U2mDlQqdChmey0v;M7zXyRsk;X^lZCuxP4S-97Hc(r^ zW;~23Y1ZtElR-(t07zq}frLOo1xX&N!f$voW6k*xX2pHrc$V$u{u-^bT znq%y@ua4%HTKPA+-6+>7q?Ss(ys<^M>($;&$>%Q%T}T5E7vgK!CZCF<;4IWn0zja7 z2~y~qN$$@noM17Ei(Q2g63;Zu1z8<l<5@U&}EFvF)ypN8lkJ+MXG)9izz3VhW81eFhk36&T6t3X$8enuOcX z0Ff-_f@@PT8Jji+3}#qGhh?Y?fmvX=cW~4wKyi|(j_iLAo` z7ULTO#T!FXXFPOVPxkK&{^8}}qZ7I=>lZ#b7`LE~=LIi0>)-w4@WB8iR{z%2(fF$f z2bY4{cRIX(^Yn2|!Qr%2L;nX7a7N-yDw(i6b=^Uyf(}z3g=0rJ>dgI^#;7(;FdnlE z2_#RD7Q7Q2#wtTTy*$e6>gF@JD3t5<%{oT3eSY-k^UCvbwNfh&37!=U5Cd^iMoW>k@*5DC6m%UfOA~L_fS|Z~N>H4vQ_Igr z7NzKBN~0{Loq%;ctmi1jyG;B?V6+L08Sxbg+*u_iaSdzNQD9yj$z+(Z1g3qF0)d}g z1Yr@&fOp19Ss8dO^|zK!MQPZXds3)Wkr`a41s(!w$h4Is>tU?!BfK=L10zRr#(%1K Bcr^e3 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/actions.py b/flex/lib/python3.6/site-packages/social_core/tests/actions/actions.py new file mode 100644 index 0000000..291b313 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/actions/actions.py @@ -0,0 +1,211 @@ +import json +import requests +import unittest2 as unittest + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlparse + +from ...utils import parse_qs, module_member +from ...actions import do_auth, do_complete +from ..models import TestStorage, User, TestUserSocialAuth, TestNonce, \ + TestAssociation +from ..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_core.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_core.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 + ('&' if '?' in target_url else '?') + \ + '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_core.backends.github.GithubOAuth2', + ), + 'SOCIAL_AUTH_PIPELINE': ( + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.tests.pipeline.ask_for_password', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.tests.pipeline.set_password', + 'social_core.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 + ('&' if '?' in target_url else '?') + \ + '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) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/test_associate.py b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_associate.py new file mode 100644 index 0000000..648d35b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_associate.py @@ -0,0 +1,87 @@ +import json + +from ...exceptions import AuthAlreadyAssociated + +from ..models import User +from .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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/test_disconnect.py b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_disconnect.py new file mode 100644 index 0000000..7edd52c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_disconnect.py @@ -0,0 +1,67 @@ +import requests + +from httpretty import HTTPretty + +from ...actions import do_disconnect +from ...exceptions import NotAllowedToDisconnect +from ...utils import parse_qs + +from ..models import User, TestUserSocialAuth +from .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_core.tests.pipeline.ask_for_password', + 'social_core.tests.pipeline.set_password', + 'social_core.pipeline.disconnect.allowed_to_disconnect', + 'social_core.pipeline.disconnect.get_entries', + 'social_core.pipeline.disconnect.revoke_tokens', + 'social_core.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) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/actions/test_login.py b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_login.py new file mode 100644 index 0000000..6142dbf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/actions/test_login.py @@ -0,0 +1,65 @@ +from ..models import User +from .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_token = self.strategy.session_get('partial_pipeline_token') + partial = self.strategy.storage.partial.load(partial_token) + partial.data['backend'] = 'foobar' + 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_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details', + 'social_core.tests.pipeline.remove_user' + ) + }) + redirect = self.do_login(after_complete_checks=False) + self.assertEqual(redirect.url, '/error') diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__init__.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e492bddc1572c4c384389f4dd42fb95c7d72ed6 GIT binary patch literal 214 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 t6+%}D*!&URD^KZ+*LahJ* literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/legacy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/legacy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..795e0d49d0802ab4b3874ed3ea075ed147b9c2a8 GIT binary patch literal 1676 zcma)6&2HO95Z+x<6eUZt<2XNU5wtn9fDQ^B#3^DRD4GOmQoyx?SibPGpt);>GDXt6 zt2k2aLm>k>`7L_so3wp|z4qi+=&3U++lhTDi5(7Sc4oevpMBJBH^2H@#DCQY`GDey5E1Jq%o5-4@4jdt3+wZ5A+$G3l^UzT0T$GeOZXU)FwsSiwBMH(Q7~? ziK!ql6EyZ<`5(z16-;<1VO$fwsGX2F5P_(}xBij%Bn|;fXge5SLk^O3g8nT@F)}{* zg-j5PqMrgW#Ly!$V?#CriDLz4hYf zXB+(4PX9Ikb@iD4wDtBEzVmit+KlpYkQEUKyA4~@<4P)3hmgQavs2kn8y3EH$^0CTneRrNbNUpDCzK3b&Jk1lWIsb$F z`&(40>^=C7-X|*BD{~>0j`mdaX#ZiM4%XIJ#Z*k!_1csDwe`{Z_iL;042q~9Raw<@ z0eg>&R6Q}P2azt*EXjMx*u3kdrIP0Y?sx;~)zLW|9px_P52}f3!a>`ZWMl~K(114K zovvIh%>$Q;E9Jq`yga^`(LsXQhwe9tGS&kxih&?* zDLKfrk;?V9_v&Te`WVNREe5ZnDwnSFbL+p^+V0!7lDaAjxKt3Q4V63?VbmhybU-#)umZA)v|8^*qw`ZV5%n-L@~l zLVpK@&=u;#%ivvR$RY33kTvOD_t#;!>GaOEPP*^|cSOiJjy&hK$@!=h;~e=G=XjF2 zM%{xz>T4u8SZW2yw?J(B`~pa2o^$>QE_C}1+=M;uuAc@lZYo0r#adzLJ9#%$-+(G^ xyfdb97q}~rt&L5VYnMFe3%FQSaN+7xlsV_-D117H{sZ2%Is_837HiR#_aCu#sx1Hj literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/oauth.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/oauth.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84483312e9e446b35107852ddec1d1c213c006cf GIT binary patch literal 4114 zcmZ`+-E!N;6~+P}2$CR0%duVC{c(RLE!#?z8z-?kabsCw?EF-6C3-r=bOr-qAqg}{ zP>ZE%v9u>sdL~!NMQ?i37wDUG`Utqqm0lrN{mufS1dR>ME_N4ZcMs0_&UY5?HJkO@ z|Ksl3vZj5fUARoNAEKmRq2d~6vF6fKkJ(Unbw+I?Hio8a4l8a2eUn#WYgl!wL)*2{ zw|F(K4eM?lZJXEP#?Wycrajhpoi|=DP@Fx1ru!(*P{T5$Be@XS1INQ}a z?Nex>ZFN{yd(!Ve6`_!KSgol}p=GYf$cxpfr?`+lcj?!!yuRNb~4XcFZVml9uW;+N<~Deh+ju&>#_M=Cd4oH6ws@1b@XWt)DQl7f+Zz*kNZW5^ z+O+MD$8i++GD=3>L7I$C|BLMdQkX7!lA|_J(mSX)n`r~>2Hb=K)ry{pUM13DNNdY= zcDFY_+xXJkc-DX7xu*9;?=RDezw$ghajV{LZ*!;DpE~|Z8U`>k4U?TlFdRM8Fy8-< zCaz$w@4xDfMRe@TuzT!_?tT(;Shl+_y6-)|I}%6t*4OxyPuJ6X_n+TeAFh9JZw=SM zsN3(3qw&h#B#QaU`=dbImupAeGzlUQB2C|37T5S5mk~LS$q5=7nE_xDTlnD_8n$!ji1-|{aK4UjE7){>- zo{Y!6er7)1-tBh`L9@g=R9!>W(M21N%mTO$6Rxb2SF~VO_44uC=oIRiY*$pzfx;LTCG``Rj{Uoxv$h*YX}>q8*YE9onpIK>K)9`)-Y1{!^fvpRl5ks#Y3hBaD(L$J`6{a%O+s;!83&=v zs;GP^MOH1GEvgXdSh;;`m@H`LIIE)tS!%nDd{`|begOFk+vS4<*9#7D4*e02Lr>}Q z6TC}l7J;U&qcD@TjAiOkOP}5=Z#N!-ErdSp~i4@1;pRk)gs6 zrVqz4SebmO0*gR)5T)R063D}hLR-D$4=xup`-IMK4+_oPCVVZg} z0eRCx+11iW@x50{gYbAk-uabNmsTI;;4USB`∓=_h!j62wFp6%W4=ig<>gQj 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 v%Ne?%v`$8cHOPI?fU-!#G^Tz literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id_connect.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id_connect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7abadde878ae7e22991e1480f97d99f7c7a62577 GIT binary patch literal 5926 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 h{FQ9&J|pXmY!oFZ|M=E4q%S4hzWz zC&c0|6;F{D;8l18-*VMg;EHp+yUn%)lpx0PnV)lJ&hK}+b92`6@8I66Das$p)G%

    +;wqql9;vbBYO1oUfC07F3e=c(uDd$!>(Ib`quLvwK38n>4c=DzZ&akV zT3SDD<=Mg7K9gBMjLAUdBV|u;!4y{o#nnJ{bb0~dU`q$2zc!Hsgp}4oK)~(7_qYcnDcF4l=UI!1DXfS7fje%+4O^eN$F)A zwUkyhdD&UYio1SXB&!|d^E}BqTiZL|K3ZMt_KI12WD{TMpr)dMFMoGBo zbR<0SGv=K5!Wr@in2^p;INhVwL>%`vR-k}lL-y8>dK>Y^gWd{`SBbOlq+!|~WhqWkA|}-Oqi{TIaE@`VMG>iTP{@^-WR@$tcg+guQuxaD}B3Jnx03g z^1RaWyqH5C(Yon*$GIPkIn8lQ(7^1VoU2j|e&&0*h{{FwbIO8@fk)?bqcjdBPs$qP zaVpEj=_2z69L~!5%NJ-bTIA%dcRi)t3vyQw)a&@m50RLvG5$2w(hOC@&(cl3l6nWU zbY+vS?IF1-Xkqq3hh_!lnK5K7Sb$aq-9@a~TjgidG2Rvgq32ATF!7AZlqZr=G5r5< z7SxOcIVT!OY)zQ7o^MjF+Q@6{jrz7UcQ)WE8z8P1bOx(t&1hoyZMC1b^1Xw{eEj}W0kH|8SkBR&{CO$zCl9hDp zZs74rt7g5>O4rNFImYI{P*qY5n*bE>zzMO$!y$qSY%EoK?dSE2+n<4$X~ zqubnHT3eYf(v(;7R-CK!VY(J69n6)=xTb?la4qySqzTV+VGOeh?X|Jrn7yt<^O3WI zW;UD6Y|e9$DYcsAYMpjA&YR))N`3q-*Ubm7J^EnKTc^@I-U=3)SIf6qNh_J#T8nR- z%dgC3?ZR>$k-z^IU>Rt#bk}#%e_M7+)7m%x^FkqTA}^7lg*M_Ca(m`YFSvCfRb%GXR5aA?s_s;MWHrmFIRPG=|q+28SP2sJ%UU4hZE3UE91qf}@xlSLg9+vv@BxvsMK+(OW*`3<|$^Ko;zT(3LRdO2W7=8FEbi`GzecyX{ zQ~3dkwlRJB)|lS>Vw$ug#vHa7Yh%XN3TkXWYZJy0mqjO`$!$F_JiDah+F-+hTkd<@ zyD+M3`%tedLl1VJ7RwY|)5)E>=MTismB_6C<^;}z9=jAL|IX9(UAttp`_&C_nI}j_ zLC?=Hh{IkG{^vxka1?nP)1E#=3$(9X>ve=@f4@`Q*G}8fmt8Hsc91nLgi)A8{{V$c Bk$nIF literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_arcgis.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_arcgis.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dc34d733865b17346b4495701fa23c5d743f89e2 GIT binary patch literal 1271 zcmb7E&2Aev5a#Y`?bTXJ9TYtj>7l10I>>8VvMm)wVLNsdCyMICHemBY&`V0Y*8Z8~ zT9wd400(`Fyg*;2kFeLC@(Mk5xUyg&$faD&?2sG|XXg9BR=w^%`!@i*ybxYt2#Wh*b2z%C;Q&OPPyu^Dsw-XFt4&T zR{Mys%j)b2{MXnwkivSgejxnb$=T!Gv+Y2sO#bY7lgVU*3V#&J4L^-N8Bh@>BTqnr z9xTLp5~`^uc{H3om#H7pi1?}C8`sny^MuL8;pH1 zmS)4o?l2Tmk%Y#)s77>Q+B~LVgq>j?MFYL(j_)tkdZadP)^7O<_?>L>{Ig5$~A9ogmmzTa0D zx4bnv+6}j+^y=zmcJc1@WZHRioZpQ3&cShKPn~uzPtvz4Ym=z8y_sF~!w>Ha)G%?4 z`aYMEs1(|OUH*F(LeEH;_!?d)K6be>dPa8 zMy+s2$vHv_mykGRdBnl45ptW;Nb`&kT-Tfr7w8Als@))(?B7t7Em{We_l8CP>7g`5WEImfkDFbb zhM|yzQ6S6#ibfpffy;bZJDs!Up?2+DQdS?I=b#Sr;6eZk(17N(b3$S9+E)}2^RNW> zt_TZY8CGyzfB?f;EtmF;NX2a!qYaZqO2jMba2v zWazRC#tSbla#?yH3ZZ%(LPm>k$JW+gSJw8`?hi^v!GV&nexPz|sB{#RUMdpV^7S0n zPuEeDzD$IQnVjk3Ve@Eeb%(i$8FQ_p^n+I{FKY6kihM| z{iF0wO_r8E43pfj<=MvLM;TnO#aj#ZteZB)#lQ2kylb1RwogF~r&&i)r{3gqXyAI( unSQQAS88>4jBa^)1+mP}EXf+*mdeUZ8UL6s>o2RUU)km~)>++YxW57WQafD$ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_azuread.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_azuread.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec1a503e67cf6909c62853b94e242b9d66afa12f GIT binary patch literal 3380 zcmb7GTW{M&7N#WIaTq(Bq%F{53+y0RY^#NA-P5R8EK2K2MI z+`Zo(^uL2QDnNrdox>TNbu)Sf=kT7J^>TVH!*w~lj}Lz2@Bzua&*^(OJL2+>%qa-- z&0xL?>9rH1ukAdll*)A^Q9BHx;3`JSocKs1w&MqGa7{^6K%#G-^1R@>D3yv(NFow0 z2nMPoYG^=h;zxw%&q?UfD58OnXoMUR5^II7L(`85E}*NB5ENV?+cCpyQb2KlOn-&u zB#b~Huws+?)W1e1vcW+fIAaIYFt3(oNPrn5Gl~M6nxGHiz)n2k$7amzT~U`r=f3IDnGd^ZmwNQh0u$fj=fU$N z0tC#@0`dY(uh^HQu;z*7(#R#576;hNBpSC?c-Qg9ZpOi^_fPQXUo` zhz;1k3sJ-yQTRa&uBPEm6OcNw&FbA$HJR&4Vzt z_c2UZK3cDhMoLGU2x=RNBXq7RW3kn4p_jr4#xDzKB5FP5LPNlz3bHmuN(Tw@6ulAU zRsppy&(-#5gcOw*rSpN<2AU{02Nx|-?xHi0lNDH?A}mu-sws#Wyi+B%L9T;U||8f0MI*MP^&ErKo_J(P9g4?Px)V<9&z$78EEsF4X+4E z>{K+^U^biB{SKk?9lX&$pdlQTEl$tkjGn_;C{sCopB&5{u~Nng7=DBg@#CKk-e>f~ zl+FYF06)P;`1q%NpgzV=@gIKVbRIv$&*A$7zrdfs_XvLq1@E)Xb77r?L}>l$%=)is z32_ZO2+65s+Bd|<(W%M!Onmx)fBmHs$MfjhViBs?+&P5}go9;t>XW$W5&hsQ2rM(y z4=e%&Dl{`WK(y+Y$fZCP+J7@H59y%UcdV|>#^S|VEX%atI0dG;U6mLyz&&+ouY<*) zDwph4y=qSCH&zv|2VUL9&DT|FW|yRv?o3yus??g+2HLQud+k`!`cxT}XOcIp$ZPRd zn)GL|iWeeXQghNNiL-fvC$|we;F2CVr-{SK>@qDHC}nwc5+Uq)+Ab zFqXT+)s&VUsWk-Jv_7qjZ={NTBY9F)reY-ea(PNY*6)m!o)g+@Hy-sm$#|x#(pr6G zwy-uF&ll5*8z>j!SGM2pE0gka>^kS8Nq;ibu311T`lSfi#oNJ5VB?ZG8M5*3#mZzC zJ6V0^xRQ2LQAVZObas6Uaat=~DOS3}l0GUsdQ119eypZ5u{QNYCR?_-5(~?N+V{u zwR`yHvNEpOX%2@~X)P@kZCd*vd4{Tn_OVWe|TOHRy*`=l{y;u=6xm-YUwOkPJf zQ}<2n96NNduF+>tIgY+P{aK^vPd{te!{rNpgNZ^7IIop{HXokB1jaT{oa32ay zc)*%R(CEi^oLkHKeguawv02q(KlWvB6W$1}UflQGjC&6x%t1lP$a$zlI561V$cHMo z2gmr;zqpVG&j0?iI1lLpZYRZs85ZHl#c;PNUWLW#P1z6M)fy$d#_L8@dwo-Dc#T)J z68x_HqE?*K`H7WK7oXI9JA56N-W9i1>coU1dcsQ89U(1S#Tcq;RNNJ>bZG1<*n9;s zhWV|J0>hDohRdwKd{Eq2Wj_6t@%KdF6Ntdvgj3yg4RD>f)F=5&_!z9Dk=Qm*V7U1# zweT@>3#N8nef1k&eJu0AL8!7Ed(#xXgw8faZ?ivSe#k;U^L=&(C(0Kb&c4syB`znY z%i#alP(G(W12hlU#1pt@k}z(+OH6kI%P|g31Dmm#&!reQdvN`OvcFBy$B18~h#x5I zGPvyC&Sj1=>p%Yn;0Gdd{&;g}7{2Kd!`SeK0oTmLW&I<=V18|9^rO2)0Qra={g^qH zx@sh$t3QXG1eZX94Mt}Yo4py_wm17Y@#c~K{Qk}~tN^d{7Z1EhX(8JN;W2BTv4*Yn zdksr-$iN|ZqrX6NnAy8~vKe@uvX;;8v3~mSxkGqTnf(6dkzv4H-Y~*XK~&!l*oqHX fep7~fhElufqB{KBHX48bf|+DndoO#KJ<9zLIRE>S literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_behance.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_behance.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c61ef11df53ff102bb988099a32f77d8741cddeb GIT binary patch literal 1579 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_box.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_box.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7b5a649f04995b90f81a6be03e3bd7b33ad0f94 GIT binary patch literal 2334 zcmb7FS#KOg5T0Y#S&w&Z$BCU2a1sKnkUcx=^|2Np$0m-G5C;+`4lSfn?^Ju;>$%)L z>tmEWIX@$hyzm?N2mFM2MMBCyKs-@B>zjbYjJo=$>hAjL>ZwY_y73#3KT4YRyLRd; z;CKO>`~nGRp!*upzyQOwgZe)xP&At*yS-kyiE0dU~&$ zY}VJ9OgzOn`mW<(0h|083DbxU8Zkh}Lkv;?lN4EB=Yl%EtwA4LfYQ6-flf;KYCl*o z049{70)sFF!*CHsQ0@}`mthpfuw8+x_+NwTa070>GvT9mW4OQngQr^XF?{k?BLgrF zx3Dk6ZMcJd1wMtl*bl;I2<-E$vKZ~1z)5e*$m7&^y-2Xh7WKAR2y(I&?M)ubrQ@4P z63cnprZLZ1UKH5%$(r4jOh`Lm>1D}i_$A$CG78v&+KnRF_*&DuKTYL>u)F``($e-r zvK*~7|*@a(n6Jqh{+xC|_(mc*4WSi{E}n*1vBqwx3mtS($nslhREh1VSvf7jwa+ z%R^GZJ`vnY7$7&ZatSxtkC|d^Aa}F3D=R2iq~Doy>ds8VnNIr^x^WRnmR_sZoccs< zdZIS9R(Iy7n)8k`QJbBw)ec!WbsB4qGq1QPIfpD#q!92hGg#=G7%9WA#EPZyKkxtg zFrVX+W@u}qk9rB;VVOp(ndw@y-f*U78|mbc^>=r7kFAfMu;~s>sIYx1+RQ%Ib~&?H zm@p9wE*U8~v$fgTy5ppm6bOZ3{DfUKvx2ffW^l*~Aq`k{Oq5X@QBee)qh&=uYNPoq zPcW)%M-ws-M8G$AKFJCq@>y2$Y1r=4HX~&jQ;)ech`M1ytlkVQ3S=mtdu})OBMPqM zDI0`HB%>Y;UCEdm&=8LTH;*qe#HB0uL(gR)ZTSqcKAF&@D@lJRYPktd zd`61#W)!mQ0tG-%N@Del<93BlZsLBn$AUOiBXjN>%c~@ zpx2nagO1yXd{FtbA?&FwCluR_W(`tEo3b&t)o2FInMMu2c4)8JF^?x&UGBrgbm)n> zq_%DM5_f`PWtvdgd$^EivYlYCOZzCF^Qo)SGTz6?M_y0AS%twsqUq_?<9t_TZt_Iv z{~*7F$YUxJRZ&o7=JSwMb#WPQ$Su$#V>r!5^Bc|!&m-$dqdOna=)C-~ateiNxm(nI z$}Jro^aK5k4#sPptGaHkrh_(?rr2lb1T=_E%7Ovb2rt1{;Z=i_uu7nZ+WTZAH@Hh% zGV;3#>+y3enTfLJ0bMy$MO@6vZh)yKzgCdcGoiZQmV%x<-_>JkV-a5MOA}b&pfLyVr@sS9aagoBP(=d6lO)}2C*a;Prn2h`>;<2 zPfp0^BWGabGU5ut5LXpZkGr_3h|1klM1`+V4po@tDyPCQCBj`=HCYAiBtdp literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_broken.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_broken.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e24488af6f72edaada52a9a186887c3528cc66b5 GIT binary patch literal 1951 zcmcgtOK%)S5bmDYNAJtn4kkc^unfF<6pxwY%2R&qCBF1IQx#Q>D@j#W-MALllDe#O_Jpar5-*t&+MoGS;Jl%l zIB#lx+`yHl;!jz(b<<-noym%X6_G8U5YFK0Eh{h4!H~_Nj%)>fv)#XchAeqM;tp z*0OON?BQ#cQD_3mq^G#_m4{~hd}SZ96P@ly1C)wPUAr*2*Wf=bQtE%FibKv ziD}#p!_!F=Uwo-h|0jbE8gSyDKY}rg&JbE~7C~^7#Y%$-jv&}Rd62^C&hD0)s_CxT zd3dt3o9uqGvxRM#28Y3TG~Vb>MzPxXIvv16yLB3vY%q%AjR=u#3^LFc3!xiZ^nwZc z1$YzCG%V0>j-Nvv5eec>P7~bCuQ`X-_ekLpic9Ax?q2$VkxO%qq;L$#_tdDTKt9ITtfH{Ya_i0bjK5%^uNWS{(cKOpR8awHDDwIR zF^q4;==kW;#jr%*t47=-?u_{@8~%xUHw`!jUYF9_r(cEElu z0C~OX+KIZ-f93d$v zeZmRE3#fHm=9Egeh#y6O@fsI=`uR;9G1E%@cS#gJL>RC}g4`|}&ge6nETXb>NrkjN z#!XkbtiO#5ZFrw#I*!aBqZnJgAU3O)gi6iN=16_{Hd5uLn~B}WqpqamYMG9T&G`RG z$oV)e548;^n4+Q5HX6ld0lPKFZeIC|IfC~V5S-uF>o$P?G@xG_S|q660A)0MMB+9H zN(%Ud#G9gBjHs-@2PoS38WMsFAD=2c?n}2N8ue;evshOO6e3n#7DcZlXFz8%)V$-8CJO?t6x9NaF1&K0`X=SXr*E0II?ojtVTi%nI!eMS(*x|iF5Yz zoBXkH9X+4TW?g>G;xyu2biAjiHBZ@EYUOsQa7F= zQB)I#(cD<3!R&1bIc7-uzNyC_p zIW?Dgkr;;wHJ1dYAv8z=rdKMm7^VHQSk z@G=SHk?vjk^J+g}*q;Zc+_&6-1Vue|5h} zOxt%?lK&?LyBGtLK+}+DJdeVJcWl|hYsy&6(H1U?{qlx~!r$Q5(yZtIX4XTc^G>s9 zP--fO(!!&dbjTt@Hz~c$STx@u%|&F;L_Hy`OmRs8lV4`NjV;Fm&4C)e(I~1H3aib9 zEKU_^uQw|l3b-Mgs}syxZW}k-_kA@LAdSP)yv#KO^%_ybt}aH!o)6EdE~q8vpJ&f$ oIOSQLsx{3H(+Mf(`VzCr~AJVh7* zA8N`=edc@Q0|5&4I|6m--_}?SyicUlxW{NRy!TLdyfXOseo~z6ol0B8?qIQ2&^Si1 zLsXJ54+!(Y!z4bVP-Au3IA3w;6Ythz&1%?lnf@dO?WlM>LS(m9uRP5+;%e|ePpx6-AS8`5n(FFvNdczKq1DY%SCCQL( zYNZ-~CuS8Rf89m7Nv=d8qbp&eu~s0Bjm9S0zu3*pWq+^(QtYZ%V@@6S70~0j>fs8qBL?4ZL}b^+VYCT{H6@t;jF+H4g`)F zLYi(aiFai6mQnwM{TX7NLD(UeD z!%kQ>dDS+~%YgG#!$e86L(VTJLb--=&cZnxSYC0)+7lcF4x$izVienu*SU-f3EcUs z5uoQ*XJs8GX>QrZ!!hF{4L59UnZfN%+d*9W8>j2KabS)6cN-}7C90-J=TG2M&!0ab oZPu!|4mD3Piq*AiuBoA2`5zGYCvn+)JHz|V4W)J0H2tvl8;DUN)c^nh literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coinbase.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coinbase.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..458b2f9e032c366c98de0c05c8b68043b47f889d GIT binary patch literal 1314 zcmb7E%}yIJ5cY01VF?KYXisfbRH+q_nk>I9R8<86i3?4s;N*+t>^QJwcfHt~sH}Q} zLmz^3rM^lZVXr;$3O#k45CT*^)YgoDX6*Sq^U15#YWc}epx+CG{33UngYzvEy@(13 zSRtVVY$$|QWK-KBdjuRPUJ@t@`=US#U>%Zr=^DLB>)JwHxAO9*?OuAaawK%>8^fqo zaI%G>DJnsz1%%pQ;UzY8pg@bFbUI}3jtt^9*J6w$d#$A(@R0dZiDsMoT@izB`Ztcv`zNx6lQd2A zJI~`uFn01~p)o)(JMG*6{I30Ta#GLosJrH+(su4t((xDu*v(+c-}p*2e-M$5~C zfu-k($Q|G*r!SVbm$QRqaj^b*PjrHn-H!)HoumEr!~LlLb~osET0E;A4&yAec$b?v zt?cCp+%0+o>>-}qF*q}Kgt$JL+X3WG%p;Mz#%5-;mMGZ?Lcv1m^E8k#of_@5136yw zR7lIbkFXWPdG0EqWl#BnR!1|I@btttEHKTCMs`rNsr^@SN-l5<2o}j|)v>}LG3;>= zBWoNj!p;w(s_6Oqo0q5nV+pU%mDiCW2&KJ_@>aXcu{vAZT!IX;O})0!UE7Q{->fZR zJC400FA0)HyBCDeSdV?RkuIHi!@@T>lCoi{f3q=Qf|nvowKw9!V2Oqydz#5P77ZPS zAfK9t*r)GcKaHps*1k#C3hK8Hxi8HWyT^2J>&q(D~z4>csR7s%1vShm8Dff8fKVl7(i#mmqhrf z05&31?wo2F=SAp6iKer6+0WV%>~{2D4T(=9RVkPc;Nk=Do`NMkN&e3YBX)H{G_% vwqx0U^@`&b250JydW0uvQJR`7{N#pr-O1-|klcMgeESE(Rc%7LcBSwKY3Xx| literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coursera.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coursera.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d2a642f0e7014e42b61462a63de6887e941217d GIT binary patch literal 1428 zcmb7EPjA~c6sIKHQ5-vMlD3y(z%XDy4Ahp}I*A>$TmKw(%LcS3UIbe2*cAhsYP$SJ_9nlaGldm4ho>#SheOuVm7a!5s z#5vY2q{$oVCL@82d%(C49xdYY1_Zo`t+NfcJhWirI=J$9SX)Bagf?tJ2fEiyvToMa zCTzn42&6anDCZ$*JJ&7pwqY0cJ~O@rkKi$l9oVK4pH#c=RiO=Paa&#a%&0_)G)|Pp z!BiyYn1LB2H-Uru8RhY*wYfQtB9Y76Af@Nd5aB0_Iuq$TVPx`~Rx_ER8C*R7dHH&Q z{QHB$As!*T7#=Z_SjN3e8*ZP-xUL*dQ>2sw0vFjft(I z;;ME%#P%}Bs*Rs=snNtTE4Nn3{X_wl57$a6F|&OM<)ix+gNa64?z{8QaZ;6t^i&yp zJRS~*5pAf7%$jJ8L&SFlU8vHs6jrK?-~#M5tA-o-W>RkxOW@s`>1uu6(FjyvV&g(f z-kxib*}C+T%9kKRcfgiz5gB+Z7jv1-s}NIMP|-#=$*d`Ye5uUN5faBP*WBp(d+Me` z;&*n&uE+$8F4{Zk5pO`nsiGB$jSYk8>`$g$0CAy?mkLw-24gQf_>$3L$tE_L6dfh6F<4i0tj;nSYFBBB1n|I=PS%`Gy;hh^` zs6u6h!6o0-xGWr?tM^gc zwr^K;y54pte{lc!juhHqpxvL(QPDgj5qb^3;knPJuT66|-kJNMUu!`a)S118-lII; fRE`Zgpm}Y-gY>^!RpF}@DnB_E8@F-jcY=QaZBCF~ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dailymotion.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dailymotion.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e8df246d44c1b5f7e8ad94e9180a41f85814bb7 GIT binary patch literal 1081 zcmb7DL2uJA6tJ^lrJ-y`HVS!w~!_pzI2Xn+9C z2`AuzpVPwQ9wi?M1kk!9(30MT&wZd@NZh_cGUXk%JOpWlt`@y?+%HgyZv?dI@L z7?FfiAlw6rhj=^ypSNWDWCeVe;D>iXc{uldXgB^UtU!24cm%5u;kX8Cn7mVOeMqu= zQYf3La*=wvV^o?Yxk#0kyh^6}_G&K!9+^~2S(Zs5QzwyYZgl-UcGE%gjhv7R z3;{t-^dLt1phT{YvJxc?jLb(ri4GC@=R2!(cAi+t&J)c>DhH_z8)>$C`l8fl{lPX& zVLCAVz0>}n7`*ClV>>F@A*-^gI~->@bYGUK-m}|hY$j6IaqBvz=7B~vX7LFPyVkC8 z#j=3a1nDAXcF#@61uF`fO`qJ;)|EqV0sbE)Hc^Qx(bl~tQjmF8%9!dE%+x3{Gpp#< zTTO<$;77Q0eY5BPxY^x(2UuO95bouKs3Rc?1>@Y!J3^d|lYF+qJ9CNfmiCyhkB!u* zlTE}}=X^tctz>E?2p4xd>fpqvvTnh+s0`m&E*~*eFyRjtCpce6j_KNMx~>$o2 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_deezer.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_deezer.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e734a5c5a4c3f204be91a4216632a920ba1564f GIT binary patch literal 1611 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 HF&gGymnZ+f literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dribbble.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dribbble.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb767a0a77997cc1eb38f9ed394a411459a4f028 GIT binary patch literal 1079 zcmb7D&2G~`5Z<+&)O8c2fHWZQ4p4*l1?<=kxr0GuiBRgU7#seQ^l+O|C5u_uD9D9TgCu z8DRt*a5Fk_m_x}&0v@!^2(+Yg>M|GTR}!`_5KVS2DC&^b!RI$sx&LfenliBum#c-F zZ4~nfl_ZP;VGdAq;xG?f){^amC2(zkbLW&Ye|~nM4NK=P-YkO;%V&fI5WouB73g62 zZhiN?PUASv{w4We%nfb724G7t^)V|aN|$m!AtrPkgG9lEuXLhV}zxtPe>vpTUn z*=4PjcPOTV>N`0gr+5Vf%c46WasY~FYA?-^SI@|7{FCSak$-+fg-(w}DWhYdqp`|B zY7>ohw0XFZ>!aal08^Nb%y8>)IGT)J364ob+iB=8kpIKMJ>;Mey0qQI zm$Tt1t;vz!h=0) zI~TfpvsOC++7_(sAGVER9-{K8XMc`I9U8b+TkE?GD)jU!VpvPrHftC6&HXhBuiV!D Qt68Jib|Pb-UivpTNH}LVl53%f|B#ihh9#2+)LZ z0t~E#ri?R6J`u3NIV0dmc4~18=vU&mE)Y$2FDPoC*52p$RXHE-NnJ+9akU&g?4al` zs3hSO2xmYs2;(+b+>x#Q6%!s>uySslQtmbu7c6Llcg|)srf6-$Ds;{W_n-@FxUWJR zi?~;JKdQKBa+}nPb)83Xn20D>axe*_1DSyy0AdE$z{T@YWcoUS&Easg1ykOd$WTdDw~A_##F20DP8dZ} zYf61PkaAngWcq_B50Ssmf+?yc{Zb1Lj`U7me2vupui#5w6&ZJLO8#6Q%EY&Jr>xWH- zyWnT&x~gdX9~Irj`<>bq!c0hr+7lwpp-RlTBgA19CQA?RTvCC>m2=)TBNh;rp{P{C zd-AxDQ7J(fyZJ|L@9R9P9jMYm^WNcbpzFqeF&b&x%(zvW+{Q+X( BBxV2r literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dropbox.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dropbox.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..302253018e71a435a9b27e9fb2372ab979b29a1f GIT binary patch literal 1451 zcmb7E&2A($5O)7eXVNp1-6ZU?2#^2?*j{O-6Ky~ngi!bqoLCOKTzoN}F3(OU?skve zon%JbvJyw`C@;XP@Cbe7w6DO4vO7bDl{ny*%XU@S{;KM8KOT+ZxBmcoHzedwve9r& zPOzIJ6hOd}gxb-UUg1;UBM?9+gCeA%N4_J_gXkrJNQ74b?Sc12=&3E0r)foJXu7iTM*jYuKg>I z_Qf{Xbsx5`16;?>9>V~JFoGSp19xHfI)FXczmCv8fH&aYOG1ZmAKt`q1P>7DTkY;Q zT2)hZc?)Zm7?tH*vP@|)neyyRlwc;%`As(cS0Bymx-y@oDX;Q>JTg_Jhbhl8)s%Ib zmor8ChX>t9qnT1uu4yzCTnnA}bcmk97*;F1sEysg>SZNXqt171M5aaEhP7@)63{)x zF@>E!>uhwep`#DFMaHGH@G+2bmg_>m>fsvi#l^+H@M2e?$~BW+rP$chA4g}r{oG% zK(Lq|GLH5^F{xhUC2}n^Le73C+Is%=OIqptoYx{f=NhAwAT&m+)5m9rrCuBzAHWJ$ z$L8qC+0k)v{OQpFwijjkJgxF-JZ*9b<4?*=KdBEEX~&SUt;5*Tldg&5 zmTb+QYKav_dx!A@8;6gAc&m3diRIqM3WMCgNP;p&b3L|uZ2^Wc{2Y8FiL?=eL zZ11@nUU&WwO>0yi{|Bnq_dHE@+CFpTWUP%DD-<+R;C#r~qT$jOnlU=ONed#Z=XBH< zp&9U+GyGA|U2$27tQNqm-cchwHL7fT&=i%SyPGeCO%*J)O&;CTV$s;7c5lZT=+5oY z&T+?vf5!^Th7Dm8p?fI$*v%t5@xq(W_hR4o>=*l?8*%P5ksk;B!0k&Sy^p)-eUo2K v3Uw~b1cQ}5 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dummy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dummy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30b04635733cccd4242f7798fc8932680a33e35a GIT binary patch literal 5726 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 RbgpETE|q3VGX=9W|6e86JxKrn literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_edmodo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_edmodo.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..316024fc87b255811e93d580f5b4033db73baece GIT binary patch literal 1602 zcmb7E&2Aev5a#ac&$8^svE8)rp+FBoguq%ml8dNFTDL{hn_Hkc@j_5LBYQ3Ga!qm# zTjFDz z=2H3y9>5knybWRdc85-`!4B-cVY~|;!y}sM6ovUo_27HRH0Znd=1;6nQmI6u4GtzU znPCogfVJA-zV`gHGv^BXWgN>QT`43wi%W~f#u@VUsn!!|4xbH2N4&KVcx!^vpy`La zCzAxN6;6|ns#{OOT^IPOie715Mc+y@|NOm97N*+{Wpak*qzW;gZ<8Ym+#KVT%nF4A ziYXl*FgE|`ziq|xQaWkA_F=rVnN;d=`SM5}9$l5E<6(9*%+e8F50lwwdO5rJV2etc zQ+%JrepoeYx4c>F_IIj|&u1Re=)P0&#i6OP2^eomzBN6Em4A-a4hzXn0djbm>Tg0vF_0VHKvp+l$=V#8-uHe)i)#8n#E| z7k18WXbBjBGdSrJ;z0|gPt%;Ls9{k}e`Dq$Y5w_ZT$q&J3gb&@;;B~T*v3;64`+wD zxfqQPU=H)K9X+3o#@YCb(E+v7JboP)X|X>kQw94+d19WsgNt|p`F%-6y62pbY%!F=z2rTDrZ-06MD{kQ4<1wDhWND3TPnqxmO zTjZq9!-8y+Snbvx-c^1|TdSZR|9epH9_jb0j;K*3MAa37K8R8w@jW3fN~t{Fgy6kp zN+>>e#y9H61}Yvwz~WnYRS--N#0tGt9RKaQ S>bzKFjUY8z34A1#m>6` literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_email.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_email.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06ce8446d31166cb3f3db377f680b879a6c43428 GIT binary patch literal 987 zcmb7D&2AGh5Vo_MbQ`6h@^j)q6mf|}vZ)$SBQ;e4Augzw_Outv*>Tz>yX&p(t(pK5 zQp>~eDx5j(l@qVPiLtjWr8j2fu|4y7JoAlrdt;;hJ<~Eyc*Q!k@B5y9`YSUS#xZIy{HH>Ap`qJ*A?9Qdix|uL15~jUEYSZiT*d#Ka_CAh2 z_x2phA(Ol)O*iM6nD$(_Ue_!O+!%3gZUbkfF*4sfEN59_cEhV}eO+kptAoatDJyoi z;|&hoLG_7zAQxx?f+f>~KE>)KOXYErBeT91>HHf}O+^0w8Wt)!<3@yMTw(AOgwo+$ zh1-j*T%C?5BUr&|qQ^Un@g$qP9FI_r^YADvl439`lN1Io@>uPd(P>!oIp9d!z><1p za0G`2Dc9jO&2GzTu8U>Ww%$6!`?d<(VM14rTpMh&hJOM7Q};uxyWq;$He)tv z%Dzcgf2~@$tsc7@?t&kn>pGjKce1&QJ?O7j0b{nqj8$#MG6`jB*B!=AOP j5qQpX(D$lV>V76=sddI%1l+`_g55glJ^P?s;T`V}NRr5T23tp_R3cA9=$OEoxWZ*bX*DQBI{&Kn^A(OF_*=rPa*Fvi0(e z)@-)I$vJSNxaSY>SNI9Ka`K%MC%Q*=cGuV#ibyrB?w-+1KfZ2ht=Y71{l-~VBjk57 zbya}hhM_nRPB@K7Oc|xf8<7#4%mm)#m8cR|SrzyyuSK=E&gztWL3o{8FA29q{m5h% z$PI3TYzqtI2FP>V0ol>A&6`h2YyKEcA}v}pzU=iLKa?_+C=6vJk|5>c*es`=`@?+q zfu95-68xB!O8aZnc6&nQ0d8}8S-@<=P>+BJ!YC)q;FOu%U=>kC2hebCpuw+dN0ik- z+W?)-=U$m0*SW)+FA1~wJih|Z244VU7mKBbhe9T4E+!|BKUQfF`jHo;Qgr%$urCs> zI^twuXXXu7>#pKG{%|+XGxe$KDnH@cWrB< z+ugkT$=1E~jj{Xhwm@rD4z{&w?5f~??EN76_p|U@89kO_5RRCwwff9rF7+X#UY>#% z%+V8Kl!a0#Hm9Yb67nm~7PWwTKgGRavvIRDv#mrRMP5{LITS6EElus{m2=XDys(vW zBJiUKqXzLy2BC}v4=}xW;SF?cJ_{4zD@sIzUx}QD zm4E)^W->hVbKxHP(jBA`7gD(c>8|Z}6M3+{waUkQyrtGR_t&>#2+EOG3mX@dM&3k63?PJ#Z_fZj zgSazU`CnqN3^B-jnIj0ncoBt(Xi?dOV{|M^as|ZVnm*y|;@dEFYSu>=nRWK1ot9l# zo|pKs@VvtIyg22<2zkfz4u*bI&M@aBGGJo93%D6&BFF{z5JVd`KFM!(D8N`hd@EXPIz#xiqdPIMo|B(W3 zAfmqmOn*b3kneHUd_ngrdsR;M8%MhNmT0dPj;|nU`4c}>Ldh!ZC9y0DyVUof;);eC z1tQDCG$|}7+bm6#Xc-y}g%QMOV}=oZABKW&0l?CsOLY9f#jc$P(KqLyya6_pek~%m zg5)hESUD$N$XlqG+s%t zFtjOvLc^*`)cr5C-I(=7e<(H7{+NauHcKo34YfPKOrS=QKLfSXHfok}$2@90o!>mP z+jn7&!rugh%J|L&R-K2EH((oL;sV<+|My@_Y-|6cZI`EBz75-5l4@+6E}n|Tqf=dx zsM~4HVe#qqr6k6cU030#6Q_qlb>QbF3j5fyp_omWEbP}cTUIrz>Tjd0!=&z18bEsM eDd6i8&~4rNtZcrYW03Wi(lOwsj=8Y1F#i`q5@Koq literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_facebook.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_facebook.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3916bb92d0aa0f57b7c388a7fdae1abd0cfc3d30 GIT binary patch literal 2817 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_foursquare.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_foursquare.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b09dbf072f0128710e13e2831e06cae7ac58e0f GIT binary patch literal 2238 zcmb7F&2HO95ayp~T9)!_C(ch3^*3P}i?XAnc2N|mU8FFII4NQj=w65wXC=|5xb*H) zvDBiMht5)REV$^QBzAT=J2N};&Ft_|y@+hZ*o#8odNgl;C z2_r68vqPgpmV#_{--$NQYmM*smCEGjK|r&3UNGW=V7L+Fk_j0|mUM~xjPq?Oh}-47 zqvsC8!-a2=8jT_*WvDowS5(U>RHKZMr92gk!Vb?dJlz*`f*cQlkni6AZT*ocxXoIEVviLYu{d~}o!S)Z|?(VnNzu#-`x3_m+?KKaw-m|fXFgEmjG zC1s9eKhBCu*5`^Bl_Zu*7G9qZ*iNiQg_qEoyrW|D!-|2pT=g+I!~x&#Kw~ E02;GycmMzZ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebbdfb16d459da19311858f52f457368996892ae GIT binary patch literal 7216 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 J2QLf`{}=JrFVFx0 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_gitlab.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_gitlab.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6034d4c3cb54febfbd60459162ed2d2a542b58ea GIT binary patch literal 3150 zcmd5;NpllN6rRyKmgSw*X3GXBi9NO~3(H}#A;nVV0xF2hT)JA*ZMp4PM0d+zRVo)# z{0a`a=C9-@^f5X4FQjtFdowZ!5Cl>=FjMpO{+8~3-<##JvC`b{%=^1+S$|psPY&wq z@TrFY%wl$6d01uwJIr_)+hSRk3$kI(%RxQQ3PCIW@w2upq zp0iL{hfnamX~3+mt`3*$Fg3Y7j}whC>qF$FLMXB2WCN^gGRAYc*|^zjkYZ| z&L-F-n_|;!270qlPQok)%40D1I6J{kvQsbTK*9(+&1RolUWv`IGfjSRnY<(P zCT+d8wRc&?zMui|W62j<)ZgV1QwwdO1KL^`oZUOOt#wDOxo&^Zk3*Msgu7RByNXLw z*oxy8g|fw|MPpl_EL@6{eIFMDKUFDe-3=A-N(VGGmf&L3KNw zcDva-Rb5=F&o5QytERPqFw$xatuGBqa$vkXn@AMF}QcJ}8AGRfaePLU}rCe#O zc&0#oE%x~R-gLTXoj;>tC*TX6S!K>l>!&f< z0t;hFctl%p-^{d6BjQU=H76Yz?{Ht6GOXDWGUSXBv}2}HEZ8Eor>9|s^q z`P)3?L@-l)%3Gu>1Cv*~$r+@&wcyHRV0^qUT9H76LW9<>lwciEq8-6Op`N_bU<7E1 zeX2zqnLH06Cd??vRszgJ`3~NVh=$x`TOu|S0ab4ZafBk2g2^eCzSR{0n_r21xvCeRxM{4-Q(%gD47h$r5`3-(>QL@c2qsO^54Fxd z1PlzPCRWi6ydjp|n;Q&KB%g)X^FGD@8SuYDrA8_M!#Ug>Ua{o@C`tAqg+*wZ_i8};^*O4&Gs;OP=#N`!jqda6Y``>gZ@alza?Ow-Zo=+p9q5xOAdPq zxC0^pF8-@~mfKNy7g;y#C^I$4I;0h|eKY=A0=DH6`nde2qj>ZZy_H3P5`1bCz_D}v zH)G>l%H)zVZGAkJvQ9RU{28Z;GwC7T>M9+FKgJN*(SJ%C=Z^?-fLI6fhIDY7M*xzH zA#t-SIXQ~G7^`v&VI1H?*+5RV{S7GVwnQm`fQ7et;zIFE1v;R}Q>5xzqB8sQs+iwKtxE+bSBzD4*B;d_8L zXeA3B39X_gq}3gp*(@XM1KY-S<`vCm@0;024{qJt*d!Z|n|H~bd(F*_n`GnRJ@WXc z&ECwAQ>$c+qqGF_ZSR}1;*dDtFHA&jCFeoiaDGbSnZmFt09bYbqO^DELznq|l=&&X z$s3^SJx<7V7?T?aNZs!-8yTn#-=Nvme>eMGQk6B-gnTUP2v-nR5gG_<2tOcPMYx9W zVG6tsQ*i13O@Rf8k}D&LN!|!vfaEV*Vs68~LZ5f=zd}j;)A#Z*=1qyGns8^bdLTKy Y;%wtwO;gErO!^85#mSWM%}%=i06M!gHUIzs literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_google.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_google.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..559c84d5dd251e3f484cecb5b58f1a868fec1009 GIT binary patch literal 8911 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_itembase.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_itembase.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be35ba864e8de520a05818e7d4dfe44dc6096eff GIT binary patch literal 1732 zcmb7EO>^5s7+!rjwqmETle8%eGdUEBhuVr8Q^yR$1Q=krq%$;J_M%zoT{lYfm0c+` z8gA)<3;&>hfWN{|(3MmF0w>;;l*BG@s5JZT?)$axKKtw*^m_J(e*!(R2>FXFeI|~N zu*pLdKtKx#C7^>5YLQNLjr>5s1nV^ci|f}0HE`a7HJqP0G*m(P>Z+D*3@!p31~y-jdiWjcBQq! zfOY6X4>sT)Y(gIfZ#ro0RMxE~X1j^8VF&KNCUhMhz%KR}hh;u22R~*!@|fgz6&6oq z;s-2r{Y3Dw$NY01gB%Bq<9Icq*gMU#RDR(&x#VKW%}*lda^ggswiZd^F;R5uev~Dc zF(AJ`CbZ>oCODc@@Hh(MAYx&$-8c%q5L}+t0Yi&+neTHc-6{dK`KvTQyBox%U8&sc zD&^FYeu7=EiLSkiM{MfNC!Ehl+@G+~bneYZY~l0K!E6Hl;cPyeP3Of{z4FoRX1$;& zjqj7>xHP`OE%XwW}ak zspxtgT20E1O3>())mA4J?k&%b|jQW0FTj60W1 zIEy3%JY{DgoP+a8EH0+=eJG%q%jx0ybRNy0P51G889OIV8l)pH4?-Ay9{b`j+rMyX zVvQJHhLK`dqo|nXWOzHIbHlf4>fWA1dWC0<4t0@_)JAj^L@Pd8<)gY|<2%py!C(t9 zNSVkK_px{$1~DIMVgr|{u&Bi*PRpHYg?F7Fqv@usPyes1cY}t#vh7wRaNW{&-6(-P zH<^s!@n~RY4@hWgXL!R7n&peY!Tw0Jvsighoi{p9; zT+xlY4Ap$Fa$PDP(N{YktZrYCLMRM`dMR7j5)phjpE-gBGT0?AFe|Cg2mt^uhhX82k-NAHG#z^>IK6GOxB6U3Yn#cEJ*5i z?09I2Ev!nY(!`Du_my~{Lw1$kTV8k-;g173dNp8x;= literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_kakao.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_kakao.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3f970edead266e53a810a009c00e6da1cc88679c GIT binary patch literal 1238 zcmb7E&5qMZ5N_K^#$%Ehp%v0FFj=Lc0ezw=U> zGO~`#)xnQ_6mx`166OG5E;tC{vKDx(Ejx=Xg!f|S!eRbu*9H$dum%1H54JzJXy1ka zuDv5HfG+g#eGPUnh247hP$WWKUA_Fps3;aGkCc|fP(+E$!3+}{HQb0@-kO(XVSb}D z5kxw9n#_&=h7~39?*3a^(#XyLxfW3(Pwf@)o@YF{wvnvah{Iuy`XQxXHSn` zAG~6Z?x)M+KeO2rFzT?u>U+%3<0#1m7O`8-t1Qe#obous99gf>Y8t1q+4kMqje!Md zETk@DX(G#~UG{C|F^cJ;dP^4M0!=`$_0CZr*#gB=HH&lPzhz`P{Ydl{M*jVt7CJr^ zrKG1q)2T{9YD1@*-c822KAG%|Ur|o;IsD4ZDU<_+3 d%dW;BQ*(Nwe_vs1|6x<@?{+GUy~KCB-anolRMP+e literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_khanacademy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_khanacademy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02d77040ad50a066de75e11b4565a9a08519f84f GIT binary patch literal 1335 zcmb7E&2H2%5O#icyUp$v3IZV^A#P}ecGJRwC_<=+0|z8HzybMUc{AQ^S|?8Jq@^ov z<;E-U0=x>3kSix%ffF-Ht1eVR!jZ@J%;(I^H6flFRF5H`@M2yLa6i6!ObpC_W^}s+ylma@OS_|4*}n-y8CMolDDt^smFV29q8PH z^{)Y*qsAUU1pUU|hXD*fGd_R~xPt2tu3}`H^^MniGJlasP}#{vD9Z=VB&pOQF;-1x zGC5E=xXA%xWs99$y0^TySC)l)8pp@S$A3VLUBt3T1ka>_3Ib7OXA(oPbS@h%>4 z&$}}vt+FGZ4`h-k=R|4HYaKO1G0=2-X8DLwzjY=p>PU*f{1N3vA$k+kvLXi(P0a~nTA*v{?Y zbY}~eu-tLeCkNA=Z0GTG3&$dl-^WE-jAvDHs#`@VBYfXrGit>PA^gdC z7->ZxkM)hl!{6XLXgZJV{y!r7``(+;M%@!4mzffxj)cezRGRh!Ar334TMHkYQG`*J zdwkdw8)PX3e!zTFeJoT`DiEZn8+EwvOkQ`O$_mFfR~uN&44haZkzcdwup)BXmL>z= zxOjB~q|UKJ64V(hg2=G+S0Iuc<=^wdv&Z)$-}mT={E%eO3mWXGYc~xklbIqj{r%en^8G6rJrk+!eyDqu+2f-lt4Gl$hQvd(} literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_linkedin.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_linkedin.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22376e644e5e281d8c279db71c1f5baea4cd6c50 GIT binary patch literal 1690 zcmb7F&2HO95Z)yzilQaQR(_f`Xwg$!qLyeUZV?AXkfuO-DcnPIS_D{7oK;Mlf8M2x zSO|J?4tRs?q_f%IoSB~(| z(gPnYK3Y0x=>h+P1zR7`13M0Q-J9uD<#7S(14mO})&B24&#IGym&#O&`9qq=bYj^) zM*0FRiU|%(Sipr1mT*+()TKzF^WM7R0{x=yQe@D3Z==NnAGY2xjHM3)9DUfvm~PcO z&m*ILOY$=XNlrfJPfx0WMKOuAa3?C#N(YwcM{%r-kyUY~^4f0(SzVT@w()@Q5B8V8+Qv%) zTX=NuG5V;j1Vpd#Noq{EQKOOiFW5~V#anjDuFwPw)8mLXW?sm&c%9^kykk^4{hR3l z?)>ksu++&#RH^VH(&4m7L1`0Cb@=G)AlK)INBb~``H?w%e0F%09sPK?kL`6Hz6{Hx z98G3P3ZozLSU;}z&qGthNtBKve8y2+XtglNW?mt#CY(eu<_#t!RenUayhJD(3kCTZ zC1RmqeEwkbZN{s8pMt<`M4*gxg#k(oU7>3ga7}PENVH-b)%tGZVRQ2rxU>xFo125$ zJaQat)t;2Z4=HP3%B+A{O7p&y=d&nnSHxgul$H=xk;JMHyXsA;;!1%eJMF2RQ&Z%1 z2WDAm#O^wU|Y6&ZdQkagX9xGbnuSO>Ro z#89jA8S$~L&j!;5zm;f^Bmlp{ZhRDoWP_i@HQBGBNA>v(yP-!}M-MDS%ich%`S8v6 zJ^`(V$APnvm%fj#*0{ZT|IcyZTMCA9^|&TI@bw)W#66R|8E3_XGGpXonoOu_kcJH` vod#8n{~t`#r?_^D$thB`VM~*AbJtByYwxF){Z9#s7B8!B@7OzchrRy+JZiSO literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_live.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_live.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e969e318a4f05abb184f426350fc233e829ab46 GIT binary patch literal 1304 zcmb7E&2HQ_5T<^1tyR{J9VbD8B4`lg5VhEqHftxbQ50?iB$vWD#J%vsay49AHdmBM z$*6_g?8op6tKkoyfPVA{`Alv!AN4&P z<{1hg;7CFVxZp`==2F)oZwdI&xg^jL?uAD^aNd)s`vtAZ{uc*jUNH`GGKH zY7Z9e7Aku<%wH%3p$-u0f`dU^>Vro+qIMvIu2&#{9)xfUd%qp4S3a!4 zZCHnms~+0*;0|nF5*or?xQ8=ZVp8{;wLh{mvCO4@YE+tW$x@}oaKh6Qk%JjZG#V}~ z>qkdrS(x28=0$dWnu{_%+a4zIOwjHdrGDp{cBe{B>^u=%3mv(%&(l;G!%BsLo3Nc& zd0vRdL!ZV&uX+Ww$EY+Nk+VPF@7K5IVfNZAQZ`AILGez%3vMj*D1J$fBbmQhHz$OaL>Dn@aUF%I- z;oYyxw4iHStG552E)Me(ioo%&yX%Cm@3@QZ&b4kkhhE^devz*qpcm~Zi?zmI+dSTl W_x$goCirDu|F4###$Fq^eeYlH9AxnT literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_livejournal.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_livejournal.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89c70c2dfa7a5b24e89abc55a1c457cad17b1b66 GIT binary patch literal 3848 zcmcIn+fp0J8J>}Z5JDV%+GHIs#wKgoNFyNuMiH#F27_4;2MKIXQZ+S9w_wDa=pG5F zYwvK?UZhfafqjAG9de(_Bg}2C{0h0s-!mg4txcR1Q1ZQik~nb{l~YKp3F*-f@CBio7QKPYP zSCcjVjoxo8Y|y_#0}HZ<1UZ64If^233`LtmTpz9nN^p76C`z4S&u)-ts80s-Jhkgt7IQkYnfqDX^(Nm}=e-dJXJO!3|=H1!R+j!e<5lc1t z6kedVrmKdc*#u`#RP7vFh-M8~+YHubKiTKS8@7F-5>H?+${m7e>FwcqZKh)y7A^7q z>THMTB}sbBJF~$=|Jc(fkK0cdvbpr^8XGFZ<~4S=T*IWTYk0;Xx=nPqyE>bL>0-Iw zb@1Am>pE~Cb+AUWHn>YiSqr<83T~C)B#G*$mTi?Ke>Gb!A058#ZMwX?9MDIzO({4+ zqVP>jvL~idmiP{qFYnlzYP??WZ4Na3WPe%etz*fu6pFsa;$_L#CRLU`+GS0S>qsG( zvIn6SH=N1l!}m z=5>TE7rZ!cI|@XUW9ybX5BUksAF3c2U5+e6rxZ$Q!Qj*c3*zJyJxfG{rcYb zXIq<#axGuQn|7l^^9QTxQF&}YBQ_d_1DAkJg`I0j*hDzglZZ`+1AUUQ$#CqWgzV18 zk$y?pt#Eii;*oIBqHHW|@_59EC9k`lm}JZS+e(2477N8(Nj@X3{e$K8?G?OvdUTGD zDl3)J!gAxeR#+%571`}zqYJrE+S;wW%+$6jdGLUoX1mfnIN3yn1ETjw^0uuYJJm zV|`bb4|Lk8URoEmABx61+_5psvn6MLk#q~rMQ6LR@vga)YrooTA2L_h+zmT-LYimh zUcOdsG=IoDPIIqoqVD;{jw54rXR$$BJMQkw%er+aZ6C8AZ2B`GwqaZYjqe0oZ&oE^ku|m~TVO zam%f5`F7Jd?(-k7IYIE3PD9}euK1&H6=_DUfVS=l0GyF38o zETFDR+%ojPAo2C$0f~oUr!VmfXt*FRyg=6|f}$w)a|8{67Eu)c(~#JkjRAE>IC+P& zK<0eb*DzWA-!R%sd<30`0Md}4stds=bus|)nTAcw%*UGwN=Mj4Mboh3W;XcU1#T)V z_0qOn0MZPnwGV#>9U%UO&#=m4$cuw<+!pm>CpPMSkzKxbamSU;T+{g7%*ARhTbLgZ ziBCKO3J)e12Pc8X`P^qj^x}cc={VuhA*TpF)hS%AAb4~yw!c@adlMl>0nI90R4=-@ zQTN6nn=}Xr()9;#h=K_KIJf15+(77e@DX|+3PFs)6M-iVZ!+TdBG8LPL^k*Nu>Qqa z1Chcpz>kzC(CEcBis*m`fCg}B<&km)kO6g!*M3AqtAyhQ8G#}_L~cRlO@hP&0qzZl zIZ3|Z^9f`thJC7AH>jNCc^s@bg7uUGjUcAP1Uxq-?Ddxak5Xg?WFQZD!F7;tfesF2 zCcilXOkqQ}@D1wR@n!qv;@`t5gPJ}2yw5(ndq7EUd*5?Up}{>DdP#I6SK^OdD{2#z7MP~4!NRzF^xkZfQwQh z`}RMm(!Vb~?Ije&f`225;w2RYt}rcww^NF8(Nc|IMoxv{0~UAB3{sM(W$#HqQuaSFKVe}c!jsmYiT36b(Nr`M zP25ey6LJ6a^cbWI@P`ZcN_m9Sz5gFCz?!>M?_@cr&@8~Hp`Y-O1e^FaNa7C1Qh?jQ yqw+B4Cb*gs&Iw-BvM+sdYVe~84;}x5*P8ZGQKB4pW{qDMXo@QvNd-@IGWu`54poo< literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_lyft.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_lyft.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a5762091fe9d9d9ee26cdcb4cfce607ca158039 GIT binary patch literal 1149 zcmb7D&2G~`5Z-m1)OC{n2!T|AL=Tl9hr}%ns6hx7ao~c&fu8oV+ITk2wvO%W+7*eo zwU5CIaON#|1YbGz6*w_#H%+Oyu&W)l_-e1|F<`Yg|&cR^I#IDT9B z=0_m_u3?^9iqZE1Blyr_2qq0iyku3tJ6H8j`tomhVs<*QGpV}W_?Wxe2oe4}@ zMkx$(7^Pv32T=l0wNPU*HPh~nItaikWW*?w(rghA0;qn8!Pit-o{ChVJ3lFg^i$n zTVnI?I&IgrEmqrK-p-MEg2bb4xgEOPzT;9nj#n!!JFebDJ62C@dvy)@^8OBlx2_BC QO;zz*JC?=<@|;%f4+`%$`~Uy| literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mapmyfitness.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mapmyfitness.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f3b3211e91638d84f61dec187caad779600ad38 GIT binary patch literal 2861 zcmb7GOLyBu6qaP!R$RNKEwrWOk(Q?cM}8zB2@Odfgbgi=1{Sl?8S9SiX(Ww0BPUpg zU0L!AAZ$5@9V>nTEBFW6vf?MeVZoiTow&f^oLJVKtI?hN_~zcZ>NOg*8@~X#aoMtd zvlf0X^1FDew~>Gac4QF?94JM0>=4JcKDNMx@>dp=ne((nN?<>-nw8&C+IslAjkIYO zwNLMivr%`SsVvkl=D+L6?BcC%qad@04Hj|0MkNk$p+w58^0=ziLj|hf!P0U0sZBgH zTZ3~@H&^4h1m{ua0z3y7;SyX%>F41ExB^%4%{Am+G~9LMZW!(*3-Hr#^O;C1s>b8nn*ZyN3`cpI80bMKsR?`m#Xf%i~j%kVa8TrnD#!Jmz7Gj>p> zd+eaC-mwR(umw8?Jp}yY;>rV>KA7zBEMZEW;+?N55po&@p^$8)Ps0P2fLe)Z8czmO;gx@{%>7Z8 zrE1If1=Ty(qA8!Q(M%{}|M2~lwm()(`ieyZpEUL)gT06+DiaAKm4Og_DoN!oqmoH( zEp1z*(x-8s=7lR6g=V>^(=bG5gG``PMa>LBHc44gV_&3PG8OQoXh=3db~Fm2B)Om_Dr0dFqdhugYWV%PKQI3% z|JvdFC)>(j?6y0dR(qq>UEgo7Z{feQ`EI+t)ov442bQ1Lbos<4@pznKSTxGJr%(hc zqnYwMoo=_+^LOZQDA|x^Ea*=HEiGU>7D+ZDm8nQkHjm9x;f^rK!r@T335{9aIg`=} zVqCbI=It{r1Ue~7NX}^Qh_Qph)ihr_lf8%!SjJ;Uyg4W9SpNB~sg{A{Iypx?^>+ri zkAcpw?I~9I)u~J>`5_G_fk^cx`#ry~d!&lHKHw1})gvJf#5f}j8fN^EXOm#SaQpsy zaR(Q7h=-{PK_rHx&SJ`=Kp~Lfh?FHNV)^nygflZiIueI-q#BBGlF13FU@;h#kqBvoTT@gi09Lj#<+s{u zcU#@wez&{TTi;siPRlR9inR}e`MI(?)H|dPpuMI;s@V}Y)$I*v$ofJY#l0$^L*4f}V^$aPW5E*!q&DDkuAvTGr4gM>^Um61w-u^gzv(8)PeD-B zf*=+!ju`UwAb2vSkz=WJX)u5l{G6o=TYNB&sf#{&6{xHwm^-t>l%A&rMgpfgY2x`LdBE b$b93k|42s44;pI8Y97+RJL=*|ctLue)ovB;wYJ zr{D#66&}G?PJIPV%sNRMDlTlavpcinnfbn%?Dl&8)=!}OHX*;r%5w33fLm{)00LG@ zC;=Os)XHpXTjVVP7g|>YTEf0`r~}p~61K0=n;c$SC_}6EKfbEUt;7b?lPnzBO4 zNs@}#@MM-b&3=tr2PnRh6LN_rAPCGl43PtnER{z|j=Z{Bq>~>+dFc83TZB>1c`2fE zuA+%dK`0$fRJ42gBv)s{(GJXDHqyhr)8Qx^JsQ-qAq^Aa}=44fzgHQQQc*-XOV*6)AtZzBGJ ztI~863l}N&Ftk($lQcZc#Tu&hgC@gW=ZE;TEb8&Ui@N(s7}OqPInM-Rwa-{4p-Rm? zVC<~o>HLKT3reuQ@|gC_v@qZ$XO&9nhWJv5xD>#Q-F#8ICtBup3#zQpbYpdH*jU1p z-n)%pbmbe2-M@jF)+>WnnLkq-w>AJh%U%3z%eP$%rQi_@% literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mixcloud.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mixcloud.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d03a730dd849b57f83fcc814326ef3b1b3ddee5f GIT binary patch literal 2307 zcmcIm&2Aev5Z)h2S;?|h$4URvqSpwBL|Kv@+bDt*1=>^H0`-ZP1+_y-Yjb~0axGct z%{lZT`~rQIKEhsm$}9BL;qJ;m0SdScOoAV|XgJhpbF=m0PoT1CS$|n~N)5+v zv8k5`V1X?xYJmf;up@^$w)LY0HK@O{pw68um%3p8YIPg$QQA6wZzFc?y!G>UnVt_% zxYB`HSeI=ayv3$80=K9Q7InZzB@V5DOY6LG`UG4Fzhm{)T zwpkExrF@;B-n>Mg^0>$7b3e7iU8J7^dk1BI3XhA)J%f|2rNY!ZX}M9)!m}&z?CC z!bk9QJ=%ykgjqzJ7{H4q!kljKrIySWOwKs1mt7Nor^OOsPB+UPew0k``s8&LF(GJE z&$DO}vrwF@o(%`PquyZD+kJg9*gqQXAB}eD4=ayU7r5KWJ#*V)7IE4sa!Qcu4FjD?u4pGzevnDY!?O9Y}sFx3ylZv}{e;MIuAZD;sLY*1)7EQ%rB#_t;b$;Wz8ly22^2Fs0hZ zUCh*w_#&BwF=qdo;$r%TC5`0Yzmrsk7syO5m_&~V;8KyPB%`z4Se}m$2e5+Gp&B2Y zjSr(kbUBt;OioA|roBlP3h3>{fjrQIb5f@19>d3_XL5Blfkxw4<7=bHI(rwN{!I!` zFER0=KxyQ@If#Q`*{iSbXLs{v^=~Eriw}0t2Pu=g^Olc+3YjqDw(p;3Oq46MU3msg)EL!8`p3v& zz6rN&zD#*w!e||+dF@msabAZkN)_F{`-J(E1TN{;tp@Wgx6Cm+_s?n~K$-(f^S7db zO}#{?YFyXDkur>qVZ7i)!$S~b63$G+uyZlSANlRK!1))Ny9(#3Hxf5*``614mh?OzLbK34dnVh>i literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_nationbuilder.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_nationbuilder.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2db7c91a378a91f667fd10d02eaf614aae243ab5 GIT binary patch literal 5676 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_naver.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_naver.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5cd1342ed1e8404c9ffff75fc1b3edb16ea4fcec GIT binary patch literal 1514 zcmb7E&2Hm15T+#CQM}F;i*9>pFKK`+>Vr)?@h;**t4**)dum#s*$Wp7ffi{;i9{+S z6(mM)*}g?zpl{G;=p*>r)4o6tJ#|Rgu@kh1QV@TJ-*9F=&hTJor~UaKjQrI==ufoK zV2zI8*Iz-$5c6_G5c64(dxcMY4}FJNgEikH*5v+eKmz9dgu1N{fJP@DJm|aLqW!~h zWzP0bxi+cYShc&uk3hl^@fadL^WYGlG+02Iymh|C0&Cz`|JEZRYmsfXMcOQ6+wT#1 z#Myr*mQ?7MlBp6z>-H@1@ zlFviKuMGw*`a5tEy@J^;IH5BsUhWP1gS}lYQpqwgd%5@K^xNLy-tMb7V${$$Qe0QL zS&e|=@n8_)TI`ll0aodZ$GUP3M0h1w!JKv}h30X@3!3F|^u^oP-yEMFzpbMhJxLK63wXNa)!RYx5hqmJXcG^WV$&@)`^k!4PHyAqg^`518$r*N6 zk=lH=&5rhe4ZrR{_cJ<2w;*B&645*9+FX@ME@zp5{BLNUPk%#d3y^>Rf=iWMQN!^S zRd_0Mh)UzB!Uq>ap)N<`0h_b=SdR`bM&n}qVl;p@6ZjODS=pOZSyoF2v&71+#-eeqrC2msRg{`M*{twn zBH0c3bbUf9h2g@uk|&SXa*I>jHum;?0&GGk%Yw2`p=C!ug)a0O%g^`PLBnr64FiYh jHq>L-CrxQ@Tm>+9@(oV!j$DLM%|L83Ra*n0-wFN!DciU+ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_ngpvan.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_ngpvan.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0879bd0387e377ad76a1ec9652d937340446273 GIT binary patch literal 7177 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_orbi.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_orbi.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e092d11ffa6d78801c0e9fc441643611ce6b53ac GIT binary patch literal 1201 zcmb7E!E)0u5S45vP28kVIvjvuhGBXjhr}sKX#xyWUhT)7cVl`%S*={TP=P9}K@D7}gAeP__*zG89h$IlNth3} z;5N?du!(`(DK10b2oiY|rwjE| z1v11ug1HtdI1+|c$5M_$#TsE039Y%2=&f+yNm;mWLY1$f^aur3-Ratqzp!r6tM2`F z384vtnV5~x=jRyTTo@3ie5dzxFzgTacJ~I|&T%G=3i=`UcZPetc6ZS3_FDC#W&=sX znPC1nR@!hY7xg4uie9Po;#r(BFO8!qt7H>tBy+VmW0g^?%p|fH*hFMO+91bE*j;UFV4zqi8Gc3@{^^*_09VeVO|jO`r^S|o8QT2$EF*B?T8*N{OHes4e9mVQ=7~VL!THHNOf0&bv&MBbFg!D1P1_XQ zb`_tigl&qmOhiTiUp7nO9c!5u6`0R5%{FhgE+0uaXB#UIcC9vSinspE)1t0yvdUf% zRV4iwS&e$vkMDTYSv=15OntZJmi8@AZJ{-*l>d{$J7?kNMj?M6P}E*7@_uETQ`_Te HPQ(2L@Sj6Z literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_patreon.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_patreon.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..004fbfd07d8c34612ecf5515df5f2b6df390bb23 GIT binary patch literal 1808 zcmb7FPjllm6t^8GPTZtycDLJIT3|UaV3@>io0e^dg)+>*z@ach4~#Az*-uH-I<_mx z%_hUGJHwagfh%8ykKik({R*6Ta++PX4Hv*I0Sf&(pZVeQ%) znH(Fmp#$si9?}QUU1<-m9pu-sKf?aF+UvjuJb}$>{uJr6>rHqc^=)0-+(bJq*oF^Y z8^nbV;UgT^po>90FT1}`C0Lf;g){$LW-+Hp7-xd@BO0Hx6l6bN$n{s`=l)o!Tpsy8 z&H1fJoK5_UYIyXr9~VNfRE33Pf^>e$#_5l#Vq(f7NgBf}i>M%th*80WXOT9IVYDbY@whiinlXA&ps-W#@<-yW})gRH(9{<>K-D!5OML zX0PZ3o#|uT^N*K+#4lFx*a-rE;pqI?Zm{!ZFWAT5X|R8^b8xgb_#z07f?)p1^7M2% zT{*6?`=)rg&>(aUzSGj+){?hbwh}5;n9>O&Z7xGu$Q*Bz0cnq^409^wRL9*K7D=)Y zYob_MoTG?4>$8&2-$U+Wfz1kFOTDGOe z#Uu&~k&wnEO}UzpI*kx$;-VH{2~!O3AQp`3Nv#qbX@7-qwF39%o=`Dq@d+IK2+`EztT}r?%8iHm!E;KPZ#{SpWb4 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_pinterest.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_pinterest.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44ce8195ec367239a89d2eb0f97d0484c4515234 GIT binary patch literal 1896 zcmb_c&2HO95MKUBrfJEs>n6=lo5rVt2Z^$+BvR1^P6D)-!VQv~cp)gxDyB`5^ekn^ zLT}Ec@8B2ctMn1}+EZR3r_NATtO!m~7bUSDcXnsFv)|0OJI$th?@tIeYeM`b<{q2+ zyVO*V00bDR2n3j5rN+n%Ohf!Ez=qm80X1o!S%F1%9hS&ms%#sKSHg4N(`vEz-XQc0 z?f&|7T<&kbma2?-V)oRjvrA3=LLfz8fCx-50t?K*1}mt^`oR)dyy3ZdW&}=U({|v{ z=<>NuW6R*e$~zIb(10d=S74O_xS=;*Bv~nuw!GYY(pGt#L}?i3DBFW5K9m`#b}{p8 zFRYn7*e}aMeee5Gk<7Wp`N%)s@<-Cw^P8gDQmL@a>F?UDK4E!U zNWIp5-0O8aovp{+-cG;sXj@x9<+-+=Md;buOhCKC1XUSk(Mam$G@5mtv69GxF%!xU zf4M!UrWypli34#)Lm=q%#vYOGKpv*~ng5PEu$+%wp`7og;rrXe*-p zwwTq6fr??jB=(g*m_ugkZEj7tgOEdSa*Pdug#feRZRRV2MVy{uZxJHYvuGcEJEt;NH=k~uO z?Y}7rS)iYZgbS=Q7s-#;isTCI;8*lFWa;4@2Ch%M$M7}7Hw^a~zGZm8@XwXLNiMup zFIz#yOa?V zJruBe=zq{3=wIni;I*fq|DdPNQc|o2y>u5lJ1_3e%zi_+*Xum{1ITYpMfp=%83ytv zxWo%2pn#evL;(%-M4f0vQ$cq?pb3=&hW-SCN`()p`55PBupVHC`1Dl57CGLI%%-xWlJ6pO=v+I)}S-fV13rOQ%SqFtw9%h zumPL!2)1Aw*A6_M8Mr?}uIA4**qycD6ZjN9gC{c`p3XGK!b6nR2FFJBqG>m`A6{Rxd4e|D0qhnL6jN&$Dl@^29s5(W4f%{TJx-<-#5Pq#9 zJ0_0&W_L*^&hd7NUy7=k6y z?+V{W&%b}Qun;m+>xObG$`W8)SP{3}D<|dGBhQ8sN>7ZAu14O(I~dtG&Qt5m%Hw~}Exobp0;8iviVrp#3N7T!@;VNP~Xtaj@gK6XAt(=w)S|1+kKzc@9! zRonOFyZL_A@%>2#(?s%J-@l&HWNsndML{sW;)3*Q00J$jKjjJ8!ggVyM4{BnFDm0w zASxO#o#cXSt@fH9WN=G19!`)le=Kv{{dc1B;knF~%O5cQlofG|q^%l@sj2nUG<8Ea zkbao@oNF$0-KLZ`nr*#4$u#&AJcl$gi2$x6U+Y(D{Nd`VeLSzocM`cm2GZ8L`aeBP Bv3&pl literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_qiita.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_qiita.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41a663f2daa67beac0465dee8b77755ad33e1a90 GIT binary patch literal 1051 zcmb7D&2AGh5Vm(W>2{ldDpZ8v!UaTx5|=ih1|h_c1e}TxJ^5leJ5IapX1%d@D;jZY zc?({ESK$%%%BiowiScgJhKdW0Job2IXXg86vfb@Q_kIE&c!c~SODn+pE8O-G3LrpJ z!U=fbr*!IZkCOKU0%)BPXesa1=RVNSq}M)2Z?bz%QTAvReSBRO2U~l}7O^ud)@ASy z_@W4>K)44KgLpgupSM)|a0Psq;JbH9d3d3>VWrkrA%xX4!Xt=a4bL^`VDfHt^HY)( z@+#^4q1ACBQxR*U24fi?sSNC3=DY?=zVpWiMUmU*j5VfMPnpaUcD%(T0#I3z#IjHz zN~;WSPqZFOJjY5JWqMxKDl|r}qWTcUNv`HA@3g-EMmnpPKow+is$xfrTkdrGEpFRE z@s%8sQ+xt~IOuKS=03|c6*&F6vFjMh$<3ds-aBXy(7e|lxbt(oeM&+zT$v) z-9!kGg%qVp`MUa&t9UUf?u#lov^uL=P)>8p*JT_lYeh3CV+|+###IET8#fN7`!`V6 zc;nC-_s?nLwzpA)G;lvJpdO9-Q!0E`wI#iw5gUH}BP6Hmy^~c6T>b6LD)L zUV<0kRd@tnIrSAdG3zw+N8-R*GrOJ{@67kjcx7S1zxNZEZxQl~Tv`sEH&E0gR6u~H zgb}d7PU+BM7A4yR9B@wwxWYQInGN(432J8;O?J*G>VTI1hnFM0|Kzh!IyR2ks^eh; zMLk0$2%|ul1r#r_m?PXl4Qz8c)G%TFyL&=e9XyyjCCmdK=5e2c2Bupm7e9`YNhl~AmezkEuE(G(%agj)&c#<(! z#W|YlBPAqHAU5QfWp*R4QB(ufS27?cXaa)R=uSWpZqCzuFUgQhM~SrmgGdh}e}4-L znH)u0gh!DK`*{jNs<1D^m4oF>9(KBIn82i~I_n3W?y$SsY2(_)-4Ie(72p5JVU(J4gCqDToqAZ~oEEH5@3Tehxrns@ZaQs3N@t+*taM>CEEHS3JT1JJ!&v$T z#{5+*roYkvlI9<*p{Tp4JnB%#a;SCw__k+P5rHFbqMy0BX`r<6T-h(t^Y_Zqdp?Eo N!VI1=2|TM|{{gz55DfqT literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_readability.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_readability.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69dec169bc82e070b3f20f2f658e57629853684f GIT binary patch literal 1645 zcmb7F&2A$_5S~9f&cx1ck_{kniI6~8D|^O&*(72ifVA8e3HH>NR?k%1?qvSbJ>w)6 z;*!1b3V8uug-4hxr+o!ZRL|Iou@Dk%b@g9Wchy&4jYpl1`{^G*P8x>sr?Jr-oL^#B zV-#S385u;6R%FH&u}lMOa3VW)h+`T*7|?*`djp!>d9X0lCam`=uiFd2_I55ti^4W}c2_$vGc zUaxQYH>}Vt!8{SO8CTxw_AHrwp=@WVuKa#E{JtX$wWOB8Y^Vb4d<)%tS;*lBbjb zVB!>caV-*vIIWM?yKe0tMV=$YSkCEHDiRK%rn_!>)ct$xN|XE-p>!K#^*jPVR^2`XV>HN z`22M2W1A)3J1-MiFDOI=y-5!2#_{*&_mLE;FJ$e7G^M zm+>&?K-XL>ohyY5Yd{faiX3cKH4Rd@ml{KIDEUpHv90S|ACUd0t1HFl8i4~XG%+@W z#zNv3+-PA}eLXRq)z30r%QE%PwVZ0KS1akbcFV48yG?nFmgFNP?)q`M;Hr;&jzpl7 nfiYHYHmXmE)c+2Z(S{S1PgPc0U)9KcttqdX+8wKHwe7zFrN!ej literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_reddit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_reddit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..70c7826ff98d5e9a21e7184fff0d27e4485efe70 GIT binary patch literal 2010 zcmb7F&rjqw6thF0QURjSngV{g6Y+7p-l0X_9SXMh2A4^8CHj{R)E&+qfgW~1RRzX9>4%Glp* z&GxQ2Vv864H?R8n z;fsA`@`wg*o*8WHVllrUC?+^C;Q+@)9N~d0Dyn)k1}<%Yr3zzEgK_X-0!tmS0h2Iw zHFe2F?bgyms~a1#n7;CHeB6%D;QT5yaqjF@1$%v%gZbA?Ou!RZz`72Lu!MC3p29NL zlkgsrc~(q+rvT#oj>TZbWKkR@GRm}CYlqRX>VjF*)@*ImdhqdKp7+eBL6C)b1~(>A zmIh%j4o)|MRIMGEtShRWENh4Q0FUC=KmYmb)u3K>tmYZ6PTh<>|6}+1eXK>btwOEz z^>6sG|5Z_oL3Q&uKNFSB&2}0N=7w{%>S$#S%Qae4h1U&JRg6Vig}H*F9!8NeM%pck ziD9?w=TYISpL#LQm2tQ5lel{P+Te%4b(18JnV@o;D?J5^e4Y!_p(kq21< zMHL%q21Uh08QM1wLnF~9?!*eDO2arQoEVD6(9P11mal6_OOF#w5Htv;2&M^0Ho8eL zM=*~tm>v@Dsvk?TgT;}a`^QkxKFymXs1rOPm_>+4u=G$(pW^ujr=boycn(UNpreD$`T9vv>TH#?vr1wcc39yBIbJ9e+{n*< z%Ap2(XBa6QE?o?WhJ(QPV0l!YbS2mB?EVk`S>)dfbxzj+9apFk)#7>qH?ikX!V-QK z^LB@a#xHQ_#;K3q?$pC6xBd|qDmFf(0*cQO2Y$&fI5_7#rZnX44Q2w=XfIJ1jq+py zQO5=-BM1R7>$aq}{a9ytm*EI(f(VgGY=X&)CCc zRFjg@k(7lmDNXbfYS*PCx5^PwAF3OrmLG}+`DH!D*d$^Ga}>rAa|!j^)aFqxEh^AY zdq&KRrZ?Hn;7m;3tzf^K_jp(v{S?{gWrAk}&k5*ej{wP9Q;6|@$;`J1HSP^xhrj#v y9q%opqrJXcqt~;mw>zKV|wa>~k{G1Y(jYiF>yZ-?tb02d6 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_saml.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_saml.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7ca373519b47b7c14285768ff9ae23a6d787b4d GIT binary patch literal 4825 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 D^%gb* literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_skyrock.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_skyrock.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ec679f6a9d9809cfe6d2d8e3fd2e99a32aac719 GIT binary patch literal 1638 zcmb7F&2rl|5C$oVmT1YXf15P9^wj7eQA*s5-Dx}S&!o5XVCRAh2IN9AEs&rA(uy>l zUYwb}LnklLSLq}0+EZSkr!Ghq#bSTIm7ewc!J|Kbe%&VIPqHyQ+@Ikz z$4G#HBMG&uE1lG(u0x;&o@}Ka^&Ijefj0PW2>8OgZP7M3=VY+;4quX&1E=biT8b=I zAl^C1LQe40w`FlTxe%s^t<-uvz|9#>^9vF|r~`z$;LsMh)C1Hk{HrZ%8QO2%TZeW; z7koT#)u*7o?LZd-*oGeR`?$AX06RFl@DY3ryRZlQI1k_uj@}-jmTmY19=sv62M^&> zT>J0{{eN8Tp3j$B#q)b-R|BKsgi97HEk+TK=OP0$GL775Q-1a2vM6%%Wf<~2c`p&G zG`x8lmPY7sCW_IOQ5p58N<~~#KN4IEJ#cA{$FVSm6$%YhLA|hInTu7wksTTlYf+%+ zp5mCwMv^sLy1l`x+9G4G&MWfbVzrHr&(P8c-KbhGl%id6BlI#-u&i8>C3~O!Myl3# zTx(mX)!2rB5iAyqe?*X^vyk8Lf@>3knDSBzRs3@@&r(3ZqvqCRv5G5MZ54;eu&6yo`wex$NribKbaz$>D z1q5;BU`x;rD27=`GHeFV2s!(?;X38q$rH(ZBPCD98LPIdTfKFRd;@#z>= zusSuzC-dXe^z`}h7{@FNFTy;@hf$eG7=Dq(`lJ|NhYeVV79c~5*gHmz-mt*tGvOM; z^^vWCoG%e{c;EOqv~jpdh_iaUiSfFDNB4sNCj5Yb`!cTY+VM9-bE{QCK|Gj z$Lg@Q@S*S+uhw3D{x4pA_~zwcr|K|fOUzgmFqSGPrQP=!yDqtGZfNf=E$Cr!N&6NU zw#2-~!LE3ni?|RCVC;*^vwd8(p-gi_cQ>1t+1}DNd2~;U>(VB*X=i&uckWf!f(JJI zLrW|gHi%7x#vf_Y!D)W58^^o*Tqkf{$5IdJfBk}1r&Vhl`1&F0qemurJxbM$Fe7X- qnMAfe7@vFJCxb<*v*z1H8>xYc9; literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_slack.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_slack.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e98c0261bb6b46529009d3121c9141b937f7935 GIT binary patch literal 1180 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_steam.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_steam.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..03ed7772938c3a0768ea4b941df2dd92347adc7a GIT binary patch literal 4679 zcmdT|OLN=E5e7(*1YeSDt>2H;YAvtnNCfq;UPZ~K-^$vQEmDb%suT*s45gt#0GFLJp~P1EfqjcC1RJoC=|Nbq{C0>1ll3)O@j+ zyYyG0Up*s9f0Kqj3iR(l5r+!t+n)Y%|SD*T|f z=lkBOrkNfq3!j=D&pn`=lCtG?w2cNzwgu(dBkDi1%^u~AZl_~%MuoOtsZ{2)+rjCn zmornHnJLnodb~RIbakqsTL^CO8HLUrDB{mhQ3;KtCy1;kNkUghvYkTXpiziQvUqe- z*hNF6kLilXbQ!ABEEyrAWQ?4G=QPcplRz?lP9k5CGvq8e_j4ZB$k9BQ^*p&iE|N>+ zGD(mt9$H`#HZWGkDPOWSU$j-@TRf5psi+-b!HVo8(u}j*%%a`Aj$( z*<;9h*M(~_(>5%Z)AEjKy`l~gu^r1pbM=EIrg$=mC}o$4VcV)ez={l)}*!4a!CUYk7R3~uWh4kly8uXD(-cZ*8&y>V;~v=h5~4^$U+$qU?fSFj97?ubJRA z8my$j8ci? zDpa+j^Q{MqC&j{c9T%33uw=8r#OKh5X+{iAMerTYJOSk3#^)z8qRr0D2U>sgPS5nb z=xVD5YHMK* z)?B!?xU{@d37!ZzC?(UbXMjU_E_3{8%Qjhu5`&RxMyTV1!KdM^?{dfRUFZqVbp(y~ znl=SErm<%_#HPX3Su?mbzu#VI{D!c*2YauUr{?ZHe7?B6HRsvxS}7kEk0A}R13wT! zzTfQtqXeh>gCSVKslMzzykN918o&(3`hp|4!I>k;FzjHwFFppp*cZk&33Bg;p*lFW zp7A1b5_%HM7%FH>_QyY6dd`vZI8t`(0v~r;kGm}(NP+jN1$=O^lrZyON%6STYhHK9 zW|qb}<}NH??wh{Jh1R0**IHPiOD%JW&aSL1&CgcIQsvgpoVhrAYq`~0Cf2M)T6Cw} z_I87VFT1DF>e_a+aqkOn3@*&N9o(AzL%#gHl-DznK^f7DKu{1`df-aHLVDf@f44X_ zeF_5)2-9^;$LvtO&>sjNbo%)t0@`PS?JyhOj98QF-z&sKJssbSo{L*Nzg~#@F(f@_ zo5Ba)a$Bvsl>m>y54LG2_n-(Jsvo4b)Ru`%5*RaN$xpPCP#}&G1#~Hr`bjyGrhkyV z1&C~@$sE`CY9!6ZQX)Zo$<*}=jm>)-wI@bxyZP8?G#}KS8ntH|#`h0i>S^#zbH_R{ zlfC;3pe-fBG}@voLS@G#y_3^hDW|K#=V0L7o?Z~tH=y_sqXb8r=mk!98QdHqWJFlWQ&4Cg2iUi)-_*W*z=|VWmO>60C@YMikr799?e9Pfk|gNn@`s2`)DXpp~(W+(fRI68th~LzTGkIwhH(2SH?~xVnV;93CW|L18SA1|?aQgNq-+ zc|Y+#1~U#|Jd^vF4uQd`%^dm(TxX)~KAg;sL7e(1#1Fqy0%M#CRl`7DFpMx~7!awt zHuejK@w#i;u|_ZSuM;r(d-^GQ2ni1mi-BH-Q$nQ;VHPsKV}8mZ`Ik6m(qZw}wfa_V zqi)nU>-Qe;ag-@-;G{4`x*bpOb2t_6g`-3839Qg@hbp`ku0nMRir9lnjeJFk{04nz z@TiH8$p+d%t0d6N5{cYLW`jJ5&sZuqt}4;4$FlMw?0yGf+=ptAmD40uklT>WFe|4D zWYr2uLss3-&ZDe4g*iIp#c5I`BhY5ZDDc-l zklEd>&F>%7|I4`=&m+t)oTTvrZuQ@)E#~Px&?UK5@zkQ2hvH_>}V}IOT`LTQ8~n8(4==V)ZRn z$dY^tE9Az{kl+oN;y{UlXB;*Mq=xMgu_e_M$GN33ejOSJd5{2LVyI^^agF}%5ho;P z1v@NvAjJ@6NE&RmgV6}?A7O<#dT3G4hhN;WBj_3Y6#>7ccm~w`EY3+f?m>t-PYc(N oPl8+>?LS1~5vND-RjtOsW*vjJI1QC5tBFGVBo%oqIrh7M0+VD*a{vGU literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stocktwits.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stocktwits.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..810c0eb4e54de2468e0e660fad019883f431463d GIT binary patch literal 1811 zcmd5+&2HO95MKUBqG?I7W2b*p%q{JXzHG;K2W2fNW+ zoIk{_j}d@@m9mI|4Nhv!?8vs*_Y7QUykO9f_LUPkxNbrV*DdMb+6C(g3%qOmo1I=; zh=Eo4KRhbU*@LH2o0tyP&Bw_@?D|Ip$s!9_WP=qsV51@@YRKla1rA;K%D%E9uU_MI ziG{n~guxE@unTSIylO$Wmf3+@u=j#RKJ3G79CzUW4LYn2pO`#8H_sEJH`Xi$I*${P z@;FzrKN9h|%s}_`-=h8PzZSzYV+#Fc7>XjfDH!K7+6o_pB98MiGvTFF$#~wM>O8BO z<2)Y;6*WgvD5(NFYNMFcnwuQ&to(Z5W?smusf{qDzWxm#WzqUB&+mUL)Z%Npm#)1O zMyS_p%%lmEnV3i&f*gx7HGC<>%~?4c^{2&T@px^vKI)5^Smc>_uKQ%PHocY3e^ZkD z^3r0J`%I$)t;#EL%*0GqZjIeI6CRzA2ERv!(Dw7m? zpJ%Z;HiL_BNt2#H8uy4RtBsmBp+R2jaLt~2$!}1om}7WRWNGF-a>Yhq*5ckayC_5Y z8`=Nz4hQ%Sg;0jrj1I^&$z))u1H7nyk0so}Wp$^%VO#hkJX#y}=|3B`{o85Kt~}0( z0-RSq=d&Ejl;&;DFG`UvPonnfeb7jA7Ii4h9FWQ!`4R2Qp9&cp34BAUI&T_K&I%pv z-%J)h%3)r0HyUWI-Xcfee%)K*fg+a`l{GANOp}+M<*q*4@@>~{I<{4RHa4s4dQL6> z=2A641?y5JohjBkueuVldw&9ZxvFV@X-mz- z`BNP72?A)KhZ@tsfKsSO1~YW+g$5>+-)d0i#+wo=fqtm9R<4j+JGjyjTe_fj(r0* z;U;XsHr#^Sa0hmdHR|x$yB)au&UmAP_m<9ozBvB{p8pc=LlYjn+d$qM@DLuo z)mRn2f?fQs!Pn@@W3_&m3HRJBd>|KQk7eQoZs>T4;GL=Ko$(lCM;2V2CFa@gNtUH@ zVp$g#7uWk<5?O8sKk2L>#!Y`cH9?acCZ9ZpNk3e^6$ehoLev zk@MMh&$jp5b{Bs~-Mz_RFtKe$)Na3RkK6X(sAnT-GPGH+iTQE0g>DE+lv!mrvUb7R z{h66Q=hNBOfI;fQ#`_(Q&mrJ)CG>4scN3W zV$K@Zte6ofiPh#J7OSl&&vML`4~Xk@*g?TQ7d-YjyB%>?=BS9r*%DoqdE^G6Dtlo* zm8?7s&NzPLNy25zU^UnCxRg$oU=Fg?MM7N}c$><2{!CS9KSh&^0ANh|?e6%f*PRT< zli}WSScXU4(PS{13MO`qLe zdJX95=)7n96!*8#{nQm1JvwMN4}+Muba5Yb6xT3^2e?$X3Jo7Ke~Vk!HtqhaO&^~) zXw_83aj2jiM^zmsN+8Eda#YQ6USU4ZcUWz4BWPcC!s@x?!ol2jom_-$lfO>6M-@Z# zqBzY{nZ&9Ld6Y^_ily{*rU_iK#(N3077xkCM;|)cq$PPP=-+7thx`ShqMM6u7>52S yy-_m%hre2?lnQMvQ#?jptejAq2zrAH`cfR4L@R<(`DT7=-%)x?GPYvWO8)@)feNhv literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stripe.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stripe.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d4a06539a3aac31f4f9b89601618d4bdacdd6984 GIT binary patch literal 1083 zcmb7D&2G~`5MKWzaoi+ORa8j5qxz7zv;j2|s)~@{Ru1iHFDu(KY1}xryK7e@;?|0% z;01UU9>G^meFaX;I!PNUF08e)yR)C?=bPE1Zr9!W37ngZ{bC!#ru`{RwNC;V7zyJH zOt2DTW^&VDpBUKCxMR>j^VZ@P80XAu-V>Ug-5Vr5qjEpLFN?|H1*#&_7wgfc%_&WF zLIN2#fN>KHZlQhE&`z{zXhI7dXn$|r8oUV(Z-EP2cZ@sGfiA6W=#k53)${X0#yLLv zTI{PVii1Q%nZ!XDL{m&b4O9&qY@&HDpNfLQ6XPrk1Ie4q^&@YF7)X@7EinL*rOBMO z53sEuiq;3O#dnvcMP7zUtR_L2pqS#EcL;_`i6Wy2xRb;;ILiPlw^oQ^o+EFlD5Iq- zaV$|yR@mOkVyRGy7$Qxm9(vXD15MQ-@s(Y%TT%fdE{rqJAbK*9WS4PD1+Wz+;~z}6 z2>JV)pUe0rDA2zNq(9CQK&kw(^pB>mQ+Yic9l!z>BQ-pp4o9=m+u;F?%hbQ{^EmH^ zWt>3&O&ZDL;^5j}GTaZSzI{#eN>NkpYetp7CcZw375n*|ve$&yA@1ljsP!nR#olIN zbju7LasQM54&|Q*vd|4cfnyS<=ozv}PHG;O!l7gJqW16pkDnusHeZ2d2Zzh zkp?pqqH=|xGMCf@qUs27T?WbWhIdw+P=LjxvaeK@Rt+d;x#HWKy&}R4=6oB!8nu5GEzrQ4~8rC^{7Blp&}FTlsN{xG0LG9l9kb>WK>>>*z$WEGndwHb#d6 z3HlUyfxb!~L2IV~eS%Is%64N1T}pyS9`A>D_noxgY%v`AO!6gXbe$`UMgo zKoi0USYRh~WHF19_XHfM-4dt?>&9j_(2vBg-=j7;xu-~dT6(9)MSi~56FQHKU^YBF z9O2SEB!Vysgjqn*iNzeSSxwY0mcTX-+*vo2xfNdrbLT=G=Q8fzor|(%Sb@eZVIDML z755cb!$8){^E>xf}8&)4k^sZRFFG3}hZ?Q%gMM7(yOY~cM)yDJdOej-~lJ3Ut5+U)| z50v;WYku0};&T?GGajdGbrJH- zgMkY6hr6k|>~wcv4CAiu91c6(QTJtM2gg|&^nxtTTKytUpmmT&>M-BA3?`toLTr?l zL1ubUp%mm;YaPt6s#rb+EW5@cqj(bHGm{4PFeWwL>^(xh{h;?h`9Hz@vry%xXE1P) z#HsM9LKG);sEVPMku9HAFFaO$j-pvm+y4&gvB`Zy|B1Go2RoEQeYP)3aT>()S&;hT5zq4b;lUb9FzdV&(hq T1Eu?VlJ^_aqS`#?S`GUTzZ6A> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_thisismyjam.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_thisismyjam.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..868866db45e6dd4f351b23a2b9c74d18b79a4019 GIT binary patch literal 1314 zcmb7E!EWO=5G5&Db{yN=WU)bkMSLXn{=+&52knL2Kwli6knd z6vRev$*q4-&T!sP`@>=X<=?>HdxZQ$TFpoO z5WAhB00J~2+>I=uDdUV1@W4;J)aO1WKN09aa6=$a{knmdx_7K>5joUD>!5Oo-F`r!2&X_e1Ij&M+y^{a1!rC789KM@n)04n zg8=7U=-qla@7L>VXz9cHErUUgT8ANwZU`U1Gx!Y0A#7lLo7L9wX>5PC@0P#FR6Rzt z`r7I!mWhb8QPa7M&Q%6>S|B*K>GDjb)7Hjj-|EZLqR3x|q0Hk?kdaQqz3@_*c(I(G zS)K7|hsGusX1_SLd^X^x)gsF1C{_JPz|XpUlfRfyp;nWL_1!7h%K9 z#9e>l=(#u4RD}hWlMNpTtEZyPTE4{OBBqJ+3wIZWg3#4Yi_lgjOdrwzCl${z6}dEp zt0BCDO5#k7saZ!?bxImB#A)@S_V5(EjY|)qef3|VJ>7UT9#uUd9Fsy+eIZf}W#aUK z5ErGaITnHs?vsRJ7N>kzT4e;tLJEATd_!I3Dk>BRXRqtSca>XppiFbiH`?VY<{Fm9 zrIBwMby2$XHU;b!_~`NI8aP6rdFF(xjh1lPFz)&Hu-k2?Q2+j8w9goIU!VDup+53^ zUX30H<~d^dmW{8bslHTpidRVDxg!9huLJAUufe!qTD_^|wvM7^qW4{stM6T%+rS0D I25jK{3%{OkjQ{`u literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tripit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tripit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c40f955e31e1c235860d2390d48bdfc5fc3433b8 GIT binary patch literal 3225 zcmeHK&2HO95GE-~mSxFy95g799)cikA)pp**bd^9CG7MMy`(|wT=ue9aaZ=*%OCHq zY)k0PMxP=t&{ydr=-N|Wp{LG}k}117w6~%N1ul1fcISsX-%vZ9PV4dSoLsFt&L7U) z<-_~{Zu1Qc+~KZr2tGaKCLZxzhu65TYKc#L*ZJPzb>6sfctiNtHBtw9i8q06iaO8@ z?*8m_m*2xq&g-r_SsrR7(m3Pdy$dYhw0=As+LP@gVQh?Av#So11Gvo_7=%My?huc= zq{cnsbFd^Dr%UJwufOxIUD6cGya6;=e^&###h1wnZ;>`%;q4oTba;oa!gH0cfk*3; z^&>5FX)EjF&rKFfrf8gL(d)DLOr+fOY@yae?H@|*@uw%&=H_J(uw2gMah3!Z+rhE0 zL9VlbRDv`HS=MKoH2Q*Rp}QVwvp5#UP@BQtlUDJdc9e^8r_|jvBG$rAe5;3|TO%78 zgXEO@w3LyRIUf1ABx0r%dJMj&1E~|i$4x7)Y;ct*c2jel@F!vNEHxqutxO=-EXkwo zo##7y`&+wv+hI^~!*rO?{>TawZEx*vg+WD!Z_3b~XfgQesXR_0Q=5r+8qEaXCQPbB z&NV~{>ld-3kd^YN4Ef*~*YqHNVK9?~(ohIIqcX`eZKDBGMufpVE^6Jz8)3!@m5PN5 zxt0mjqe?tuR5sFnc14Af$Fi?PrC2&}R~QD<49o`_E#zYO=%A7!wbiaux^yj@iwsUp z6dHK@BBd!y#56r&FwYC~VPDCZa%pnKMy29+S@z@=(_yek7*0yGRW#U1< ze4B+RJjoJ4V`)bb?Dqw3s94IwpyG!tw(lpkA)q{%YD;0jki1}4 z1k)2^Po$X>_n0kS{@->FnG)KXe`v1IU2Lil&CJ}}Gn7y~MKG754^t@XMbV(v!p*1n zAY%ikZ?@I#iGZ_lTX``e9$xsw`)jyO8-`z;Q|B63+yQ8E0Vhe5XH;d!G6jV6jZlN% z9K8&hfBt&w#0AsvCKVUj1OpxHoNcH2e0P6~kNJ4t?7leL-B0$P?{2|$oCZfhF7wU) zP%6H;m&W>q-8v6Sz}rNO*+kTvG71C?0068BW;j@obQ)|v0`LNH>Ef3dh6jV=jvpgV z&ZBCt()%~@KZN*mrY+(zI1bgBimt1hV5G=HIkbSBd{kJtuN;AB=G144ow~o|b$4yj zq(x0pI%!dwWPGRu%-fWn51GQUq?EL$IROXl3F!=t(3CUF@DPy);wl%h#rgm~u9*0z zCQB!EK1^~$9?Z7@+Ryk%W9rC;7Ux4uY1tG|Kps_F1E^WRk)l~lRx5t7!3t&t&(U0J z@UUpikH$udc{88j#Pz2?&uw|0i@%oVd;YU#tpw=eO<`esN z8t)UjU0VGV~3915hU zh5|Dlg5VY01%fs`jdXWOBMgv7Mqc%j463^afTv_1ff{PRfU$bm2tmdmKsCL#*RHL<{0rcbCMEy? literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tumblr.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tumblr.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c30014bc1fd0fa4a10abea9f00f541826d074e1 GIT binary patch literal 1667 zcmb7F&2HQ_5Z1rt^{(rmCUN5Y6h+Zu4_-NqTi9(;q$toHS|o?K7hVWT!`+ov5@nKd z;ze%``UpMw1^OmMfxZB*J^2-S>QLH+7eOxNfQ z0Qb*vsv{KOfLl1ku3q7m9`RfUdy0dUl?mWCE~-$|d-+|Xr%zA0(wX&Ij(fOyj#K@Pf;+?ohj`!;A3PEOM&{Akntd~b z*WQ&&V!jR$p4TA0_VL_n?d#BkjcX73Fn~??0OuBLquhaAl*{=cp6}rF;qLX$#D#mf z+l0OAFn8g88+`*F!2Vl@^x+{q!gTeDEJ!pKB4v_9DQA+)p-1{G%eYch3k+;} z?Lzfs&F6#8_dxMXa%}=F8$R^Oj$l~d&YyNRvazBgd)*;pMPbv$#H(>GOAa@GV&#qZ z@~5F^BBhybl<`Y7F@a4``e>E*5{*ORUn-sJg2Vj&0#%pHDlp@|IQ_vyb(0o(Hrz35 zO$|(QFdM*CCi7b7q9PGUHrC37Z9L;6UrbygVm9H5g!(n-nrx^kmV*}g%<5{Pv?6P@ z%u6OOO?ck$hLey%nO9_eEQ&(B<`Q#fYDVHQ%Xlip3=4!+BM+8mDr#O$B&(;CS1d&= zCa74+O)%AEF$|ZH@@hiDv=9?Tc7cysQ)p^4pkpCRrsW=1goJHsSht|$nweN~Rf`I% zZ|h};UG4lHr|P5l)j4ynPz4V1%EeY8F$jvU$t!Gms~KJ zAU;5J$yg@OW=|`5esppObC{o~qvP4pNqO?+(IJjWm7FGZULT}QUckW@RVI)1;d#=r z^}urEz*2UhXz811Y)X|ZN!%Xkg30wIk`K*YipLg)hr)5^`zs`G+j?~C_kZy3BL135 zZJP-b=S5!ep(`KaOWHbg;xQi0o%RjyI)9C(Wm2EMKdE;gc`@8HF{Sq2DK$MxO94$` z_kBvw8&-4{(qGgB6Vy{Ou>C-h%#0#k$u@se^9-3yt-j;XnG%%=p($%cwpUx2rUEXd zEgjjB9I1;X?V8#e$mVV6*71o&|B)4z3M<0aL53(|oa$%0af8Lrb9lB3nFa)O{D#1v=6PM*k;_` VSNu&^*KchLDE!_yvDf$d{(l9*-v`yP-0!9zp^+A>$dmTF{iWEH*y<`vdi7i0T8akmZOA1LR z8=`V@VEzDgfK*Piwjdg@TN5o1xHD2W-8!x_%sCk@-};MJc%1D}w;$icufxj@oy zkO2WFA(Vg%UgE4>>N?~H0zT9}5U2_F!J{5HKap|PF1(_h!0YY7H@DZ2#;L)0>FQ3@wrSss>Ms=!#2Tz~@M-WH{n$W^?8;;>K zc=~%2wT|Efo_!!RfamZ6*CxEgfIcsu&hDg%uOA~R-smirJYjLBL~qXHl}JJNOr_R4 z)GofdHYV5SQN(k(S1`BHN^!J_Hd?5N1_rZZc0Zsk9>+p!W-`2|^+lG=xhj2oS-d#y z4+dd>9QKE^!P)s}dOn!aN_uLggNc<6rsr?ZXz6Q_EQ;_yz3b@Fc>z`(W7UQm8HX{X zy`{NL?|P{)5$>uOSuSI|9cUji?@#}SmcPf$1yxS~&bDxXSPx)HVAXb7K z0gM|OY;xG|>nxG!N>e|{uCmf&5@-vg&J%vmtYKLbYc3OdlyhZLp#)G5J;oMlcG%U@ z_ei>h>{qfR4=4hHIB+hz2qefDzAn?Qqw1*f2T@I2{{Ah>mAu73qFb(_MV5e2I$Ee` zxEiGDW;E?X0mW30CackOJspqwIIhxY7UePz=Np+o_%@Bz#Pn~XEx1CCzz;3Lc7h6R zRl9Vw$K1AiJK*K_eOo+>eGFV+y~VB) zRUNj>macqRdH7VhgID{YUjKVgpPF2DTV;b)4P>kg7+YtsL8!5^#n{b;C$_bW(blde z7@)bPZHpU*(BN#N5_%%u=OVV4vGNKG{!(XYS%b|w*YxD@vt#oN?&}p8*HL`}|?`Gioj=P<1-Nmsx1zy9eY`VUBh8DDz*>)=r_m%wM{mNZc T)5bU3+`qLb)z)RhX?gzuT%(H8 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitter.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitter.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16d584b16ee2650c7f2eae9f5f16f6d861f8b384 GIT binary patch literal 5565 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_uber.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_uber.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dcf92562317a3a5f2bddeb8f43086e4672f2d589 GIT binary patch literal 1474 zcmb7E&2HO95MGM(K- zrD_Si+1I{7UZAhiN8q*S!LQI$XDKUogI>DC&d$!x4!_y?c6S;L`-?w-{$&#KC%My# zsPAK!$0&e+5fZA$LTE$3QJL$mQgQ5C9FgiwW2Dm8svz81=TA8 zRaU(;sSU;}(zLHJDtX>Cvg(tQlVib@n(Ij_LKgcR*fruIXleYAs>x$6MgaheuMKT2 zWcCYf?w&BIZuD)f`#R{VS*Yw|mv2xoLJc6a00uRoK#O3~5-XooG;lCAWDW9N`O=_P zZnvQZb!fmEeDtn_)+(&S#uY*M_h1vp8r;V;9%O5;M@-~sas-RdCrTyqTgUN|pq=7c z+vkxp>pBrzY)S41Ug-K2w=qQ5$oD5=h0x{Gnje z6pPj@(YojROiEXAJd(`L2UpD#1jR0rKrrbBF>MGoMs>1uqb2F{g!!5gy6I8(AF<0i zieJeYxkM8XER=y%qZV*CPfeLb@(( zq>>32JmtA~`)U2=a1gDU&<(6V^A!VEd*u`^o=F~OB}k)0(zV;oBMx)AahFN1Hi=E0 z;{AW;sjDt@vZ9KjhF$9XETgEuLeVI^|7`PiW}B9o?`YPB7}-S;#LkI4!&5<&ZOPe`%`zUyu|0e!#*)+LHmM18~t;Sx4`+r`U_WdlI`}LYO&M`(f|hr0Pj0vUIJaTgr!fy;d$&)7a#u#u@##7qC&;T>EAtLwnx zrAO<)+Qf`zOAx@)1>;@lLXXBCEK^>0s(T-0A@{QKc%pCen7%Ms6iX$dOyh7YqeD!= z3`-!};q3#{XZuBwn>S&2a&j`PO{~A+M29ku!;LV(khdpUHkO*V$0#-G>1wmy!Bd&! z3Wrga^nKoyQG~{bBBOv+P!FOw%aI3@SertmGQp~aiHy~>*SHuMj5HRNU+5D19$(EQ z!P&SF%^qKx)A5JRB9cnko6#Lpa-JYWwjj3I(e@A2O_#)XcEHX_1&lCrsEoV=nNZnY zoKgXOgKF}V>3}T%{0MU$AIk#6W2wVQrU11G35(6cjZ`0vw%1__(`_@_Ivi~$+pkCK z)b`TwYnaFRU|hxu2CvdcZx!oDVN->Hz0kmx;#yIcB`k3NA+%|qpTeHNT z|2whU$9DV6RY%lR5TXi%NHQoD(!49gQ7M%ri4eSd4Gkq(?DJk}Pz%dWK`)T6;AxIg zfgr5Drj>tSvb1VJndFAA%rjApGdR;0LcXf;sI<^F+_W2f`Nnl^Xc?n>R#-;N35$hp zk?2r2Pwd3;uRqtZU*OKh!0mXoZNIHo$dW%W@#!$hj*;G>Rw^D_@G18?tyaxxZGTkt bp}C-S4s{heZyTiES#BHqd+E4cx9j}{Rs~`! literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_upwork.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_upwork.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6dfaad36d252ed949542139c50211c7c10454775 GIT binary patch literal 1704 zcmb6a%Wm67kT1y;CHa-uaq6Z?FDV4nq7=(*Toggl0`1K`Bsp|1ixp>S(jy5iyBdFgHaf{es5eIDIfL9_+#W8xKB#{aU^a zpTdK87U{re@DS%NJVH-CuQp#7S3+LgT0Q$hi6~(yjfCX=F^evE4ys?)Z2cAP*_UTp z7wVfZyt=wto<|}JS&@VcgAh)JVUow9f38GM{NZ5!k!CE!m`Tzcb0)d$Ii$m)h$}_4 zz{{$jo~WJ{eAb=w9w;73t}9Q=lJ{J)EzAXTemXxR?G+l?n{OhPrp5(yCXW-Daj2}y z$wlQ(xk5dSaXICZY_T{_(u8SFGnTVcB>9CDlLWY|?BUEm;a87-#xN20^~)D1=S7L0 zRt=?5gserP%(a}7wd1dkjt+;T+2j9+OsTk3;V;Wb^jc)25K>E)XnOdBCfRA_N338* zJ85Gq3zknQ)))!Sn4)M{9!*zdjs`}CY<^HO9MqJ`PesIZBJ#?WJSJ^)1|8%^)vSXq zgiD7i_XmN|_zu(2K;gll=T*K=GCmVICjrJxX_~W)SFMySmSkheKt&uYu1PTbFnVs~ znWU3eogw2fX(O6&NsU-!`os(~8~7eUbr8L^&aG=CfrW)>V|NiB1WmNC*_V-1Jp!zRr02vi3!J-P^M`z oHi<#sYO@>lVqV-Pz~aH*ePaySu$7-@yA$3Hd>8nThiw9MVPy1ZYB1 z>QPGG5@>;OO~7DuV|XnvkIA6@6Tc?|THbpdNcN7M2GQz-NflxA##%T#!XclbW5lCC z@N?=J(DF<$yf&C#2inlNCSDi1VBy<>9ZcUV`|l+aS&%ZFb^TQGC=L=o;)0FBAUbCm z$WgI{j2i5#LY;Y?CFfxv)_cwNd>h#GY_!>ui_}h;I^!^RaxRr)2a$?-Hg`UbKRbsN zszSzDUMO3Wm^)1}$5yKx*NR=K+t{#Bif4z%MVQ8F=5CkuiLO4f&KqgjwCT4VZ*a&y zx=-W-*evqbTw`3Z|=I6LKh$mbh@zWB3RVaPseOoMzymT e7-c7^elej1u7bBTxXDvmvzqjwzFAf6ed9O69SlDJ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6370016eb2449ac477c70d3ee46a2e60a9b2e4a9 GIT binary patch literal 2510 zcmc&$Uvt|;5WkaTS@Hjd0-YAx5dNfQh#lI%G&2lST4-Lt3~36CUNnmC>?)Qe=k8n@ zPr{4KV~5YdSK%Y*l?NtYAy4d{EZa%l8Qv<7_HIvmcYDA6?Vj&etEC%%GxuMckblU+ zV?qBuyz+Mtj4&DzmokHykufk`lM?7#ku@l|1?U%;9od7TTO2s9qxE96GAOwv-FKq$ zpyF02*(Gd+m0l25;`FdOG2I%BlvxEvD!c}I9rP-zfnL*kgVD#NSw90GNaxG|*`#TC zpG);vCBpAlRb<2NJq^bY8? zA3`a^xc4Bl4{*Eu!A@Af~B#nJZ878|qij@-W2-`*MQe7U^^PcLrow};_y z^I#N4Z1ama5cky9QClWK=trAAz_J-6f=>rL-B)lH+0G$tqw0Tvv7@&nc5p0253G@?RiP3SWW9pFr zl-V=m%vBhAl{0WUD{Xua44rc2B2RGL0=ycPpP{25VHj}irEy9XJ)wujgiPqEaY7Ew z6NA~T$ef-zF(~PhQ&W)>U}a{F7Rs8>nNYTZb{gSw=lpgSaUI}R9@NzOl8 zYT5Mzevl;n)}v`BOR4jX<dRU7KPppgm#Zn+_7`gsG8OrzxrTrM*9y#<0Mmdwz%^ye_+r0#zKCn! zBG6zB8u0;&^=zPs{TnD8958gq)8aI^3;62THgseb?gU&#VwIUk)~4fI7eUM7xiITO z9m7(kQTzltfSs=G6XVYYB?@!2fH?jFnfDXfBfn-DSnP~%POqM|sjF-2-6T2i#rhhL zl{kLm843xg{Q=NFhJy(_o!9`;G~LxK)%N^Qa=FKQ{P|F<;CGD1Ys&Vu8T5nf&dsmo zPhrk91U94 zah<%I+Hh0wNb0*-UrPFFc58X@Xz7bk7bAUmw?6xlRWwb@w8WnWe5k@CmKrG6!DM_0 ne;V{1qElS^vCQSv`6|1wzQ$Up|3ElK1u9Pi-io>EH0u8WxtVvY literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_vk.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_vk.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afcf8780348a093f6c7f52571f3f8a30c3cbeac7 GIT binary patch literal 1254 zcmb7E!EW0|5Z$FHS(YisP8#IU0u9=e4${iDl3GEKrsyGP3%G{{0lE;dpg1du7RjY| zmqIM`P{jF!9s}guTM?kA0{wuVlq;`2B8JF_$7%$qmTQM=vx@DJdh z*9iHOTw6Zsk8zs;3Lroel2T5o)4jyw9@M~3YN^kCN}dp?L*pfZhV(CLybj)Xq_g!7 z-y|Jc?iE=aDUc$Gt<+&+PTtYAr}ga@g`FKfmBvOcc(b-qd5qgULm>&LKrkWYHSo9( zm_Rm8x4?HvUwIdlH!FJ!ny?KmENlk?XuoPqX-v_(4ZCpTCE+dDgMB=A;3k%Ls|=of zbCrL2*Qh8C6A>vbd*d*g%M46!G4E}nmY>e7&CLVG!aR14NTn>5cBX(8MrzZWN{iSF ztrom7QED7&-WbbJOWpB!5Jr(SMp%VkE?ddorc z)o*L_r?(exe_Z{(dQ+0+eOJVT5gW0{eAe$Dxk6GoVhd*+AKV+U!^7}k#GX%|3@796 zS6?4}-#g8xokm$JV&JVwtc?{}n96cH2`^Q?VM-GCAg3#ri4a05@dsi|42lp++orl`G3BrP95@)hQ zwQEv^e=T;=EN@pC-Vgp5U7LbF`ENn*ho5wUvMDOQgeY4=qzZ~eq89>D0IU2>4UgP=rhy{xnqE-*8_iZ)hyVZp literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_wunderlist.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_wunderlist.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ea3efbc9729cb61fb88d48df03c3fb0736849e20 GIT binary patch literal 1207 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 L)2U4;-wvHWOZPN9 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_zotero.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_zotero.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b3982371ad554973a386510fef75e7ad46e38b8 GIT binary patch literal 1146 zcmb7E%}yIJ5cdA$hb4hRMTN?})k;k^X#yfrRY8kD{ZV=-z4&5nb{yDdvrguh3J+yNQTUFSX^dJsx{zzWH`L^}4h33+PXikl$pf*| z0!Bo}1~m)IG+yF zwnn zhR`b1ppNqjtYH}I*&`|vF2%ji*>fd=(2rOkBySG~wecm_G=B4_!N36+Y(35`m9rn@by} zcp!N~%c-BDK6bL@}#tX_C&6b6}{$dtR5tRNUtLqoTVX97QpbkrDsJPhTSL!g1x zP0xC~#i42_zLBrw8dX3L3#0EE2#;WqI16Jejjebz{6XX@TK@U&O=NiCC)~U6r8g82 zaH+hZ^g5$_ zG|Y2F4!M_L;gz=lKHsuA`je~7(Z@xITRIJ-BqlZ6St?9Uo#wsW|K$G|^Pl)K(FDN2 zc@)OnHKc=1avTbwvCFn{4-bvE@n{j$vwsKm@JioxvMOV`%8X?WV`Bknq}MgZrl}tl zC$u)l1OrUY=?1@?@F3y978H>h3%cP40avQv78?k-qMMRWQw>|rw_GRdI-@l$ba|JA z&P|q3Xi}-CD5{2y-JEyFELM(HwQ{?@TcS@?c-b6_3$B{@JxAd{gNV${BP!*@l=^3p ZUs!UM`_9p + + + + """ + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_evernote.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_evernote.py new file mode 100644 index 0000000..fb10316 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_evernote.py @@ -0,0 +1,56 @@ +from requests import HTTPError + +from six.moves.urllib_parse import urlencode + +from ...exceptions import AuthCanceled +from .oauth import OAuth1Test + + +class EvernoteOAuth1Test(OAuth1Test): + backend_path = 'social_core.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) as cm: + self.do_login() + self.assertTrue(cm.exception.response is not None) + + def test_partial_pipeline(self): + with self.assertRaises(AuthCanceled) as cm: + self.do_partial_pipeline() + self.assertTrue(cm.exception.response is not None) + + +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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_facebook.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_facebook.py new file mode 100644 index 0000000..9822e33 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_facebook.py @@ -0,0 +1,69 @@ +import json + +from ...backends.facebook import API_VERSION +from ...exceptions import AuthUnknownError, AuthCanceled + +from .oauth import OAuth2Test + + +class FacebookOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.facebook.FacebookOAuth2' + user_data_url = 'https://graph.facebook.com/v{version}/me'.format( + version=API_VERSION + ) + 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() + + +class FacebookOAuth2AuthCancelTest(FacebookOAuth2Test): + access_token_status = 400 + access_token_body = json.dumps({ + 'error': { + 'message': "redirect_uri isn't an absolute URI. Check RFC 3986.", + 'code': 191, + 'type': 'OAuthException', + 'fbtrace_id': '123Abc' + } + }) + + def test_login(self): + with self.assertRaises(AuthCanceled) as cm: + self.do_login() + self.assertIn('error', cm.exception.response.json()) + + def test_partial_pipeline(self): + with self.assertRaises(AuthCanceled) as cm: + self.do_partial_pipeline() + self.assertIn('error', cm.exception.response.json()) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_fitbit.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_fitbit.py new file mode 100644 index 0000000..72275d0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_fitbit.py @@ -0,0 +1,51 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class FitbitOAuth1Test(OAuth1Test): + backend_path = 'social_core.backends.fitbit.FitbitOAuth1' + 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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_five_hundred_px.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_five_hundred_px.py new file mode 100644 index 0000000..44916ab --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_five_hundred_px.py @@ -0,0 +1,108 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class FiveHundredPxOAuth1Test(OAuth1Test): + backend_path = 'social_core.backends.five_hundred_px.FiveHundredPxOAuth' + user_data_url = 'https://api.500px.com/v1/users' + 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({ + 'user': { + 'id': 10101010, + 'username': 'foobar', + 'firstname': '', + 'lastname': '', + 'birthday': None, + 'sex': 1, + 'city': '', + 'state': '', + 'country': '', + 'registration_date': '2011-11-11T00:00:00-00:00', + 'about': '', + 'usertype': 0, + 'domain': 'UserName.500px.com', + 'fotomoto_on': False, + 'locale': 'en', + 'show_nude': False, + 'allow_sale_requests': 1, + 'fullname': 'UserName', + 'userpic_url': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100', + 'userpic_https_url': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?' + 'height=100&width=100', + 'cover_url': None, + 'upgrade_status': 0, + 'store_on': False, + 'photos_count': 0, + 'galleries_count': 0, + 'affection': 51, + 'in_favorites_count': 0, + 'friends_count': 2, + 'followers_count': 3, + 'analytics_code': None, + 'invite_pending': False, + 'invite_accepted': False, + 'email': 'user@user.com', + 'shadow_email': 'user@user.com', + 'upload_limit': 20, + 'upload_limit_expiry': '2021-11-11T00:00:00-00:00', + 'upgrade_type': 0, + 'upgrade_status_expiry': '2011-11-21', + 'portfolio_enabled': False, + 'auth': { + 'facebook': 1, + 'twitter': 0, + 'google_oauth2': 1 + }, + 'presubmit_for_licensing': None, + 'contacts': { + 'facebook': '1000000000' + }, + 'equipment': {}, + 'avatars': { + 'default': { + 'http': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100', + 'https': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100' + }, + 'large': { + 'http': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100', + 'https': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100' + }, + 'small': { + 'http': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100', + 'https': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100' + }, + 'tiny': { + 'http': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100', + 'https': 'https://graph.facebook.com/v2.7/' + '1000000000/picture?height=100&width=100' + } + } + } + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_flickr.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_flickr.py new file mode 100644 index 0000000..ba6073c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_flickr.py @@ -0,0 +1,25 @@ +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class FlickrOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_foursquare.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_foursquare.py new file mode 100644 index 0000000..1aa2f8d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_foursquare.py @@ -0,0 +1,127 @@ +import json + +from .oauth import OAuth2Test + + +class FoursquareOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github.py new file mode 100644 index 0000000..f3e5345 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github.py @@ -0,0 +1,191 @@ +import json + +from httpretty import HTTPretty + +from ...exceptions import AuthFailed + +from .oauth import OAuth2Test + + +class GithubOAuth2Test(OAuth2Test): + backend_path = 'social_core.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_core.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_core.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_core.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_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github_enterprise.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github_enterprise.py new file mode 100644 index 0000000..a5cfe6b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_github_enterprise.py @@ -0,0 +1,243 @@ +import json + +from httpretty import HTTPretty + +from ...exceptions import AuthFailed + +from .oauth import OAuth2Test + + +class GithubEnterpriseOAuth2Test(OAuth2Test): + backend_path = 'social_core.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_core.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_core.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_core.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_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_gitlab.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_gitlab.py new file mode 100644 index 0000000..29bc75b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_gitlab.py @@ -0,0 +1,107 @@ +import json + +from httpretty import HTTPretty + +from ...exceptions import AuthFailed + +from .oauth import OAuth2Test + + +class GitLabOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.gitlab.GitLabOAuth2' + user_data_url = 'https://gitlab.com/api/v4/user' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + 'expires_in': 7200, + 'refresh_token': 'barfoo' + }) + user_data_body = json.dumps({ + 'two_factor_enabled': False, + 'can_create_project': True, + 'confirmed_at': '2016-12-28T12:26:19.256Z', + 'twitter': '', + 'linkedin': '', + 'color_scheme_id': 1, + 'web_url': 'https://gitlab.com/foobar', + 'skype': '', + 'identities': [], + 'id': 123456, + 'projects_limit': 100000, + 'current_sign_in_at': '2016-12-28T12:26:19.795Z', + 'state': 'active', + 'location': None, + 'email': 'foobar@example.com', + 'website_url': '', + 'username': 'foobar', + 'bio': None, + 'last_sign_in_at': '2016-12-28T12:26:19.795Z', + 'is_admin': False, + 'external': False, + 'organization': None, + 'name': 'Foo Bar', + 'can_create_group': True, + 'created_at': '2016-12-28T12:26:19.638Z', + 'avatar_url': 'https://secure.gravatar.com/avatar/94d093eda664addd6e450d7e9881bcae?s=32&d=identicon', + 'theme_id': 2 + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() + + +class GitLabCustomDomainOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.gitlab.GitLabOAuth2' + user_data_url = 'https://example.com/api/v4/user' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + 'expires_in': 7200, + 'refresh_token': 'barfoo' + }) + user_data_body = json.dumps({ + 'two_factor_enabled': False, + 'can_create_project': True, + 'confirmed_at': '2016-12-28T12:26:19.256Z', + 'twitter': '', + 'linkedin': '', + 'color_scheme_id': 1, + 'web_url': 'https://example.com/foobar', + 'skype': '', + 'identities': [], + 'id': 123456, + 'projects_limit': 100000, + 'current_sign_in_at': '2016-12-28T12:26:19.795Z', + 'state': 'active', + 'location': None, + 'email': 'foobar@example.com', + 'website_url': '', + 'username': 'foobar', + 'bio': None, + 'last_sign_in_at': '2016-12-28T12:26:19.795Z', + 'is_admin': False, + 'external': False, + 'organization': None, + 'name': 'Foo Bar', + 'can_create_group': True, + 'created_at': '2016-12-28T12:26:19.638Z', + 'avatar_url': 'https://secure.gravatar.com/avatar/94d093eda664addd6e450d7e9881bcae?s=32&d=identicon', + 'theme_id': 2 + }) + + def test_login(self): + self.strategy.set_settings({ + 'SOCIAL_AUTH_GITLAB_API_URL': 'https://example.com' + }) + self.do_login() + + def test_partial_pipeline(self): + self.strategy.set_settings({ + 'SOCIAL_AUTH_GITLAB_API_URL': 'https://example.com' + }) + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_google.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_google.py new file mode 100644 index 0000000..c4d3423 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_google.py @@ -0,0 +1,294 @@ +import datetime +import json +import unittest2 + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from ...actions import do_disconnect + +from ..models import User +from .oauth import OAuth1Test, OAuth2Test +from .open_id import OpenIdTest +from .open_id_connect import OpenIdConnectTestMixin, NO_JWKEST + + +class GoogleOAuth2Test(OAuth2Test): + backend_path = 'social_core.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 GoogleOAuth1Test(OAuth1Test): + backend_path = 'social_core.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_core.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) + + +@unittest2.skipIf(NO_JWKEST, 'No Jwkest installed') +class GoogleOpenIdConnectTest(OpenIdConnectTestMixin, GoogleOAuth2Test): + backend_path = \ + 'social_core.backends.google_openidconnect.GoogleOpenIdConnect' + user_data_url = \ + 'https://www.googleapis.com/plus/v1/people/me/openIdConnect' + issuer = 'accounts.google.com' + openid_config_body = ''.join([ + '{', + ' "issuer": "https://accounts.google.com",', + ' "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",', + ' "token_endpoint": "https://www.googleapis.com/oauth2/v4/token",', + ' "userinfo_endpoint": "https://www.googleapis.com/oauth2/v3/userinfo",', + ' "revocation_endpoint": "https://accounts.google.com/o/oauth2/revoke",', + ' "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",', + ' "response_types_supported": [', + ' "code",', + ' "token",', + ' "id_token",', + ' "code token",', + ' "code id_token",', + ' "token id_token",', + ' "code token id_token",', + ' "none"', + ' ],', + ' "subject_types_supported": [', + ' "public"', + ' ],', + ' "id_token_signing_alg_values_supported": [', + ' "RS256"', + ' ],', + ' "scopes_supported": [', + ' "openid",', + ' "email",', + ' "profile"', + ' ],', + ' "token_endpoint_auth_methods_supported": [', + ' "client_secret_post",', + ' "client_secret_basic"', + ' ],', + ' "claims_supported": [', + ' "aud",', + ' "email",', + ' "email_verified",', + ' "exp",', + ' "family_name",', + ' "given_name",', + ' "iat",', + ' "iss",', + ' "locale",', + ' "name",', + ' "picture",', + ' "sub"', + ' ]', + '}' + ]) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_instagram.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_instagram.py new file mode 100644 index 0000000..3a559d1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_instagram.py @@ -0,0 +1,64 @@ +import json + +from .oauth import OAuth2Test + + +class InstagramOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_itembase.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_itembase.py new file mode 100644 index 0000000..e4979e0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_itembase.py @@ -0,0 +1,45 @@ +import json + +from .oauth import OAuth2Test + + +class ItembaseOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.itembase.ItembaseOAuth2' + user_data_url = 'https://users.itembase.com/v1/me' + expected_username = 'foobar' + access_token_body = json.dumps({ + "access_token": "foobar-token", + "expires_in": 2592000, + "token_type": "bearer", + "scope": "user.minimal", + "refresh_token": "foobar-refresh-token" + }) + user_data_body = json.dumps({ + "uuid": "a4b91ee7-ec1a-49b9-afce-371dc8797749", + "username": "foobar", + "email": "foobar@itembase.biz", + "first_name": "Foo", + "middle_name": None, + "last_name": "Bar", + "name_format": "first middle last", + "locale": "en", + "preferred_currency": "EUR" + }) + refresh_token_body = json.dumps({ + "access_token": "foobar-new-token", + "expires_in": 2592000, + "token_type": "bearer", + "scope": "user.minimal", + "refresh_token": "foobar-new-refresh-token" + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() + + +class ItembaseOAuth2SandboxTest(OAuth2Test): + backend_path = 'social_core.backends.itembase.ItembaseOAuth2Sandbox' + user_data_url = 'http://sandbox.users.itembase.io/v1/me' diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_kakao.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_kakao.py new file mode 100644 index 0000000..221b7d1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_kakao.py @@ -0,0 +1,28 @@ +import json + +from .oauth import OAuth2Test + + +class KakaoOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_khanacademy.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_khanacademy.py new file mode 100644 index 0000000..ad0ac44 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_khanacademy.py @@ -0,0 +1,31 @@ +import json + +from six.moves.urllib_parse import urlencode + + +from .oauth import OAuth1Test + + +class KhanAcademyOAuth1Test(OAuth1Test): + backend_path = 'social_core.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({ + "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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_linkedin.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_linkedin.py new file mode 100644 index 0000000..fd196b2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_linkedin.py @@ -0,0 +1,40 @@ +import json + +from six.moves.urllib_parse import urlencode + + +from .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_core.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_core.backends.linkedin.LinkedinOAuth2' diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_live.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_live.py new file mode 100644 index 0000000..42c4403 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_live.py @@ -0,0 +1,35 @@ +import json + +from .oauth import OAuth2Test + + +class LiveOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_livejournal.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_livejournal.py new file mode 100644 index 0000000..9f8994b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_livejournal.py @@ -0,0 +1,100 @@ +import datetime + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from ...exceptions import AuthMissingParameter + +from .open_id import OpenIdTest + + +JANRAIN_NONCE = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') + + +class LiveJournalOpenIdTest(OpenIdTest): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_lyft.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_lyft.py new file mode 100644 index 0000000..e6e338b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_lyft.py @@ -0,0 +1,26 @@ +import json + +from .oauth import OAuth2Test + + +class LyftOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.lyft.LyftOAuth2' + user_data_url = 'https://api.lyft.com/v1/profile' + access_token_body = json.dumps({ + 'access_token': 'atoken_foo', + 'refresh_token': 'rtoken_bar', + 'token_type': 'bearer', + 'expires_in': 3600, + 'scope': 'public profile rides.read rides.request', + 'id': 'user_foobar' + }) + user_data_body = json.dumps({ + 'id': 'user_foobar' + }) + expected_username = 'user_foobar' + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mapmyfitness.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mapmyfitness.py new file mode 100644 index 0000000..b44780d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mapmyfitness.py @@ -0,0 +1,158 @@ +import json + +from .oauth import OAuth2Test + + +class MapMyFitnessOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mineid.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mineid.py new file mode 100644 index 0000000..bd56490 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mineid.py @@ -0,0 +1,23 @@ +import json + +from .oauth import OAuth2Test + + +class MineIDOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mixcloud.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mixcloud.py new file mode 100644 index 0000000..fe81e91 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_mixcloud.py @@ -0,0 +1,58 @@ +import json + +from .oauth import OAuth2Test + + +class MixcloudOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_nationbuilder.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_nationbuilder.py new file mode 100644 index 0000000..3b5fc6c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_nationbuilder.py @@ -0,0 +1,232 @@ +import json + +from .oauth import OAuth2Test + + +class NationBuilderOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_naver.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_naver.py new file mode 100644 index 0000000..ecaf52d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_naver.py @@ -0,0 +1,41 @@ +import json + +from .oauth import OAuth2Test + + +class NaverOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.naver.NaverOAuth2' + user_data_url = 'https://openapi.naver.com/v1/nid/getUserProfile.xml' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + }) + + user_data_content_type = 'text/xml' + user_data_body = \ + '' \ + '' \ + '' \ + '00' \ + 'success' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + '' \ + 'M' \ + '' \ + '' \ + '' \ + '' \ + '' + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_ngpvan.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_ngpvan.py new file mode 100644 index 0000000..fff0f6e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_ngpvan.py @@ -0,0 +1,197 @@ +"""Tests for NGP VAN ActionID Backend""" +import datetime + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from .open_id import OpenIdTest + + +JANRAIN_NONCE = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') + + +class NGPVANActionIDOpenIDTest(OpenIdTest): + """Test the NGP VAN ActionID OpenID 1.1 Backend""" + backend_path = 'social_core.backends.ngpvan.ActionIDOpenID' + expected_username = 'testuser@user.local' + discovery_body = ' '.join([ + '', + '', + '', + '', + 'http://specs.openid.net/auth/2.0/signon', + 'http://openid.net/extensions/sreg/1.1', + 'http://axschema.org/contact/email', + 'https://accounts.ngpvan.com/OpenId/Provider', + '', + '', + 'http://openid.net/signon/1.0', + 'http://openid.net/extensions/sreg/1.1', + 'http://axschema.org/contact/email', + 'https://accounts.ngpvan.com/OpenId/Provider', + '', + '', + '' + ]) + server_response = urlencode({ + 'openid.claimed_id': 'https://accounts.ngpvan.com/user/abcd123', + 'openid.identity': 'https://accounts.ngpvan.com/user/abcd123', + 'openid.sig': 'Midw8F/rCDwW7vMz3y+vK6rjz6s=', + 'openid.signed': 'claimed_id,identity,assoc_handle,op_endpoint,return_' + 'to,response_nonce,ns.alias3,alias3.mode,alias3.type.' + 'alias1,alias3.value.alias1,alias3.type.alias2,alias3' + '.value.alias2,alias3.type.alias3,alias3.value.alias3' + ',alias3.type.alias4,alias3.value.alias4,alias3.type.' + 'alias5,alias3.value.alias5,alias3.type.alias6,alias3' + '.value.alias6,alias3.type.alias7,alias3.value.alias7' + ',alias3.type.alias8,alias3.value.alias8,ns.sreg,sreg' + '.fullname', + 'openid.assoc_handle': '{635790678917902781}{GdSyFA==}{20}', + 'openid.op_endpoint': 'https://accounts.ngpvan.com/OpenId/Provider', + 'openid.return_to': 'http://myapp.com/complete/actionid-openid/', + 'openid.response_nonce': JANRAIN_NONCE + 'MMgBGEre', + 'openid.mode': 'id_res', + 'openid.ns': 'http://specs.openid.net/auth/2.0', + 'openid.ns.alias3': 'http://openid.net/srv/ax/1.0', + 'openid.alias3.mode': 'fetch_response', + 'openid.alias3.type.alias1': 'http://openid.net/schema/contact/phone/b' + 'usiness', + 'openid.alias3.value.alias1': '+12015555555', + 'openid.alias3.type.alias2': 'http://openid.net/schema/contact/interne' + 't/email', + 'openid.alias3.value.alias2': 'testuser@user.local', + 'openid.alias3.type.alias3': 'http://openid.net/schema/namePerson/firs' + 't', + 'openid.alias3.value.alias3': 'John', + 'openid.alias3.type.alias4': 'http://openid.net/schema/namePerson/las' + 't', + 'openid.alias3.value.alias4': 'Smith', + 'openid.alias3.type.alias5': 'http://axschema.org/namePerson/first', + 'openid.alias3.value.alias5': 'John', + 'openid.alias3.type.alias6': 'http://axschema.org/namePerson/last', + 'openid.alias3.value.alias6': 'Smith', + 'openid.alias3.type.alias7': 'http://axschema.org/namePerson', + 'openid.alias3.value.alias7': 'John Smith', + 'openid.alias3.type.alias8': 'http://openid.net/schema/namePerson', + 'openid.alias3.value.alias8': 'John Smith', + 'openid.ns.sreg': 'http://openid.net/extensions/sreg/1.1', + 'openid.sreg.fullname': 'John Smith', + }) + + def setUp(self): + """Setup the test""" + super(NGPVANActionIDOpenIDTest, self).setUp() + + # Mock out the NGP VAN endpoints + HTTPretty.register_uri( + HTTPretty.POST, + 'https://accounts.ngpvan.com/Home/Xrds', + status=200, + body=self.discovery_body + ) + HTTPretty.register_uri( + HTTPretty.GET, + 'https://accounts.ngpvan.com/user/abcd123', + status=200, + body=self.discovery_body + ) + HTTPretty.register_uri( + HTTPretty.GET, + 'https://accounts.ngpvan.com/OpenId/Provider', + status=200, + body=self.discovery_body + ) + + def test_login(self): + """Test the login flow using python-social-auth's built in test""" + self.do_login() + + def test_partial_pipeline(self): + """Test the partial flow using python-social-auth's built in test""" + self.do_partial_pipeline() + + def test_get_ax_attributes(self): + """Test that the AX attributes that NGP VAN responds with are present""" + records = self.backend.get_ax_attributes() + + self.assertEqual(records, [ + ('http://openid.net/schema/contact/internet/email', 'email'), + ('http://openid.net/schema/contact/phone/business', 'phone'), + ('http://openid.net/schema/namePerson/first', 'first_name'), + ('http://openid.net/schema/namePerson/last', 'last_name'), + ('http://openid.net/schema/namePerson', 'fullname'), + ]) + + def test_setup_request(self): + """Test the setup_request functionality in the NGP VAN backend""" + # We can grab the requested attributes by grabbing the HTML of the + # OpenID auth form and pulling out the hidden fields + _, inputs = self.get_form_data(self.backend.auth_html()) + + # Confirm that the only required attribute is email + self.assertEqual(inputs['openid.ax.required'], 'ngpvanemail') + + # Confirm that the 3 optional attributes are requested "if available" + self.assertIn('ngpvanphone', inputs['openid.ax.if_available']) + self.assertIn('ngpvanfirstname', inputs['openid.ax.if_available']) + self.assertIn('ngpvanlastname', inputs['openid.ax.if_available']) + + # Verify the individual attribute properties + self.assertEqual( + inputs['openid.ax.type.ngpvanemail'], + 'http://openid.net/schema/contact/internet/email' + ) + self.assertEqual( + inputs['openid.ax.type.ngpvanfirstname'], + 'http://openid.net/schema/namePerson/first' + ) + self.assertEqual( + inputs['openid.ax.type.ngpvanlastname'], + 'http://openid.net/schema/namePerson/last' + ) + self.assertEqual( + inputs['openid.ax.type.ngpvanphone'], + 'http://openid.net/schema/contact/phone/business' + ) + + def test_user_data(self): + """Ensure that the correct user data is being passed to create_user""" + self.strategy.set_settings({ + 'USER_FIELDS': [ + 'email', + 'first_name', + 'last_name', + 'username', + 'phone', + 'fullname' + ] + }) + user = self.do_start() + self.assertEqual(user.username, u'testuser@user.local') + self.assertEqual(user.email, u'testuser@user.local') + self.assertEqual(user.extra_user_fields['phone'], u'+12015555555') + self.assertEqual(user.extra_user_fields['first_name'], u'John') + self.assertEqual(user.extra_user_fields['last_name'], u'Smith') + self.assertEqual(user.extra_user_fields['fullname'], u'John Smith') + + def test_extra_data_phone(self): + """Confirm that you can get a phone number via the relevant setting""" + self.strategy.set_settings({ + 'SOCIAL_AUTH_ACTIONID_OPENID_AX_EXTRA_DATA': [ + ('http://openid.net/schema/contact/phone/business', 'phone') + ] + }) + user = self.do_start() + self.assertEqual(user.social_user.extra_data['phone'], u'+12015555555') + + def test_association_uid(self): + """Test that the correct association uid is stored in the database""" + user = self.do_start() + self.assertEqual( + user.social_user.uid, + 'https://accounts.ngpvan.com/user/abcd123' + ) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_orbi.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_orbi.py new file mode 100644 index 0000000..9105b8b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_orbi.py @@ -0,0 +1,30 @@ +import json + +from .oauth import OAuth2Test + + +class OrbiOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_patreon.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_patreon.py new file mode 100644 index 0000000..ddaea4e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_patreon.py @@ -0,0 +1,65 @@ +import json + +from .oauth import OAuth2Test + + +class PatreonOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.patreon.PatreonOAuth2' + user_data_url = 'https://api.patreon.com/oauth2/api/current_user' + expected_username = 'JohnInterwebs' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + }) + user_data_body = json.dumps({ + "data": { + "relationships": { + "pledges": { + "data": [{ + "type": "pledge", "id": "123456" + }] + } + }, + "attributes": { + "last_name": "Interwebs", + "is_suspended": False, + "has_password": True, + "full_name": "John Interwebs", + "is_nuked": False, + "first_name": "John", + "social_connections": { + "spotify": None, + "discord": None, + "twitter": None, + "youtube": None, + "facebook": None, + "deviantart": None, + "twitch": None + }, + "twitter": None, + "is_email_verified": True, + "facebook_id": None, + "email": "john@example.com", + "facebook": None, + "thumb_url": "https://c8.patreon.com/100/123456", + "vanity": None, + "about": None, + "is_deleted": False, + "created": "2017-05-05T05:16:34+00:00", + "url": "https://www.patreon.com/user?u=123456", + "gender": 0, + "youtube": None, + "discord_id": None, + "image_url": "https://c8.patreon.com/400/123456", + "twitch": None + }, + "type": "user", + "id": "123456" + } + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_pinterest.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_pinterest.py new file mode 100644 index 0000000..681161c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_pinterest.py @@ -0,0 +1,49 @@ +import json + +from .oauth import OAuth2Test + + +class PinterestOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.pinterest.PinterestOAuth2' + user_data_url = 'https://api.pinterest.com/v1/me/' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + user_data_body = json.dumps({ + 'id': '4788400174839062', + 'first_name': 'Foo', + 'last_name': 'Bar', + 'username': 'foobar', + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() + + +class PinterestOAuth2BrokenServerResponseTest(OAuth2Test): + backend_path = 'social_core.backends.pinterest.PinterestOAuth2' + user_data_url = 'https://api.pinterest.com/v1/me/' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + user_data_body = json.dumps({ + 'data': { + 'id': '4788400174839062', + 'first_name': 'Foo', + 'last_name': 'Bar', + 'url': 'https://www.pinterest.com/foobar/', + } + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_podio.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_podio.py new file mode 100644 index 0000000..c5b956c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_podio.py @@ -0,0 +1,56 @@ +import json + +from .oauth import OAuth2Test + + +class PodioOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_qiita.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_qiita.py new file mode 100644 index 0000000..b3ec393 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_qiita.py @@ -0,0 +1,25 @@ +import json + +from .oauth import OAuth2Test + + +class QiitaOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_quizlet.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_quizlet.py new file mode 100644 index 0000000..d9d2c7b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_quizlet.py @@ -0,0 +1,22 @@ +import json + +from .oauth import OAuth2Test + + +class QuizletOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.quizlet.QuizletOAuth2' + expected_username = 'foo_bar' + + access_token_body = json.dumps({ + "access_token": "EE1IDxytP04tJ767GbjH7ED9PpGmYvL", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read", + 'user_id': 'foo_bar' + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_readability.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_readability.py new file mode 100644 index 0000000..9045a92 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_readability.py @@ -0,0 +1,41 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class ReadabilityOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_reddit.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_reddit.py new file mode 100644 index 0000000..a499a28 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_reddit.py @@ -0,0 +1,59 @@ +import json + +from .oauth import OAuth2Test + + +class RedditOAuth2Test(OAuth2Test): + backend_path = 'social_core.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') diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_saml.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_saml.py new file mode 100644 index 0000000..775decf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_saml.py @@ -0,0 +1,131 @@ +import re +import json +import sys +import unittest2 +import requests +import os +from os import path + + +try: + from unittest.mock import patch +except ImportError: + from mock import patch +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlparse, urlunparse, urlencode, parse_qs + +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 .base import BaseBackendTest +from ...exceptions import AuthMissingParameter + + +DATA_DIR = path.join(path.dirname(__file__), 'data') + + +@unittest2.skipIf('TRAVIS' in os.environ, + 'Travis-ci segfaults probably due to a bad ' + 'dependencies build') +@unittest2.skipIf('__pypy__' in sys.builtin_module_names, + 'dm.xmlsec not compatible with pypy') +class SAMLTest(BaseBackendTest): + backend_path = 'social_core.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): + 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.decode()[0], '<') + + def test_login(self): + """Test that we can authenticate with a SAML IdP (TestShib)""" + # pretend we've started with a URL like /login/saml/?idp=testshib: + self.strategy.set_request_data({'idp': 'testshib'}, self.backend) + self.do_login() + + def test_login_no_idp(self): + """Logging in without an idp param should raise AuthMissingParameter""" + with self.assertRaises(AuthMissingParameter): + self.do_start() + + 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).items()) + xml = OneLogin_Saml2_Utils.decode_base64_and_inflate( + query['SAMLRequest'] + ) + # Modify the XML: + xml = xml.decode() + 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) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_sketchfab.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_sketchfab.py new file mode 100644 index 0000000..55c8eee --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_sketchfab.py @@ -0,0 +1,26 @@ +import json + +from .oauth import OAuth2Test + + +class SketchfabOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.sketchfab.SketchfabOAuth2' + user_data_url = 'https://sketchfab.com/v2/users/me' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + user_data_body = json.dumps({ + 'uid': '42', + 'email': 'foo@bar.com', + 'displayName': 'foo bar', + 'username': 'foobar', + 'apiToken': 'XXX' + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_skyrock.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_skyrock.py new file mode 100644 index 0000000..ba937f0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_skyrock.py @@ -0,0 +1,49 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class SkyrockOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_slack.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_slack.py new file mode 100644 index 0000000..c75c0ce --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_slack.py @@ -0,0 +1,31 @@ +import json + +from .oauth import OAuth2Test + + +class SlackOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.slack.SlackOAuth2' + user_data_url = 'https://slack.com/api/users.identity' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + user_data_body = json.dumps({ + 'ok': True, + 'user': { + 'email': 'foobar@example.com', + 'name': 'Foo Bar', + 'id': u'123456' + }, + 'team': { + 'id': u'456789' + }, + 'scope': u'identity.basic,identity.email' + }) + expected_username = 'foobar' + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_soundcloud.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_soundcloud.py new file mode 100644 index 0000000..68f4295 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_soundcloud.py @@ -0,0 +1,54 @@ +import json + +from .oauth import OAuth2Test + + +class SoundcloudOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_spotify.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_spotify.py new file mode 100644 index 0000000..e109a9a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_spotify.py @@ -0,0 +1,34 @@ +import json + +from .oauth import OAuth2Test + + +class SpotifyOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.spotify.SpotifyOAuth2' + user_data_url = 'https://api.spotify.com/v1/me' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer' + }) + user_data_body = json.dumps({ + 'display_name': None, + 'external_urls': { + 'spotify': 'https://open.spotify.com/user/foobar' + }, + 'followers': { + 'href': None, + 'total': 0 + }, + 'href': 'https://api.spotify.com/v1/users/foobar', + 'id': 'foobar', + 'images': [], + 'type': 'user', + 'uri': 'spotify:user:foobar' + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stackoverflow.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stackoverflow.py new file mode 100644 index 0000000..7400f49 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stackoverflow.py @@ -0,0 +1,53 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth2Test + + +class StackoverflowOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_steam.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_steam.py new file mode 100644 index 0000000..0878e65 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_steam.py @@ -0,0 +1,136 @@ +import json +import datetime + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from ...exceptions import AuthFailed + +from .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_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stocktwits.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stocktwits.py new file mode 100644 index 0000000..e7a5b04 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stocktwits.py @@ -0,0 +1,54 @@ +import json + +from .oauth import OAuth2Test + + +class StocktwitsOAuth2Test(OAuth2Test): + backend_path = 'social_core.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' + } + }) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_strava.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_strava.py new file mode 100644 index 0000000..de0b0ab --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_strava.py @@ -0,0 +1,62 @@ +import json + +from .oauth import OAuth2Test + + +class StravaOAuthTest(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stripe.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stripe.py new file mode 100644 index 0000000..4297f96 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_stripe.py @@ -0,0 +1,23 @@ +import json + +from .oauth import OAuth2Test + + +class StripeOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_taobao.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_taobao.py new file mode 100644 index 0000000..ba0f677 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_taobao.py @@ -0,0 +1,29 @@ +import json + +from .oauth import OAuth2Test + + +class TaobaoOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_thisismyjam.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_thisismyjam.py new file mode 100644 index 0000000..4f38565 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_thisismyjam.py @@ -0,0 +1,33 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class ThisIsMyJameOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tripit.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tripit.py new file mode 100644 index 0000000..ad7e39f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tripit.py @@ -0,0 +1,102 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class TripitOAuth1Test(OAuth1Test): + backend_path = 'social_core.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' \ + '' \ + '
    ' \ + '
    ' diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tumblr.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tumblr.py new file mode 100644 index 0000000..73cbe7d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_tumblr.py @@ -0,0 +1,60 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class TumblrOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitch.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitch.py new file mode 100644 index 0000000..b6f5e97 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitch.py @@ -0,0 +1,33 @@ +import json +from .oauth import OAuth2Test + + +class TwitchOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitter.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitter.py new file mode 100644 index 0000000..b3d2e22 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_twitter.py @@ -0,0 +1,261 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class TwitterOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() + + +class TwitterOAuth1IncludeEmailTest(OAuth1Test): + backend_path = 'social_core.backends.twitter.TwitterOAuth' + user_data_url = 'https://api.twitter.com/1.1/account/' \ + 'verify_credentials.json?include_email=true' + 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, + 'email': 'foo@bar.bas' + }) + + def test_login(self): + user = self.do_login() + self.assertEquals(user.email, 'foo@bar.bas') + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_uber.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_uber.py new file mode 100644 index 0000000..7846590 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_uber.py @@ -0,0 +1,37 @@ +import json + +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from ...exceptions import AuthForbidden +from .oauth import OAuth1Test, OAuth2Test + + +class UberOAuth2Test(OAuth2Test): + user_data_url = 'https://api.uber.com/v1/me' + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_udata.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_udata.py new file mode 100644 index 0000000..3488b6a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_udata.py @@ -0,0 +1,29 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth2Test + + +class DatagouvfrOAuth2Test(OAuth2Test): + backend_path = 'social_core.backends.udata.DatagouvfrOAuth2' + user_data_url = 'https://www.data.gouv.fr/api/1/me/' + expected_username = 'foobar' + access_token_body = json.dumps({ + 'access_token': 'foobar', + 'token_type': 'bearer', + 'first_name': 'foobar', + 'email': 'foobar@example.com' + }) + request_token_body = urlencode({ + 'oauth_token_secret': 'foobar-secret', + 'oauth_token': 'foobar', + 'oauth_callback_confirmed': 'true' + }) + user_data_body = json.dumps({}) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_upwork.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_upwork.py new file mode 100644 index 0000000..194216e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_upwork.py @@ -0,0 +1,54 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class UpworkOAuth1Test(OAuth1Test): + backend_path = 'social_core.backends.upwork.UpworkOAuth' + user_data_url = 'https://www.upwork.com/api/auth/v1/info.json' + 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({ + 'info': { + 'portrait_32_img': '', + 'capacity': { + 'buyer': 'no', + 'affiliate_manager': 'no', + 'provider': 'yes' + }, + 'company_url': '', + 'has_agency': '1', + 'portrait_50_img': '', + 'portrait_100_img': '', + 'location': { + 'city': 'New York', + 'state': '', + 'country': 'USA' + }, + 'ref': '9755314', + 'profile_url': 'https://www.upwork.com/users/~10101010' + }, + 'auth_user': { + 'timezone': 'USA/New York', + 'first_name': 'Foo', + 'last_name': 'Bar', + 'timezone_offset': '10000' + }, + 'server_time': '1111111111' + }) + + def test_login(self): + self.do_login() + + def test_partial_pipeline(self): + self.do_partial_pipeline() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_username.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_username.py new file mode 100644 index 0000000..bc79e5f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_username.py @@ -0,0 +1,19 @@ +from .legacy import BaseLegacyTest + + +class UsernameTest(BaseLegacyTest): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_utils.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_utils.py new file mode 100644 index 0000000..9eeed48 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_utils.py @@ -0,0 +1,48 @@ +import unittest2 as unittest + +from ..models import TestStorage +from ..strategy import TestStrategy +from ...backends.utils import load_backends, get_backend +from ...backends.github import GithubOAuth2 +from ...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_core.backends.github.GithubOAuth2', + 'social_core.backends.facebook.FacebookOAuth2', + 'social_core.backends.flickr.FlickrOAuth' + ), force_load=True) + keys = list(loaded_backends.keys()) + self.assertEqual(keys, ['github', 'facebook', 'flickr']) + + 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_core.backends.github.GithubOAuth2', + 'social_core.backends.facebook.FacebookOAuth2', + 'social_core.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_core.backends.github.GithubOAuth2', + 'social_core.backends.facebook.FacebookOAuth2', + 'social_core.backends.flickr.FlickrOAuth'), + 'foobar') diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_vk.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_vk.py new file mode 100644 index 0000000..9748170 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_vk.py @@ -0,0 +1,32 @@ +# coding: utf-8 +from __future__ import unicode_literals + +import json + +from .oauth import OAuth2Test + + +class VKOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_wunderlist.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_wunderlist.py new file mode 100644 index 0000000..58974d5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_wunderlist.py @@ -0,0 +1,26 @@ +import json + +from .oauth import OAuth2Test + + +class WunderlistOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_xing.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_xing.py new file mode 100644 index 0000000..6ed4711 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_xing.py @@ -0,0 +1,170 @@ +import json + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class XingOAuth1Test(OAuth1Test): + backend_path = 'social_core.backends.xing.XingOAuth' + user_data_url = 'https://api.xing.com/v1/users/me.json' + expected_username = 'FooBar' + access_token_body = urlencode({ + 'access_token': 'foobar', + 'token_type': 'bearer', + 'user_id': '123456_abcdef', + 'oauth_token_secret': 'foobar-secret', + 'oauth_token': 'foobar', + }) + 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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yahoo.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yahoo.py new file mode 100644 index 0000000..74fd57f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yahoo.py @@ -0,0 +1,83 @@ +import json +import requests +from httpretty import HTTPretty + +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class YahooOAuth1Test(OAuth1Test): + backend_path = 'social_core.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') diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yammer.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yammer.py new file mode 100644 index 0000000..88214a8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yammer.py @@ -0,0 +1,115 @@ +import json + +from .oauth import OAuth2Test + + +class YammerOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yandex.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yandex.py new file mode 100644 index 0000000..b7f4343 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_yandex.py @@ -0,0 +1,27 @@ +import json + +from .oauth import OAuth2Test + + +class YandexOAuth2Test(OAuth2Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_zotero.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_zotero.py new file mode 100644 index 0000000..ff052e1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_zotero.py @@ -0,0 +1,25 @@ +from six.moves.urllib_parse import urlencode + +from .oauth import OAuth1Test + + +class ZoteroOAuth1Test(OAuth1Test): + backend_path = 'social_core.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() diff --git a/flex/lib/python3.6/site-packages/social_core/tests/models.py b/flex/lib/python3.6/site-packages/social_core/tests/models.py new file mode 100644 index 0000000..6a28b08 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/models.py @@ -0,0 +1,230 @@ +import base64 + +from ..storage import UserMixin, NonceMixin, AssociationMixin, \ + CodeMixin, PartialMixin, 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, **extra_user_fields): + self.id = User.next_id() + self.username = username + self.email = email + self.password = None + self.slug = None + self.social = [] + self.extra_data = {} + self.extra_user_fields = extra_user_fields + 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, **extra_user_fields): + return User(username=username, email=email, **extra_user_fields) + + @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 TestPartial(PartialMixin, BaseModel): + NEXT_ID = 1 + cache = {} + + def save(self): + TestPartial.cache[self.token] = self + + @classmethod + def load(cls, token): + return cls.cache.get(token) + + @classmethod + def destroy(cls, token): + cls.cache.pop(token) + + +class TestStorage(BaseStorage): + user = TestUserSocialAuth + nonce = TestNonce + association = TestAssociation + code = TestCode + partial = TestPartial + + @classmethod + def is_integrity_error(cls, exception): + pass diff --git a/flex/lib/python3.6/site-packages/social_core/tests/pipeline.py b/flex/lib/python3.6/site-packages/social_core/tests/pipeline.py new file mode 100644 index 0000000..e61b0bd --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/pipeline.py @@ -0,0 +1,45 @@ +from ..pipeline.partial import partial + + +@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')) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/requirements-base.txt b/flex/lib/python3.6/site-packages/social_core/tests/requirements-base.txt new file mode 100644 index 0000000..50f78e1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/requirements-base.txt @@ -0,0 +1,4 @@ +httpretty==0.6.5 +coverage>=3.6 +nose>=1.2.1 +rednose>=0.4.1 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/requirements-pypy.txt b/flex/lib/python3.6/site-packages/social_core/tests/requirements-pypy.txt new file mode 100644 index 0000000..5ff9aa8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/requirements-pypy.txt @@ -0,0 +1,3 @@ +-r requirements-base.txt +unittest2==0.5.1 +mock==1.0.1 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/requirements-python2.txt b/flex/lib/python3.6/site-packages/social_core/tests/requirements-python2.txt new file mode 100644 index 0000000..5ff9aa8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/requirements-python2.txt @@ -0,0 +1,3 @@ +-r requirements-base.txt +unittest2==0.5.1 +mock==1.0.1 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/requirements-python3.txt b/flex/lib/python3.6/site-packages/social_core/tests/requirements-python3.txt new file mode 100644 index 0000000..b41e8a2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/requirements-python3.txt @@ -0,0 +1,2 @@ +-r requirements-base.txt +unittest2py3k==0.5.1 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/requirements.txt b/flex/lib/python3.6/site-packages/social_core/tests/requirements.txt new file mode 100644 index 0000000..a66bc61 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/requirements.txt @@ -0,0 +1 @@ +-r requirements-python2.txt diff --git a/flex/lib/python3.6/site-packages/social_core/tests/strategy.py b/flex/lib/python3.6/site-packages/social_core/tests/strategy.py new file mode 100644 index 0000000..b06fc38 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/strategy.py @@ -0,0 +1,124 @@ +from ..strategy 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, backend=None): + return self.setting( + 'PIPELINE', + ( + 'social_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.auth_allowed', + 'social_core.pipeline.social_auth.social_user', + 'social_core.pipeline.user.get_username', + 'social_core.pipeline.social_auth.associate_by_email', + 'social_core.pipeline.user.create_user', + 'social_core.pipeline.social_auth.associate_user', + 'social_core.pipeline.social_auth.load_extra_data', + 'social_core.pipeline.user.user_details' + ), + backend + ) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/test_exceptions.py b/flex/lib/python3.6/site-packages/social_core/tests/test_exceptions.py new file mode 100644 index 0000000..d38176e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/test_exceptions.py @@ -0,0 +1,115 @@ +import unittest2 as unittest + +from ..exceptions import SocialAuthBaseException, WrongBackend, \ + AuthFailed, AuthTokenError, \ + AuthMissingParameter, AuthStateMissing, \ + NotAllowedToDisconnect, AuthException, \ + AuthCanceled, AuthUnknownError, \ + AuthStateForbidden, AuthAlreadyAssociated, \ + AuthTokenRevoked, AuthForbidden, \ + AuthUnreachableProvider, InvalidEmail, \ + MissingBackend + + +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 AuthCanceledWithExtraMessageTest(BaseExceptionTestCase): + exception = AuthCanceled('foobar', 'error_message') + expected_message = 'Authentication process canceled: error_message' + + +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' + + +class AuthForbiddenTest(BaseExceptionTestCase): + exception = AuthForbidden('foobar') + expected_message = 'Your credentials aren\'t allowed' + + +class AuthUnreachableProviderTest(BaseExceptionTestCase): + exception = AuthUnreachableProvider('foobar') + expected_message = 'The authentication provider could not be reached' + + +class InvalidEmailTest(BaseExceptionTestCase): + exception = InvalidEmail('foobar') + expected_message = 'Email couldn\'t be validated' + + +class MissingBackendTest(BaseExceptionTestCase): + exception = MissingBackend('backend') + expected_message = 'Missing backend "backend" entry' diff --git a/flex/lib/python3.6/site-packages/social_core/tests/test_partial.py b/flex/lib/python3.6/site-packages/social_core/tests/test_partial.py new file mode 100644 index 0000000..350a35f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/test_partial.py @@ -0,0 +1,101 @@ +import unittest2 as unittest +try: + from mock import Mock, patch +except ImportError: + from unittest.mock import Mock, patch + +from ..pipeline.partial import partial, partial_step + +class PartialDecoratorTestCase(unittest.TestCase): + + def setUp(self): + super(PartialDecoratorTestCase, self).setUp() + self.mock_current_partial_token = Mock() + self.mock_current_partial = Mock(token=self.mock_current_partial_token) + + self.mock_strategy = Mock() + self.mock_backend = Mock() + self.mock_pipeline_index = Mock() + self.mock_partial_store = Mock() + self.mock_strategy.storage.partial.store = self.mock_partial_store + + self.mock_sesstion_set = Mock() + self.mock_strategy.session_set = self.mock_sesstion_set + + def test_save_to_session(self): + # GIVEN + expected_response = Mock() + + @partial_step(save_to_session=True) + def decorated_func(*args, **kwargs): + return expected_response + + # WHEN + with patch('social_core.pipeline.partial.partial_prepare', return_value=self.mock_current_partial): + response = decorated_func(self.mock_strategy, self.mock_backend, self.mock_pipeline_index) + + # THEN + self.assertEqual(expected_response, response) + + self.assertEqual(1, self.mock_partial_store.call_count) + self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) + + self.assertEqual(1, self.mock_sesstion_set.call_count) + self.assertEqual(('partial_pipeline_token', self.mock_current_partial_token), self.mock_sesstion_set.call_args[0]) + + def test_not_to_save_to_session(self): + # GIVEN + expected_response = Mock() + + @partial_step(save_to_session=False) + def decorated_func(*args, **kwargs): + return expected_response + + # WHEN + with patch('social_core.pipeline.partial.partial_prepare', return_value=self.mock_current_partial): + response = decorated_func(self.mock_strategy, self.mock_backend, self.mock_pipeline_index) + + # THEN + self.assertEqual(expected_response, response) + + self.assertEqual(1, self.mock_partial_store.call_count) + self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) + + self.assertEqual(0, self.mock_sesstion_set.call_count) + + def test_save_to_session_by_backward_compatible_decorator(self): + # GIVEN + expected_response = Mock() + + @partial + def decorated_func(*args, **kwargs): + return expected_response + + # WHEN + with patch('social_core.pipeline.partial.partial_prepare', return_value=self.mock_current_partial): + response = decorated_func(self.mock_strategy, self.mock_backend, self.mock_pipeline_index) + + # THEN + self.assertEqual(expected_response, response) + + self.assertEqual(1, self.mock_partial_store.call_count) + self.assertEqual((self.mock_current_partial,), self.mock_partial_store.call_args[0]) + + self.assertEqual(1, self.mock_sesstion_set.call_count) + self.assertEqual(('partial_pipeline_token', self.mock_current_partial_token), self.mock_sesstion_set.call_args[0]) + + def test_not_to_save_to_session_when_the_response_is_a_dict(self): + # GIVEN + expected_response = {'test_key': 'test_value'} + + @partial_step(save_to_session=True) + def decorated_func(*args, **kwargs): + return expected_response + + # WHEN + response = decorated_func(self.mock_strategy, self.mock_backend, self.mock_pipeline_index) + + # THEN + self.assertEqual(expected_response, response) + self.assertEqual(0, self.mock_partial_store.call_count) + self.assertEqual(0, self.mock_sesstion_set.call_count) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/test_pipeline.py b/flex/lib/python3.6/site-packages/social_core/tests/test_pipeline.py new file mode 100644 index 0000000..5921ffe --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/test_pipeline.py @@ -0,0 +1,233 @@ +import json + +from ..exceptions import AuthException + +from .models import TestUserSocialAuth, TestStorage, User +from .strategy import TestStrategy +from .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_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.associate_by_email', + 'social_core.tests.pipeline.set_user_from_kwargs' + ) + }) + + self.do_login(after_complete_checks=False) + + # Handle the partial pipeline + self.strategy.session_set('attribute', 'testing') + token = self.strategy.session_pop('partial_pipeline_token') + partial = self.strategy.partial_load(token) + self.backend.continue_pipeline(partial) + + 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_core.pipeline.social_auth.social_details', + 'social_core.pipeline.social_auth.social_uid', + 'social_core.pipeline.social_auth.associate_by_email', + 'social_core.tests.pipeline.set_user_from_args' + ) + }) + + self.do_login(after_complete_checks=False) + + # Handle the partial pipeline + self.strategy.session_set('attribute', 'testing') + token = self.strategy.session_pop('partial_pipeline_token') + partial = self.strategy.partial_load(token) + self.backend.continue_pipeline(partial) diff --git a/flex/lib/python3.6/site-packages/social_core/tests/test_storage.py b/flex/lib/python3.6/site-packages/social_core/tests/test_storage.py new file mode 100644 index 0000000..680d0b5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/test_storage.py @@ -0,0 +1,202 @@ +import six +import random +import unittest2 as unittest + +from ..strategy import BaseStrategy +from ..storage import UserMixin, NonceMixin, AssociationMixin, \ + CodeMixin, BaseStorage + +from .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 diff --git a/flex/lib/python3.6/site-packages/social_core/tests/test_utils.py b/flex/lib/python3.6/site-packages/social_core/tests/test_utils.py new file mode 100644 index 0000000..874a2cf --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/test_utils.py @@ -0,0 +1,194 @@ +import sys +import unittest2 as unittest + +try: + from unittest.mock import Mock +except ImportError: + from mock import Mock + +from ..utils import sanitize_redirect, user_is_authenticated, \ + user_is_active, slugify, build_absolute_uri, \ + partial_pipeline_data +from .models import TestPartial + + +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/') + + def test_multiple_hosts(self): + allowed_hosts = ['myapp1.com', 'myapp2.com'] + for host in allowed_hosts: + url = 'http://{}/path/'.format(host) + self.assertEqual(sanitize_redirect(allowed_hosts, url), url) + + def test_multiple_hosts_wrong_host(self): + self.assertEqual(sanitize_redirect( + ['myapp1.com', 'myapp2.com'], 'http://notmyapp.com/path/'), None) + + +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_returns_partial_when_uid_and_email_do_match(self): + email = 'foo@example.com' + backend = self._backend({'uid': email}) + backend.strategy.request_data.return_value = { + backend.ID_KEY: email + } + key, val = ('foo', 'bar') + partial = partial_pipeline_data(backend, None, + *(), **dict([(key, val)])) + self.assertTrue(key in partial.kwargs) + self.assertEqual(partial.kwargs[key], val) + self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) + + def test_clean_pipeline_when_uid_does_not_match(self): + backend = self._backend({'uid': 'foo@example.com'}) + backend.strategy.request_data.return_value = { + backend.ID_KEY: 'bar@example.com' + } + key, val = ('foo', 'bar') + partial = partial_pipeline_data(backend, None, + *(), **dict([(key, val)])) + self.assertIsNone(partial) + self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 1) + + def test_kwargs_included_in_result(self): + backend = self._backend() + key, val = ('foo', 'bar') + partial = partial_pipeline_data(backend, None, + *(), **dict([(key, val)])) + self.assertTrue(key in partial.kwargs) + self.assertEqual(partial.kwargs[key], val) + self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) + + def test_update_user(self): + user = object() + backend = self._backend(session_kwargs={'user': None}) + partial = partial_pipeline_data(backend, user) + self.assertTrue('user' in partial.kwargs) + self.assertEqual(partial.kwargs['user'], user) + self.assertEqual(backend.strategy.clean_partial_pipeline.call_count, 0) + + def _backend(self, session_kwargs=None): + backend = Mock() + backend.ID_KEY = 'email' + backend.name = 'mock-backend' + + strategy = Mock() + strategy.request = None + strategy.request_data.return_value = {} + strategy.session_get.return_value = object() + strategy.partial_load.return_value = TestPartial.prepare(backend.name, 0, { + 'args': [], + 'kwargs': session_kwargs or {} + }) + + backend.strategy = strategy + return backend diff --git a/flex/lib/python3.6/site-packages/social_core/tests/testkey.pem b/flex/lib/python3.6/site-packages/social_core/tests/testkey.pem new file mode 100644 index 0000000..bfc31dc --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/tests/testkey.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAtavb41y83n7xY/sIS23Rv+TEwI85xmML57BT9dyb9JeAFPYG +RIaN/kWYW8pR0CZDu2nLwU2lUA0d0wKTOMaHQFs6L/rPN1zOx13HYO+TKybgU+e8 +TaOncKmJeyp3MP/OM6KBYp7Cx6eLlyvw3HJh6nT+Y5vj3rwS4PYjX1hUcr51IOcq +Ls5Sx/UQ4OUVHJLlcUxP6k2n44nGoDYK8zRc5dEBxbTL4yKzCR9unWAlDHVQYXOx +vzxxFtUID7dy38e0MZ+3RnmIY2wl7/PIRCsbIKrsAiONnLkJQitMVkU8jTEPrXNC +Lj5Hzgf0kDD+4ukQdUPCXlv9y668anaxabXFIwIDAQABAoIBAQCUAfT+UiKpnoBp +dS53zXvSO740Jzvpre4YD2cH6njAl+FIsnu+vmTWoLqVJcrrqxEJkhLnZKvnARio +PfPVkKDGRMyuJEdIEnuAn519ExIyWNTwZt7Z3hffSLByQNGkgPPsy1tgDnogERc/ +zRPJdgLh8fkDDBFk+JLk5oNA/YSnibV5MtriNPoDONxaHsguJ7JvikgLdQs5nyuQ +loMx4JhqP+fXsGIU0DFwaLPvEEntKodaRZXvAMLOuGi3O83OGkhevVvEVFSzCeS1 +fYnKwySQgNctztoBpf09zATeL9xt8tipVnyBKOIBJyf/BQV7fESXp0R8d+rW1cs0 +21qakP0pAoGBAOUFPT/pLP3w676dGvggK0kDy8gjgDHl2/x3fhbeua/6yfetVpZE +CuFez/9E24kMS4/GCi3WgPA6DHDYBH5t1S4e0/cgGJ8sjuX8ELLgy4q+WmvgTwDS +FbXKqY929Hlu65hUvl2xVP2MYUGLHiUE8yKk4RKdZO+3GkWXHhq9A+mvAoGBAMsS +rBYyAt9XeI8W8w2dWgZnBp+7WQeMUAZfCpq65urycD9on9R3gJ5zbOfq6CG2FXML +kslY1R4CVYge+HoWITFECvwPdbDYx/QIVvuhRH8omrG5iYY+ljGuF6wlzETgW117 +vYTSf5xXJ4LiuFD6TY4rxZ0UdjKba9bJfsQb45zNAoGBAMm/zM4xVn46LyLS+YAd +rqP6oRklFPhf+mQ0y7HP8Royvprea0milUcmI+tHOHJJj4MPPcZVkW6OZIk/8u2B +SewAIlAwSSBnu1akr/00hHor6DHh/xbE+3UTHD4S15jl+stN1Alrf2iAsuhvalXI +P3uEbfXdC58U2SL21gFOA1VnAoGBAIv93EoUwewrhb3GyVlaXyRIw8U7xv0wGj66 +KDpQnD3dUco0mvJCS5Vv5uTeCJasyo6brN5G8ewVGdeT2iF3vfwTdysakTyPxRAS +67veCbVLTZE1YXv9C1dGN6WCDRZyQCnq0tSMtFIXtvJAz2VrmClpPXqzD2SFxkq0 +b0JMI5YFAoGAL+1E+JUfPHf69K8XDXY+zrZL/KRPuXHcGk4qm7JXCDxr90zMVJYj +RWTQZHgGCEZGGlChJD8JwGiWEtsn0zlhn4H4dPWzeS7D1fsJK30lhYpDp3TuOY0x +lQAatQXzunglAxXwlOOazX4uoVtiFLjY3U4G6gCwKYEtjP1r0GULlk0= +-----END RSA PRIVATE KEY----- diff --git a/flex/lib/python3.6/site-packages/social_core/utils.py b/flex/lib/python3.6/site-packages/social_core/utils.py new file mode 100644 index 0000000..872362b --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/utils.py @@ -0,0 +1,312 @@ +import re +import sys +import time +import unicodedata +import collections +import functools +import logging + +import six +import requests +import social_core + +from six.moves.urllib_parse import urlparse, urlunparse, urlencode, \ + parse_qs as battery_parse_qs + +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.poolmanager import PoolManager + +from .exceptions import AuthCanceled, AuthForbidden, AuthUnreachableProvider + + +SETTING_PREFIX = 'SOCIAL_AUTH' + +social_logger = logging.getLogger('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 'social-auth-' + social_core.__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(hosts, redirect_to): + """ + Given a list of hostnames 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 host that's not in the list + netloc = urlparse(redirect_to)[1] or hosts[0] + except (TypeError, AttributeError): + pass + else: + if netloc in hosts: + 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', six.text_type(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, partial_token=None, + *args, **kwargs): + request_data = backend.strategy.request_data() + + partial_argument_name = backend.setting('PARTIAL_PIPELINE_TOKEN_NAME', + 'partial_token') + partial_token = partial_token or \ + request_data.get(partial_argument_name) or \ + backend.strategy.session_get('partial_pipeline_token', None) + + if partial_token: + partial = backend.strategy.partial_load(partial_token) + partial_matches_request = False + + if partial and partial.backend == backend.name: + partial_matches_request = True + + # Normally when resuming a pipeline, request_data will be empty. We + # only need to check for a uid match if new data was provided (i.e. + # if current request specifies the ID_KEY). + if backend.ID_KEY in request_data: + id_from_partial = partial.kwargs.get('uid') + id_from_request = request_data.get(backend.ID_KEY) + + if id_from_partial != id_from_request: + partial_matches_request = False + + if partial_matches_request: + if user: # don't update user if it's None + kwargs.setdefault('user', user) + kwargs.setdefault('request', request_data) + partial.extend_kwargs(kwargs) + return partial + else: + backend.strategy.clean_partial_pipeline(partial_token) + + +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], response=err.response) + elif err.response.status_code == 401: + raise AuthForbidden(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 + + +def get_strategy(strategy, storage, *args, **kwargs): + Strategy = module_member(strategy) + Storage = module_member(storage) + return Strategy(Storage, *args, **kwargs) + + +class cache(object): + """ + Cache decorator that caches the return value of a method for a + specified time. + + It maintains a cache per class, so subclasses have a different cache entry + for the same cached method. + + Does not work for methods with arguments. + """ + def __init__(self, ttl): + self.ttl = ttl + self.cache = {} + + def __call__(self, fn): + def wrapped(this): + now = time.time() + last_updated = None + cached_value = None + if this.__class__ in self.cache: + last_updated, cached_value = self.cache[this.__class__] + if not cached_value or now - last_updated > self.ttl: + try: + cached_value = fn(this) + self.cache[this.__class__] = (now, cached_value) + except: + # Use previously cached value when call fails, if available + if not cached_value: + raise + return cached_value + return wrapped diff --git a/flex/lib/python3.6/site-packages/social_django/__init__.py b/flex/lib/python3.6/site-packages/social_django/__init__.py new file mode 100644 index 0000000..5041050 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/__init__.py @@ -0,0 +1,23 @@ +__version__ = '2.1.0' + + +from social_core.backends.base import BaseAuth + +# django.contrib.auth.load_backend() will import and instanciate the +# authentication backend ignoring the possibility that it might +# require more arguments. Here we set a monkey patch to +# BaseAuth.__init__ to ignore the mandatory strategy argument and load +# it. + +def baseauth_init_workaround(original_init): + def fake_init(self, strategy=None, *args, **kwargs): + from .utils import load_strategy + original_init(self, strategy or load_strategy(), *args, **kwargs) + return fake_init + + +if not getattr(BaseAuth, '__init_patched', False): + BaseAuth.__init__ = baseauth_init_workaround(BaseAuth.__init__) + BaseAuth.__init_patched = True + +default_app_config = 'social_django.config.PythonSocialAuthConfig' diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fbddbb8127de3ce1c0482ee4bbc86b9f407eadd GIT binary patch literal 821 zcmZ`%y>8nu5GEFMLdp(oD3b%U zQy-ywU!{+rwNqZ9Q;)J!Wa$BSB#+0t@B6^BZnyp77Y|-r2>nLS%!d7Ya6N{=QGht+ zB*hC75RACR?FSTC!WM)(ys@?Pxv&Jeakf|x>hLDS26uxdZ(X5Yd*i(IPQAe+v_d^x zxgVJpAC|=|#3lnqz|BNJf%FjsZhb`&HZvbu_qfE+#h!$w%$NgiN?(ENHxO!YXo2r- zjJ}}~l{h~x?JbH(Nq&&h`i=oKf+p7F(aZ!8-BdD8b)i@x;&qQy&ay~StqhEwQ`uUi zQQ$t^S2k0zt{U?@v+jzZhg3oq8QxzT80Tr`wWEYvpnzf8+?BR)O z8wrSN!J;}Q)Cs&x96+Kv{$EE^z8`#ODFdTA+(XP+5zYkve7!l@G22t{LYYNL?0s1m zGnrl0q|N8}RHr9Bw>qG71v(}&qcnKAe;i6ByeSLkBI6o1S}5~9b~Br?C|FUb%6-yD t9l~XB#6`rGX+c3xx|>^*dlqVb?&U)AWh%zzvuH!gwmPoc?0}pN@)zZl+B5(F literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/admin.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/admin.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..21fcbf4722d1938b8420ccd3e51984a3e98dec43 GIT binary patch literal 2748 zcma)8OK;mo5Z>im)Wgp&5Un0o9^1*%>T0GP`r_vndMuGH&Z)vd%kW+qtj2EKn!(+bRF9YDq(;1q z5^Hsdw=`)Tuq+vF6tNJ=uwT3Y4~R#l86Abm5E$oT2bCdy3rwR@2E#CmUr_vsE?ZBF zn0NCi3DbkIIFe>3A4ajP9~1>DMFJw8e)0SlwcDVHiBBPN8l%3>8ot4rzR6m?#Vp@u zODYo7cj8(Zff@B(W`q69a((gE??!mf(LwNXd|C8;G$IwPK+CJZEf zoUkXINUJQf;BCUvVHH0E5kyb{rp>^_nSQQKHAapdekAmPF(c=?AXA-zZl;g+gbBPp z)A(Hm5+mmZGnmP&snIje%_$jJQ+;61=x^i~(xc2~&U0sKJ|HuCuCW?OWU8T5-RaI~ zAI`l(z}b4ozk2ZC(Zf#gw0rnC=pG(CzW+_|)x*PYA9Vc|>N*FH4oih}&`(T{@AeX2 z2$d>XOT!D+jI%IFBTODX=sad*4Oa01kUp7`AJ)(5lyYNAeD-5w}l6-tOfo#JXtrc>Ct@jUhkT+1+LnHrXw9 z?i}yzX1lj{w&Cdy+fUk~WVHEgoTO~?)-d9C#P&&BVJO`UA)%Y$Xtc=&;jo`q>Y$Po zKDS0^FWtQ~DMXZKqx}!jA@s~58lBv|3@%&!SOi5J^5`h&C2`7%t-Uml!nD{2=Hj5& zLe<5S*MX=|HO>JYQ+kDzA@ZniVG4qv3Khr5a{OE=O7bp>SAcv3tH2ys#9u3uypUhd zkv3Qv3c)$zk|RL54a9fVHCfK$RN6;jf#1mW0DZ!QD2K|l_-2s78J;DLB2IhK=JClm zE`)3@I1is;l=u>yE&)9d5X^9yRgdy_@M^SNuDljFghg=`2%#EnX%2O0gSMyvv_V(3 z2AzERb{SZRHhO#?Pre33kO9R83zb3!+KgbeK=n|e3jiqcP%C<8;Q-8kfol&4;Ccsf zJs|lyP)OUcQ)oh)!m&`e*FmSlGJ4w3LcT}8CzPB(fy2RCN7@i!1$h&CbvQf=L>}bB z*l+SU1+=q3<$}KlSC#qR0M64C0Hmqn`9BD}tnKyVVf@pG?|%Z{D!3Y&rcd@R4c2A3 zokQ%!QJ%>WQL@?Nc@`uh=Hasx`T-`bgt;^j-BO2k4z+BS7&2cadk&=U!$}{bZ7}?x zTNu>RTIR%iYx_Kd*Wrh}fuxC~1q9H9JiKtzqaCjA2&5ZeD933Gd?N@>#$k%K zfn)~(%Og~y0|85lQ5v4fx{`WH3cf-*!XJZ#Rj&GLY7cN9M)#j)&Qnn7J95tPPJ z&NwPi0LcP=+GRMo_?EDmoBU70+$BiQho-B@l=vDm;sShAI!q z=SaRlavcda#Y>%R!v?MbhhXU>M|r_HEXRAW)SOsw&I0FbaL$3rZ7K&>=Ei^J=GvTU z!C7UtbVK}yArNPyxZ~+_4(`B};9K=lF4}K)aTC6Nv!6F%_ZGTv0` z8h-9R!p5!s2~#Li(N=~)j==)y@VE~chkmS}_=>0w?Cg~lvX67TKozuFXtTK1_0?;u K)~elt2K+Biy`kU$ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/compat.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/compat.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..91e4db970363a55c8ac5394bc4c863d0125a8bfb GIT binary patch literal 1115 zcmYjQ%}yLS5Vregrn_f`Wl18DNGt8N4>Lm+HiBpsB}zC%T3`ief2@^8&DcOU?jOn? zHlX1&Ip#(3DtQE7_rxpYl(Kt3am(c@SCw5~eeTV{AbIqKvrV6n|H*gb5$dmTs!u35 z;ZzdVu9Q)`M zUB>z{RBV7BO9+5P@!-~j$N=>J%xsVFAE{H1uN}&3~Ue37` z|79Td@(+16r8XQ#fD=)Yw>Z^16q*#22Nxl%%}L=E{t>}7D8gH+J9DbLMYQnX^_&(l z_ZReE@7BBZbyD=^-rQe!w{&i+3HR?jJPYhChK^DX!h19An+`9s>Z~^1KX(t0-XH9< zK8Ui`Vse@b$xY}07$1Z*o~})jtGrS=tER&ED*s^mN<&_qP4smmlnGTMa}D-B7M}?{ zap`GbLU$ZXW=cT2$>OYOCNet_k|i!|Qe~z1OyEb%{(3WRAiv7A7++;DKCLCk?u}1j zym|4wg3GO)4SvIKcGTAP#nw)_^I~fQ$5}N#9yfV2JelQ^4}Yzuu&p;P$Eu#@nH**_ zeLlonhb`vi_GD>I>$1u8sJS)+TR9?b3thu<)Cvkh6B>CD?b8*UVBg;Zbr$ghb-$qD zsO#KQw;+x>e}VK_sw_1;cuJZLBsApVQ}n0Z{X5IiU5w%@ed%aoy`$Yjq-OHR!QnAd z_ZWTf1V!3`uGJ$HEV2bRHE83s^IB;WHyP+$=XJ$`z1`zCsb}Bd_3T#(Q?|o^@+XbkB ztuNiv8pH@=K&V>UpX0$Z)45b5E*cQi%t8SBYnV=bQi!Q`op)7ySG;{R;1EQ=;cJ>n Z@yc?d)=@+hN%K9rio~sX?V9*e?;CN97lHr) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/config.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/config.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..17d3411e10f8c7d675c6e21aa13e124864c2282f GIT binary patch literal 495 zcmYjNJx{|h5VeyOwWVD^LX2HAq%LYejf4;t16zeuCNGv7r)_AyYB!ad75)r=g`bd> ziNCPGDniU?6G5OiW{!*zs3Tfznvr@nYP?ACcU z&%>A}({i^qTC}ekgoziPt@7&3&<2J#(8QU0Qnwk)cp5QQcNt4_Q6{?XF}5vva{O`h zTelLvh!Wb{M{<#?sA+pgvIFj#^j(oc#(Tbt$ev5G%o7nwMV69`H&>b5`oTz4q6(Bh z+4w;k-1sAiRYsmk5f|Q~j1%EqXQ7~#SuQvM8alk!{Q)qaG}?) qP9U`{2Tp^{T$`Sn-XX{+X=%Hf{b$@%UBK%0ty#gHUcS=Yp8W%6X@U~~ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/context_processors.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/context_processors.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ebb098d88d7232ba3a924a88d93f4e3d5e3cca8 GIT binary patch literal 1920 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/managers.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/managers.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..52caafa1073efc017bad3f24aa44f4e0def9b68a GIT binary patch literal 931 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 F{l5WN;d=l8 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/storage.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/storage.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..547c9e2954040781eee14bbd4e61cd3ac6c3896b GIT binary patch literal 6616 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 F{{!)ms$c*B literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/strategy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/strategy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49471469d4202497b0d796e0590bb0f64640f02e GIT binary patch literal 6994 zcma)BTXPi06`tGfj#jI?Ku8D}u#GJnBrvjL5^RG3gA-UNw6>GUWir|xNwb=nU3z+D zv$d3~EK*KY%6ZFM@&i(pUy`3N4|zzQ{1@_+?{x1BtCd8&V){0xdrp6S`rNoSF;SiS zCv*RCLeu`E4gD;%Z{kV+g@S2JkF;2Kb)DKqWW=Ux#+GZzz8RI`vRjsIE2_k{Ys+>i z8jGuLRkq8~cwBR9@q{}e`;}-io^q$+X?HrFacAV19nHpb?wo9oMf33q_k?U$qm%K1 zyCB=+(W!XRU6k!wv=pCqPshvdGWrv2GCC8Vb!3f+PN07xIF0^!HnpLxEc}2yYAbqXr-2Y*vYoCNS?#_MohL!sX(wrrjjP_%ZjcHP zmL9fQ5TP-CyPb$25shb^0NtA36+7sLO*0 z@|d^dFqPjY>c~&FyZ$zPzxIir1{;FoL)*`?dFtgK&PRXH)!wJ&hXT1nuhZtD-ZTjo z{74q)xs4}XMj;>%i&{(X>-+k1vv0PHzR`zVd~X0vv-has?*@+Vh`}D6FmV8j1FmDI zy@fYwCl0pd@F2mKolwwgBC9(1ZHIe7;xq}|uZ3G?E$pd{4OIq1S%oWBS4>_5NACC* zKo}sDmDQpv7C(u1nYAP0h(p4)%+7iGyT+F=@ZbJghlhK>XKl~tYg_FI%%p2ueC^uq z)r3D?zkY@FSnqne{=x40_4xYx>sRpGPSzT0ov?Fxvl~Y2@_R{>e;}?rT}#`|(2p*Y z5M1^N{3(itPTr($iPKY`nF-C>GdCaH_SqWw5u|JLoU5OYAZzPP0|E zhI*M@VOLR~`3^_yp2b=IB%4(DZwv&l-pnDOnuLL#^b!hERvt<|-8Wi#VuTP3PZx$T z``W(IvLGPlzCk8N2}wz*kxCk*Y1mG(QrhhVoWF^s=rF@16rKlVSdfE9da@ z7=nr(^Sox{r>WkGj-t)JP@#pzd} z*vYD48YZdmlV-rD@QP1TF@|DrLo$mbH7o7;Q8yTo&up9&!oZf&E>lv`nqD=kxX6%K zy|*;7&!Gv81A8UDsiwwgiI5{1lq{Ai=GQdY-n-n-k#tY#Z(x!mnAXfJ*Z-^|K!Aa>d;-*+hv_;!$4 zAn-@`T^krBk6zN`lr%XVt%biD4!8you>I2`+aI3SB!f&ps_Bv2pGSk(hs?@t?*oKf zJ`J@2n?T%YlY^p^*h|$R)oZ~W(R_^c2D-I;JU>KvG(Xh=KP{a&S-}#YrP9a`w&HXl zK-1*?kDjxTn| z4I!5Vyg;D~cp=KQ8$f9h%`rR{FcQr@f^op!afeED2RJh^N$!iTg|bSpuR6(OZ}~A? zFLIc=sdSjYWtbN1=@Li4N#dZ|M+5niNi6q^AHyW*4lr3r9x*N_d^Ct5>gb zQgM6^MO~>n-GXIku&;<^ass+OaY9QY z?PJbx2Ot*)S~R+t0$5!M(1kduM?miWsMbIU1CkI~jVqC<4CB%Z1k2G6lWn!55y*_~7>Rm73 zmza?MU!auwdoUiG&m78At$Gq6XRDh?{8tq|WZL1;5mqY|2_C{goR9>L{|>`$H3uzZ z?zjX_1^)aYO;RZW?xD?TF}Etu`ZlEoMnRob3C&?x|AHwgNxo(*!KJYES^dy0Iy|ee z&t&cjub$3YXaj$K;YlF69QoEkG*o1ebxxkl(ayYl^*6j$cgHy7BoGwEN=6;dFbqMR zYWeRRr<=nm1Hvy-aR!CEc<1h?w?2Q+@EUg?K7Mekao5{uJVABuS2AD1DdoX`N(Ffs z{6i`zmgP68xJAV$RM=EVuZ==9=>+iy)FSVMKcS*Q#TQhNdg3!E+=*Kc9z6Q;?j7&; z{aa5q_^;{3@2S|Ng22co2DzMO5Jh=Dhx8^;xwPET}wAP3CjvQOZ99xgE1wnI8KeF_MKT literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/urls.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/urls.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..176d1a33d3f88f54054a2648d8f24fc514c8a2b5 GIT binary patch literal 857 zcmaKq&2G~`5P-e5R^tRl z5xcrp6>S{uZ|&^v?7j96cDD|;4=;(A z8`M!r$b%?*(2tTK7vAHSZ#RyD@PrB2eBbvzK52gKe0!9aju_tJ3wDt?TkVHQoJLHu z+XCEw4%x##a1|y(uu$LTUAcD2Ui{yGk|I!(vNcbH&tdZeOv|Jz*Li#{fR8zhf@rU~ zn3sJYlF;{Q6^;WjOxjq{K;n29*yC%`#y%=(6?k#l89z)UYmYQXZX=h)2uTN3ULGzN4Uy6&8M z)52TD+bt}qHT~ZV`?FK=(RTVJcQ>X}wOLGyx{t~xHBvKCtb3+yH;6+}%wl)QUy4Tg A?EnA( literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e05a5ad47907d2716797cc9cc45a6a8a5bf2c3a9 GIT binary patch literal 1823 zcmZuyOK%%D5GJ`R?doaEk{@-0)-_NRI@od%wTOYhsN)z(P*edT6>at+tVK$ey$_kB z>{?h~vIgu+`xAQaALx(hPuOcuIro-RhuoFo0$qW_;gFoyH$y$HREqV#sPj)Ap}*0C z&jkKE7~)SL6jAIUA3GQ;o_NGJ97FSlXZkrOr+L%M`<7$*wqyGRr=VpyujrSYlIHVX z*{?Vi;4NxyJx=d@g$l0J3wmu|uh1Nm(39WQdbM-y=j+!{hU2%6JfUyOWavY2lZ@rXq(%x$~ zjij83`+@7Tq~eEk>ao7h{1e8L@`%a4kldAQbPe~aKa7O{9najsISVLAO6tT(x_pmQ zlNM)}j0=|7-QJtS-tmjKvoA$B7`tAdp1Hv&+?}i3eXiHt%tB&yd#~RdzBrz3|Mxt2 z#XPY%9ddWXIs;`+eQH*i9l;P~AQDL|NrYkpGJ!iygIgfwYKvEZs0?s+F=ayjf%ptdJ0SPu#*9q99hqm) zr7r zUgQCWvcgE()Qs^v{1ye|XK)kp43S0TcM=)DDfe=n54Z*OWV(DLxC&`j%hI_OgmP;b zPN7cv1MQjs-^T4a$?5l}z zcb|IU!1ctzE@cDgHyQGeqD)g0a|KP4@Xui43KrlOwqaY#we`Y6%@!QX3TdM@Tl;@o z+HdGv9fQ`WuBFlkCep|Z(4lK0EzfFQ$EX^AGbyNi$T0MT_SGq6FVG+ihFlfxLUIaE zqRz2X%b_b1p8&cCUP7GLgVCYKHSU@6qz2~nfXEJPh6SS z!TRBky_d(G-m9a--mBN0{=93sYNW)1=T#TQ;_Y(m`D^J|T|fn1!ZhWoC3WB_<{J2q k=-W@zm;FE?Cboc-Ng0<(0|UinBM)7j4jbD>(X?~_0*NuUPXGV_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/__pycache__/views.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/__pycache__/views.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65a061fd43365adba4cf636aad27d0d9addf2427 GIT binary patch literal 2582 zcmZ`5%W~UB5;J)41yT|zS+?wKn8$?&kxE5L#i>*(Dx1n`%a&Xgoj7v{0&#{kC_L!F zP_`6k4@+N@>t43@zB%o8>?h#5r~Ja6wtGNPicJys zp&Qj%UBjiY5v{Nl4VS~!XpOB!P1cNBtOa`&UJciy4YolHaZ3=f!fSKF-MKmanb+AS zciC-G7aP1Wxg$2`1kP6Y>Kg+gwgo5U-<+-SW^o3mcj43#8*@X{M1^nh7GHk@Tvso< z$M5o6eDe)q@9^9F&KraMiMz)}=iaaAoDRv$sZc72$7zS=c9@KVIBz}eKRkHae=zX& z5BiTDdWU(< zklxTAz7Tl4_^ucGk?5Fti6@>vQ!nxw0K+7jhC+$F4hRp@VG_q6^;d`;>TFI^pVgl| zeWX=8ezNzVf5@CP83ulM;r_UD`T3QAAw@Ic!PSEC07lw?Ng0_jA&PSH69s?JLy%hs zv%s{WS|}xJK$w?K{NbsHxkP@jx`c*_RLM2K|7FM)EdM+2O=WQAE73diWp9*(T*$OH zlD&^lKZ@nc-TOQIf?wQEcRxGby&v8Gbaw}yaoij9ronXkWEO;c`;&MmKT|s|dxa(2 z=#FiFI^E_IKOQHGZEt~{1>#lOou20w(yfCMsW#S>8vrj2N2P5TM@aBKOjm}3`R2&X z=s&5lGAM|D(9HZh{h6RMF7EAtOcASK=&K-!+gJW<<5xRWu7V)hgsEdo7h&{8UhyDY zMGjfBRLH_(mL@DHEMJb(+&O)PtM*(mq566G>ML7-rKq@JH>gBxXd(s=t_}v9?2wX4`)(>eQnToEO&>Mq+@ShTf!QCs>6it@dEy~E!FF+{Sf#IYf3JWcLCd9nuDgGN!pyh0gw?Hf-%P2 zxHL0UZe`|#0BpsUqL)@?apSZscQab7uxG#g>tgpWdbc8BUG7%o4-48KG7EQ=nOPZ~ z14Z0ioLr!k7Q2Sd$Zs%tTA{{oq@BMVgw|N7rC?-8W~v8HPTMdU)UwL9MXJ=&B>+xtr6d|3{_RN5k0NS6Jp@YjZ6DGBc{&{B2nT~;d^a~FzZB(W8<*7c5(!ATbzwf2T?CvX$%zK#sb z<1UBd0!Jk>mDsl70xyV)c7J`^#p_mL|B#rQe@yp6sj0-@ANT|KBT;xK%L>x0Eov5& z5Z##y!c= (2, 0): + return field.remote_field.model + + user_model = field.rel.to + if isinstance(user_model, six.string_types): + app_label, model_name = user_model.split('.') + user_model = models.get_model(app_label, model_name) + return user_model + + +def get_request_port(request): + if django.VERSION >= (1, 9): + return request.get_port() + + host_parts = request.get_host().partition(':') + return host_parts[2] or request.META['SERVER_PORT'] diff --git a/flex/lib/python3.6/site-packages/social_django/config.py b/flex/lib/python3.6/site-packages/social_django/config.py new file mode 100644 index 0000000..c1491bb --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/config.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class PythonSocialAuthConfig(AppConfig): + # Full Python path to the application eg. 'django.contrib.admin'. + name = 'social_django' + # Last component of the Python path to the application eg. 'admin'. + label = 'social_django' + # Human-readable name for the application eg. "Admin". + verbose_name = 'Python Social Auth' diff --git a/flex/lib/python3.6/site-packages/social_django/context_processors.py b/flex/lib/python3.6/site-packages/social_django/context_processors.py new file mode 100644 index 0000000..07e875c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/context_processors.py @@ -0,0 +1,52 @@ +from django.contrib.auth import REDIRECT_FIELD_NAME +from django.utils.functional import SimpleLazyObject +from django.utils.http import urlquote + +try: + from django.utils.functional import empty as _empty + empty = _empty +except ImportError: # django < 1.4 + empty = None + + +from social_core.backends.utils import user_backends_data +from .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_core.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) + if value: + value = urlquote(value) + querystring = REDIRECT_FIELD_NAME + '=' + value + else: + querystring = '' + + return { + 'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME, + 'REDIRECT_FIELD_VALUE': value, + 'REDIRECT_QUERYSTRING': querystring + } diff --git a/flex/lib/python3.6/site-packages/social_django/fields.py b/flex/lib/python3.6/site-packages/social_django/fields.py new file mode 100644 index 0000000..d547ce8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/fields.py @@ -0,0 +1,94 @@ +import json +import six +import functools + +import django + +from django.core.exceptions import ValidationError +from django.conf import settings +from django.db import models + +from social_core.utils import setting_name + +try: + from django.utils.encoding import smart_unicode as smart_text + smart_text # placate pyflakes +except ImportError: + from django.utils.encoding import smart_text + +# SubfieldBase causes RemovedInDjango110Warning in 1.8 and 1.9, and +# will not work in 1.10 or later +if django.VERSION[:2] >= (1, 8): + field_metaclass = type +else: + from django.db.models import SubfieldBase + field_metaclass = SubfieldBase + +field_class = functools.partial(six.with_metaclass, field_metaclass) + +if getattr(settings, setting_name('POSTGRES_JSONFIELD'), False): + from django.contrib.postgres.fields import JSONField as JSONFieldBase +else: + JSONFieldBase = field_class(models.TextField) + + +class JSONField(JSONFieldBase): + """Simple JSON field that stores python structures as JSON strings + on database. + """ + + def __init__(self, *args, **kwargs): + kwargs.setdefault('default', dict) + super(JSONField, self).__init__(*args, **kwargs) + + def from_db_value(self, value, expression, connection, context): + return self.to_python(value) + + 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.value_from_object(obj)) + + def value_from_object(self, obj): + """Return value dumped to string.""" + orig_val = super(JSONField, self).value_from_object(obj) + return self.get_prep_value(orig_val) + diff --git a/flex/lib/python3.6/site-packages/social_django/managers.py b/flex/lib/python3.6/site-packages/social_django/managers.py new file mode 100644 index 0000000..1fa91b6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/managers.py @@ -0,0 +1,15 @@ +from django.db import models + + +class UserSocialAuthManager(models.Manager): + """Manager for the UserSocialAuth django model.""" + + class Meta: + app_label = "social_django" + + def get_social_auth(self, provider, uid): + try: + return self.select_related('user').get(provider=provider, + uid=uid) + except self.model.DoesNotExist: + return None diff --git a/flex/lib/python3.6/site-packages/social_django/middleware.py b/flex/lib/python3.6/site-packages/social_django/middleware.py new file mode 100644 index 0000000..36e69e9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/middleware.py @@ -0,0 +1,60 @@ +# -*- 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_core.exceptions import SocialAuthBaseException +from social_core.utils import social_logger +from .compat import MiddlewareMixin + + +class SocialAuthExceptionMiddleware(MiddlewareMixin): + """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: + if url: + url += ('?' in url and '&' or '?') + \ + 'message={0}&backend={1}'.format(urlquote(message), + backend_name) + if url: + 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') or 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') diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0001_initial.py b/flex/lib/python3.6/site-packages/social_django/migrations/0001_initial.py new file mode 100644 index 0000000..dacdd72 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0001_initial.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + +from social_core.utils import setting_name + +from ..fields import JSONField +from ..storage import DjangoAssociationMixin, DjangoCodeMixin, \ + DjangoNonceMixin, DjangoUserMixin + +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 Migration(migrations.Migration): + replaces = [ + ('default', '0001_initial'), + ('social_auth', '0001_initial') + ] + + 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=ASSOCIATION_SERVER_URL_LENGTH)), + ('handle', + models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH)), + ('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, 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, 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=NONCE_SERVER_URL_LENGTH)), + ('timestamp', models.IntegerField()), + ('salt', models.CharField(max_length=65)), + ], + options={ + 'db_table': 'social_auth_nonce', + }, + bases=(models.Model, 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=UID_LENGTH)), + ('extra_data', JSONField(default='{}')), + ('user', models.ForeignKey( + related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE)), + ], + options={ + 'db_table': 'social_auth_usersocialauth', + }, + bases=(models.Model, DjangoUserMixin), + ), + migrations.AlterUniqueTogether( + name='usersocialauth', + unique_together={('provider', 'uid')}, + ), + migrations.AlterUniqueTogether( + name='code', + unique_together={('email', 'code')}, + ), + migrations.AlterUniqueTogether( + name='nonce', + unique_together={('server_url', 'timestamp', 'salt')}, + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0002_add_related_name.py b/flex/lib/python3.6/site-packages/social_django/migrations/0002_add_related_name.py new file mode 100644 index 0000000..90653ba --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0002_add_related_name.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings + +from social_core.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): + replaces = [ + ('default', '0002_add_related_name'), + ('social_auth', '0002_add_related_name') + ] + + dependencies = [ + ('social_django', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='usersocialauth', + name='user', + field=models.ForeignKey( + related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE, + ) + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0003_alter_email_max_length.py b/flex/lib/python3.6/site-packages/social_django/migrations/0003_alter_email_max_length.py new file mode 100644 index 0000000..ba4e8c1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0003_alter_email_max_length.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import models, migrations + +from social_core.utils import setting_name + +EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) + + +class Migration(migrations.Migration): + replaces = [ + ('default', '0003_alter_email_max_length'), + ('social_auth', '0003_alter_email_max_length') + ] + + dependencies = [ + ('social_django', '0002_add_related_name'), + ] + + operations = [ + migrations.AlterField( + model_name='code', + name='email', + field=models.EmailField(max_length=EMAIL_LENGTH), + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0004_auto_20160423_0400.py b/flex/lib/python3.6/site-packages/social_django/migrations/0004_auto_20160423_0400.py new file mode 100644 index 0000000..8636ad1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0004_auto_20160423_0400.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + +from ..fields import JSONField + + +class Migration(migrations.Migration): + replaces = [ + ('default', '0004_auto_20160423_0400'), + ('social_auth', '0004_auto_20160423_0400') + ] + + dependencies = [ + ('social_django', '0003_alter_email_max_length'), + ] + + operations = [ + migrations.AlterField( + model_name='usersocialauth', + name='extra_data', + field=JSONField(default=dict), + ) + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0005_auto_20160727_2333.py b/flex/lib/python3.6/site-packages/social_django/migrations/0005_auto_20160727_2333.py new file mode 100644 index 0000000..67241a5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0005_auto_20160727_2333.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-07-28 02:33 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + replaces = [ + ('social_auth', '0005_auto_20160727_2333') + ] + + dependencies = [ + ('social_django', '0004_auto_20160423_0400'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='association', + unique_together=set([('server_url', 'handle')]), + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0006_partial.py b/flex/lib/python3.6/site-packages/social_django/migrations/0006_partial.py new file mode 100644 index 0000000..4805fd2 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0006_partial.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-02 11:54 +from __future__ import unicode_literals + +from django.db import migrations, models +import social_django.fields +import social_django.storage + + +class Migration(migrations.Migration): + + dependencies = [ + ('social_django', '0005_auto_20160727_2333'), + ] + + operations = [ + migrations.CreateModel( + name='Partial', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('token', models.CharField(db_index=True, max_length=32)), + ('next_step', models.PositiveSmallIntegerField(default=0)), + ('backend', models.CharField(max_length=32)), + ('data', social_django.fields.JSONField(default=dict)), + ], + options={ + 'db_table': 'social_auth_partial', + }, + bases=(models.Model, social_django.storage.DjangoPartialMixin), + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0007_code_timestamp.py b/flex/lib/python3.6/site-packages/social_django/migrations/0007_code_timestamp.py new file mode 100644 index 0000000..099975e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0007_code_timestamp.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-06-08 06:54 +from __future__ import unicode_literals + +from django.db import migrations, models + +from django.utils import timezone + + +class Migration(migrations.Migration): + dependencies = [ + ('social_django', '0006_partial'), + ] + + operations = [ + migrations.AddField( + model_name='code', + name='timestamp', + field=models.DateTimeField(auto_now_add=True, + db_index=True, + default=timezone.now), + preserve_default=False + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/0008_partial_timestamp.py b/flex/lib/python3.6/site-packages/social_django/migrations/0008_partial_timestamp.py new file mode 100644 index 0000000..659457a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/migrations/0008_partial_timestamp.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-06-08 06:57 +from __future__ import unicode_literals + +from django.db import migrations, models + +from django.utils import timezone + + +class Migration(migrations.Migration): + dependencies = [ + ('social_django', '0007_code_timestamp'), + ] + + operations = [ + migrations.AddField( + model_name='partial', + name='timestamp', + field=models.DateTimeField(auto_now_add=True, + db_index=True, + default=timezone.now), + preserve_default=False, + ), + ] diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__init__.py b/flex/lib/python3.6/site-packages/social_django/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0001_initial.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0001_initial.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf1df89ae0692c3d3e1d0278663e15b38744b4ac GIT binary patch literal 2496 zcmZt{+fLj@)VKB8Uhjo_AfZi~Ce5~mC4p2ys;X>(Bs8!{0s1g6mf4wIOvd&y_EMng z6Md`rkp4;^`T>5b)F*zSFYP&F16zq*`RLrwnRA(0u2!9iKPVX+Q;s^+G4(1zjuXiGr>d^khLd3VQ0TLdWR% zuL>P!#?dr*n5Oow(ww-0Gbv3qYYd~H8&l@-AZ5bmNmI+r5EwkkoG|DMKMmq20Zl7m zX&OX*#N7h-B0pqTTJFmJVRxtVCSaU4jcj`T$dCGQJ4xbhAbo5Hr$Lm}bN(7c$Qg~C z(TSsO!6{Vs6DD#MXMcC&gSWl2zOmII_4fYWJMWfrZu`UZa1pgLXMc0u+uG>7-FtWS zFC>-Cbapyx8y=Yd2p;x7Y!%XMzP-DApPF4pb(R|n)Z ze92#`qA-Q1R3RE&qR;yp(V^ZYW?ra$m6QRus6k82K60o@%M?0J?MtJlQ3rSx=F-Zg z3Y06UYC%bjDJTc<=(>p!qXPPWLKLk&C-j8IA&x&l(=i|cX z)QHc|@AUbk$S<$cQ>55*5#b9qL!afD(k4jmQ609*KANRpUh0s22_k(}M4G#abf11r zUmU7*;ZlPt-KVp3d4yV|D>>CKlLxc~JM%KLx33o>D^u3u69$&4z^No=kR(I!Vez2H(ja8S@o^Wu z^lZScaA=7|54_Yr0Lz(?tUalguPp4bfg7HgJzVP~E5`$Q9b}~gKVb5KY8tL#sG4S~rfR90>dLRCI`D7k zrta#u(wf>pp5|&dJn-P=nxkB`27ebk-e{{9+7wV`(VW4F=?&ANfEf|^N)tOjz#=`6 z?as%gQZT!5)DtCCyWX@e^k;FH20Rfa@Wm$xWb4XF8Ver=0edfGhh=lIds!K_z)w>l zM=tNTZ2!#|$^33Rm}ARmYJHoJ_;;9Y@(_TlHB=YQ?yC26z*GZjq^UKii~NlL02>>j AasU7T literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0002_add_related_name.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0002_add_related_name.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0a024caeaf915c68b3fa678612155c16db279dc8 GIT binary patch literal 1023 zcmZuv$!^p@5cM|RCj=qj8|E0^q>6%a$suOFYVE1A>PJQFHwL7I@fZY=(@rN zH*%Ahxkao_H~Ap9iQQp~JGo0-U1J`znG1tk9qe1PI#xP3JqDNfA2sm6|E7V@tYvU( zk^t-zZ4~^*eA-BF*P9|sN??>{l8KntBcru*H1N9h^DGsy%*vw1NVjHEW<{!SxWcrE zbJqH2Cws^A@XPMr=OglDes+35A1pUs+{mw!vzm!S<-Gq~1lxG(cPN-fbkK+a0febR zOci}EC4VUfn>2&&v#sE|> z9!my&r9oXLSIMaY*T96La+* zrNkAi;&H-i5(2AO0jx+eRtvRD#GiAV@mWlYLqAqFb!>1q`cA}vUPsyBo*{pu6+UC zfGdx|Bjn1dufPE@PExfe9NGHjvpwVaX0kJ#hEIM0{b>^Nn>-jc;ys)-#RUi$lH>+8 z42?}`<`%VjY)LzJsFS~%BCTg;=jOO6>n_Z*RPiP&3w6j+K&*Kyo9@M8vDjf;;=7s1c_vxTzc49^wE5gk>N3f=WV}UV zIvTLyMOt=G@!~7S0hkh!Hv)zr6;EnVBlYywn62 z#L)vh)%cO-C!NpwyNq=qW9X}u0&&FHMa!l3#~AgMs3cEBO(PJMC_ofRCTiuN3mWe0 zMBnR}N_Pnj4|L80;UUq5WrY>&OZ0=NXGrzSpYL&{vP;Y}zT_%imlA}kBx<)=T*QNtb1mzfSJixwbfnp6 z-S$MpadpuS>DV5l6n@rY9w_@j|qTx-Wlvb^eaxd0^p6g@&+o4SFbj(`UI5Hz+ J%Z$t|>mQ)_3XcE) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0004_auto_20160423_0400.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0004_auto_20160423_0400.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccf1e96b6d152530ada2bf5e424775d2efe7b6d1 GIT binary patch literal 849 zcmZ`%%}(4f5O#hx$%b8oRJA?y336Bx*ezlugy^M}DwQ7S$rsCHyi36O!A=n6+FrT# z9r`ML1YdjN6?*E}3!;}g^5pY)W<2xF%%{`z#V?>spOD|=pA{lL!L5%`00B>u+M}Ll zu`m5Ppg|qd5YGXGa#Tk&^2i4QBZ$5eh(vH1e+y^~@fpdI2aG{7&+H6MRkk3Qta_oi z)R}M6x>_sVS8bzBRO6-8515QKAAdaieEPl;5=tB3X+;Q5aO-^(f=~|#^+Zq66SmvGbv*jYmfr* zgt6;^OB;_dnkdmpUJ6YoAUe^2Xv#`x6<`ROzO&gomX&4Jgiw{eN!t!PdoBT`ORo(1- zs3gqZHl;f3i|c$t&djni<6Sq~u==QGkEiGP$p6!#-R`T3F*!?>-bRu5z88g27x#L3Fp*NA^Upq3K>vQ!R^vYT#M;o$_Z6IxloA^8a GB=`$Y7UQY_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0005_auto_20160727_2333.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0005_auto_20160727_2333.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce4007326104afdb20f50f674a926fe38999c9c5 GIT binary patch literal 765 zcmZ{iyKdVs6oyGjR%F>m(`M*1c(A2d%EdrYq*JG6X}WMB7#&(xLS0Ns2AVZpyY?OW zDt!d5o%#x$N=gorr385J^XTU};zuU4S$Ogb2zC(qjb04T=qoeT(gZ+&1u8MY*lI^O zrAu6lJ|b|z`-Z^d?w$AO5)Ve7(cJ%I_Mkb|$6ZxqHE=45mP;m-$%SQ+OV$>3r7~-K z8Y3gF%S5w_9|9#^2(fb*Kyj`Id8XIUe6tA&m?^lj6SkD81&fc(CV=cD7E`3)gh&2 z4V|!hO6jg+V)#M=$s55kuE-R4!zaF%cDJ$(WVx_rHVE(O72f9xl7kEJixrK%1`ZqyZ$&dqreClwZPakD@m|pL0-ej8O;g2D0xhF=hKqovHf|^|H== literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0006_partial.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0006_partial.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b31b38d4d2e43448b80c47bc498dba3c39d30f60 GIT binary patch literal 1132 zcmY*YOK%%D5Z?DgmL2(#2I;BSJ;Iq^bJ8cL?jeVC*S+t=n+ z_=ohb^e6DzQ~p9vo#o0%!vzO3oZ-yy`(~%x+k^eTfn<^A{p0;_q3ylFt$#rQ9{AF$ zeB%3^9>|~yNmxZBa&{pEI}biuPn>rEBWLaM!D?$YHx3>mthftL@jcIMe)ib3-PX-qFiBy>>Sbe}O(v6@QTFFzm}MSuv;sHlP8B9QEsN?tF`CCO`kYp6E7Rel%e+Z6bMt);Q)jLq>r6uugw z%SONqb7>5lNlx}R@4?P4Y1{W)L^g7@lZD_Cv`w3~XDpsE&GmT^s05R#v@5n}GwNEU z)D9>`PS=v7KBV+|&19E|QZfYIVjH}k3$9g&7s=LJ2gF%71gaZClK+fAzVB+1{Ou)E zz54uPqlFPS{Gwt~ey9y!^4`^VvFon>cJceUi^nI5xgoL3OHyqb#g?4xy>pChXgL$N zqPByk#kq7(`^!_$F^?1f{#hb5H%L->!&JFwBygq6g(|04FKTstd^&-9xIfj$udj|z ztJ9NXoPx=+EaBu~F}ZIE;6zk<_i-(!B8{n(TJQ%9%HPz-~SpZQt%AJI-i&;S4c literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0007_code_timestamp.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_django/migrations/__pycache__/0007_code_timestamp.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e5ff4bdc066bf8fa4bf9c1723aaca3d1fb799ae4 GIT binary patch literal 807 zcmYk4&2AGh5XZgWn`I+KDlf5zbb}g7BcZBDsGR79oP4pIj1!!CKWgu$5Q!69d*Lm3 z6&}G?PP_sqW_MGHS02rdJu`p*?6dJWIr$0ni%ZBa^55{$zrtxQ(EtHQk;0*l)A_D) z3y*q*PyPPiQ$Z2Z&>`;#_z-*}5D0GmORgB~c)6`?I-AYTSvj0h82?~h)*^GvEi+1t}~w|{TW!v3t|1<1{*09&`GU@5qdA!araH; z>Ie^_yJMlUnDtqV*@Q8y(kg*|%Gh1YRac2Inu4eW_AJ*@80}#qjo$!VcO7(hXLS6Q zH)4sZhgg6y>!DJ{ss#6Qrd^=BQTx&)`0po!hk_Z=6%5NGW4t2bHPVcN7!s}YRG_eqbCA@r zPV-upg|T6do9K0HT+3V>%R+40(1#VHafxE)s05)75b8p*bEqf$eE=T#BHTw1U<2`f z0O7p{5ybaCo=9MTtvs`X4-fV1$dnt&6@xu5cBP%nX0z9<>#wbDYMxg%s<^K4kR3ew zR;fpays1l8ly{5+&=}TC79c*`2*j2*s=i8nnsn2!g6G2eoes8AD4^p?3nTPFu+!=5 z)YTD=q}u_p$C!0djM;=S%+e@<_K>mLhO0IcV{{0j5?Hg?NMW>xfi!*#aMf1O?d{Rg zJ6?+wPSX1V^jQyEwnzj;yU+xc<^ zM>sCcd~q{h=F3-e1UB0h*($4KHC;DS!SrRZ(TjR^oAqp`$m!FRSJkwKd~%+Whb)%7u#>Y(Pps#oa>yA#( lWO#BmwNys8NV)`15PIu5ZrJ!ukN%>CYuYY`?${lA{{W2MeASl@i4O1dl-k3@`#24nSPY0whuxf*CX!{Z=v*frJsnuXg={qRg_yl2rY& z#3KE){G623qGJ8DB7Ng*!@QzG6H9}Xs+20rViSvO6U$snGZO&`m1M z%t_HT%}Xw_C^0D1FV0WSOw7?uEG@~ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_django/models.py b/flex/lib/python3.6/site-packages/social_django/models.py new file mode 100644 index 0000000..a203018 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/models.py @@ -0,0 +1,139 @@ +"""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_core.utils import setting_name + +from .compat import get_rel_model +from .storage import DjangoUserMixin, DjangoAssociationMixin, \ + DjangoNonceMixin, DjangoCodeMixin, \ + DjangoPartialMixin, BaseDjangoStorage +from .fields import JSONField +from .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) +EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) +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', + on_delete=models.CASCADE) + 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: + app_label = "social_django" + abstract = True + + @classmethod + def get_social_auth(cls, provider, uid): + try: + return cls.objects.select_related('user').get(provider=provider, + uid=uid) + except cls.DoesNotExist: + return None + + @classmethod + def username_max_length(cls): + username_field = cls.username_field() + field = cls.user_model()._meta.get_field(username_field) + return field.max_length + + @classmethod + def user_model(cls): + user_model = get_rel_model(field=cls._meta.get_field('user')) + return user_model + + +class UserSocialAuth(AbstractUserSocialAuth): + """Social Auth association model""" + + class Meta: + """Meta data""" + app_label = "social_django" + 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: + app_label = "social_django" + 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: + app_label = "social_django" + db_table = 'social_auth_association' + unique_together = ( + ('server_url', 'handle',) + ) + + +class Code(models.Model, DjangoCodeMixin): + email = models.EmailField(max_length=EMAIL_LENGTH) + code = models.CharField(max_length=32, db_index=True) + verified = models.BooleanField(default=False) + timestamp = models.DateTimeField(auto_now_add=True, db_index=True) + + class Meta: + app_label = "social_django" + db_table = 'social_auth_code' + unique_together = ('email', 'code') + + +class Partial(models.Model, DjangoPartialMixin): + token = models.CharField(max_length=32, db_index=True) + next_step = models.PositiveSmallIntegerField(default=0) + backend = models.CharField(max_length=32) + data = JSONField() + timestamp = models.DateTimeField(auto_now_add=True, db_index=True) + + class Meta: + app_label = "social_django" + db_table = 'social_auth_partial' + + +class DjangoStorage(BaseDjangoStorage): + user = UserSocialAuth + nonce = Nonce + association = Association + code = Code + partial = Partial + + @classmethod + def is_integrity_error(cls, exception): + return exception.__class__ is IntegrityError diff --git a/flex/lib/python3.6/site-packages/social_django/storage.py b/flex/lib/python3.6/site-packages/social_django/storage.py new file mode 100644 index 0000000..afc21fe --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/storage.py @@ -0,0 +1,198 @@ +"""Django ORM models for Social Auth""" +import base64 +import six +import sys +from django.db import transaction +from django.db.utils import IntegrityError + +from social_core.storage import UserMixin, AssociationMixin, NonceMixin, \ + CodeMixin, PartialMixin, 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') + try: + if hasattr(transaction, 'atomic'): + # In Django versions that have an "atomic" transaction decorator / context + # manager, there's a transaction wrapped around this call. + # If the create fails below due to an IntegrityError, ensure that the transaction + # stays undamaged by wrapping the create in an atomic. + with transaction.atomic(): + user = cls.user_model().objects.create_user(*args, **kwargs) + else: + user = cls.user_model().objects.create_user(*args, **kwargs) + except IntegrityError: + # User might have been created on a different thread, try and find them. + # If we don't, re-raise the IntegrityError. + exc_info = sys.exc_info() + # If email comes in as None it won't get found in the get + if kwargs.get('email', True) is None: + kwargs['email'] = '' + try: + user = cls.user_model().objects.get(*args, **kwargs) + except cls.user_model().DoesNotExist: + six.reraise(*exc_info) + return user + + @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 = cls.objects.filter(user=user) + + 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) + if hasattr(transaction, 'atomic'): + # In Django versions that have an "atomic" transaction decorator / context + # manager, there's a transaction wrapped around this call. + # If the create fails below due to an IntegrityError, ensure that the transaction + # stays undamaged by wrapping the create in an atomic. + with transaction.atomic(): + social_auth = cls.objects.create(user=user, uid=uid, provider=provider) + else: + social_auth = cls.objects.create(user=user, uid=uid, provider=provider) + return social_auth + + +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 DjangoPartialMixin(PartialMixin): + @classmethod + def load(cls, token): + try: + return cls.objects.get(token=token) + except cls.DoesNotExist: + return None + + @classmethod + def destroy(cls, token): + partial = cls.load(token) + if partial: + partial.delete() + + +class BaseDjangoStorage(BaseStorage): + user = DjangoUserMixin + nonce = DjangoNonceMixin + association = DjangoAssociationMixin + code = DjangoCodeMixin diff --git a/flex/lib/python3.6/site-packages/social_django/strategy.py b/flex/lib/python3.6/site-packages/social_django/strategy.py new file mode 100644 index 0000000..1a3a820 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/strategy.py @@ -0,0 +1,159 @@ +# coding=utf-8 +from django.conf import settings +from django.http import HttpResponse, HttpRequest +from django.db.models import Model +from django.contrib.contenttypes.models import ContentType +from django.contrib.auth import authenticate +from django.shortcuts import redirect, resolve_url +from django.template import TemplateDoesNotExist, loader, engines +from django.utils.crypto import get_random_string +from django.utils.encoding import force_text +from django.utils.functional import Promise +from django.utils.translation import get_language + +from social_core.strategy import BaseStrategy, BaseTemplateStrategy +from .compat import get_request_port + + +def render_template_string(request, html, context=None): + """Take a template in the form of a string and render it for the + given context""" + template = engines['django'].from_string(html) + return template.render(context=context, request=request) + + +class DjangoTemplateStrategy(BaseTemplateStrategy): + def render_template(self, tpl, context): + template = loader.get_template(tpl) + return template.render(context=context, request=self.strategy.request) + + def render_string(self, html, context): + return render_template_string(self.strategy.request, html, 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'): + if isinstance(value, Promise): + value = force_text(value) + value = resolve_url(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 get_request_port(request=self.request) + + 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) + return template.render(context=context, request=self.request) + except TemplateDoesNotExist: + return render_template_string(self.request, html, context) + + def authenticate(self, backend, *args, **kwargs): + kwargs['strategy'] = self + kwargs['storage'] = self.storage + kwargs['backend'] = backend + return authenticate(*args, **kwargs) + + def clean_authenticate_args(self, *args, **kwargs): + """Cleanup request argument if present, which is passed to + authenticate as for Django 1.11""" + if len(args) > 0 and isinstance(args[0], HttpRequest): + kwargs['request'], args = args[0], args[1:] + return 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): + 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() diff --git a/flex/lib/python3.6/site-packages/social_django/urls.py b/flex/lib/python3.6/site-packages/social_django/urls.py new file mode 100644 index 0000000..68a07c7 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/urls.py @@ -0,0 +1,24 @@ +"""URLs module""" +from django.conf import settings +from django.conf.urls import url + +from social_core.utils import setting_name +from . import views + + +extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' + +app_name = 'social' + +urlpatterns = [ + # authentication / association + url(r'^login/(?P[^/]+){0}$'.format(extra), views.auth, + name='begin'), + url(r'^complete/(?P[^/]+){0}$'.format(extra), views.complete, + name='complete'), + # disconnection + url(r'^disconnect/(?P[^/]+){0}$'.format(extra), views.disconnect, + name='disconnect'), + url(r'^disconnect/(?P[^/]+)/(?P\d+){0}$' + .format(extra), views.disconnect, name='disconnect_individual'), +] diff --git a/flex/lib/python3.6/site-packages/social_django/utils.py b/flex/lib/python3.6/site-packages/social_django/utils.py new file mode 100644 index 0000000..281bdd4 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/utils.py @@ -0,0 +1,51 @@ +# coding=utf-8 +from functools import wraps + +from django.conf import settings +from django.http import Http404 + +from social_core.utils import setting_name, module_member, get_strategy +from social_core.exceptions import MissingBackend +from social_core.backends.utils import get_backend +from .compat import reverse + + +BACKENDS = settings.AUTHENTICATION_BACKENDS +STRATEGY = getattr(settings, setting_name('STRATEGY'), + 'social_django.strategy.DjangoStrategy') +STORAGE = getattr(settings, setting_name('STORAGE'), + 'social_django.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 diff --git a/flex/lib/python3.6/site-packages/social_django/views.py b/flex/lib/python3.6/site-packages/social_django/views.py new file mode 100644 index 0000000..380b1c8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_django/views.py @@ -0,0 +1,129 @@ +from 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_core.utils import setting_name +from social_core.actions import do_auth, do_complete, do_disconnect +from .utils import psa + + +NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social' + +# Calling `session.set_expiry(None)` results in a session lifetime equal to +# platform default session lifetime. +DEFAULT_SESSION_TIMEOUT = None + + +@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, request=request, + *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 get_session_timeout(social_user, enable_session_expiration=False, + max_session_length=None): + if enable_session_expiration: + # Retrieve an expiration date from the social user who just finished + # logging in; this value was set by the social auth backend, and was + # typically received from the server. + expiration = social_user.expiration_datetime() + + # We've enabled session expiration. Check to see if we got + # a specific expiration time from the provider for this user; + # if not, use the platform default expiration. + if expiration: + received_expiration_time = expiration.total_seconds() + else: + received_expiration_time = DEFAULT_SESSION_TIMEOUT + + # Check to see if the backend set a value as a maximum length + # that a session may be; if they did, then we should use the minimum + # of that and the received session expiration time, if any, to + # set the session length. + if received_expiration_time is None and max_session_length is None: + # We neither received an expiration length, nor have a maximum + # session length. Use the platform default. + session_expiry = DEFAULT_SESSION_TIMEOUT + elif received_expiration_time is None and max_session_length is not None: + # We only have a maximum session length; use that. + session_expiry = max_session_length + elif received_expiration_time is not None and max_session_length is None: + # We only have an expiration time received by the backend + # from the provider, with no set maximum. Use that. + session_expiry = received_expiration_time + else: + # We received an expiration time from the backend, and we also + # have a set maximum session length. Use the smaller of the two. + session_expiry = min(received_expiration_time, max_session_length) + else: + # If there's an explicitly-set maximum session length, use that + # even if we don't want to retrieve session expiry times from + # the backend. If there isn't, then use the platform default. + if max_session_length is None: + session_expiry = DEFAULT_SESSION_TIMEOUT + else: + session_expiry = max_session_length + + return session_expiry + + +def _do_login(backend, user, social_user): + user.backend = '{0}.{1}'.format(backend.__module__, + backend.__class__.__name__) + # Get these details early to avoid any issues involved in the + # session switch that happens when we call login(). + enable_session_expiration = backend.setting('SESSION_EXPIRATION', False) + max_session_length_setting = backend.setting('MAX_SESSION_LENGTH', None) + + # Log the user in, creating a new session. + login(backend.strategy.request, user) + + # Make sure that the max_session_length value is either an integer or + # None. Because we get this as a setting from the backend, it can be set + # to whatever the backend creator wants; we want to be resilient against + # unexpected types being presented to us. + try: + max_session_length = int(max_session_length_setting) + except (TypeError, ValueError): + # We got a response that doesn't look like a number; use the default. + max_session_length = None + + # Get the session expiration length based on the maximum session length + # setting, combined with any session length received from the backend. + session_expiry = get_session_timeout( + social_user, + enable_session_expiration=enable_session_expiration, + max_session_length=max_session_length, + ) + + try: + # Set the session length to our previously determined expiry length. + backend.strategy.request.session.set_expiry(session_expiry) + except OverflowError: + # The timestamp we used wasn't in the range of values supported by + # Django for session length; use the platform default. We tried. + backend.strategy.request.session.set_expiry(DEFAULT_SESSION_TIMEOUT)

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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/server/__pycache__/trustroot.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/server/__pycache__/trustroot.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9a4daa047dabf84f0d651a0aabbe29e8183ac17 GIT binary patch literal 11347 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 J{58|F-v} and + L{encodeResponse} methods of the + L{Server} object. Who gets to do the intermediate task -- deciding + how to respond to the request -- will depend on what type of request it + is. + + If it's a request to authenticate a user (a X{C{checkid_setup}} or + X{C{checkid_immediate}} request), you need to decide if you will assert + that this user may claim the identity in question. Exactly how you do + that is a matter of application policy, but it generally involves making + sure the user has an account with your system and is logged in, checking + to see if that identity is hers to claim, and verifying with the user that + she does consent to releasing that information to the party making the + request. + + Examine the properties of the L{CheckIDRequest} object, optionally + check L{CheckIDRequest.returnToVerified}, and and when you've come + to a decision, form a response by calling L{CheckIDRequest.answer}. + + Other types of requests relate to establishing associations between client + and server and verifying the authenticity of previous communications. + L{Server} contains all the logic and data necessary to respond to + such requests; just pass the request to L{Server.handleRequest}. + + +OpenID Extensions +================= + + Do you want to provide other information for your users + in addition to authentication? Version 2.0 of the OpenID + protocol allows consumers to add extensions to their requests. + For example, with sites using the U{Simple Registration + Extension}, + a user can agree to have their nickname and e-mail address sent to a + site when they sign up. + + Since extensions do not change the way OpenID authentication works, + code to handle extension requests may be completely separate from the + L{OpenIDRequest} class here. But you'll likely want data sent back by + your extension to be signed. L{OpenIDResponse} provides methods with + which you can add data to it which can be signed with the other data in + the OpenID signature. + + For example:: + + # when request is a checkid_* request + response = request.answer(True) + # this will a signed 'openid.sreg.timezone' parameter to the response + # as well as a namespace declaration for the openid.sreg namespace + response.fields.setArg('http://openid.net/sreg/1.0', 'timezone', 'America/Los_Angeles') + + There are helper modules for a number of extensions, including + L{Attribute Exchange}, + L{PAPE}, and + L{Simple Registration} in the L{openid.extensions} + package. + +Stores +====== + + The OpenID server needs to maintain state between requests in order + to function. Its mechanism for doing this is called a store. The + store interface is defined in C{L{openid.store.interface.OpenIDStore}}. + Additionally, several concrete store implementations are provided, so that + most sites won't need to implement a custom store. For a store backed + by flat files on disk, see C{L{openid.store.filestore.FileOpenIDStore}}. + For stores based on MySQL or SQLite, see the C{L{openid.store.sqlstore}} + module. + + +Upgrading +========= + +From 1.0 to 1.1 +--------------- + + The keys by which a server looks up associations in its store have changed + in version 1.2 of this library. If your store has entries created from + version 1.0 code, you should empty it. + +From 1.1 to 2.0 +--------------- + + One of the additions to the OpenID protocol was a specified nonce + format for one-way nonces. As a result, the nonce table in the store + has changed. You'll need to run contrib/upgrade-store-1.1-to-2.0 to + upgrade your store, or you'll encounter errors about the wrong number + of columns in the oid_nonces table. + + If you've written your own custom store or code that interacts + directly with it, you'll need to review the change notes in + L{openid.store.interface}. + +@group Requests: OpenIDRequest, AssociateRequest, CheckIDRequest, + CheckAuthRequest + +@group Responses: OpenIDResponse + +@group HTTP Codes: HTTP_OK, HTTP_REDIRECT, HTTP_ERROR + +@group Response Encodings: ENCODE_KVFORM, ENCODE_HTML_FORM, ENCODE_URL +""" + +import time +import warnings +import logging +from copy import deepcopy + +from openid import cryptutil +from openid import oidutil +from openid import kvform +from openid.dh import DiffieHellman +from openid.store.nonce import mkNonce +from openid.server.trustroot import TrustRoot, verifyReturnTo +from openid.association import Association, default_negotiator, getSecretSize +from openid.message import Message, InvalidOpenIDNamespace, \ + OPENID_NS, OPENID2_NS, IDENTIFIER_SELECT, OPENID1_URL_LIMIT +from openid.urinorm import urinorm + +HTTP_OK = 200 +HTTP_REDIRECT = 302 +HTTP_ERROR = 400 + +BROWSER_REQUEST_MODES = ['checkid_setup', 'checkid_immediate'] + +ENCODE_KVFORM = ('kvform', ) +ENCODE_URL = ('URL/redirect', ) +ENCODE_HTML_FORM = ('HTML form', ) + +UNUSED = None + + +class OpenIDRequest(object): + """I represent an incoming OpenID request. + + @cvar mode: the C{X{openid.mode}} of this request. + @type mode: str + """ + mode = None + + +class CheckAuthRequest(OpenIDRequest): + """A request to verify the validity of a previous response. + + @cvar mode: "X{C{check_authentication}}" + @type mode: str + + @ivar assoc_handle: The X{association handle} the response was signed with. + @type assoc_handle: str + @ivar signed: The message with the signature which wants checking. + @type signed: L{Message} + + @ivar invalidate_handle: An X{association handle} the client is asking + about the validity of. Optional, may be C{None}. + @type invalidate_handle: str + + @see: U{OpenID Specs, Mode: check_authentication + } + """ + mode = "check_authentication" + + required_fields = ["identity", "return_to", "response_nonce"] + + def __init__(self, assoc_handle, signed, invalidate_handle=None): + """Construct me. + + These parameters are assigned directly as class attributes, see + my L{class documentation} for their descriptions. + + @type assoc_handle: str + @type signed: L{Message} + @type invalidate_handle: str + """ + self.assoc_handle = assoc_handle + self.signed = signed + self.invalidate_handle = invalidate_handle + self.namespace = OPENID2_NS + + @classmethod + def fromMessage(klass, message, op_endpoint=UNUSED): + """Construct me from an OpenID Message. + + @param message: An OpenID check_authentication Message + @type message: L{openid.message.Message} + + @returntype: L{CheckAuthRequest} + """ + self = klass.__new__(klass) + self.message = message + self.namespace = message.getOpenIDNamespace() + self.assoc_handle = message.getArg(OPENID_NS, 'assoc_handle') + self.sig = message.getArg(OPENID_NS, 'sig') + + if (self.assoc_handle is None or self.sig is None): + fmt = "%s request missing required parameter from message %s" + raise ProtocolError(message, text=fmt % (self.mode, message)) + + self.invalidate_handle = message.getArg(OPENID_NS, 'invalidate_handle') + + self.signed = message.copy() + # openid.mode is currently check_authentication because + # that's the mode of this request. But the signature + # was made on something with a different openid.mode. + # http://article.gmane.org/gmane.comp.web.openid.general/537 + if self.signed.hasKey(OPENID_NS, "mode"): + self.signed.setArg(OPENID_NS, "mode", "id_res") + + return self + + def answer(self, signatory): + """Respond to this request. + + Given a L{Signatory}, I can check the validity of the signature and + the X{C{invalidate_handle}}. + + @param signatory: The L{Signatory} to use to check the signature. + @type signatory: L{Signatory} + + @returns: A response with an X{C{is_valid}} (and, if + appropriate X{C{invalidate_handle}}) field. + @returntype: L{OpenIDResponse} + """ + is_valid = signatory.verify(self.assoc_handle, self.signed) + # Now invalidate that assoc_handle so it this checkAuth message cannot + # be replayed. + signatory.invalidate(self.assoc_handle, dumb=True) + response = OpenIDResponse(self) + valid_str = (is_valid and "true") or "false" + response.fields.setArg(OPENID_NS, 'is_valid', valid_str) + + if self.invalidate_handle: + assoc = signatory.getAssociation( + self.invalidate_handle, dumb=False) + if not assoc: + response.fields.setArg(OPENID_NS, 'invalidate_handle', + self.invalidate_handle) + return response + + def __str__(self): + if self.invalidate_handle: + ih = " invalidate? %r" % (self.invalidate_handle, ) + else: + ih = "" + s = "<%s handle: %r sig: %r: signed: %r%s>" % ( + self.__class__.__name__, self.assoc_handle, self.sig, self.signed, + ih) + return s + + +class PlainTextServerSession(object): + """An object that knows how to handle association requests with no + session type. + + @cvar session_type: The session_type for this association + session. There is no type defined for plain-text in the OpenID + specification, so we use 'no-encryption'. + @type session_type: str + + @see: U{OpenID Specs, Mode: associate + } + @see: AssociateRequest + """ + session_type = 'no-encryption' + allowed_assoc_types = ['HMAC-SHA1', 'HMAC-SHA256'] + + def fromMessage(cls, unused_request): + return cls() + + fromMessage = classmethod(fromMessage) + + def answer(self, secret): + return {'mac_key': oidutil.toBase64(secret)} + + +class DiffieHellmanSHA1ServerSession(object): + """An object that knows how to handle association requests with the + Diffie-Hellman session type. + + @cvar session_type: The session_type for this association + session. + @type session_type: str + + @ivar dh: The Diffie-Hellman algorithm values for this request + @type dh: DiffieHellman + + @ivar consumer_pubkey: The public key sent by the consumer in the + associate request + @type consumer_pubkey: long + + @see: U{OpenID Specs, Mode: associate + } + @see: AssociateRequest + """ + session_type = 'DH-SHA1' + hash_func = staticmethod(cryptutil.sha1) + allowed_assoc_types = ['HMAC-SHA1'] + + def __init__(self, dh, consumer_pubkey): + self.dh = dh + self.consumer_pubkey = consumer_pubkey + + def fromMessage(cls, message): + """ + @param message: The associate request message + @type message: openid.message.Message + + @returntype: L{DiffieHellmanSHA1ServerSession} + + @raises ProtocolError: When parameters required to establish the + session are missing. + """ + dh_modulus = message.getArg(OPENID_NS, 'dh_modulus') + dh_gen = message.getArg(OPENID_NS, 'dh_gen') + if (dh_modulus is None and dh_gen is not None or dh_gen is None and + dh_modulus is not None): + + if dh_modulus is None: + missing = 'modulus' + else: + missing = 'generator' + + raise ProtocolError( + message, 'If non-default modulus or generator is ' + 'supplied, both must be supplied. Missing %s' % (missing, )) + + if dh_modulus or dh_gen: + dh_modulus = cryptutil.base64ToLong(dh_modulus) + dh_gen = cryptutil.base64ToLong(dh_gen) + dh = DiffieHellman(dh_modulus, dh_gen) + else: + dh = DiffieHellman.fromDefaults() + + consumer_pubkey = message.getArg(OPENID_NS, 'dh_consumer_public') + if consumer_pubkey is None: + raise ProtocolError(message, "Public key for DH-SHA1 session " + "not found in message %s" % (message, )) + + consumer_pubkey = cryptutil.base64ToLong(consumer_pubkey) + + return cls(dh, consumer_pubkey) + + fromMessage = classmethod(fromMessage) + + def answer(self, secret): + mac_key = self.dh.xorSecret(self.consumer_pubkey, secret, + self.hash_func) + return { + 'dh_server_public': cryptutil.longToBase64(self.dh.public), + 'enc_mac_key': oidutil.toBase64(mac_key), + } + + +class DiffieHellmanSHA256ServerSession(DiffieHellmanSHA1ServerSession): + session_type = 'DH-SHA256' + hash_func = staticmethod(cryptutil.sha256) + allowed_assoc_types = ['HMAC-SHA256'] + + +class AssociateRequest(OpenIDRequest): + """A request to establish an X{association}. + + @cvar mode: "X{C{check_authentication}}" + @type mode: str + + @ivar assoc_type: The type of association. The protocol currently only + defines one value for this, "X{C{HMAC-SHA1}}". + @type assoc_type: str + + @ivar session: An object that knows how to handle association + requests of a certain type. + + @see: U{OpenID Specs, Mode: associate + } + """ + + mode = "associate" + + session_classes = { + 'no-encryption': PlainTextServerSession, + 'DH-SHA1': DiffieHellmanSHA1ServerSession, + 'DH-SHA256': DiffieHellmanSHA256ServerSession, + } + + def __init__(self, session, assoc_type): + """Construct me. + + The session is assigned directly as a class attribute. See my + L{class documentation} for its description. + """ + super(AssociateRequest, self).__init__() + self.session = session + self.assoc_type = assoc_type + self.namespace = OPENID2_NS + + def fromMessage(klass, message, op_endpoint=UNUSED): + """Construct me from an OpenID Message. + + @param message: The OpenID associate request + @type message: openid.message.Message + + @returntype: L{AssociateRequest} + """ + if message.isOpenID1(): + session_type = message.getArg(OPENID_NS, 'session_type') + if session_type == 'no-encryption': + logging.warning( + 'Received OpenID 1 request with a no-encryption ' + 'assocaition session type. Continuing anyway.') + elif not session_type: + session_type = 'no-encryption' + else: + session_type = message.getArg(OPENID2_NS, 'session_type') + if session_type is None: + raise ProtocolError( + message, text="session_type missing from request") + + try: + session_class = klass.session_classes[session_type] + except KeyError: + raise ProtocolError(message, + "Unknown session type %r" % (session_type, )) + + try: + session = session_class.fromMessage(message) + except ValueError as why: + raise ProtocolError(message, 'Error parsing %s session: %s' % + (session_class.session_type, why)) + + assoc_type = message.getArg(OPENID_NS, 'assoc_type', 'HMAC-SHA1') + if assoc_type not in session.allowed_assoc_types: + fmt = 'Session type %s does not support association type %s' + raise ProtocolError(message, fmt % (session_type, assoc_type)) + + self = klass(session, assoc_type) + self.message = message + self.namespace = message.getOpenIDNamespace() + return self + + fromMessage = classmethod(fromMessage) + + def answer(self, assoc): + """Respond to this request with an X{association}. + + @param assoc: The association to send back. + @type assoc: L{openid.association.Association} + + @returns: A response with the association information, encrypted + to the consumer's X{public key} if appropriate. + @returntype: L{OpenIDResponse} + """ + response = OpenIDResponse(self) + response.fields.updateArgs(OPENID_NS, { + 'expires_in': str(assoc.expiresIn), + 'assoc_type': self.assoc_type, + 'assoc_handle': assoc.handle, + }) + response.fields.updateArgs(OPENID_NS, + self.session.answer(assoc.secret)) + + if not (self.session.session_type == 'no-encryption' and + self.message.isOpenID1()): + # The session type "no-encryption" did not have a name + # in OpenID v1, it was just omitted. + response.fields.setArg(OPENID_NS, 'session_type', + self.session.session_type) + + return response + + def answerUnsupported(self, + message, + preferred_association_type=None, + preferred_session_type=None): + """Respond to this request indicating that the association + type or association session type is not supported.""" + if self.message.isOpenID1(): + raise ProtocolError(self.message) + + response = OpenIDResponse(self) + response.fields.setArg(OPENID_NS, 'error_code', 'unsupported-type') + response.fields.setArg(OPENID_NS, 'error', message) + + if preferred_association_type: + response.fields.setArg(OPENID_NS, 'assoc_type', + preferred_association_type) + + if preferred_session_type: + response.fields.setArg(OPENID_NS, 'session_type', + preferred_session_type) + + return response + + +class CheckIDRequest(OpenIDRequest): + """A request to confirm the identity of a user. + + This class handles requests for openid modes X{C{checkid_immediate}} + and X{C{checkid_setup}}. + + @cvar mode: "X{C{checkid_immediate}}" or "X{C{checkid_setup}}" + @type mode: str + + @ivar immediate: Is this an immediate-mode request? + @type immediate: bool + + @ivar identity: The OP-local identifier being checked. + @type identity: str + + @ivar claimed_id: The claimed identifier. Not present in OpenID 1.x + messages. + @type claimed_id: str + + @ivar trust_root: "Are you Frank?" asks the checkid request. "Who wants + to know?" C{trust_root}, that's who. This URL identifies the party + making the request, and the user will use that to make her decision + about what answer she trusts them to have. Referred to as "realm" in + OpenID 2.0. + @type trust_root: str + + @ivar return_to: The URL to send the user agent back to to reply to this + request. + @type return_to: str + + @ivar assoc_handle: Provided in smart mode requests, a handle for a + previously established association. C{None} for dumb mode requests. + @type assoc_handle: str + """ + + def __init__(self, + identity, + return_to, + trust_root=None, + immediate=False, + assoc_handle=None, + op_endpoint=None, + claimed_id=None): + """Construct me. + + These parameters are assigned directly as class attributes, see + my L{class documentation} for their descriptions. + + @raises MalformedReturnURL: When the C{return_to} URL is not a URL. + """ + self.assoc_handle = assoc_handle + self.identity = identity + self.claimed_id = claimed_id or identity + self.return_to = return_to + self.trust_root = trust_root or return_to + self.op_endpoint = op_endpoint + assert self.op_endpoint is not None + if immediate: + self.immediate = True + self.mode = "checkid_immediate" + else: + self.immediate = False + self.mode = "checkid_setup" + + if self.return_to is not None and \ + not TrustRoot.parse(self.return_to): + raise MalformedReturnURL(None, self.return_to) + if not self.trustRootValid(): + raise UntrustedReturnURL(None, self.return_to, self.trust_root) + self.message = None + + def _getNamespace(self): + warnings.warn( + 'The "namespace" attribute of CheckIDRequest objects ' + 'is deprecated. Use "message.getOpenIDNamespace()" ' + 'instead', + DeprecationWarning, + stacklevel=2) + return self.message.getOpenIDNamespace() + + namespace = property(_getNamespace) + + def fromMessage(klass, message, op_endpoint): + """Construct me from an OpenID message. + + @raises ProtocolError: When not all required parameters are present + in the message. + + @raises MalformedReturnURL: When the C{return_to} URL is not a URL. + + @raises UntrustedReturnURL: When the C{return_to} URL is outside + the C{trust_root}. + + @param message: An OpenID checkid_* request Message + @type message: openid.message.Message + + @param op_endpoint: The endpoint URL of the server that this + message was sent to. + @type op_endpoint: str + + @returntype: L{CheckIDRequest} + """ + self = klass.__new__(klass) + self.message = message + self.op_endpoint = op_endpoint + mode = message.getArg(OPENID_NS, 'mode') + if mode == "checkid_immediate": + self.immediate = True + self.mode = "checkid_immediate" + else: + self.immediate = False + self.mode = "checkid_setup" + + self.return_to = message.getArg(OPENID_NS, 'return_to') + if message.isOpenID1() and not self.return_to: + fmt = "Missing required field 'return_to' from %r" + raise ProtocolError(message, text=fmt % (message, )) + + self.identity = message.getArg(OPENID_NS, 'identity') + self.claimed_id = message.getArg(OPENID_NS, 'claimed_id') + if message.isOpenID1(): + if self.identity is None: + s = "OpenID 1 message did not contain openid.identity" + raise ProtocolError(message, text=s) + else: + if self.identity and not self.claimed_id: + s = ("OpenID 2.0 message contained openid.identity but not " + "claimed_id") + raise ProtocolError(message, text=s) + elif self.claimed_id and not self.identity: + s = ("OpenID 2.0 message contained openid.claimed_id but not " + "identity") + raise ProtocolError(message, text=s) + + # There's a case for making self.trust_root be a TrustRoot + # here. But if TrustRoot isn't currently part of the "public" API, + # I'm not sure it's worth doing. + + if message.isOpenID1(): + trust_root_param = 'trust_root' + else: + trust_root_param = 'realm' + + # Using 'or' here is slightly different than sending a default + # argument to getArg, as it will treat no value and an empty + # string as equivalent. + self.trust_root = (message.getArg(OPENID_NS, trust_root_param) or + self.return_to) + + if not message.isOpenID1(): + if self.return_to is self.trust_root is None: + raise ProtocolError( + message, + "openid.realm required when " + "openid.return_to absent") + + self.assoc_handle = message.getArg(OPENID_NS, 'assoc_handle') + + # Using TrustRoot.parse here is a bit misleading, as we're not + # parsing return_to as a trust root at all. However, valid URLs + # are valid trust roots, so we can use this to get an idea if it + # is a valid URL. Not all trust roots are valid return_to URLs, + # however (particularly ones with wildcards), so this is still a + # little sketchy. + if self.return_to is not None and \ + not TrustRoot.parse(self.return_to): + raise MalformedReturnURL(message, self.return_to) + + # I first thought that checking to see if the return_to is within + # the trust_root is premature here, a logic-not-decoding thing. But + # it was argued that this is really part of data validation. A + # request with an invalid trust_root/return_to is broken regardless of + # application, right? + if not self.trustRootValid(): + raise UntrustedReturnURL(message, self.return_to, self.trust_root) + + return self + + fromMessage = classmethod(fromMessage) + + def idSelect(self): + """Is the identifier to be selected by the IDP? + + @returntype: bool + """ + # So IDPs don't have to import the constant + return self.identity == IDENTIFIER_SELECT + + def trustRootValid(self): + """Is my return_to under my trust_root? + + @returntype: bool + """ + if not self.trust_root: + return True + tr = TrustRoot.parse(self.trust_root) + if tr is None: + raise MalformedTrustRoot(self.message, self.trust_root) + + if self.return_to is not None: + return tr.validateURL(self.return_to) + else: + return True + + def returnToVerified(self): + """Does the relying party publish the return_to URL for this + response under the realm? It is up to the provider to set a + policy for what kinds of realms should be allowed. This + return_to URL verification reduces vulnerability to data-theft + attacks based on open proxies, cross-site-scripting, or open + redirectors. + + This check should only be performed after making sure that the + return_to URL matches the realm. + + @see: L{trustRootValid} + + @raises openid.yadis.discover.DiscoveryFailure: if the realm + URL does not support Yadis discovery (and so does not + support the verification process). + + @raises openid.fetchers.HTTPFetchingError: if the realm URL + is not reachable. When this is the case, the RP may be hosted + on the user's intranet. + + @returntype: bool + + @returns: True if the realm publishes a document with the + return_to URL listed + + @since: 2.1.0 + """ + return verifyReturnTo(self.trust_root, self.return_to) + + def answer(self, allow, server_url=None, identity=None, claimed_id=None): + """Respond to this request. + + @param allow: Allow this user to claim this identity, and allow the + consumer to have this information? + @type allow: bool + + @param server_url: DEPRECATED. Passing C{op_endpoint} to the + L{Server} constructor makes this optional. + + When an OpenID 1.x immediate mode request does not succeed, + it gets back a URL where the request may be carried out + in a not-so-immediate fashion. Pass my URL in here (the + fully qualified address of this server's endpoint, i.e. + C{http://example.com/server}), and I will use it as a base for the + URL for a new request. + + Optional for requests where C{CheckIDRequest.immediate} is C{False} + or C{allow} is C{True}. + + @type server_url: str + + @param identity: The OP-local identifier to answer with. Only for use + when the relying party requested identifier selection. + @type identity: str or None + + @param claimed_id: The claimed identifier to answer with, for use + with identifier selection in the case where the claimed identifier + and the OP-local identifier differ, i.e. when the claimed_id uses + delegation. + + If C{identity} is provided but this is not, C{claimed_id} will + default to the value of C{identity}. When answering requests + that did not ask for identifier selection, the response + C{claimed_id} will default to that of the request. + + This parameter is new in OpenID 2.0. + @type claimed_id: str or None + + @returntype: L{OpenIDResponse} + + @change: Version 2.0 deprecates C{server_url} and adds C{claimed_id}. + + @raises NoReturnError: when I do not have a return_to. + """ + assert self.message is not None + + if not self.return_to: + raise NoReturnToError + + if not server_url: + if not self.message.isOpenID1() and not self.op_endpoint: + # In other words, that warning I raised in Server.__init__? + # You should pay attention to it now. + raise RuntimeError("%s should be constructed with op_endpoint " + "to respond to OpenID 2.0 messages." % + (self, )) + server_url = self.op_endpoint + + if allow: + mode = 'id_res' + elif self.message.isOpenID1(): + if self.immediate: + mode = 'id_res' + else: + mode = 'cancel' + else: + if self.immediate: + mode = 'setup_needed' + else: + mode = 'cancel' + + response = OpenIDResponse(self) + + if claimed_id and self.message.isOpenID1(): + namespace = self.message.getOpenIDNamespace() + raise VersionError("claimed_id is new in OpenID 2.0 and not " + "available for %s" % (namespace, )) + + if allow: + if self.identity == IDENTIFIER_SELECT: + if not identity: + raise ValueError( + "This request uses IdP-driven identifier selection." + "You must supply an identifier in the response.") + response_identity = identity + response_claimed_id = claimed_id or identity + + elif self.identity: + if identity and (self.identity != identity): + normalized_request_identity = urinorm(self.identity) + normalized_answer_identity = urinorm(identity) + + if (normalized_request_identity != + normalized_answer_identity): + raise ValueError( + "Request was for identity %r, cannot reply " + "with identity %r" % (self.identity, identity)) + + # The "identity" value in the response shall always be + # the same as that in the request, otherwise the RP is + # likely to not validate the response. + response_identity = self.identity + response_claimed_id = self.claimed_id + else: + if identity: + raise ValueError( + "This request specified no identity and you " + "supplied %r" % (identity, )) + response_identity = None + + if self.message.isOpenID1() and response_identity is None: + raise ValueError( + "Request was an OpenID 1 request, so response must " + "include an identifier.") + + response.fields.updateArgs(OPENID_NS, { + 'mode': mode, + 'return_to': self.return_to, + 'response_nonce': mkNonce(), + }) + + if server_url: + response.fields.setArg(OPENID_NS, 'op_endpoint', server_url) + + if response_identity is not None: + response.fields.setArg(OPENID_NS, 'identity', + response_identity) + if self.message.isOpenID2(): + response.fields.setArg(OPENID_NS, 'claimed_id', + response_claimed_id) + else: + response.fields.setArg(OPENID_NS, 'mode', mode) + if self.immediate: + if self.message.isOpenID1() and not server_url: + raise ValueError("setup_url is required for allow=False " + "in OpenID 1.x immediate mode.") + # Make a new request just like me, but with immediate=False. + setup_request = self.__class__( + self.identity, + self.return_to, + self.trust_root, + immediate=False, + assoc_handle=self.assoc_handle, + op_endpoint=self.op_endpoint, + claimed_id=self.claimed_id) + + # XXX: This API is weird. + setup_request.message = self.message + + setup_url = setup_request.encodeToURL(server_url) + response.fields.setArg(OPENID_NS, 'user_setup_url', setup_url) + + return response + + def encodeToURL(self, server_url): + """Encode this request as a URL to GET. + + @param server_url: URL of the OpenID server to make this request of. + @type server_url: str + + @returntype: str + + @raises NoReturnError: when I do not have a return_to. + """ + if not self.return_to: + raise NoReturnToError + + # Imported from the alternate reality where these classes are used + # in both the client and server code, so Requests are Encodable too. + # That's right, code imported from alternate realities all for the + # love of you, id_res/user_setup_url. + q = { + 'mode': self.mode, + 'identity': self.identity, + 'claimed_id': self.claimed_id, + 'return_to': self.return_to + } + if self.trust_root: + if self.message.isOpenID1(): + q['trust_root'] = self.trust_root + else: + q['realm'] = self.trust_root + if self.assoc_handle: + q['assoc_handle'] = self.assoc_handle + + response = Message(self.message.getOpenIDNamespace()) + response.updateArgs(OPENID_NS, q) + return response.toURL(server_url) + + def getCancelURL(self): + """Get the URL to cancel this request. + + Useful for creating a "Cancel" button on a web form so that operation + can be carried out directly without another trip through the server. + + (Except you probably want to make another trip through the server so + that it knows that the user did make a decision. Or you could simulate + this method by doing C{.answer(False).encodeToURL()}) + + @returntype: str + @returns: The return_to URL with openid.mode = cancel. + + @raises NoReturnError: when I do not have a return_to. + """ + if not self.return_to: + raise NoReturnToError + + if self.immediate: + raise ValueError("Cancel is not an appropriate response to " + "immediate mode requests.") + + response = Message(self.message.getOpenIDNamespace()) + response.setArg(OPENID_NS, 'mode', 'cancel') + return response.toURL(self.return_to) + + def __repr__(self): + return '<%s id:%r im:%s tr:%r ah:%r>' % ( + self.__class__.__name__, self.identity, self.immediate, + self.trust_root, self.assoc_handle) + + +class OpenIDResponse(object): + """I am a response to an OpenID request. + + @ivar request: The request I respond to. + @type request: L{OpenIDRequest} + + @ivar fields: My parameters as a dictionary with each key mapping to + one value. Keys are parameter names with no leading "C{openid.}". + e.g. "C{identity}" and "C{mac_key}", never "C{openid.identity}". + @type fields: L{openid.message.Message} + + @ivar signed: The names of the fields which should be signed. + @type signed: list of str + """ + + # Implementer's note: In a more symmetric client/server + # implementation, there would be more types of OpenIDResponse + # object and they would have validated attributes according to the + # type of response. But as it is, Response objects in a server are + # basically write-only, their only job is to go out over the wire, + # so this is just a loose wrapper around OpenIDResponse.fields. + + def __init__(self, request): + """Make a response to an L{OpenIDRequest}. + + @type request: L{OpenIDRequest} + """ + self.request = request + self.fields = Message(request.namespace) + + def __str__(self): + return "%s for %s: %s" % (self.__class__.__name__, + self.request.__class__.__name__, self.fields) + + def toFormMarkup(self, form_tag_attrs=None): + """Returns the form markup for this response. + + @param form_tag_attrs: Dictionary of attributes to be added to + the form tag. 'accept-charset' and 'enctype' have defaults + that can be overridden. If a value is supplied for + 'action' or 'method', it will be replaced. + + @returntype: str + + @since: 2.1.0 + """ + return self.fields.toFormMarkup( + self.request.return_to, form_tag_attrs=form_tag_attrs) + + def toHTML(self, form_tag_attrs=None): + """Returns an HTML document that auto-submits the form markup + for this response. + + @returntype: str + + @see: toFormMarkup + + @since: 2.1.? + """ + return oidutil.autoSubmitHTML(self.toFormMarkup(form_tag_attrs)) + + def renderAsForm(self): + """Returns True if this response's encoding is + ENCODE_HTML_FORM. Convenience method for server authors. + + @returntype: bool + + @since: 2.1.0 + """ + return self.whichEncoding() == ENCODE_HTML_FORM + + def needsSigning(self): + """Does this response require signing? + + @returntype: bool + """ + return self.fields.getArg(OPENID_NS, 'mode') == 'id_res' + + # implements IEncodable + def whichEncoding(self): + """How should I be encoded? + + @returns: one of ENCODE_URL, ENCODE_HTML_FORM, or ENCODE_KVFORM. + + @change: 2.1.0 added the ENCODE_HTML_FORM response. + """ + if self.request.mode in BROWSER_REQUEST_MODES: + if self.fields.getOpenIDNamespace() == OPENID2_NS and \ + len(self.encodeToURL()) > OPENID1_URL_LIMIT: + return ENCODE_HTML_FORM + else: + return ENCODE_URL + else: + return ENCODE_KVFORM + + def encodeToURL(self): + """Encode a response as a URL for the user agent to GET. + + You will generally use this URL with a HTTP redirect. + + @returns: A URL to direct the user agent back to. + @returntype: str + """ + return self.fields.toURL(self.request.return_to) + + def addExtension(self, extension_response): + """ + Add an extension response to this response message. + + @param extension_response: An object that implements the + extension interface for adding arguments to an OpenID + message. + @type extension_response: L{openid.extension} + + @returntype: None + """ + extension_response.toMessage(self.fields) + + def encodeToKVForm(self): + """Encode a response in key-value colon/newline format. + + This is a machine-readable format used to respond to messages which + came directly from the consumer and not through the user agent. + + @see: OpenID Specs, + U{Key-Value Colon/Newline format} + + @returntype: str + """ + return self.fields.toKVForm() + + +class WebResponse(object): + """I am a response to an OpenID request in terms a web server understands. + + I generally come from an L{Encoder}, either directly or from + L{Server.encodeResponse}. + + @ivar code: The HTTP code of this response. + @type code: int + + @ivar headers: Headers to include in this response. + @type headers: dict + + @ivar body: The body of this response. + @type body: str + """ + + def __init__(self, code=HTTP_OK, headers=None, body=b""): + """Construct me. + + These parameters are assigned directly as class attributes, see + my L{class documentation} for their descriptions. + """ + self.code = code + if headers is not None: + self.headers = headers + else: + self.headers = {} + if isinstance(body, bytes): + body = str(body, encoding="utf-8") + self.body = body + + +class Signatory(object): + """I sign things. + + I also check signatures. + + All my state is encapsulated in an + L{OpenIDStore}, which means + I'm not generally pickleable but I am easy to reconstruct. + + @cvar SECRET_LIFETIME: The number of seconds a secret remains valid. + @type SECRET_LIFETIME: int + """ + + SECRET_LIFETIME = 14 * 24 * 60 * 60 # 14 days, in seconds + + # keys have a bogus server URL in them because the filestore + # really does expect that key to be a URL. This seems a little + # silly for the server store, since I expect there to be only one + # server URL. + _normal_key = 'http://localhost/|normal' + _dumb_key = 'http://localhost/|dumb' + + def __init__(self, store): + """Create a new Signatory. + + @param store: The back-end where my associations are stored. + @type store: L{openid.store.interface.OpenIDStore} + """ + assert store is not None + self.store = store + + def verify(self, assoc_handle, message): + """Verify that the signature for some data is valid. + + @param assoc_handle: The handle of the association used to sign the + data. + @type assoc_handle: str + + @param message: The signed message to verify + @type message: openid.message.Message + + @returns: C{True} if the signature is valid, C{False} if not. + @returntype: bool + """ + assoc = self.getAssociation(assoc_handle, dumb=True) + if not assoc: + logging.error("failed to get assoc with handle %r to verify " + "message %r" % (assoc_handle, message)) + return False + + try: + valid = assoc.checkMessageSignature(message) + except ValueError as ex: + logging.exception("Error in verifying %s with %s: %s" % + (message, assoc, ex)) + return False + return valid + + def sign(self, response): + """Sign a response. + + I take a L{OpenIDResponse}, create a signature for everything + in its L{signed} list, and return a new + copy of the response object with that signature included. + + @param response: A response to sign. + @type response: L{OpenIDResponse} + + @returns: A signed copy of the response. + @returntype: L{OpenIDResponse} + """ + signed_response = deepcopy(response) + assoc_handle = response.request.assoc_handle + if assoc_handle: + # normal mode + # disabling expiration check because even if the association + # is expired, we still need to know some properties of the + # association so that we may preserve those properties when + # creating the fallback association. + assoc = self.getAssociation( + assoc_handle, dumb=False, checkExpiration=False) + + if not assoc or assoc.expiresIn <= 0: + # fall back to dumb mode + signed_response.fields.setArg(OPENID_NS, 'invalidate_handle', + assoc_handle) + assoc_type = assoc and assoc.assoc_type or 'HMAC-SHA1' + if assoc and assoc.expiresIn <= 0: + # now do the clean-up that the disabled checkExpiration + # code didn't get to do. + self.invalidate(assoc_handle, dumb=False) + assoc = self.createAssociation( + dumb=True, assoc_type=assoc_type) + else: + # dumb mode. + assoc = self.createAssociation(dumb=True) + + try: + signed_response.fields = assoc.signMessage(signed_response.fields) + except kvform.KVFormError as err: + raise EncodingError(response, explanation=str(err)) + return signed_response + + def createAssociation(self, dumb=True, assoc_type='HMAC-SHA1'): + """Make a new association. + + @param dumb: Is this association for a dumb-mode transaction? + @type dumb: bool + + @param assoc_type: The type of association to create. Currently + there is only one type defined, C{HMAC-SHA1}. + @type assoc_type: str + + @returns: the new association. + @returntype: L{openid.association.Association} + """ + secret = cryptutil.getBytes(getSecretSize(assoc_type)) + uniq = oidutil.toBase64(cryptutil.getBytes(4)) + handle = '{%s}{%x}{%s}' % (assoc_type, int(time.time()), uniq) + + assoc = Association.fromExpiresIn(self.SECRET_LIFETIME, handle, secret, + assoc_type) + + if dumb: + key = self._dumb_key + else: + key = self._normal_key + self.store.storeAssociation(key, assoc) + return assoc + + def getAssociation(self, assoc_handle, dumb, checkExpiration=True): + """Get the association with the specified handle. + + @type assoc_handle: str + + @param dumb: Is this association used with dumb mode? + @type dumb: bool + + @returns: the association, or None if no valid association with that + handle was found. + @returntype: L{openid.association.Association} + """ + # Hmm. We've created an interface that deals almost entirely with + # assoc_handles. The only place outside the Signatory that uses this + # (and thus the only place that ever sees Association objects) is + # when creating a response to an association request, as it must have + # the association's secret. + + if assoc_handle is None: + raise ValueError("assoc_handle must not be None") + + if dumb: + key = self._dumb_key + else: + key = self._normal_key + assoc = self.store.getAssociation(key, assoc_handle) + if assoc is not None and assoc.expiresIn <= 0: + logging.info("requested %sdumb key %r is expired (by %s seconds)" % + ((not dumb) and 'not-' or '', assoc_handle, + assoc.expiresIn)) + if checkExpiration: + self.store.removeAssociation(key, assoc_handle) + assoc = None + return assoc + + def invalidate(self, assoc_handle, dumb): + """Invalidates the association with the given handle. + + @type assoc_handle: str + + @param dumb: Is this association used with dumb mode? + @type dumb: bool + """ + if dumb: + key = self._dumb_key + else: + key = self._normal_key + self.store.removeAssociation(key, assoc_handle) + + +class Encoder(object): + """I encode responses in to L{WebResponses}. + + If you don't like L{WebResponses}, you can do + your own handling of L{OpenIDResponses} with + L{OpenIDResponse.whichEncoding}, L{OpenIDResponse.encodeToURL}, and + L{OpenIDResponse.encodeToKVForm}. + """ + + responseFactory = WebResponse + + def encode(self, response): + """Encode a response to a L{WebResponse}. + + @raises EncodingError: When I can't figure out how to encode this + message. + """ + encode_as = response.whichEncoding() + if encode_as == ENCODE_KVFORM: + wr = self.responseFactory(body=response.encodeToKVForm()) + if isinstance(response, Exception): + wr.code = HTTP_ERROR + elif encode_as == ENCODE_URL: + location = response.encodeToURL() + wr = self.responseFactory( + code=HTTP_REDIRECT, headers={'location': location}) + elif encode_as == ENCODE_HTML_FORM: + wr = self.responseFactory(code=HTTP_OK, body=response.toHTML()) + else: + # Can't encode this to a protocol message. You should probably + # render it to HTML and show it to the user. + raise EncodingError(response) + return wr + + +class SigningEncoder(Encoder): + """I encode responses in to L{WebResponses}, signing them when required. + """ + + def __init__(self, signatory): + """Create a L{SigningEncoder}. + + @param signatory: The L{Signatory} I will make signatures with. + @type signatory: L{Signatory} + """ + self.signatory = signatory + + def encode(self, response): + """Encode a response to a L{WebResponse}, signing it first if appropriate. + + @raises EncodingError: When I can't figure out how to encode this + message. + + @raises AlreadySigned: When this response is already signed. + + @returntype: L{WebResponse} + """ + # the isinstance is a bit of a kludge... it means there isn't really + # an adapter to make the interfaces quite match. + if (not isinstance(response, Exception)) and response.needsSigning(): + if not self.signatory: + raise ValueError("Must have a store to sign this request: %s" % + (response, ), response) + if response.fields.hasKey(OPENID_NS, 'sig'): + raise AlreadySigned(response) + response = self.signatory.sign(response) + return super(SigningEncoder, self).encode(response) + + +class Decoder(object): + """I decode an incoming web request in to a L{OpenIDRequest}. + """ + + _handlers = { + 'checkid_setup': CheckIDRequest.fromMessage, + 'checkid_immediate': CheckIDRequest.fromMessage, + 'check_authentication': CheckAuthRequest.fromMessage, + 'associate': AssociateRequest.fromMessage, + } + + def __init__(self, server): + """Construct a Decoder. + + @param server: The server which I am decoding requests for. + (Necessary because some replies reference their server.) + @type server: L{Server} + """ + self.server = server + + def decode(self, query): + """I transform query parameters into an L{OpenIDRequest}. + + If the query does not seem to be an OpenID request at all, I return + C{None}. + + @param query: The query parameters as a dictionary with each + key mapping to one value. + @type query: dict + + @raises ProtocolError: When the query does not seem to be a valid + OpenID request. + + @returntype: L{OpenIDRequest} + """ + if not query: + return None + + try: + message = Message.fromPostArgs(query) + except InvalidOpenIDNamespace as err: + # It's useful to have a Message attached to a ProtocolError, so we + # override the bad ns value to build a Message out of it. Kinda + # kludgy, since it's made of lies, but the parts that aren't lies + # are more useful than a 'None'. + query = query.copy() + query['openid.ns'] = OPENID2_NS + message = Message.fromPostArgs(query) + raise ProtocolError(message, str(err)) + + mode = message.getArg(OPENID_NS, 'mode') + if not mode: + fmt = "No mode value in message %s" + raise ProtocolError(message, text=fmt % (message, )) + + handler = self._handlers.get(mode, self.defaultDecoder) + return handler(message, self.server.op_endpoint) + + def defaultDecoder(self, message, server): + """Called to decode queries when no handler for that mode is found. + + @raises ProtocolError: This implementation always raises + L{ProtocolError}. + """ + mode = message.getArg(OPENID_NS, 'mode') + fmt = "Unrecognized OpenID mode %r" + raise ProtocolError(message, text=fmt % (mode, )) + + +class Server(object): + """I handle requests for an OpenID server. + + Some types of requests (those which are not C{checkid} requests) may be + handed to my L{handleRequest} method, and I will take care of it and + return a response. + + For your convenience, I also provide an interface to L{Decoder.decode} + and L{SigningEncoder.encode} through my methods L{decodeRequest} and + L{encodeResponse}. + + All my state is encapsulated in an + L{OpenIDStore}, which means + I'm not generally pickleable but I am easy to reconstruct. + + Example:: + + oserver = Server(FileOpenIDStore(data_path), "http://example.com/op") + request = oserver.decodeRequest(query) + if request.mode in ['checkid_immediate', 'checkid_setup']: + if self.isAuthorized(request.identity, request.trust_root): + response = request.answer(True) + elif request.immediate: + response = request.answer(False) + else: + self.showDecidePage(request) + return + else: + response = oserver.handleRequest(request) + + webresponse = oserver.encode(response) + + @ivar signatory: I'm using this for associate requests and to sign things. + @type signatory: L{Signatory} + + @ivar decoder: I'm using this to decode things. + @type decoder: L{Decoder} + + @ivar encoder: I'm using this to encode things. + @type encoder: L{Encoder} + + @ivar op_endpoint: My URL. + @type op_endpoint: str + + @ivar negotiator: I use this to determine which kinds of + associations I can make and how. + @type negotiator: L{openid.association.SessionNegotiator} + """ + + def __init__(self, + store, + op_endpoint=None, + signatoryClass=Signatory, + encoderClass=SigningEncoder, + decoderClass=Decoder): + """A new L{Server}. + + @param store: The back-end where my associations are stored. + @type store: L{openid.store.interface.OpenIDStore} + + @param op_endpoint: My URL, the fully qualified address of this + server's endpoint, i.e. C{http://example.com/server} + @type op_endpoint: str + + @change: C{op_endpoint} is new in library version 2.0. It + currently defaults to C{None} for compatibility with + earlier versions of the library, but you must provide it + if you want to respond to any version 2 OpenID requests. + """ + self.store = store + self.signatory = signatoryClass(self.store) + self.encoder = encoderClass(self.signatory) + self.decoder = decoderClass(self) + self.negotiator = default_negotiator.copy() + + if not op_endpoint: + warnings.warn( + "%s.%s constructor requires op_endpoint parameter " + "for OpenID 2.0 servers" % + (self.__class__.__module__, self.__class__.__name__), + stacklevel=2) + self.op_endpoint = op_endpoint + + def handleRequest(self, request): + """Handle a request. + + Give me a request, I will give you a response. Unless it's a type + of request I cannot handle myself, in which case I will raise + C{NotImplementedError}. In that case, you can handle it yourself, + or add a method to me for handling that request type. + + @raises NotImplementedError: When I do not have a handler defined + for that type of request. + + @returntype: L{OpenIDResponse} + """ + handler = getattr(self, 'openid_' + request.mode, None) + if handler is not None: + return handler(request) + else: + raise NotImplementedError( + "%s has no handler for a request of mode %r." % + (self, request.mode)) + + def openid_check_authentication(self, request): + """Handle and respond to C{check_authentication} requests. + + @returntype: L{OpenIDResponse} + """ + return request.answer(self.signatory) + + def openid_associate(self, request): + """Handle and respond to C{associate} requests. + + @returntype: L{OpenIDResponse} + """ + # XXX: TESTME + assoc_type = request.assoc_type + session_type = request.session.session_type + if self.negotiator.isAllowed(assoc_type, session_type): + assoc = self.signatory.createAssociation( + dumb=False, assoc_type=assoc_type) + return request.answer(assoc) + else: + message = ('Association type %r is not supported with ' + 'session type %r' % (assoc_type, session_type)) + (preferred_assoc_type, preferred_session_type) = \ + self.negotiator.getAllowedType() + return request.answerUnsupported(message, preferred_assoc_type, + preferred_session_type) + + def decodeRequest(self, query): + """Transform query parameters into an L{OpenIDRequest}. + + If the query does not seem to be an OpenID request at all, I return + C{None}. + + @param query: The query parameters as a dictionary with each + key mapping to one value. + @type query: dict + + @raises ProtocolError: When the query does not seem to be a valid + OpenID request. + + @returntype: L{OpenIDRequest} + + @see: L{Decoder.decode} + """ + return self.decoder.decode(query) + + def encodeResponse(self, response): + """Encode a response to a L{WebResponse}, signing it first if appropriate. + + @raises EncodingError: When I can't figure out how to encode this + message. + + @raises AlreadySigned: When this response is already signed. + + @returntype: L{WebResponse} + + @see: L{SigningEncoder.encode} + """ + return self.encoder.encode(response) + + +class ProtocolError(Exception): + """A message did not conform to the OpenID protocol. + + @ivar message: The query that is failing to be a valid OpenID request. + @type message: openid.message.Message + """ + + def __init__(self, message, text=None, reference=None, contact=None): + """When an error occurs. + + @param message: The message that is failing to be a valid + OpenID request. + @type message: openid.message.Message + + @param text: A message about the encountered error. Set as C{args[0]}. + @type text: str + """ + self.openid_message = message + self.reference = reference + self.contact = contact + assert type(message) not in [str, str] + Exception.__init__(self, text) + + def getReturnTo(self): + """Get the return_to argument from the request, if any. + + @returntype: str + """ + if self.openid_message is None: + return None + else: + return self.openid_message.getArg(OPENID_NS, 'return_to') + + def hasReturnTo(self): + """Did this request have a return_to parameter? + + @returntype: bool + """ + return self.getReturnTo() is not None + + def toMessage(self): + """Generate a Message object for sending to the relying party, + after encoding. + """ + namespace = self.openid_message.getOpenIDNamespace() + reply = Message(namespace) + reply.setArg(OPENID_NS, 'mode', 'error') + reply.setArg(OPENID_NS, 'error', str(self)) + + if self.contact is not None: + reply.setArg(OPENID_NS, 'contact', str(self.contact)) + + if self.reference is not None: + reply.setArg(OPENID_NS, 'reference', str(self.reference)) + + return reply + + # implements IEncodable + + def encodeToURL(self): + return self.toMessage().toURL(self.getReturnTo()) + + def encodeToKVForm(self): + return self.toMessage().toKVForm() + + def toFormMarkup(self): + """Encode to HTML form markup for POST. + + @since: 2.1.0 + """ + return self.toMessage().toFormMarkup(self.getReturnTo()) + + def toHTML(self): + """Encode to a full HTML page, wrapping the form markup in a page + that will autosubmit the form. + + @since: 2.1.? + """ + return oidutil.autoSubmitHTML(self.toFormMarkup()) + + def whichEncoding(self): + """How should I be encoded? + + @returns: one of ENCODE_URL, ENCODE_KVFORM, or None. If None, + I cannot be encoded as a protocol message and should be + displayed to the user. + """ + if self.hasReturnTo(): + if self.openid_message.getOpenIDNamespace() == OPENID2_NS and \ + len(self.encodeToURL()) > OPENID1_URL_LIMIT: + return ENCODE_HTML_FORM + else: + return ENCODE_URL + + if self.openid_message is None: + return None + + mode = self.openid_message.getArg(OPENID_NS, 'mode') + if mode: + if mode not in BROWSER_REQUEST_MODES: + return ENCODE_KVFORM + + # According to the OpenID spec as of this writing, we are probably + # supposed to switch on request type here (GET versus POST) to figure + # out if we're supposed to print machine-readable or human-readable + # content at this point. GET/POST seems like a pretty lousy way of + # making the distinction though, as it's just as possible that the + # user agent could have mistakenly been directed to post to the + # server URL. + + # Basically, if your request was so broken that you didn't manage to + # include an openid.mode, I'm not going to worry too much about + # returning you something you can't parse. + return None + + +class VersionError(Exception): + """Raised when an operation was attempted that is not compatible with + the protocol version being used.""" + + +class NoReturnToError(Exception): + """Raised when a response to a request cannot be generated because + the request contains no return_to URL. + """ + pass + + +class EncodingError(Exception): + """Could not encode this as a protocol message. + + You should probably render it and show it to the user. + + @ivar response: The response that failed to encode. + @type response: L{OpenIDResponse} + """ + + def __init__(self, response, explanation=None): + Exception.__init__(self, response) + self.response = response + self.explanation = explanation + + def __str__(self): + if self.explanation: + s = '%s: %s' % (self.__class__.__name__, self.explanation) + else: + s = '%s for Response %s' % (self.__class__.__name__, self.response) + return s + + +class AlreadySigned(EncodingError): + """This response is already signed.""" + + +class UntrustedReturnURL(ProtocolError): + """A return_to is outside the trust_root.""" + + def __init__(self, message, return_to, trust_root): + ProtocolError.__init__(self, message) + self.return_to = return_to + self.trust_root = trust_root + + def __str__(self): + return "return_to %r not under trust_root %r" % (self.return_to, + self.trust_root) + + +class MalformedReturnURL(ProtocolError): + """The return_to URL doesn't look like a valid URL.""" + + def __init__(self, openid_message, return_to): + self.return_to = return_to + ProtocolError.__init__(self, openid_message) + + +class MalformedTrustRoot(ProtocolError): + """The trust root is not well-formed. + + @see: OpenID Specs, U{openid.trust_root} + """ + pass + + +#class IEncodable: # Interface +# def encodeToURL(return_to): +# """Encode a response as a URL for redirection. +# +# @returns: A URL to direct the user agent back to. +# @returntype: str +# """ +# pass +# +# def encodeToKvform(): +# """Encode a response in key-value colon/newline format. +# +# This is a machine-readable format used to respond to messages which +# came directly from the consumer and not through the user agent. +# +# @see: OpenID Specs, +# U{Key-Value Colon/Newline format} +# +# @returntype: str +# """ +# pass +# +# def whichEncoding(): +# """How should I be encoded? +# +# @returns: one of ENCODE_URL, ENCODE_KVFORM, or None. If None, +# I cannot be encoded as a protocol message and should be +# displayed to the user. +# """ +# pass diff --git a/flex/lib/python3.6/site-packages/openid/server/trustroot.py b/flex/lib/python3.6/site-packages/openid/server/trustroot.py new file mode 100644 index 0000000..84f08d6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/server/trustroot.py @@ -0,0 +1,454 @@ +# -*- test-case-name: openid.test.test_rpverify -*- +""" +This module contains the C{L{TrustRoot}} class, which helps handle +trust root checking. This module is used by the +C{L{openid.server.server}} module, but it is also available to server +implementers who wish to use it for additional trust root checking. + +It also implements relying party return_to URL verification, based on +the realm. +""" + +__all__ = [ + 'TrustRoot', + 'RP_RETURN_TO_URL_TYPE', + 'extractReturnToURLs', + 'returnToMatches', + 'verifyReturnTo', +] + +from openid import urinorm +from openid.yadis import services + +from urllib.parse import urlparse, urlunparse +import re +import logging + +############################################ +_protocols = ['http', 'https'] +_top_level_domains = [ + 'ac', 'ad', 'ae', 'aero', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', + 'ar', 'arpa', 'as', 'asia', 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', + 'be', 'bf', 'bg', 'bh', 'bi', 'biz', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', + 'bt', 'bv', 'bw', 'by', 'bz', 'ca', 'cat', 'cc', 'cd', 'cf', 'cg', 'ch', + 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 'com', 'coop', 'cr', 'cu', 'cv', 'cx', + 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 'ec', 'edu', 'ee', 'eg', + 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 'fo', 'fr', 'ga', 'gb', + 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 'gn', 'gov', 'gp', 'gq', + 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 'hr', 'ht', 'hu', + 'id', 'ie', 'il', 'im', 'in', 'info', 'int', 'io', 'iq', 'ir', 'is', 'it', + 'je', 'jm', 'jo', 'jobs', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', + 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', + 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mil', 'mk', 'ml', + 'mm', 'mn', 'mo', 'mobi', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'museum', + 'mv', 'mw', 'mx', 'my', 'mz', 'na', 'name', 'nc', 'ne', 'net', 'nf', 'ng', + 'ni', 'nl', 'no', 'np', 'nr', 'nu', 'nz', 'om', 'org', 'pa', 'pe', 'pf', + 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 'pro', 'ps', 'pt', 'pw', 'py', + 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 'sc', 'sd', 'se', 'sg', + 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 'sr', 'st', 'su', 'sv', + 'sy', 'sz', 'tc', 'td', 'tel', 'tf', 'tg', 'th', 'tj', 'tk', 'tl', 'tm', + 'tn', 'to', 'tp', 'tr', 'travel', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'uk', + 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', + 'xn--0zwm56d', 'xn--11b5bs3a9aj6g', 'xn--80akhbyknj4f', 'xn--9t4b11yi5a', + 'xn--deba0ad', 'xn--g6w251d', 'xn--hgbk6aj7f53bba', 'xn--hlcj6aya9esc7a', + 'xn--jxalpdlp', 'xn--kgbechtv', 'xn--zckzah', 'ye', 'yt', 'yu', 'za', 'zm', + 'zw' +] + +# Build from RFC3986, section 3.2.2. Used to reject hosts with invalid +# characters. +host_segment_re = re.compile( + r"(?:[-a-zA-Z0-9!$&'\(\)\*+,;=._~]|%[a-zA-Z0-9]{2})+$") + + +class RealmVerificationRedirected(Exception): + """Attempting to verify this realm resulted in a redirect. + + @since: 2.1.0 + """ + + def __init__(self, relying_party_url, rp_url_after_redirects): + self.relying_party_url = relying_party_url + self.rp_url_after_redirects = rp_url_after_redirects + + def __str__(self): + return ("Attempting to verify %r resulted in " + "redirect to %r" % (self.relying_party_url, + self.rp_url_after_redirects)) + + +def _parseURL(url): + try: + url = urinorm.urinorm(url) + except ValueError: + return None + proto, netloc, path, params, query, frag = urlparse(url) + if not path: + # Python <2.4 does not parse URLs with no path properly + if not query and '?' in netloc: + netloc, query = netloc.split('?', 1) + + path = '/' + + path = urlunparse(('', '', path, params, query, frag)) + + if ':' in netloc: + try: + host, port = netloc.split(':') + except ValueError: + return None + + if not re.match(r'\d+$', port): + return None + else: + host = netloc + port = '' + + host = host.lower() + if not host_segment_re.match(host): + return None + + return proto, host, port, path + + +class TrustRoot(object): + """ + This class represents an OpenID trust root. The C{L{parse}} + classmethod accepts a trust root string, producing a + C{L{TrustRoot}} object. The method OpenID server implementers + would be most likely to use is the C{L{isSane}} method, which + checks the trust root for given patterns that indicate that the + trust root is too broad or points to a local network resource. + + @sort: parse, isSane + """ + + def __init__(self, unparsed, proto, wildcard, host, port, path): + self.unparsed = unparsed + self.proto = proto + self.wildcard = wildcard + self.host = host + self.port = port + self.path = path + + def isSane(self): + """ + This method checks the to see if a trust root represents a + reasonable (sane) set of URLs. 'http://*.com/', for example + is not a reasonable pattern, as it cannot meaningfully specify + the site claiming it. This function attempts to find many + related examples, but it can only work via heuristics. + Negative responses from this method should be treated as + advisory, used only to alert the user to examine the trust + root carefully. + + + @return: Whether the trust root is sane + + @rtype: C{bool} + """ + + if self.host == 'localhost': + return True + + host_parts = self.host.split('.') + if self.wildcard: + assert host_parts[0] == '', host_parts + del host_parts[0] + + # If it's an absolute domain name, remove the empty string + # from the end. + if host_parts and not host_parts[-1]: + del host_parts[-1] + + if not host_parts: + return False + + # Do not allow adjacent dots + if '' in host_parts: + return False + + tld = host_parts[-1] + if tld not in _top_level_domains: + return False + + if len(host_parts) == 1: + return False + + if self.wildcard: + if len(tld) == 2 and len(host_parts[-2]) <= 3: + # It's a 2-letter tld with a short second to last segment + # so there needs to be more than two segments specified + # (e.g. *.co.uk is insane) + return len(host_parts) > 2 + + # Passed all tests for insanity. + return True + + def validateURL(self, url): + """ + Validates a URL against this trust root. + + + @param url: The URL to check + + @type url: C{str} + + + @return: Whether the given URL is within this trust root. + + @rtype: C{bool} + """ + + url_parts = _parseURL(url) + if url_parts is None: + return False + + proto, host, port, path = url_parts + + if proto != self.proto: + return False + + if port != self.port: + return False + + if '*' in host: + return False + + if not self.wildcard: + if host != self.host: + return False + elif ((not host.endswith(self.host)) and ('.' + host) != self.host): + return False + + if path != self.path: + path_len = len(self.path) + trust_prefix = self.path[:path_len] + url_prefix = path[:path_len] + + # must be equal up to the length of the path, at least + if trust_prefix != url_prefix: + return False + + # These characters must be on the boundary between the end + # of the trust root's path and the start of the URL's + # path. + if '?' in self.path: + allowed = '&' + else: + allowed = '?/' + + return (self.path[-1] in allowed or path[path_len] in allowed) + + return True + + def parse(cls, trust_root): + """ + This method creates a C{L{TrustRoot}} instance from the given + input, if possible. + + + @param trust_root: This is the trust root to parse into a + C{L{TrustRoot}} object. + + @type trust_root: C{str} + + + @return: A C{L{TrustRoot}} instance if trust_root parses as a + trust root, C{None} otherwise. + + @rtype: C{NoneType} or C{L{TrustRoot}} + """ + url_parts = _parseURL(trust_root) + if url_parts is None: + return None + + proto, host, port, path = url_parts + + # check for valid prototype + if proto not in _protocols: + return None + + # check for URI fragment + if path.find('#') != -1: + return None + + # extract wildcard if it is there + if host.find('*', 1) != -1: + # wildcard must be at start of domain: *.foo.com, not foo.*.com + return None + + if host.startswith('*'): + # Starts with star, so must have a dot after it (if a + # domain is specified) + if len(host) > 1 and host[1] != '.': + return None + + host = host[1:] + wilcard = True + else: + wilcard = False + + # we have a valid trust root + tr = cls(trust_root, proto, wilcard, host, port, path) + + return tr + + parse = classmethod(parse) + + def checkSanity(cls, trust_root_string): + """str -> bool + + is this a sane trust root? + """ + trust_root = cls.parse(trust_root_string) + if trust_root is None: + return False + else: + return trust_root.isSane() + + checkSanity = classmethod(checkSanity) + + def checkURL(cls, trust_root, url): + """quick func for validating a url against a trust root. See the + TrustRoot class if you need more control.""" + tr = cls.parse(trust_root) + return tr is not None and tr.validateURL(url) + + checkURL = classmethod(checkURL) + + def buildDiscoveryURL(self): + """Return a discovery URL for this realm. + + This function does not check to make sure that the realm is + valid. Its behaviour on invalid inputs is undefined. + + @rtype: str + + @returns: The URL upon which relying party discovery should be run + in order to verify the return_to URL + + @since: 2.1.0 + """ + if self.wildcard: + # Use "www." in place of the star + assert self.host.startswith('.'), self.host + www_domain = 'www' + self.host + return '%s://%s%s' % (self.proto, www_domain, self.path) + else: + return self.unparsed + + def __repr__(self): + return "TrustRoot(%r, %r, %r, %r, %r, %r)" % ( + self.unparsed, self.proto, self.wildcard, self.host, self.port, + self.path) + + def __str__(self): + return repr(self) + + +# The URI for relying party discovery, used in realm verification. +# +# XXX: This should probably live somewhere else (like in +# openid.consumer or openid.yadis somewhere) +RP_RETURN_TO_URL_TYPE = 'http://specs.openid.net/auth/2.0/return_to' + + +def _extractReturnURL(endpoint): + """If the endpoint is a relying party OpenID return_to endpoint, + return the endpoint URL. Otherwise, return None. + + This function is intended to be used as a filter for the Yadis + filtering interface. + + @see: C{L{openid.yadis.services}} + @see: C{L{openid.yadis.filters}} + + @param endpoint: An XRDS BasicServiceEndpoint, as returned by + performing Yadis dicovery. + + @returns: The endpoint URL or None if the endpoint is not a + relying party endpoint. + @rtype: str or NoneType + """ + if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE]): + return endpoint.uri + else: + return None + + +def returnToMatches(allowed_return_to_urls, return_to): + """Is the return_to URL under one of the supplied allowed + return_to URLs? + + @since: 2.1.0 + """ + + for allowed_return_to in allowed_return_to_urls: + # A return_to pattern works the same as a realm, except that + # it's not allowed to use a wildcard. We'll model this by + # parsing it as a realm, and not trying to match it if it has + # a wildcard. + + return_realm = TrustRoot.parse(allowed_return_to) + if ( # Parses as a trust root + return_realm is not None and + + # Does not have a wildcard + not return_realm.wildcard and + + # Matches the return_to that we passed in with it + return_realm.validateURL(return_to)): + return True + + # No URL in the list matched + return False + + +def getAllowedReturnURLs(relying_party_url): + """Given a relying party discovery URL return a list of return_to URLs. + + @since: 2.1.0 + """ + (rp_url_after_redirects, return_to_urls) = services.getServiceEndpoints( + relying_party_url, _extractReturnURL) + + if rp_url_after_redirects != relying_party_url: + # Verification caused a redirect + raise RealmVerificationRedirected(relying_party_url, + rp_url_after_redirects) + + return return_to_urls + + +# _vrfy parameter is there to make testing easier +def verifyReturnTo(realm_str, return_to, _vrfy=getAllowedReturnURLs): + """Verify that a return_to URL is valid for the given realm. + + This function builds a discovery URL, performs Yadis discovery on + it, makes sure that the URL does not redirect, parses out the + return_to URLs, and finally checks to see if the current return_to + URL matches the return_to. + + @raises DiscoveryFailure: When Yadis discovery fails + @returns: True if the return_to URL is valid for the realm + + @since: 2.1.0 + """ + realm = TrustRoot.parse(realm_str) + if realm is None: + # The realm does not parse as a URL pattern + return False + + try: + allowable_urls = _vrfy(realm.buildDiscoveryURL()) + except RealmVerificationRedirected as err: + logging.exception(str(err)) + return False + + if returnToMatches(allowable_urls, return_to): + return True + else: + logging.error("Failed to validate return_to %r for realm %r, was not " + "in %s" % (return_to, realm_str, allowable_urls)) + return False diff --git a/flex/lib/python3.6/site-packages/openid/sreg.py b/flex/lib/python3.6/site-packages/openid/sreg.py new file mode 100644 index 0000000..d665a5d --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/sreg.py @@ -0,0 +1,7 @@ +"""moved to L{openid.extensions.sreg}""" + +import warnings +warnings.warn("openid.sreg has moved to openid.extensions.sreg", + DeprecationWarning) + +from openid.extensions.sreg import * diff --git a/flex/lib/python3.6/site-packages/openid/store/__init__.py b/flex/lib/python3.6/site-packages/openid/store/__init__.py new file mode 100644 index 0000000..76509b5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/store/__init__.py @@ -0,0 +1,8 @@ +""" +This package contains the modules related to this library's use of +persistent storage. + +@sort: interface, filestore, sqlstore, memstore +""" + +__all__ = ['interface', 'filestore', 'sqlstore', 'memstore', 'nonce'] diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8710ee10d0bce18927f15c49834a2934f8777a39 GIT binary patch literal 421 zcmX|-O>V+45QRfZ_=&`}Yws$gf~iO=QdJcv=%%c)u}sDR3&%FLL4q6fD7{KAP>F4C z(NzZuj5N9_-`;{imVDK7YCFhR2-JK^JFNmp9E1Z`;2pxzI z#^Io0nohlO;h9JsgsXTd?nxza8os0TYjt+Ds822}x>vO5UeTg+L5t1>JtW%bQoQx! zLz^)$C1Xd!m168Oa!vg2Nq#-GPHr(f-Ev1OqX2`_%F*%aLA!O9k6;h`+-H+jme=`H zHo{tHI-^$F!F(eX3?6mqCSkOub{CdLj|avIErFhoQ+CRj)G{zOw7Wy+<$rIlt>f}z HlxF+~@X&@X literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/filestore.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/filestore.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2283f60896935c59e929cea189449cf85a1afe10 GIT binary patch literal 9933 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�SI!?c9ebIVB z=k|-%XbJB^+Pn=@KAm8|A7QpmFWG{(I2KRx*{z;^6;;2z3HPF#-(34vmg+5;HK@m2 z#R*OIGW|XgORL@}f5$x+MXN_Q&M-u;z1zDy?+gYh63JjN?+ym;g)Dww84RA6Joa~1 z2Lqv_!N9E2c9-bXrPCEU)$EvS^moCG{Y5RkA?T*1*0Ps+yS gcQF~bfhXn{-TP8|@hn>N&IgXk52;MI;;k(I8s6Dz5maXj`UPV31;(}~Ri zD_tJO&vEOo(FoRIf(vKB&z+9LY3vT%j%#|a4d_Ek=P-m%<;Yi2HNth;st1wP>hbrw!t;2J9o~o#wx^7-CFTqjBUb$b{OcaKK#KqHe9xLYP=DfD4(yy+T*#-vl{Wq8P z$IGRy7ueRZgf>3o6F%b>-C|q!L}taFY7Ns?c2u;KEWdt7EMD4i9W%{g?_+RPw}<_b z9kOK0V;S$)RLJ3W3ho2;0~5}Hg~8qb<~6J^Km^~)tCvcp>VKG(`w9VkDQ}Tw32dsAn^1@8y?0et07oHMu2 z)YinA@{6rQ_zw^|jdT92d%1){Z8tA)jt zs&GF|WEdaQTx4k%caoOBq+5&G`%N$R2C>eHNV)6|MpAct`|YM{BgQ+zeu}_Y_2CWt z_G{c0sfx$9@E7p$jU}XL`2r{M!~&-u@NmL_(|sF6Ge#Kw=uDldJ8{oI3VlayTBK&2 zvy06`_GW8BNY;TQMZEJRyZHQ&WgZ0}TKqQS$8bMzOusk24CR|)tbkPsPJpmL;khz! ze-UOQVz=}-{Mb5odBuYezjl$TV7>B}nu`x9;+7L6d%4<<{ zo{G_c#`P$=7mxL8IUfA(2p9bAS;+yv8>s=mzh@Ypml<9(Fmp}(N^GL9i5uc3`gL(jY@x4< z+hQC2h6v#7oqYW}qdW(sP2=9HRYkI*Re1exT876+6;d#Ge2^w5yK0me*A_x1bt^E8Ur<$G`Sv9y z72wxa3}2nPzhwDB8$ZSuN|rl<^#gSmw8m$!n&^#AQRoz^64#!=q#z~OlHo={uQR*> z6ia5mBC{!Y7#IR6>WuG}F<+j1z1>?_T(TSXT05z7a;%L+MgO09H!PEuEc`3hz6JLE zvUTsw?V0Gu0;*hS{1q*xFF;qF4Astz?SStoB>JAZOX6QBL4}OQ*o7LnQ*T1b;P-v? zjOGYBpg-|YD=ifY)!gkK>DBy^BY z$fxt>Qyj+>84$*grfvrD6v+GuH{f_@6@asYV#6mmAfr1*Mja!gF0GxokkM}-qt(LK z&i*e(%mVchHZ#Z0y}rEC>UZ=^*r?xALxqx}kSXFJca!v|PPLF+)uh+U+?H?5g^J+N z5oTy&O@?^&z!KX|19(8)?v!I=Y0Ux_;!2_o6Bj>XK-GlcfaEcbg@c)j;yx-069ZI- zVO*K|6K7KS6Oo{=QBQzSEHJ3S7WD-IgER#&e?ettDvEup@Q5^QrEXGV#F9X0R!vlN z)@>9=AWGe7*auONz6lvy9#o4r`5Fecs#wM8R&nN1^5{Nx(gZgHR#b1*4QdV7AojsE zh?Q3_mDCHO+QHY%S@{|3JL$Fw2;^3u&TNw29M^>Y{GW)Fs>@Y_{V0+bQFKK%Qm``e z;?1&6cEa^$5I0W{vwf#-S>1eoUaNnKx7X~KpstzX)%?GRmEGty9igGNY5jZD&~fu> z6p6GKMYaku;t|rz-%|4-HTS7$QS%8kkEq$B=4~{ak&){=ctI~UZ4-E&6L>*2@M^e& z^`IU!0#DJLW}rSp*Qwk823tl-Y&Mb5nPkiT^yp0XG9$lc)pc9u!Bbn;J)=A|LhCto KhpMv8KmHe-A8`Bt literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/nonce.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/nonce.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62f2fd1653deed5958142645986e8df3f23533c3 GIT binary patch literal 2593 zcma)8TW{Mo6ecNIb{xk|)2{8-J?LQpW}vP!G%FgT*cxQ%G7R&EChJ8Q;3*<)tJbX~ zUDtvA(sVy&e`P;GuY1a0*vobfJ!qYWgJ|vBeyri-y z)Ok4`cG4sec|912V0aY9LTNwF&cII6p!rH5Cz&4UFlyL&MQNF7*kYb~=E>>D=5FH* z2OPZj11Q`MRPN5Zt|negZAck?af2)pVGH<%BDnvER@t^5D3TjKq|hUosJ6T7 zW_~E$vS(*4hKas|J~Ymy`=Jubedkh0V(iST7dAJn zykRwl#Oz80<5zHIf6GUk+Y^tFc zpPvs#fSN=bgnaoQ3_1c`g@$u0js?adfi6Bk5 za@8OmMch3W?zzbT;sv0CG|nQSQ7`%~#j6n@>Q1p2-@@j?M;GPaU9#$Zy}-CjzJf96 z;&M1ZuKF9TmqaEh9J-LSOzGn^jf$O%O7UzUz-DO-zep)$bHUpszGCet(zoy(9X^lA zza_A6_;IIU8EDH50IOVqsZo~K;O5nq!;o9xfmbq=%xTxNGhgZ4>5ZVa2}8+Zx`9%V z3{4mcWs(g>*MTSwt5C2MctpXVG-=2LbzXvo+NNna_4GK3;<3rO3^VQDxoxT0;-ofsZ zLs@}!i8V}Om4p8aHk#|%*q3suy`q|fnU_-zC5SwMp|Iy;b%d(ODy&Mw^*SNp50CM$ zNT+cFO0x=HSUR!By^=BpqKq02zCk}7w2 z*blXmx8aCqAAY<4%$S~A*oLma%L3g9Gl@nn8IFxaB4G2%!#L%mNPK}i)JHHeDDRF{ Tqqiuy!fGX(*%YvQqrUkUv_93_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/sqlstore.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/sqlstore.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ed7de2111c995a36aed4fa5c2ab6dbd3c897a2f GIT binary patch literal 15005 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 bool + """ + try: + os.unlink(filename) + except OSError as why: + if why.errno == ENOENT: + # Someone beat us to it, but it's gone, so that's OK + return 0 + else: + raise + else: + # File was present + return 1 + + +def _ensureDir(dir_name): + """Create dir_name as a directory if it does not exist. If it + exists, make sure that it is, in fact, a directory. + + Can raise OSError + + str -> NoneType + """ + try: + os.makedirs(dir_name) + except OSError as why: + if why.errno != EEXIST or not os.path.isdir(dir_name): + raise + + +class FileOpenIDStore(OpenIDStore): + """ + This is a filesystem-based store for OpenID associations and + nonces. This store should be safe for use in concurrent systems + on both windows and unix (excluding NFS filesystems). There are a + couple race conditions in the system, but those failure cases have + been set up in such a way that the worst-case behavior is someone + having to try to log in a second time. + + Most of the methods of this class are implementation details. + People wishing to just use this store need only pay attention to + the C{L{__init__}} method. + + Methods of this object can raise OSError if unexpected filesystem + conditions, such as bad permissions or missing directories, occur. + """ + + def __init__(self, directory): + """ + Initializes a new FileOpenIDStore. This initializes the + nonce and association directories, which are subdirectories of + the directory passed in. + + @param directory: This is the directory to put the store + directories in. + + @type directory: C{str} + """ + # Make absolute + directory = os.path.normpath(os.path.abspath(directory)) + + self.nonce_dir = os.path.join(directory, 'nonces') + + self.association_dir = os.path.join(directory, 'associations') + + # Temp dir must be on the same filesystem as the assciations + # directory + self.temp_dir = os.path.join(directory, 'temp') + + self.max_nonce_age = 6 * 60 * 60 # Six hours, in seconds + + self._setup() + + def _setup(self): + """Make sure that the directories in which we store our data + exist. + + () -> NoneType + """ + _ensureDir(self.nonce_dir) + _ensureDir(self.association_dir) + _ensureDir(self.temp_dir) + + def _mktemp(self): + """Create a temporary file on the same filesystem as + self.association_dir. + + The temporary directory should not be cleaned if there are any + processes using the store. If there is no active process using + the store, it is safe to remove all of the files in the + temporary directory. + + () -> (file, str) + """ + fd, name = mkstemp(dir=self.temp_dir) + try: + file_obj = os.fdopen(fd, 'wb') + return file_obj, name + except: + _removeIfPresent(name) + raise + + def getAssociationFilename(self, server_url, handle): + """Create a unique filename for a given server url and + handle. This implementation does not assume anything about the + format of the handle. The filename that is returned will + contain the domain name from the server URL for ease of human + inspection of the data directory. + + (str, str) -> str + """ + if server_url.find('://') == -1: + raise ValueError('Bad server URL: %r' % server_url) + + proto, rest = server_url.split('://', 1) + domain = _filenameEscape(rest.split('/', 1)[0]) + url_hash = _safe64(server_url) + if handle: + handle_hash = _safe64(handle) + else: + handle_hash = '' + + filename = '%s-%s-%s-%s' % (proto, domain, url_hash, handle_hash) + + return os.path.join(self.association_dir, filename) + + def storeAssociation(self, server_url, association): + """Store an association in the association directory. + + (str, Association) -> NoneType + """ + association_s = association.serialize() # NOTE: UTF-8 encoded bytes + filename = self.getAssociationFilename(server_url, association.handle) + tmp_file, tmp = self._mktemp() + + try: + try: + tmp_file.write(association_s) + os.fsync(tmp_file.fileno()) + finally: + tmp_file.close() + + try: + os.rename(tmp, filename) + except OSError as why: + if why.errno != EEXIST: + raise + + # We only expect EEXIST to happen only on Windows. It's + # possible that we will succeed in unlinking the existing + # file, but not in putting the temporary file in place. + try: + os.unlink(filename) + except OSError as why: + if why.errno == ENOENT: + pass + else: + raise + + # Now the target should not exist. Try renaming again, + # giving up if it fails. + os.rename(tmp, filename) + except: + # If there was an error, don't leave the temporary file + # around. + _removeIfPresent(tmp) + raise + + def getAssociation(self, server_url, handle=None): + """Retrieve an association. If no handle is specified, return + the association with the latest expiration. + + (str, str or NoneType) -> Association or NoneType + """ + if handle is None: + handle = '' + + # The filename with the empty handle is a prefix of all other + # associations for the given server URL. + filename = self.getAssociationFilename(server_url, handle) + + if handle: + return self._getAssociation(filename) + else: + association_files = os.listdir(self.association_dir) + matching_files = [] + # strip off the path to do the comparison + name = os.path.basename(filename) + for association_file in association_files: + if association_file.startswith(name): + matching_files.append(association_file) + + matching_associations = [] + # read the matching files and sort by time issued + for name in matching_files: + full_name = os.path.join(self.association_dir, name) + association = self._getAssociation(full_name) + if association is not None: + matching_associations.append( + (association.issued, association)) + + matching_associations.sort() + + # return the most recently issued one. + if matching_associations: + (_, assoc) = matching_associations[-1] + return assoc + else: + return None + + def _getAssociation(self, filename): + try: + assoc_file = open(filename, 'rb') + except IOError as why: + if why.errno == ENOENT: + # No association exists for that URL and handle + return None + else: + raise + + try: + assoc_s = assoc_file.read() + finally: + assoc_file.close() + + try: + association = Association.deserialize(assoc_s) + except ValueError: + _removeIfPresent(filename) + return None + + # Clean up expired associations + if association.expiresIn == 0: + _removeIfPresent(filename) + return None + else: + return association + + def removeAssociation(self, server_url, handle): + """Remove an association if it exists. Do nothing if it does not. + + (str, str) -> bool + """ + assoc = self.getAssociation(server_url, handle) + if assoc is None: + return 0 + else: + filename = self.getAssociationFilename(server_url, handle) + return _removeIfPresent(filename) + + def useNonce(self, server_url, timestamp, salt): + """Return whether this nonce is valid. + + str -> bool + """ + if abs(timestamp - time.time()) > nonce.SKEW: + return False + + if server_url: + proto, rest = server_url.split('://', 1) + else: + # Create empty proto / rest values for empty server_url, + # which is part of a consumer-generated nonce. + proto, rest = '', '' + + domain = _filenameEscape(rest.split('/', 1)[0]) + url_hash = _safe64(server_url) + salt_hash = _safe64(salt) + + filename = '%08x-%s-%s-%s-%s' % (timestamp, proto, domain, url_hash, + salt_hash) + + filename = os.path.join(self.nonce_dir, filename) + try: + fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o200) + except OSError as why: + if why.errno == EEXIST: + return False + else: + raise + else: + os.close(fd) + return True + + def _allAssocs(self): + all_associations = [] + + association_filenames = [ + os.path.join(self.association_dir, filename) + for filename in os.listdir(self.association_dir) + ] + for association_filename in association_filenames: + try: + association_file = open(association_filename, 'rb') + except IOError as why: + if why.errno == ENOENT: + logging.exception("%s disappeared during %s._allAssocs" % ( + association_filename, self.__class__.__name__)) + else: + raise + else: + try: + assoc_s = association_file.read() + finally: + association_file.close() + + # Remove expired or corrupted associations + try: + association = Association.deserialize(assoc_s) + except ValueError: + _removeIfPresent(association_filename) + else: + all_associations.append( + (association_filename, association)) + + return all_associations + + def cleanup(self): + """Remove expired entries from the database. This is + potentially expensive, so only run when it is acceptable to + take time. + + () -> NoneType + """ + self.cleanupAssociations() + self.cleanupNonces() + + def cleanupAssociations(self): + removed = 0 + for assoc_filename, assoc in self._allAssocs(): + if assoc.expiresIn == 0: + _removeIfPresent(assoc_filename) + removed += 1 + return removed + + def cleanupNonces(self): + nonces = os.listdir(self.nonce_dir) + now = time.time() + + removed = 0 + # Check all nonces for expiry + for nonce_fname in nonces: + timestamp = nonce_fname.split('-', 1)[0] + timestamp = int(timestamp, 16) + if abs(timestamp - now) > nonce.SKEW: + filename = os.path.join(self.nonce_dir, nonce_fname) + _removeIfPresent(filename) + removed += 1 + return removed diff --git a/flex/lib/python3.6/site-packages/openid/store/interface.py b/flex/lib/python3.6/site-packages/openid/store/interface.py new file mode 100644 index 0000000..6377657 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/store/interface.py @@ -0,0 +1,198 @@ +""" +This module contains the definition of the C{L{OpenIDStore}} +interface. +""" + + +class OpenIDStore(object): + """ + This is the interface for the store objects the OpenID library + uses. It is a single class that provides all of the persistence + mechanisms that the OpenID library needs, for both servers and + consumers. + + @change: Version 2.0 removed the C{storeNonce}, C{getAuthKey}, and C{isDumb} + methods, and changed the behavior of the C{L{useNonce}} method + to support one-way nonces. It added C{L{cleanupNonces}}, + C{L{cleanupAssociations}}, and C{L{cleanup}}. + + @sort: storeAssociation, getAssociation, removeAssociation, + useNonce + """ + + def storeAssociation(self, server_url, association): + """ + This method puts a C{L{Association + }} object into storage, + retrievable by server URL and handle. + + + @param server_url: The URL of the identity server that this + association is with. Because of the way the server + portion of the library uses this interface, don't assume + there are any limitations on the character set of the + input string. In particular, expect to see unescaped + non-url-safe characters in the server_url field. + + @type server_url: C{str} + + + @param association: The C{L{Association + }} to store. + + @type association: C{L{Association + }} + + + @return: C{None} + + @rtype: C{NoneType} + """ + raise NotImplementedError + + def getAssociation(self, server_url, handle=None): + """ + This method returns an C{L{Association + }} object from storage that + matches the server URL and, if specified, handle. It returns + C{None} if no such association is found or if the matching + association is expired. + + If no handle is specified, the store may return any + association which matches the server URL. If multiple + associations are valid, the recommended return value for this + method is the one most recently issued. + + This method is allowed (and encouraged) to garbage collect + expired associations when found. This method must not return + expired associations. + + + @param server_url: The URL of the identity server to get the + association for. Because of the way the server portion of + the library uses this interface, don't assume there are + any limitations on the character set of the input string. + In particular, expect to see unescaped non-url-safe + characters in the server_url field. + + @type server_url: C{str} + + + @param handle: This optional parameter is the handle of the + specific association to get. If no specific handle is + provided, any valid association matching the server URL is + returned. + + @type handle: C{str} or C{NoneType} + + + @return: The C{L{Association + }} for the given identity + server. + + @rtype: C{L{Association }} or + C{NoneType} + """ + raise NotImplementedError + + def removeAssociation(self, server_url, handle): + """ + This method removes the matching association if it's found, + and returns whether the association was removed or not. + + + @param server_url: The URL of the identity server the + association to remove belongs to. Because of the way the + server portion of the library uses this interface, don't + assume there are any limitations on the character set of + the input string. In particular, expect to see unescaped + non-url-safe characters in the server_url field. + + @type server_url: C{str} + + + @param handle: This is the handle of the association to + remove. If there isn't an association found that matches + both the given URL and handle, then there was no matching + handle found. + + @type handle: C{str} + + + @return: Returns whether or not the given association existed. + + @rtype: C{bool} or C{int} + """ + raise NotImplementedError + + def useNonce(self, server_url, timestamp, salt): + """Called when using a nonce. + + This method should return C{True} if the nonce has not been + used before, and store it for a while to make sure nobody + tries to use the same value again. If the nonce has already + been used or the timestamp is not current, return C{False}. + + You may use L{openid.store.nonce.SKEW} for your timestamp window. + + @change: In earlier versions, round-trip nonces were used and + a nonce was only valid if it had been previously stored + with C{storeNonce}. Version 2.0 uses one-way nonces, + requiring a different implementation here that does not + depend on a C{storeNonce} call. (C{storeNonce} is no + longer part of the interface.) + + @param server_url: The URL of the server from which the nonce + originated. + + @type server_url: C{str} + + @param timestamp: The time that the nonce was created (to the + nearest second), in seconds since January 1 1970 UTC. + @type timestamp: C{int} + + @param salt: A random string that makes two nonces from the + same server issued during the same second unique. + @type salt: str + + @return: Whether or not the nonce was valid. + + @rtype: C{bool} + """ + raise NotImplementedError + + def cleanupNonces(self): + """Remove expired nonces from the store. + + Discards any nonce from storage that is old enough that its + timestamp would not pass L{useNonce}. + + This method is not called in the normal operation of the + library. It provides a way for store admins to keep + their storage from filling up with expired data. + + @return: the number of nonces expired. + @returntype: int + """ + raise NotImplementedError + + def cleanupAssociations(self): + """Remove expired associations from the store. + + This method is not called in the normal operation of the + library. It provides a way for store admins to keep + their storage from filling up with expired data. + + @return: the number of associations expired. + @returntype: int + """ + raise NotImplementedError + + def cleanup(self): + """Shortcut for C{L{cleanupNonces}()}, C{L{cleanupAssociations}()}. + + This method is not called in the normal operation of the + library. It provides a way for store admins to keep + their storage from filling up with expired data. + """ + return self.cleanupNonces(), self.cleanupAssociations() diff --git a/flex/lib/python3.6/site-packages/openid/store/memstore.py b/flex/lib/python3.6/site-packages/openid/store/memstore.py new file mode 100644 index 0000000..21e3e69 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/store/memstore.py @@ -0,0 +1,126 @@ +"""A simple store using only in-process memory.""" + +from openid.store import nonce + +import copy +import time + + +class ServerAssocs(object): + def __init__(self): + self.assocs = {} + + def set(self, assoc): + self.assocs[assoc.handle] = assoc + + def get(self, handle): + return self.assocs.get(handle) + + def remove(self, handle): + try: + del self.assocs[handle] + except KeyError: + return False + else: + return True + + def best(self): + """Returns association with the oldest issued date. + + or None if there are no associations. + """ + best = None + for assoc in list(self.assocs.values()): + if best is None or best.issued < assoc.issued: + best = assoc + return best + + def cleanup(self): + """Remove expired associations. + + @return: tuple of (removed associations, remaining associations) + """ + remove = [] + for handle, assoc in self.assocs.items(): + if assoc.expiresIn == 0: + remove.append(handle) + for handle in remove: + del self.assocs[handle] + return len(remove), len(self.assocs) + + +class MemoryStore(object): + """In-process memory store. + + Use for single long-running processes. No persistence supplied. + """ + + def __init__(self): + self.server_assocs = {} + self.nonces = {} + + def _getServerAssocs(self, server_url): + try: + return self.server_assocs[server_url] + except KeyError: + assocs = self.server_assocs[server_url] = ServerAssocs() + return assocs + + def storeAssociation(self, server_url, assoc): + assocs = self._getServerAssocs(server_url) + assocs.set(copy.deepcopy(assoc)) + + def getAssociation(self, server_url, handle=None): + assocs = self._getServerAssocs(server_url) + if handle is None: + return assocs.best() + else: + return assocs.get(handle) + + def removeAssociation(self, server_url, handle): + assocs = self._getServerAssocs(server_url) + return assocs.remove(handle) + + def useNonce(self, server_url, timestamp, salt): + if abs(timestamp - time.time()) > nonce.SKEW: + return False + + anonce = (str(server_url), int(timestamp), str(salt)) + if anonce in self.nonces: + return False + else: + self.nonces[anonce] = None + return True + + def cleanupNonces(self): + now = time.time() + expired = [] + for anonce in self.nonces.keys(): + if abs(anonce[1] - now) > nonce.SKEW: + # removing items while iterating over the set could be bad. + expired.append(anonce) + + for anonce in expired: + del self.nonces[anonce] + return len(expired) + + def cleanupAssociations(self): + remove_urls = [] + removed_assocs = 0 + for server_url, assocs in self.server_assocs.items(): + removed, remaining = assocs.cleanup() + removed_assocs += removed + if not remaining: + remove_urls.append(server_url) + + # Remove entries from server_assocs that had none remaining. + for server_url in remove_urls: + del self.server_assocs[server_url] + return removed_assocs + + def __eq__(self, other): + return ((self.server_assocs == other.server_assocs) and + (self.nonces == other.nonces)) + + def __ne__(self, other): + return not (self == other) diff --git a/flex/lib/python3.6/site-packages/openid/store/nonce.py b/flex/lib/python3.6/site-packages/openid/store/nonce.py new file mode 100644 index 0000000..06c1214 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/store/nonce.py @@ -0,0 +1,101 @@ +__all__ = [ + 'split', + 'mkNonce', + 'checkTimestamp', +] + +from openid import cryptutil +from time import strptime, strftime, gmtime, time +from calendar import timegm +import string + +NONCE_CHARS = string.ascii_letters + string.digits + +# Keep nonces for five hours (allow five hours for the combination of +# request time and clock skew). This is probably way more than is +# necessary, but there is not much overhead in storing nonces. +SKEW = 60 * 60 * 5 + +time_fmt = '%Y-%m-%dT%H:%M:%SZ' +time_str_len = len('0000-00-00T00:00:00Z') + + +def split(nonce_string): + """Extract a timestamp from the given nonce string + + @param nonce_string: the nonce from which to extract the timestamp + @type nonce_string: str + + @returns: A pair of a Unix timestamp and the salt characters + @returntype: (int, str) + + @raises ValueError: if the nonce does not start with a correctly + formatted time string + """ + timestamp_str = nonce_string[:time_str_len] + try: + timestamp = timegm(strptime(timestamp_str, time_fmt)) + except AssertionError: # Python 2.2 + timestamp = -1 + if timestamp < 0: + raise ValueError('time out of range') + return timestamp, nonce_string[time_str_len:] + + +def checkTimestamp(nonce_string, allowed_skew=SKEW, now=None): + """Is the timestamp that is part of the specified nonce string + within the allowed clock-skew of the current time? + + @param nonce_string: The nonce that is being checked + @type nonce_string: str + + @param allowed_skew: How many seconds should be allowed for + completing the request, allowing for clock skew. + @type allowed_skew: int + + @param now: The current time, as a Unix timestamp + @type now: int + + @returntype: bool + @returns: Whether the timestamp is correctly formatted and within + the allowed skew of the current time. + """ + try: + stamp, _ = split(nonce_string) + except ValueError: + return False + else: + if now is None: + now = time() + + # Time after which we should not use the nonce + past = now - allowed_skew + + # Time that is too far in the future for us to allow + future = now + allowed_skew + + # the stamp is not too far in the future and is not too far in + # the past + return past <= stamp <= future + + +def mkNonce(when=None): + """Generate a nonce with the current timestamp + + @param when: Unix timestamp representing the issue time of the + nonce. Defaults to the current time. + @type when: int + + @returntype: str + @returns: A string that should be usable as a one-way nonce + + @see: time + """ + salt = cryptutil.randomString(6, NONCE_CHARS) + if when is None: + t = gmtime() + else: + t = gmtime(when) + + time_str = strftime(time_fmt, t) + return time_str + salt diff --git a/flex/lib/python3.6/site-packages/openid/store/sqlstore.py b/flex/lib/python3.6/site-packages/openid/store/sqlstore.py new file mode 100644 index 0000000..2005ee5 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/store/sqlstore.py @@ -0,0 +1,509 @@ +""" +This module contains C{L{OpenIDStore}} implementations that use +various SQL databases to back them. + +Example of how to initialize a store database:: + +python -c 'from openid.store import sqlstore; import pysqlite2.dbapi2;' + 'sqlstore.SQLiteStore(pysqlite2.dbapi2.connect("cstore.db")).createTables()' +""" +import re +import time + +try: + import psycopg2 +except ImportError: + from psycopg2cffi import compat + compat.register() + +from openid.association import Association +from openid.store.interface import OpenIDStore +from openid.store import nonce + + +def _inTxn(func): + def wrapped(self, *args, **kwargs): + return self._callInTransaction(func, self, *args, **kwargs) + + if hasattr(func, '__name__'): + try: + wrapped.__name__ = func.__name__[4:] + except TypeError: + pass + + if hasattr(func, '__doc__'): + wrapped.__doc__ = func.__doc__ + + return wrapped + + +class SQLStore(OpenIDStore): + """ + This is the parent class for the SQL stores, which contains the + logic common to all of the SQL stores. + + The table names used are determined by the class variables + C{L{associations_table}} and + C{L{nonces_table}}. To change the name of the tables used, pass + new table names into the constructor. + + To create the tables with the proper schema, see the + C{L{createTables}} method. + + This class shouldn't be used directly. Use one of its subclasses + instead, as those contain the code necessary to use a specific + database. + + All methods other than C{L{__init__}} and C{L{createTables}} + should be considered implementation details. + + + @cvar associations_table: This is the default name of the table to + keep associations in + + @cvar nonces_table: This is the default name of the table to keep + nonces in. + + + @sort: __init__, createTables + """ + + associations_table = 'oid_associations' + nonces_table = 'oid_nonces' + + def __init__(self, conn, associations_table=None, nonces_table=None): + """ + This creates a new SQLStore instance. It requires an + established database connection be given to it, and it allows + overriding the default table names. + + + @param conn: This must be an established connection to a + database of the correct type for the SQLStore subclass + you're using. + + @type conn: A python database API compatible connection + object. + + + @param associations_table: This is an optional parameter to + specify the name of the table used for storing + associations. The default value is specified in + C{L{SQLStore.associations_table}}. + + @type associations_table: C{str} + + + @param nonces_table: This is an optional parameter to specify + the name of the table used for storing nonces. The + default value is specified in C{L{SQLStore.nonces_table}}. + + @type nonces_table: C{str} + """ + self.conn = conn + self.cur = None + self._statement_cache = {} + self._table_names = { + 'associations': associations_table or self.associations_table, + 'nonces': nonces_table or self.nonces_table, + } + self.max_nonce_age = 6 * 60 * 60 # Six hours, in seconds + + # DB API extension: search for "Connection Attributes .Error, + # .ProgrammingError, etc." in + # http://www.python.org/dev/peps/pep-0249/ + if (hasattr(self.conn, 'IntegrityError') and + hasattr(self.conn, 'OperationalError')): + self.exceptions = self.conn + + if not (hasattr(self.exceptions, 'IntegrityError') and + hasattr(self.exceptions, 'OperationalError')): + raise RuntimeError("Error using database connection module " + "(Maybe it can't be imported?)") + + def blobDecode(self, blob): + """Convert a blob as returned by the SQL engine into a str object. + + str -> str""" + return blob + + def blobEncode(self, s): + """Convert a str object into the necessary object for storing + in the database as a blob.""" + return s + + def _getSQL(self, sql_name): + try: + return self._statement_cache[sql_name] + except KeyError: + sql = getattr(self, sql_name) + sql %= self._table_names + self._statement_cache[sql_name] = sql + return sql + + def _execSQL(self, sql_name, *args): + sql = self._getSQL(sql_name) + + # Kludge because we have reports of postgresql not quoting + # arguments if they are passed in as unicode instead of str. + # Currently the strings in our tables just have ascii in them, + # so this ought to be safe. + def unicode_to_str(arg): + if isinstance(arg, str): + return str(arg) + else: + return arg + + str_args = list(map(unicode_to_str, args)) + self.cur.execute(sql, str_args) + + def __getattr__(self, attr): + # if the attribute starts with db_, use a default + # implementation that looks up the appropriate SQL statement + # as an attribute of this object and executes it. + if attr[:3] == 'db_': + sql_name = attr[3:] + '_sql' + + def func(*args): + return self._execSQL(sql_name, *args) + + setattr(self, attr, func) + return func + else: + raise AttributeError('Attribute %r not found' % (attr, )) + + def _callInTransaction(self, func, *args, **kwargs): + """Execute the given function inside of a transaction, with an + open cursor. If no exception is raised, the transaction is + comitted, otherwise it is rolled back.""" + # No nesting of transactions + self.conn.rollback() + + try: + self.cur = self.conn.cursor() + try: + ret = func(*args, **kwargs) + finally: + self.cur.close() + self.cur = None + except: + self.conn.rollback() + raise + else: + self.conn.commit() + + return ret + + def txn_createTables(self): + """ + This method creates the database tables necessary for this + store to work. It should not be called if the tables already + exist. + """ + self.db_create_nonce() + self.db_create_assoc() + + createTables = _inTxn(txn_createTables) + + def txn_storeAssociation(self, server_url, association): + """Set the association for the server URL. + + Association -> NoneType + """ + a = association + self.db_set_assoc(server_url, a.handle, + self.blobEncode(a.secret), a.issued, a.lifetime, + a.assoc_type) + + storeAssociation = _inTxn(txn_storeAssociation) + + def txn_getAssociation(self, server_url, handle=None): + """Get the most recent association that has been set for this + server URL and handle. + + str -> NoneType or Association + """ + if handle is not None: + self.db_get_assoc(server_url, handle) + else: + self.db_get_assocs(server_url) + + rows = self.cur.fetchall() + if len(rows) == 0: + return None + else: + associations = [] + for values in rows: + values = list(values) + values[1] = self.blobDecode(values[1]) + assoc = Association(*values) + if assoc.expiresIn == 0: + self.txn_removeAssociation(server_url, assoc.handle) + else: + associations.append((assoc.issued, assoc)) + + if associations: + associations.sort() + return associations[-1][1] + else: + return None + + getAssociation = _inTxn(txn_getAssociation) + + def txn_removeAssociation(self, server_url, handle): + """Remove the association for the given server URL and handle, + returning whether the association existed at all. + + (str, str) -> bool + """ + self.db_remove_assoc(server_url, handle) + return self.cur.rowcount > 0 # -1 is undefined + + removeAssociation = _inTxn(txn_removeAssociation) + + def txn_useNonce(self, server_url, timestamp, salt): + """Return whether this nonce is present, and if it is, then + remove it from the set. + + str -> bool""" + if abs(timestamp - time.time()) > nonce.SKEW: + return False + + try: + self.db_add_nonce(server_url, timestamp, salt) + except self.exceptions.IntegrityError: + # The key uniqueness check failed + return False + else: + # The nonce was successfully added + return True + + useNonce = _inTxn(txn_useNonce) + + def txn_cleanupNonces(self): + self.db_clean_nonce(int(time.time()) - nonce.SKEW) + return self.cur.rowcount + + cleanupNonces = _inTxn(txn_cleanupNonces) + + def txn_cleanupAssociations(self): + self.db_clean_assoc(int(time.time())) + return self.cur.rowcount + + cleanupAssociations = _inTxn(txn_cleanupAssociations) + + +class SQLiteStore(SQLStore): + """ + This is an SQLite-based specialization of C{L{SQLStore}}. + + To create an instance, see C{L{SQLStore.__init__}}. To create the + tables it will use, see C{L{SQLStore.createTables}}. + + All other methods are implementation details. + """ + + create_nonce_sql = """ + CREATE TABLE %(nonces)s ( + server_url VARCHAR, + timestamp INTEGER, + salt CHAR(40), + UNIQUE(server_url, timestamp, salt) + ); + """ + + create_assoc_sql = """ + CREATE TABLE %(associations)s + ( + server_url VARCHAR(2047), + handle VARCHAR(255), + secret BLOB(128), + issued INTEGER, + lifetime INTEGER, + assoc_type VARCHAR(64), + PRIMARY KEY (server_url, handle) + ); + """ + + set_assoc_sql = ('INSERT OR REPLACE INTO %(associations)s ' + '(server_url, handle, secret, issued, ' + 'lifetime, assoc_type) ' + 'VALUES (?, ?, ?, ?, ?, ?);') + get_assocs_sql = ('SELECT handle, secret, issued, lifetime, assoc_type ' + 'FROM %(associations)s WHERE server_url = ?;') + get_assoc_sql = ( + 'SELECT handle, secret, issued, lifetime, assoc_type ' + 'FROM %(associations)s WHERE server_url = ? AND handle = ?;') + + get_expired_sql = ('SELECT server_url ' + 'FROM %(associations)s WHERE issued + lifetime < ?;') + + remove_assoc_sql = ('DELETE FROM %(associations)s ' + 'WHERE server_url = ? AND handle = ?;') + + clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < ?;' + + add_nonce_sql = 'INSERT INTO %(nonces)s VALUES (?, ?, ?);' + + clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < ?;' + + def blobEncode(self, s): + return memoryview(s) + + def useNonce(self, *args, **kwargs): + # Older versions of the sqlite wrapper do not raise + # IntegrityError as they should, so we have to detect the + # message from the OperationalError. + try: + return super(SQLiteStore, self).useNonce(*args, **kwargs) + except self.exceptions.OperationalError as why: + if re.match('^columns .* are not unique$', str(why)): + return False + else: + raise + + +class MySQLStore(SQLStore): + """ + This is a MySQL-based specialization of C{L{SQLStore}}. + + Uses InnoDB tables for transaction support. + + To create an instance, see C{L{SQLStore.__init__}}. To create the + tables it will use, see C{L{SQLStore.createTables}}. + + All other methods are implementation details. + """ + + try: + import MySQLdb as exceptions + except ImportError: + exceptions = None + + create_nonce_sql = """ + CREATE TABLE %(nonces)s ( + server_url BLOB NOT NULL, + timestamp INTEGER NOT NULL, + salt CHAR(40) NOT NULL, + PRIMARY KEY (server_url(255), timestamp, salt) + ) + ENGINE=InnoDB; + """ + + create_assoc_sql = """ + CREATE TABLE %(associations)s + ( + server_url BLOB NOT NULL, + handle VARCHAR(255) NOT NULL, + secret BLOB NOT NULL, + issued INTEGER NOT NULL, + lifetime INTEGER NOT NULL, + assoc_type VARCHAR(64) NOT NULL, + PRIMARY KEY (server_url(255), handle) + ) + ENGINE=InnoDB; + """ + + set_assoc_sql = ('REPLACE INTO %(associations)s ' + 'VALUES (%%s, %%s, %%s, %%s, %%s, %%s);') + get_assocs_sql = ('SELECT handle, secret, issued, lifetime, assoc_type' + ' FROM %(associations)s WHERE server_url = %%s;') + get_expired_sql = ('SELECT server_url ' + 'FROM %(associations)s WHERE issued + lifetime < %%s;') + + get_assoc_sql = ( + 'SELECT handle, secret, issued, lifetime, assoc_type' + ' FROM %(associations)s WHERE server_url = %%s AND handle = %%s;') + remove_assoc_sql = ('DELETE FROM %(associations)s ' + 'WHERE server_url = %%s AND handle = %%s;') + + clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < %%s;' + + add_nonce_sql = 'INSERT INTO %(nonces)s VALUES (%%s, %%s, %%s);' + + clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < %%s;' + + +class PostgreSQLStore(SQLStore): + """ + This is a PostgreSQL-based specialization of C{L{SQLStore}}. + + To create an instance, see C{L{SQLStore.__init__}}. To create the + tables it will use, see C{L{SQLStore.createTables}}. + + All other methods are implementation details. + """ + exceptions = None + + create_nonce_sql = """ + CREATE TABLE %(nonces)s ( + server_url VARCHAR(2047) NOT NULL, + timestamp INTEGER NOT NULL, + salt CHAR(40) NOT NULL, + PRIMARY KEY (server_url, timestamp, salt) + ); + """ + + create_assoc_sql = """ + CREATE TABLE %(associations)s + ( + server_url VARCHAR(2047) NOT NULL, + handle VARCHAR(255) NOT NULL, + secret BYTEA NOT NULL, + issued INTEGER NOT NULL, + lifetime INTEGER NOT NULL, + assoc_type VARCHAR(64) NOT NULL, + PRIMARY KEY (server_url, handle), + CONSTRAINT secret_length_constraint CHECK (LENGTH(secret) <= 128) + ); + """ + + def db_set_assoc(self, server_url, handle, secret, issued, lifetime, + assoc_type): + """ + Set an association. This is implemented as a method because + REPLACE INTO is not supported by PostgreSQL (and is not + standard SQL). + """ + result = self.db_get_assoc(server_url, handle) + rows = self.cur.fetchall() + if len(rows): + # Update the table since this associations already exists. + return self.db_update_assoc(secret, issued, lifetime, assoc_type, + server_url, handle) + else: + # Insert a new record because this association wasn't + # found. + return self.db_new_assoc(server_url, handle, secret, issued, + lifetime, assoc_type) + + new_assoc_sql = ('INSERT INTO %(associations)s ' + 'VALUES (%%s, %%s, %%s, %%s, %%s, %%s);') + update_assoc_sql = ('UPDATE %(associations)s SET ' + 'secret = %%s, issued = %%s, ' + 'lifetime = %%s, assoc_type = %%s ' + 'WHERE server_url = %%s AND handle = %%s;') + get_assocs_sql = ('SELECT handle, secret, issued, lifetime, assoc_type' + ' FROM %(associations)s WHERE server_url = %%s;') + get_expired_sql = ('SELECT server_url ' + 'FROM %(associations)s WHERE issued + lifetime < %%s;') + + get_assoc_sql = ( + 'SELECT handle, secret, issued, lifetime, assoc_type' + ' FROM %(associations)s WHERE server_url = %%s AND handle = %%s;') + remove_assoc_sql = ('DELETE FROM %(associations)s ' + 'WHERE server_url = %%s AND handle = %%s;') + + clean_assoc_sql = 'DELETE FROM %(associations)s WHERE issued + lifetime < %%s;' + + add_nonce_sql = 'INSERT INTO %(nonces)s VALUES (%%s, %%s, %%s);' + + clean_nonce_sql = 'DELETE FROM %(nonces)s WHERE timestamp < %%s;' + + def blobEncode(self, blob): + from psycopg2 import Binary + + return Binary(blob) + + def blobDecode(self, blob): + return blob.tobytes() diff --git a/flex/lib/python3.6/site-packages/openid/urinorm.py b/flex/lib/python3.6/site-packages/openid/urinorm.py new file mode 100644 index 0000000..485245c --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/urinorm.py @@ -0,0 +1,161 @@ +import re + +from openid import codecutil # registers 'oid_percent_escape' encoding handler + +# from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt) +uri_pattern = r'^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?' +uri_re = re.compile(uri_pattern) + +# gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" +# +# sub-delims = "!" / "$" / "&" / "'" / "(" / ")" +# / "*" / "+" / "," / ";" / "=" +# +# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + +uri_illegal_char_re = re.compile("[^-A-Za-z0-9:/?#[\]@!$&'()*+,;=._~%]", + re.UNICODE) + +authority_pattern = r'^([^@]*@)?([^:]*)(:.*)?' +authority_re = re.compile(authority_pattern) + +pct_encoded_pattern = r'%([0-9A-Fa-f]{2})' +pct_encoded_re = re.compile(pct_encoded_pattern) + +_unreserved = [False] * 256 +for _ in range(ord('A'), ord('Z') + 1): + _unreserved[_] = True +for _ in range(ord('0'), ord('9') + 1): + _unreserved[_] = True +for _ in range(ord('a'), ord('z') + 1): + _unreserved[_] = True +_unreserved[ord('-')] = True +_unreserved[ord('.')] = True +_unreserved[ord('_')] = True +_unreserved[ord('~')] = True + + +def _pct_encoded_replace_unreserved(mo): + try: + i = int(mo.group(1), 16) + if _unreserved[i]: + return chr(i) + else: + return mo.group().upper() + + except ValueError: + return mo.group() + + +def _pct_encoded_replace(mo): + try: + return chr(int(mo.group(1), 16)) + except ValueError: + return mo.group() + + +def remove_dot_segments(path): + result_segments = [] + + while path: + if path.startswith('../'): + path = path[3:] + elif path.startswith('./'): + path = path[2:] + elif path.startswith('/./'): + path = path[2:] + elif path == '/.': + path = '/' + elif path.startswith('/../'): + path = path[3:] + if result_segments: + result_segments.pop() + elif path == '/..': + path = '/' + if result_segments: + result_segments.pop() + elif path == '..' or path == '.': + path = '' + else: + i = 0 + if path[0] == '/': + i = 1 + i = path.find('/', i) + if i == -1: + i = len(path) + result_segments.append(path[:i]) + path = path[i:] + + return ''.join(result_segments) + + +def urinorm(uri): + ''' + Normalize a URI + ''' + # TODO: use urllib.parse instead of these complex regular expressions + if isinstance(uri, bytes): + uri = str(uri, encoding='utf-8') + + uri = uri.encode('ascii', errors='oid_percent_escape').decode('utf-8') + # _escapeme_re.sub(_pct_escape_unicode, uri).encode('ascii').decode() + + illegal_mo = uri_illegal_char_re.search(uri) + if illegal_mo: + raise ValueError('Illegal characters in URI: %r at position %s' % + (illegal_mo.group(), illegal_mo.start())) + + uri_mo = uri_re.match(uri) + + scheme = uri_mo.group(2) + if scheme is None: + raise ValueError('No scheme specified') + + scheme = scheme.lower() + if scheme not in ('http', 'https'): + raise ValueError('Not an absolute HTTP or HTTPS URI: %r' % (uri, )) + + authority = uri_mo.group(4) + if authority is None: + raise ValueError('Not an absolute URI: %r' % (uri, )) + + authority_mo = authority_re.match(authority) + if authority_mo is None: + raise ValueError('URI does not have a valid authority: %r' % (uri, )) + + userinfo, host, port = authority_mo.groups() + + if userinfo is None: + userinfo = '' + + if '%' in host: + host = host.lower() + host = pct_encoded_re.sub(_pct_encoded_replace, host) + host = host.encode('idna').decode() + else: + host = host.lower() + + if port: + if (port == ':' or (scheme == 'http' and port == ':80') or + (scheme == 'https' and port == ':443')): + port = '' + else: + port = '' + + authority = userinfo + host + port + + path = uri_mo.group(5) + path = pct_encoded_re.sub(_pct_encoded_replace_unreserved, path) + path = remove_dot_segments(path) + if not path: + path = '/' + + query = uri_mo.group(6) + if query is None: + query = '' + + fragment = uri_mo.group(8) + if fragment is None: + fragment = '' + + return scheme + '://' + authority + path + query + fragment diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__init__.py b/flex/lib/python3.6/site-packages/openid/yadis/__init__.py new file mode 100644 index 0000000..ef806e8 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/__init__.py @@ -0,0 +1,17 @@ +#-*- coding: utf-8 -*- + +__all__ = [ + 'constants', + 'discover', + 'etxrd', + 'filters', + 'manager', + 'parsehtml', + 'services', + 'xri', + 'xrires', +] + +version_info = (2, 0, 0) + +__version__ = ".".join(str(x) for x in version_info) diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..317887a4261866f5b5f5b499339b5fb918edd034 GIT binary patch literal 522 zcmY*VO>Yx15VdzVAKN6QNEQEK4~YW_sFA9Q1Lt0fI9Ni~vg4#~H}+=iMoCpp?VsVV z@DqIH)W1-1U=~pX&w6i0W9#XSXQR@7_lH2TR8DlJ*x$ z8Eb-4lV<;vpRTmky9Uo*w!zyiGKNDc=S7McRqQ-Vk;R@;WnC*3hxCWuI7?Pb7qcs8 kY#b={JXOC}`bP$268ooX2RF4oYkmDONy7g>-84&n0n~$sFaQ7m literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/accept.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/accept.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02976caf9a8eb8163350ffd22c70dcfa3af6322e GIT binary patch literal 3648 zcmbVPO>Y~=8Qz)w;BrN&PO7RY>NwLPg2aL%W!Q0JM^+m&@kam`1}xOAY*k)yht!JX zE;+lDC1N*+vX=t6^x9(!6li`y5%d@IC+x93<<_%KecoB>)9$4U%?D>^-g)<(=Xu`I zt@(Mc{?B0hFAIk8Z{xzl(f>J0vWhA&wvB)V{P)H-3(UYm%L6-b(3(LdaM4;p^_fxk zCeed&)K0@VN<=4CqAMe*{4|WZ!jA$m^i@LrM_XG@#i#AI9H#fgzVrjB^n5QF4~MbR z^Ao8KrCJL@CEKYCL_3aB8KokU-8cQ%2L*I)rSGg&5Y+(bSL94(dKr_GkeN%JF^4cvy#uynmwkTruf#(UNWZacPzl$ z4qGy?mK|7`nOPlJWX8yQZeYZo8jSXIj_za(dp2R&&apaRJ+A(gIap)q`Cn2$a5B8> zaIy^>Up_a2%7K$PQ-&zIlLrsWxJg7!_&QFrFo{FIKbD>+@VrP;B@pCQv<=be$9`Jh z5o???#C-xrtD{=CdN(6A zx_x&&m;{r%$?cDOx9<+_-nqSwryI4lTElR-wl@y@!P-YrTYa3a!_;xQA4j*=V)z#Z ztyy#I2Vv4GW7-@Z7mG89$`X!`G#DK?lYB@qvn}Sa8nc+gUFNcDtj0b5F8UT*!0#Gg z;_ADY*QO}bQ$v|Y`2|YyFH|LQGULDq%qgG&pfLc2AAPNX^5+~NBWh(PW&nnI2EcN_ z0>B}+gGy%iD(Z{Tjm*Xj&}~Yvb@Iy5R%QWek8(GtW^T_>uQC@iszg2l%}@ zUI4^BSetulJU2$KEF(30p86{TXqoXFjQkn}Ae_tRG7hNDWlmPydOrie1KLwdHm#aLC|TG-DJF{e`&CjzKq6zcY2K3su#qsArSf%@dUJZQ4-}M zTlDL*vm?z4LJ8!aq4Mo7tc?`>{<0i!dwAR%@##90>RFdiz}R}m={>AZ4L6%{3s9}-!w z6}A>db&ZCdlGVeY)aE@y;8Evy4?q1C`+}g7zL; z9UdcII^W**jz# z`c3bjQ=W3MZ}n(TOVBoi!aLvr@{`V5PB}NTASr*RJVh&iVC0pkR_=&g%Hdr_eFecW z1C~|ZGqP${$-IuvagZJMQzNgAzJheP2bPu$eP77jnPgP_uMvia;h~H~x-W&2$+)k% zPw_7Wfu2Dkk%-q}x=(MyGC5w=iex4h z50Fvikw1U}-7Do|b8~YhUE11xeT})q-_?~AsA}z}gMLHk?ywJ}QyN`weza289+R1Y zpSJh6$eq=5{~F>&xn3_?X^3-c^dlVjUD-Mq%I?ZeY3ap<{V)SbzTObCu~Qq^x4z?* zA3z(51XAoHI`W=aeTx#p2?*Lw{en==ZWP*iEe={K3icEALty!mFc7$}3WEh7z(yq@=(*1ZUH4DNkFw&P0%@2&Ku|Pdz)m|?ZO=RM}^sysrrBnoXJIfZo5`S zd{?8P@b>*fIqO#f+3^9tZBN_ZC6(H)mVMHyGx5-#ZqJv7WUxzOy?rfKxTfeuDHcjq zD$eCkDN57&=%3IRkp>$J7Iz>S7G#BF#6#<{_xO+Xh=&_S4Wd%RuO-|xT&9{>b0#Vz z$ds+Ls0egGe7armltC^SC<51$V$Ql2(_F(~k zKBa%iL#VH*uNB=hmGD(OhM(s+AW#7x5@Y^j^*{Sq9h?Cu6}dv{k~T+jE_?=M)?045q1?#3Vv#(lX- Qfl4U*TBiGfyXvn07eqyh{r~^~ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/constants.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/constants.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b000e1feb73ae8f25ac8a31970f9e17c10e15c4 GIT binary patch literal 519 zcmY*VJ#X7E5Tz*D`E-IN>5ni9nQrYCF;K)6Yym0Ix+Gqdm7#qEAKx7hxOdlUwRYP-AbxZZdPM)AVe|)c%QuDqfdhE%#t2;S zxRc`;I|v%F3(eSL7+P?^JlLDzZ*JUx{V8f6RxhrD@yBU$9tPv^GMNOQ!|JF`jlN8x za1tfa^+i}6*STOc3NNDP>ffd(YCF}*oC&6=WVGOysWY61ZvBW z(c4zz;zy0NJ2-jrD;QrMDf=sQw-R$E$YB31TaMj{VX zky*jbL?xLi(z|^v)Lnnjg%zv@W&iZHKgb8~`dx#$AQ6$7^uOL`9Q?N;Rj0apN90o9 t6r$(XM#%HC@k7!=l$r`%cI2|!exDa`&)J!ERvsAIb@s96cpi4_`3v{Ck3Rqa literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/discover.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/discover.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d6351e5807e7e08e6289186cd7a7e39734f028c GIT binary patch literal 3944 zcmaJ^&yU;273Po>MQOEKt?jzDn--G*NvhWSqp@Sy+x+l$y|y>76~o@Zv5G?AYDOz+ zDN-Je)|Q~XRJj)Lv425;UV7+XQS{J5kG%G@{R4V1dg%9tlCpN1Qs8hnoO$!!oA-V1 z>Duygx&AlN{PT}3>z~${%SHb-?sSYsSb_=r&}mx25pKl#cGG6mFGNn?ZMvrKMTNfC z^!mkSvF|s1GggdB{c^M1UurHf>z*ZiVSi%@TUMq{a~ba?!SK#x74IveEUa&=W=$-K z3ZAQC`H5ApX1)m1c5*CLwsL3I8$FikFw$9VF>p5sqoIvmRoD0R?C^xb3_i>%TKV{w1~;R70N)5i15M&~x}^c^(PYBFIpZNZw3u$wM<5Y`oh z4eJWZ!IbfD9tUA61%J_%F%P)eoexx^lXenusZ^qP(ryn`%EOrJuH|>wcP^v0~{>f6Sc)U(G1}Z!bwA?rj)J7+X1U$XbQ5)Beeif_d z*Kci#u^8V)ky^&iw0lZXYOx9%Uw%6h*5}sLJ+dqg`Vy9vTdjT~h7t8Et=98l z5Y4_6TP=~aX*d)z)}bD?hAJ{U8AH!B=dhcmNfd;K*eHvl@bO$SF;Y?_YrM6S#5xG$)SN#K2LRwnhYzIcB&v^S=g-Jd zXF?;PVEXFuUZYJC=%OdxCN|3GOb{6@AQ16jj}P~7{$UV>V`(-?HO`gxpomKjda|v{ zWj?!7sZ$s>aJzAqp!YhMNru@L^h}30jh%U3`Iq$Sc?HqulnEEL--+9;cOo-57lem=L z07VBs*R-}@^u2}mNJ+X)@c#uJ4gXEq3Dc|xS`A=2C*>=S!p_gvv071a%05CwYc zu!|tisr`F~H*dmPMLhjpNqAF+x3XTsSL#(JtXEOrqFtU^>RTflBiJXGT7uL+LMp9a zNIh{dzc_JP)rn(vI3pSJRv@=FA&{1Xe<8l-PUfjp$06vSg93o{!|)gh1c+bHae8|Y zsG!dgom+e#7@kubP2q_9fWMGYv_|}az#!J=kVDUO0alV`7N40KBM~5i5K9m`hYSyt zCCLau9m?;t5sBHN zi##*8QAs^i@fM#uWmX)#L0GpHCD@cNvfUQnjz=?|B7o~|0L1|cK*P)x{9wdQWg*rX};4!n^|SD#h+4gCpX;R-+x5D$iXQR=tXYp2FHLU|7dfQ zw-X_AahbQ>rUVVfEOuo;$@TQF2;I}aqZ#iSQ6R!x6!G;CZdFPD>`_19@TTT)IBf&@ z>--)%v)S*f@f`AN1o`o2KP)uo$p0@5Wd1I6Q}3a9qrYnMn4)}{6^zeRiTYlyh{>o~ z2_Xp-4~fVeJxjtLWva{fa4TM7g5U z1J7Vb_o)?s;aYm-sQk)O|6$0TY%xQWW;5Q)XQ+SY-{@wTjQ?@Z&{tppG7svjKnmg8ASaro z(0NUg$KyPO%?ROPFd`Ha(g?J@Az~jgR49*$lmdVNK}6rK65E-A1(XvfNRgoq4x)S= z$iM&^qB>OX&!qo0PU5u#C~^c|_}z?Va~0DqU8APU;DQ9rLZE%<;I6w*Ny>Z}kYFYuRuGP8Q0nj(Gl zRQ}b8WQ>_GcaW8VETvPTt*jiTs2z0>x22+3&k6@4lx&$NB(Y6a`6>>nvrB(A)Dp?1 z=vGuD;%~|?sZaU2SsGE5*@6c$U&dGhKV-#O(eO6BKE#SH?({kut5R`Yc8PgT&GvjB zz^VXlS8Nv`vssPRTxzS{JGN(2?}FpmiX=4Z=Q7miC^k`_+hJlfYkK+L14Agyi}}mC zQEnaDkf}N~zR6uhT=Uj6WXDu?bw4sz=0;aAEvF(cuXuCTGRH=`oUw3Gfn@$C`M-+K X=tR>RnzCKN?}A-(YNgr-{?-2i4-W&+ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/etxrd.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/etxrd.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7736ddc9eb38ce98f68691ce0c3065b4a308f4b GIT binary patch literal 8150 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 R>g%gZt8c7cr+fd2{{ag44cPzy literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/manager.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/manager.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c89fe5b5a680fe45600a4c355fe6fe04c9e403f6 GIT binary patch literal 6286 zcmb_g%W~Yt6~$v7;E1GYO4iGdf^FH>DCAI%B$vffY`Q4PVq()SQHmwzfdZ#{NRSv{ z)WA@hsZmwR)Gn1(J|U}oLB1fHd;&IEWR+jYD(Bt?Ff%k+PEtV)8W^A-w{M?&&TZV7 zn`<=xCbs{2!7%<|OdJ*5ui;Ftp!m`dhA>~@l*W<0ZQ{QrZ2Y(Rza=VLMzh*R&GN8t zw}vzM7KJpnO<`;KJ`VAY)N$(wB^@+MdGG=(oIr7m2TW|eK!n}lDU!p>-mYB#3br2|Sa^Pw8!ZD3u;3?}aMI zN=VgeS21Hc=t^!_$C-SHVq}gC&lp)K&D0v%NA%wwRZv!hfwC$LOwQbF*0NblK+#ZM zUxnGsfM(0L6`D@w6xL^Fas#~sImjHZ-B(IRX|tMDFyD?M1C&h#nWJhb{%oijT>hn2 zdMY@Cn^q2ewbFs>q)JvgYURek$B}w=^Y(Qy6vNxe%})<*-tON1uG=?{Omf}u-3Re=TF`;pk5Uz2wF^(VN_PDq`n+jn zb|@qEN7GaedSHL(f;!CVo`)KqcMg9N8q2VTZ=P%%*PF79?BgY;5E|(P<-SiDj9da- zdEQ0T*Q1%Ix`HcC4R@v(?i1V$7U2pwL%{EgfRe6{Gm4Aqi5tpJ+A1A`+0cAXnN&R=>?4{J1G0KDAoF@{u7k>SrRZls zF5reZXrBXS*E*JCBW09BN{Y`nf5bb?l~R0MT6Rjf=JihY&ODo@{c{Hf zvU#@vIjkEaHQw2Weh|^Kz-V1qa(4#Mg#?ysod6BD$X>!t;mvyf^0-Hp4ld*E3zFO+`d_IotuWG_0$$)~smC07LqSJAZy+Uar_93TnOxJ~zn znF(+JD|@XU1sGm4@tC4>n<&en2`s~3QQC4J5n1P5b^EY+2NR%a!$R)0q%t%L{4f~u zTiqg=vJ%{~$_@mZ;0m+QHO7KhV`w)_ChrytFetpF33h6tl=OH>ztag`tinR90pIIR zS)yQN(Q_a$k)Sgu4Lv=o^NDJD&MJOC-P>O92AqWD%ba+dZBlqT=vq=7CCrnV-pB^o zIqEX$?Mr)PQ_h#O%i9~sh1gdFhVg{8(>9wGMLB_b14TA}_ul<49)I=7+j@Ne{>GDR zaU$=_U4Z(W&6JJQyVQIf;2hh;3*X=@vM;k{I%drpF5WGprp&o$QgjfRQHZbOOn!@k zIQ+lJYPR$H+?VFp*kE=C3j7mq>Bt3wUTKug$04vvgGUx+-yD5wL4JilCCLNBt-UO(mJ zy^K8#_fyv2U*FE-#!3T%P!V>zVf@}%1~TUd}`bia8sZ)!Bavq?GSVp zgePSelsk=gAbQI&F+e)#jiJ7ZNK)?dXYQ}^x&3C+7TvdwoRg`i9CWi;&l(`Kul2?# zn-^di6%X?N8c}m?C6WCXbt=@SmIMM&v&LWoJLj;->Y8QMcsr3n*my?AFqX|vlj#$J z4atr%=O7V-prD*ZiGL86;l(lZrW@WbrJ6#lMBq>HhY&bIqWcGvQD^~$*MKJA4?%`d z)9)Bn9CkpJ?WaK1dO@cwwaZF|aKgkkVOrlOR<-mlu~RorQnYOYkKtgMfi$?4cv8YMtn&V}W4 zb4@NIq9Ec4FKU|okD#v|E=Ae>I0on=&;N*hTD}6jC(M$gm&1wDOUfI{;R*Se@e;oN zxH3#6$4L2>cC^H%nT?fFOSseNH%U4|f#rIP{9{T@f$~S32~{&13+6==WO;6C^>g-a z(~{Fc-&N>b$C>;b1>rsCL~x^eFNI=L60&c1tn&cF_vXkU0fcUe%dB!j6{E&TL6 zNcR}_KVZbtbw3{E8^xCpcz66Er1UKZ7V$7=6y;-7rkEQiR3hkfutv}&4m)5c3GfvT zGVF>NK#5|+XS6#Bg#UtG{twDZj6er?okF{mJdS=&ttJD11yzozoq8Y7{z9urc{c^V zV@~Es)5#otFE;PRI3Inq-LQx#yLebFxjl8l|V-VJv^zu%(2bY4m z6T^LlSNQbzntPeg@L80vg2NoY1P+%10~}xD^ISvw>)hJbs+ZGBJB;|}kM4hQKahS} zp}ZN(Pvb$vUt%pcYgUX;(ic?d?_!Ed>$rCYWW@6>(={6qJ;TpN!JE4+f@JmpUn_7 z?)a;Vx1kkBGKeILN1EM%+S}|PU_9h3iZiV${n5Vo{l18y1n7X>?ZKc{ne=y{RAFNh zQKrnHY>Kk6=Cax}wUIEdB4w*Fj<=lOI?k1kSJ>C@9kMGQKe*co4p-S%eyx4AcIS@^ ztm+J50$;zU`gqVf}`z#+NzH8VT(+YKAOlxx`&iVWJ7HizP4wGx1^3b z%=JX)#)K&T^d5f zBDKg2QX6{|$}mXM88Xo8t(XV>n1-1<0kT#{RE^Z=#c6rmHKBged|F8c&k}GM$l(6g z71GK)GEH~%d89dGM!#&>x()rHb*x5G=U9!jp;Ft`r7_e_$kX*EoV^I)JQ1WOCQyTc zBzkgw`uWr88~I{B06S#|nl2dXXRMkgr7wAk>@36pYfa#+1&l4YC0=yxAGEg4OWgk6 zcFVlX9iX7Yr+5YB6rbiZC@YF)md}A^`Uk+8JA(yWN@u?$mpjEl{}70mL1G3_1S<*i z8JRolNZGFi`-AW~89oa$xatHxCl{jrURQYw&|}bpV(O0i`-G6K9Rv!XFpB%qUnEZ> zuTq>6*7IcxFvns+sC5u_yDSQx2ZB8d6puC`_u?r6ii}41A+T}`xC#yx(LjU@;7T*J zLy<_u#FtWp&tL<&U^I?EGm+%eJoI;j-&2$yUG!zES&xp`AnJw*fp!@60lqvA5``Cj zyB)Lux|mh*Tv^G+5R#9+Ym4mqBD*>Id#=2#rk`kBsQu!%z}P}<;rsxCj9>WBHDtpx z1D2@?-;I1oF5M#gfEu1t@AL71M_UhAZIzL|s1ipdJC$ygJ!(9>zu8=W)Yupg!F)Mq zReY0AH_?WsZ_bd9L;;Nwc!D->njk-;9;J{~_UYE+=14F!oi6x{C>h&ZWA3-}+9Ww? zV@z;y<&^x4D5q*UTU!_z#I-u}VM%bDyGzevezdxX3VBdgfm5xLj>js^*L=Os?ke(& zI&Ywp+)<6BKqeowOzp&aw0n8`&B^xjGu=YxIk*4l{in634>z`JUr@{fn0{YzRQk?Y zelRJ*d6u{9jJzjvnx8FB5bC`o%za7c*Gu)6BG8}VG4&)X$Ow<=Fxvx$N>f^d%)1nX znm_8g7R{WtooG58g2kccmDJ&6HN|`2@G0=Rm;e@9vT6gxkCLp9Effrm<%QfTEJE8F`V5qHw3(#m{h!c6;n7cMfrWW`^yi!&p^@n4^xs@a zue1?$bNY-6>BMDRq3knA^qbp@bx2y>1*iGD$Dm?Bg89Wt7xVG>QJnC@E zM-+;9xDA~*fZ2RgaiR(d6F30Vglt-~C}gXcw&9?3jEXg`!8mMvPOl)ivk}-ihHV`G z<`;}*#P^JOlethzeus=yn{T1fHlXY`@y{efmln2>K&P0+y%ZFdGTmLpK=Bb}WZ{DN zWbk28C^WM8LaT!y1Wvj{{mMznKgAldzaS+PJZr{0mT`Rf`~noC*$b~9v~z+WQrw|T z$)O}79LglRNwtqDTSJyk=cv^Ry4?|Skvt>Wlvsp+A0j2MA+z7H^zqNAIJRXM?V4KT z7{18$=8%-@{RtHbBYmOoXfKSzL(-|togjJZfi^VYa=3ly+R@BVhqI~r6K%g@X|k~A zh;MZ`h^`&ihZY<||CRQlFa+G=2C}Qhg5L>1QP&=5ku@xAYfrQ<>5am8VSQ^o1s_UJ zG;ZP+L-Q7KUr}zPuI`q9fYaxTINoDz#Gw~Qy>#JL0iJp;U<7c8Qt4un2bF8M!1_JJ ze5cc}rfI?^rzR}IER4E4U z#Ponb4+pW>%N__QD(QxM6y|n=I+11(#+nG^KtwC-5rR>AaA=FThew961@U8qJ(oSB-aWFHsVELcEavYyrQfy6?@~|Vx*AKb$ax&kw*j!PZ7RQ!Tn@F zPR|@r8V;AbB2=e;Q_!`jTFxDXR+BpSK7ZW&y|=yTHqs*Q;5Y}A!789hEnn;;skMvy zpt@dHOX@g>>eC`-z;!Y$wmO4oFG=BQC{y#GbCjC6ABfwS>m1k!3P1jil)Q^fn=a~> zVE{ys17r++2B1QaGJ}>k(GqHFkoS!gPbNOOO*%z8T+e0}bVjDrss&rA?y&0AOAQf- z_tELTmOXvc75LSmL4Xl9Jww$E8eSg1U8n`CMW)W?XU(mLjeG0cjkNT*xp{AE1C>8C z9)7a5-2kWtUc2k>0CO>|*Y_nMeXkD#5pRR1x~D44R(e)zp3uV>gi`x%_CmBuT1kG5 a3~&ozRj_MW=3H^EFkiL^f@f`OZvTIZ>(hP! literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/services.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/services.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc30e93665dcbd85a45592174b5a4e44d563b14a GIT binary patch literal 1961 zcmZ`)-A*Gn6t-t3Aqfl1QreqJmDS$tNI>X9s|Bqp+OpeLqNrMS1v}LWO~#(YFk=t4 z0|bR$G<})AN*`fvd(~IyRgXQB@YB}diR1A({?0kyce1&#&^4i<1R4L`LLJ94{VXW?ER7)!eaFUn|0dQvd07WG^YNbn zx`dq!Rki~jnadoc@^ZkFfpuE^Z^r91RVl8F1=J?a_%+19DHDd9(X14)xR?KFJP*Om zBB!J`auuyppAJQ0xD?a!C^IV94tc7LS|vYE-MtT|uwt&6CJza|1CcRNb0-DTyUfX+ ziMgc-u^cBM>aTt-FQd7eIjSMXb81%qUa3o2u^S+2=x? zZ*6bTYkIw{x1OJEZRguxZf(Hmi|(6l!Hc!tkY{x53z4YjX5+kDjLbla%{5st!D+Yt z7+L*vbuI7pVpM+6XQr0>lsmcwX!IgH&~giVxC4Jnm|*n;b`qNwJDMl0LtHih>q3sl-oX6R*0`6*kE^C~`znC9Aek<=wV`n%b3yANG?e&8jYN zzt|PoXPsu1#3^(Z0#(Z7rFEwm<;wPw3WjR~7MO%MYkDKp6cG&YTCrlDJyAiEzb`tDktkXye%$Lhwgp==vhws6W;ux!FrkX~2Y zu&LUx7qzCAYTh34m{ALM|8Y&66P7Wn6-V3ZVSf;m<;`a>EAA|`aLWtuA`bAf=iY_* z!2ZuiD-|8r9Ua#zv1^Ac`taT>V5|Gpa$hEr+K^3+{;2cT7$vM9^e Wi(JxS#=f#lc3gi1^j`5hAO8aZxi-N7 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xri.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xri.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdee01692c1427564ff08c7952a66881024400a1 GIT binary patch literal 3356 zcma)8-EteZ6<+M}XGKxAtR!;mq%NYyt>{Li8aWwDwPm@sGi5WQ%t-RI!%2r-EF3o=>M5$aoWA+)n-_=mL$iFu{caTaUPxcYy54Xa`(Sym^sP7f`)9r~@F1-=5&A zFCw`^Uw*d+tky0l)-i_2+JzAr7N8NZ5c+DV1EJKx>VCyeOHz44$+JLp)7w~RzIb!= z`)>~FNA=f-P`LEd`Tw!RZG1k7YKaI=J;iIMo~X6skZ~bvZBg4g`AsC=KHlDBLpI!& zkDr`8-tKLG`FIn*PEIWg)_#*Pf6SeuamJC!kj<$?#0#?%|LO1YOAZxgD zH5sJKKV9L_@A4kkw~#IxD}RfWNzN8`Fuoc7`&gO7zrx7Kx@%nk{E;;#fZQH_dL+C^ zf_%LhxGJXggZf5w=8Pv36Q}Y){7y%gx7d z+H4Gkio>s8o4aZDL>-9^?hru2YVfeR4}S;-^*$RI+90wvRYYoNG{H7_9~g@>wg6L*rgT zAD<px9ZVpXRp<{16;EUE;SAKR<|JP?j_&x$9u_yficP_vmkCMYR!=R&%sbJs{C#DRG($U!QxuB;-3g8SFYe>Q82X4A$4OM7YN@!L6(v><&gES+joF{S< z`)#iR(HGOwsCCSJa3l=lGaZ98DxL^r9MQO~i>E15>Wo8X@LA@}XK_Ddl*On=!*CjN zgCX1*pP?q5Is0@siD_eblqWXP?nP(c`{UIDtc! zw(uUe%@%VXpB<+IXe9f>q$W@vJfzapJ-Pr)1ebt$R{B9Tua{93eqcnE{xT!%%{-nL zGsGDG<{V6j$3kzZr*j>MVI~$QR7b{ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xrires.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xrires.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..600bdda5beb247bc12c2c663fbce6abd822367cc GIT binary patch literal 3027 zcmcImTXP#p74Er88p~eqB?~F4s97l3Rjnh1ig($V0P!YSa8)qDaUeSdHH_6Qd+gDS z+}-k8TItE*$MDXNpm>1ydF3g8Ay0g#N1Dh9#Tz4a&zbH%-RJV1bNbHudeHd?kN&pF z*uUAzhV zwDv2;o4oac@s?;`Ing=~_}U8=ZSeL})>)r!K0nwCrBFpS)@hMAmYK-= z1sC{e4utNX2&q7OLSIS_Voev%4)*p7IZQHqwo@(SQz0+XzEIN}Ct8nodcBb>E+^ef znRaubdwtq``8Xi%qp2=B!6FOl+T4m9;SQiQcV1YL$6MUvJ}93zcoTv*xC_C9a{USI zd_a<42s!=i$wZ$Nd6?#qVvzJjsEaUxX2U{@kd!o%RH}lZ8-#fJHjbu;-qaqTsQ}%~ znz0xTz**Klv}+mm7)I&XrK?0XkWDQ6E0eVP)j1SRFA^_ywx2|%4Qs=d8*_1J(AfdVog81W|i)8$$p=VO-}=jFlH3p2cem2??*EiI>>kth%$<2x_S~QO zFjgb^oyFLfq!7&i?BFr^;e0IQB-AJ3EhiW+kSFes5}6D`sJ9dD=Eh-QG{C$Nm9xw; zJsF8=)i_THoxz)1s0xBHt=tI@ap+~qHan)uXu9{Nl-C7OTCRvHg&xaX?Nm0ZkG@}xSE3UK$wJRdAFkSu{1~AZ z%jpNnXq2V>gaiU4xcc;Rm@Q`YH29ekqn{6wOo@&oJ#ZSVmTNKCpwX^8R-J}yVWM;g+#{K=B5?+P205s2h>||Tr$*X zq}Zv^nB(IZ2;|n>z5L_Me#riG2=uX23Pyu4ZJpXP|H_&-W{p#z^QfxKl zo1hz5-2@(8-PA2mK=-^gb54U<>&mj&`Nkg@5bF$Vpw&zMg}_-Pkx&)G8Z0Ut6f#T< z61%S*w~uVH6Yhn)$UoL$IvizUC;*QnpVXju8#CdNm=rk=zd1>C?NSPd1-@mt_wlfb zk~BZY=0a^j8WS;37a0|YR^SMS$t3(nWLdpan)kCY7h%0r;~XrJBFqaN76Y>pfl3*? zC+bXa(r-nv-(XXjq)LR#RNwve;o+07DDOk2M^eHZ_&@&Er2p>ogNIMe;2cp@OF}vs zeo_Og5!9;Eh|G0dkWwW2%EAAiz(-{5hFPkN_6u|?lK{{!6B1bNy@&mjiMQCSDzE8A+uJnwTN?++jkCmhtj9TO^L5?lm zqk>POjbou#WG-FOR8l4?eQ~LQ^G;y0hr*K_T_Y9qx2j*U0{HoWQk*Q=jNH&nixR4nrZ z`^RMv$HRh;GossZd_GRH#Y`iPdC|x4`ol+G?LK>a7(Y4q?eni?8z)w#BPmuSqwFOq z6-uhA8)%B|1jv7O+X+50qxB7g%ucEJ0WEuujtVcgu59^zZOxF_t{4&(QO*^!n08HB zw$E(Yo#7V3sW7ATURh_p{F1wKJ4b<@+Z+jUW^12T51bF_zT!`}c1@nI5*jl2*dWZ@ zi1Luay2>>bu1gOWx1o|K+w7I~%U9Nqr~Z!={W^`ZagwM+YbkGHR{jtjE9ed*RvSWMFVe&-$e5#D7R z0Ljv$u7RZ4Kk}=4kil=%u0FcvQdGfrb9IProTlLade8(ZoFlL dSP{JTaM`?9q2?FlM^o(J str + """ + parts = [] + for element in elements: + if type(element) is str: + qs = "1.0" + mtype = element + else: + mtype, q = element + q = float(q) + if q > 1 or q <= 0: + raise ValueError('Invalid preference factor: %r' % q) + + qs = '%0.1f' % (q, ) + + parts.append((qs, mtype)) + + parts.sort() + chunks = [] + for q, mtype in parts: + if q == '1.0': + chunks.append(mtype) + else: + chunks.append('%s; q=%s' % (mtype, q)) + + return ', '.join(chunks) + + +def parseAcceptHeader(value): + """Parse an accept header, ignoring any accept-extensions + + returns a list of tuples containing main MIME type, MIME subtype, + and quality markdown. + + str -> [(str, str, float)] + """ + chunks = [chunk.strip() for chunk in value.split(',')] + accept = [] + for chunk in chunks: + parts = [s.strip() for s in chunk.split(';')] + + mtype = parts.pop(0) + if '/' not in mtype: + # This is not a MIME type, so ignore the bad data + continue + + main, sub = mtype.split('/', 1) + + for ext in parts: + if '=' in ext: + k, v = ext.split('=', 1) + if k == 'q': + try: + q = float(v) + break + except ValueError: + # Ignore poorly formed q-values + pass + else: + q = 1.0 + + accept.append((q, main, sub)) + + accept.sort() + accept.reverse() + return [(main, sub, q) for (q, main, sub) in accept] + + +def matchTypes(accept_types, have_types): + """Given the result of parsing an Accept: header, and the + available MIME types, return the acceptable types with their + quality markdowns. + + For example: + + >>> acceptable = parseAcceptHeader('text/html, text/plain; q=0.5') + >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg']) + [('text/html', 1.0), ('text/plain', 0.5)] + + + Type signature: ([(str, str, float)], [str]) -> [(str, float)] + """ + if not accept_types: + # Accept all of them + default = 1 + else: + default = 0 + + match_main = {} + match_sub = {} + for (main, sub, q) in accept_types: + if main == '*': + default = max(default, q) + continue + elif sub == '*': + match_main[main] = max(match_main.get(main, 0), q) + else: + match_sub[(main, sub)] = max(match_sub.get((main, sub), 0), q) + + accepted_list = [] + order_maintainer = 0 + for mtype in have_types: + main, sub = mtype.split('/') + if (main, sub) in match_sub: + q = match_sub[(main, sub)] + else: + q = match_main.get(main, default) + + if q: + accepted_list.append((1 - q, order_maintainer, q, mtype)) + order_maintainer += 1 + + accepted_list.sort() + return [(mtype, q) for (_, _, q, mtype) in accepted_list] + + +def getAcceptable(accept_header, have_types): + """Parse the accept header and return a list of available types in + preferred order. If a type is unacceptable, it will not be in the + resulting list. + + This is a convenience wrapper around matchTypes and + parseAcceptHeader. + + (str, [str]) -> [str] + """ + accepted = parseAcceptHeader(accept_header) + preferred = matchTypes(accepted, have_types) + return [mtype for (mtype, _) in preferred] diff --git a/flex/lib/python3.6/site-packages/openid/yadis/constants.py b/flex/lib/python3.6/site-packages/openid/yadis/constants.py new file mode 100644 index 0000000..5d30723 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/constants.py @@ -0,0 +1,12 @@ +__all__ = ['YADIS_HEADER_NAME', 'YADIS_CONTENT_TYPE', 'YADIS_ACCEPT_HEADER'] +from openid.yadis.accept import generateAcceptHeader + +YADIS_HEADER_NAME = 'X-XRDS-Location' +YADIS_CONTENT_TYPE = 'application/xrds+xml' + +# A value suitable for using as an accept header when performing YADIS +# discovery, unless the application has special requirements +YADIS_ACCEPT_HEADER = generateAcceptHeader( + ('text/html', 0.3), + ('application/xhtml+xml', 0.5), + (YADIS_CONTENT_TYPE, 1.0), ) diff --git a/flex/lib/python3.6/site-packages/openid/yadis/discover.py b/flex/lib/python3.6/site-packages/openid/yadis/discover.py new file mode 100644 index 0000000..af11b10 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/discover.py @@ -0,0 +1,169 @@ +# -*- test-case-name: openid.test.test_yadis_discover -*- +__all__ = ['discover', 'DiscoveryResult', 'DiscoveryFailure'] + +from io import StringIO + +from openid import fetchers + +from openid.yadis.constants import \ + YADIS_HEADER_NAME, YADIS_CONTENT_TYPE, YADIS_ACCEPT_HEADER +from openid.yadis.parsehtml import MetaNotFound, findHTMLMeta + + +class DiscoveryFailure(Exception): + """Raised when a YADIS protocol error occurs in the discovery process""" + identity_url = None + + def __init__(self, message, http_response): + Exception.__init__(self, message) + self.http_response = http_response + + +class DiscoveryResult(object): + """Contains the result of performing Yadis discovery on a URI""" + + # The URI that was passed to the fetcher + request_uri = None + + # The result of following redirects from the request_uri + normalized_uri = None + + # The URI from which the response text was returned (set to + # None if there was no XRDS document found) + xrds_uri = None + + # The content-type returned with the response_text + content_type = None + + # The document returned from the xrds_uri + response_text = None + + def __init__(self, request_uri): + """Initialize the state of the object + + sets all attributes to None except the request_uri + """ + self.request_uri = request_uri + + def usedYadisLocation(self): + """Was the Yadis protocol's indirection used?""" + if self.xrds_uri is None: + return False + return self.normalized_uri != self.xrds_uri + + def isXRDS(self): + """Is the response text supposed to be an XRDS document?""" + return (self.usedYadisLocation() or + self.content_type == YADIS_CONTENT_TYPE) + + +def discover(uri): + """Discover services for a given URI. + + @param uri: The identity URI as a well-formed http or https + URI. The well-formedness and the protocol are not checked, but + the results of this function are undefined if those properties + do not hold. + + @return: DiscoveryResult object + + @raises Exception: Any exception that can be raised by fetching a URL with + the given fetcher. + @raises DiscoveryFailure: When the HTTP response does not have a 200 code. + """ + result = DiscoveryResult(uri) + resp = fetchers.fetch(uri, headers={'Accept': YADIS_ACCEPT_HEADER}) + if resp.status not in (200, 206): + raise DiscoveryFailure( + 'HTTP Response status from identity URL host is not 200. ' + 'Got status %r' % (resp.status, ), resp) + + # Note the URL after following redirects + result.normalized_uri = resp.final_url + + # Attempt to find out where to go to discover the document + # or if we already have it + result.content_type = resp.headers.get('content-type') + + result.xrds_uri = whereIsYadis(resp) + + if result.xrds_uri and result.usedYadisLocation(): + resp = fetchers.fetch(result.xrds_uri) + if resp.status not in (200, 206): + exc = DiscoveryFailure( + 'HTTP Response status from Yadis host is not 200. ' + 'Got status %r' % (resp.status, ), resp) + exc.identity_url = result.normalized_uri + raise exc + result.content_type = resp.headers.get('content-type') + + result.response_text = resp.body + return result + + +def whereIsYadis(resp): + """Given a HTTPResponse, return the location of the Yadis document. + + May be the URL just retrieved, another URL, or None if no suitable URL can + be found. + + [non-blocking] + + @returns: str or None + """ + # Attempt to find out where to go to discover the document + # or if we already have it + content_type = resp.headers.get('content-type') + + # According to the spec, the content-type header must be an exact + # match, or else we have to look for an indirection. + if (content_type and + content_type.split(';', 1)[0].lower() == YADIS_CONTENT_TYPE): + return resp.final_url + else: + # Try the header + yadis_loc = resp.headers.get(YADIS_HEADER_NAME.lower()) + + if not yadis_loc: + # Parse as HTML if the header is missing. + # + # XXX: do we want to do something with content-type, like + # have a whitelist or a blacklist (for detecting that it's + # HTML)? + + # Decode body by encoding of file + content_type = content_type or '' + encoding = content_type.rsplit(';', 1) + if (len(encoding) == 2 and + encoding[1].strip().startswith('charset=')): + encoding = encoding[1].split('=', 1)[1].strip() + else: + encoding = 'utf-8' + + if isinstance(resp.body, bytes): + try: + content = resp.body.decode(encoding) + except UnicodeError: + # All right, the detected encoding has failed. Try with + # UTF-8 (even if there was no detected encoding and we've + # defaulted to UTF-8, it's not that expensive an operation) + try: + content = resp.body.decode('utf-8') + except UnicodeError: + # At this point the content cannot be decoded to a str + # using the detected encoding or falling back to utf-8, + # so we have to resort to replacing undecodable chars. + # This *will* result in broken content but there isn't + # anything else that can be done. + content = resp.body.decode(encoding, 'replace') + else: + content = resp.body + + try: + yadis_loc = findHTMLMeta(StringIO(content)) + except (MetaNotFound, UnicodeError): + # UnicodeError: Response body could not be encoded and xrds + # location could not be found before troubles occur. + pass + + return yadis_loc diff --git a/flex/lib/python3.6/site-packages/openid/yadis/etxrd.py b/flex/lib/python3.6/site-packages/openid/yadis/etxrd.py new file mode 100644 index 0000000..85e739a --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/etxrd.py @@ -0,0 +1,318 @@ +# -*- test-case-name: yadis.test.test_etxrd -*- +""" +ElementTree interface to an XRD document. +""" + +__all__ = [ + 'nsTag', + 'mkXRDTag', + 'isXRDS', + 'parseXRDS', + 'getCanonicalID', + 'getYadisXRD', + 'getPriorityStrict', + 'getPriority', + 'prioSort', + 'iterServices', + 'expandService', + 'expandServices', +] + +import sys +import random +import functools + +from datetime import datetime +from time import strptime + +from openid.oidutil import importElementTree, importSafeElementTree + +ElementTree = importElementTree() +SafeElementTree = importSafeElementTree() + +from openid.yadis import xri + + +class XRDSError(Exception): + """An error with the XRDS document.""" + + # The exception that triggered this exception + reason = None + + +class XRDSFraud(XRDSError): + """Raised when there's an assertion in the XRDS that it does not have + the authority to make. + """ + + +def parseXRDS(text): + """Parse the given text as an XRDS document. + + @return: ElementTree containing an XRDS document + + @raises XRDSError: When there is a parse error or the document does + not contain an XRDS. + """ + try: + # lxml prefers to parse bytestrings, and occasionally chokes on a + # combination of text strings and declared XML encodings -- see + # https://github.com/necaris/python3-openid/issues/19 + # To avoid this, we ensure that the 'text' we're parsing is actually + # a bytestring + bytestring = text.encode('utf8') if isinstance(text, str) else text + element = SafeElementTree.XML(bytestring) + except (SystemExit, MemoryError, AssertionError, ImportError): + raise + except Exception as why: + exc = XRDSError('Error parsing document as XML') + exc.reason = why + raise exc + else: + tree = ElementTree.ElementTree(element) + if not isXRDS(tree): + raise XRDSError('Not an XRDS document') + + return tree + + +XRD_NS_2_0 = 'xri://$xrd*($v*2.0)' +XRDS_NS = 'xri://$xrds' + + +def nsTag(ns, t): + return '{%s}%s' % (ns, t) + + +def mkXRDTag(t): + """basestring -> basestring + + Create a tag name in the XRD 2.0 XML namespace suitable for using + with ElementTree + """ + return nsTag(XRD_NS_2_0, t) + + +def mkXRDSTag(t): + """basestring -> basestring + + Create a tag name in the XRDS XML namespace suitable for using + with ElementTree + """ + return nsTag(XRDS_NS, t) + + +# Tags that are used in Yadis documents +root_tag = mkXRDSTag('XRDS') +service_tag = mkXRDTag('Service') +xrd_tag = mkXRDTag('XRD') +type_tag = mkXRDTag('Type') +uri_tag = mkXRDTag('URI') +expires_tag = mkXRDTag('Expires') + +# Other XRD tags +canonicalID_tag = mkXRDTag('CanonicalID') + + +def isXRDS(xrd_tree): + """Is this document an XRDS document?""" + root = xrd_tree.getroot() + return root.tag == root_tag + + +def getYadisXRD(xrd_tree): + """Return the XRD element that should contain the Yadis services""" + xrd = None + + # for the side-effect of assigning the last one in the list to the + # xrd variable + for xrd in xrd_tree.findall(xrd_tag): + pass + + # There were no elements found, or else xrd would be set to the + # last one + if xrd is None: + raise XRDSError('No XRD present in tree') + + return xrd + + +def getXRDExpiration(xrd_element, default=None): + """Return the expiration date of this XRD element, or None if no + expiration was specified. + + @type xrd_element: ElementTree node + + @param default: The value to use as the expiration if no + expiration was specified in the XRD. + + @rtype: datetime.datetime + + @raises ValueError: If the xrd:Expires element is present, but its + contents are not formatted according to the specification. + """ + expires_element = xrd_element.find(expires_tag) + if expires_element is None: + return default + else: + expires_string = expires_element.text + + # Will raise ValueError if the string is not the expected format + expires_time = strptime(expires_string, "%Y-%m-%dT%H:%M:%SZ") + return datetime(*expires_time[0:6]) + + +def getCanonicalID(iname, xrd_tree): + """Return the CanonicalID from this XRDS document. + + @param iname: the XRI being resolved. + @type iname: unicode + + @param xrd_tree: The XRDS output from the resolver. + @type xrd_tree: ElementTree + + @returns: The XRI CanonicalID or None. + @returntype: unicode or None + """ + xrd_list = xrd_tree.findall(xrd_tag) + xrd_list.reverse() + + try: + canonicalID = xri.XRI(xrd_list[0].findall(canonicalID_tag)[0].text) + except IndexError: + return None + + childID = canonicalID.lower() + + for xrd in xrd_list[1:]: + parent_sought = childID.rsplit("!", 1)[0] + parent = xri.XRI(xrd.findtext(canonicalID_tag)) + if parent_sought != parent.lower(): + raise XRDSFraud("%r can not come from %s" % (childID, parent)) + + childID = parent_sought + + root = xri.rootAuthority(iname) + if not xri.providerIsAuthoritative(root, childID): + raise XRDSFraud("%r can not come from root %r" % (childID, root)) + + return canonicalID + + +@functools.total_ordering +class _Max(object): + """ + Value that compares greater than any other value. + + Should only be used as a singleton. Implemented for use as a + priority value for when a priority is not specified. + """ + + def __lt__(self, other): + return isinstance(other, self.__class__) + + def __eq__(self, other): + return isinstance(other, self.__class__) + + +Max = _Max() + + +def getPriorityStrict(element): + """Get the priority of this element. + + Raises ValueError if the value of the priority is invalid. If no + priority is specified, it returns a value that compares greater + than any other value. + """ + prio_str = element.get('priority') + if prio_str is not None: + prio_val = int(prio_str) + if prio_val >= 0: + return prio_val + else: + raise ValueError('Priority values must be non-negative integers') + + # Any errors in parsing the priority fall through to here + return Max + + +def getPriority(element): + """Get the priority of this element + + Returns Max if no priority is specified or the priority value is invalid. + """ + try: + return getPriorityStrict(element) + except ValueError: + return Max + + +def prioSort(elements): + """Sort a list of elements that have priority attributes""" + # Randomize the services before sorting so that equal priority + # elements are load-balanced. + random.shuffle(elements) + + sorted_elems = sorted(elements, key=getPriority) + return sorted_elems + + +def iterServices(xrd_tree): + """Return an iterable over the Service elements in the Yadis XRD + + sorted by priority""" + xrd = getYadisXRD(xrd_tree) + return prioSort(xrd.findall(service_tag)) + + +def sortedURIs(service_element): + """Given a Service element, return a list of the contents of all + URI tags in priority order.""" + return [ + uri_element.text + for uri_element in prioSort(service_element.findall(uri_tag)) + ] + + +def getTypeURIs(service_element): + """Given a Service element, return a list of the contents of all + Type tags""" + return [ + type_element.text for type_element in service_element.findall(type_tag) + ] + + +def expandService(service_element): + """Take a service element and expand it into an iterator of: + ([type_uri], uri, service_element) + """ + uris = sortedURIs(service_element) + if not uris: + uris = [None] + + expanded = [] + for uri in uris: + type_uris = getTypeURIs(service_element) + expanded.append((type_uris, uri, service_element)) + + return expanded + + +def expandServices(service_elements): + """Take a sorted iterator of service elements and expand it into a + sorted iterator of: + ([type_uri], uri, service_element) + + There may be more than one item in the resulting list for each + service element if there is more than one URI or type for a + service, but each triple will be unique. + + If there is no URI or Type for a Service element, it will not + appear in the result. + """ + expanded = [] + for service_element in service_elements: + expanded.extend(expandService(service_element)) + + return expanded diff --git a/flex/lib/python3.6/site-packages/openid/yadis/filters.py b/flex/lib/python3.6/site-packages/openid/yadis/filters.py new file mode 100644 index 0000000..4a7fbc6 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/filters.py @@ -0,0 +1,210 @@ +"""This module contains functions and classes used for extracting +endpoint information out of a Yadis XRD file using the ElementTree XML +parser. +""" + +__all__ = [ + 'BasicServiceEndpoint', + 'mkFilter', + 'IFilter', + 'TransformFilterMaker', + 'CompoundFilter', +] + +from openid.yadis.etxrd import expandService +import collections + + +class BasicServiceEndpoint(object): + """Generic endpoint object that contains parsed service + information, as well as a reference to the service element from + which it was generated. If there is more than one xrd:Type or + xrd:URI in the xrd:Service, this object represents just one of + those pairs. + + This object can be used as a filter, because it implements + fromBasicServiceEndpoint. + + The simplest kind of filter you can write implements + fromBasicServiceEndpoint, which takes one of these objects. + """ + + def __init__(self, yadis_url, type_uris, uri, service_element): + self.type_uris = type_uris + self.yadis_url = yadis_url + self.uri = uri + self.service_element = service_element + + def matchTypes(self, type_uris): + """Query this endpoint to see if it has any of the given type + URIs. This is useful for implementing other endpoint classes + that e.g. need to check for the presence of multiple versions + of a single protocol. + + @param type_uris: The URIs that you wish to check + @type type_uris: iterable of str + + @return: all types that are in both in type_uris and + self.type_uris + """ + return [uri for uri in type_uris if uri in self.type_uris] + + def fromBasicServiceEndpoint(endpoint): + """Trivial transform from a basic endpoint to itself. This + method exists to allow BasicServiceEndpoint to be used as a + filter. + + If you are subclassing this object, re-implement this function. + + @param endpoint: An instance of BasicServiceEndpoint + @return: The object that was passed in, with no processing. + """ + return endpoint + + fromBasicServiceEndpoint = staticmethod(fromBasicServiceEndpoint) + + +class IFilter(object): + """Interface for Yadis filter objects. Other filter-like things + are convertable to this class.""" + + def getServiceEndpoints(self, yadis_url, service_element): + """Returns an iterator of endpoint objects""" + raise NotImplementedError + + +class TransformFilterMaker(object): + """Take a list of basic filters and makes a filter that transforms + the basic filter into a top-level filter. This is mostly useful + for the implementation of mkFilter, which should only be needed + for special cases or internal use by this library. + + This object is useful for creating simple filters for services + that use one URI and are specified by one Type (we expect most + Types will fit this paradigm). + + Creates a BasicServiceEndpoint object and apply the filter + functions to it until one of them returns a value. + """ + + def __init__(self, filter_functions): + """Initialize the filter maker's state + + @param filter_functions: The endpoint transformer functions to + apply to the basic endpoint. These are called in turn + until one of them does not return None, and the result of + that transformer is returned. + """ + self.filter_functions = filter_functions + + def getServiceEndpoints(self, yadis_url, service_element): + """Returns an iterator of endpoint objects produced by the + filter functions.""" + endpoints = [] + + # Do an expansion of the service element by xrd:Type and xrd:URI + for type_uris, uri, _ in expandService(service_element): + + # Create a basic endpoint object to represent this + # yadis_url, Service, Type, URI combination + endpoint = BasicServiceEndpoint(yadis_url, type_uris, uri, + service_element) + + e = self.applyFilters(endpoint) + if e is not None: + endpoints.append(e) + + return endpoints + + def applyFilters(self, endpoint): + """Apply filter functions to an endpoint until one of them + returns non-None.""" + for filter_function in self.filter_functions: + e = filter_function(endpoint) + if e is not None: + # Once one of the filters has returned an + # endpoint, do not apply any more. + return e + + return None + + +class CompoundFilter(object): + """Create a new filter that applies a set of filters to an endpoint + and collects their results. + """ + + def __init__(self, subfilters): + self.subfilters = subfilters + + def getServiceEndpoints(self, yadis_url, service_element): + """Generate all endpoint objects for all of the subfilters of + this filter and return their concatenation.""" + endpoints = [] + for subfilter in self.subfilters: + endpoints.extend( + subfilter.getServiceEndpoints(yadis_url, service_element)) + return endpoints + + +# Exception raised when something is not able to be turned into a filter +filter_type_error = TypeError( + 'Expected a filter, an endpoint, a callable or a list of any of these.') + + +def mkFilter(parts): + """Convert a filter-convertable thing into a filter + + @param parts: a filter, an endpoint, a callable, or a list of any of these. + """ + # Convert the parts into a list, and pass to mkCompoundFilter + if parts is None: + parts = [BasicServiceEndpoint] + + try: + parts = list(parts) + except TypeError: + return mkCompoundFilter([parts]) + else: + return mkCompoundFilter(parts) + + +def mkCompoundFilter(parts): + """Create a filter out of a list of filter-like things + + Used by mkFilter + + @param parts: list of filter, endpoint, callable or list of any of these + """ + # Separate into a list of callables and a list of filter objects + transformers = [] + filters = [] + for subfilter in parts: + try: + subfilter = list(subfilter) + except TypeError: + # If it's not an iterable + if hasattr(subfilter, 'getServiceEndpoints'): + # It's a full filter + filters.append(subfilter) + elif hasattr(subfilter, 'fromBasicServiceEndpoint'): + # It's an endpoint object, so put its endpoint + # conversion attribute into the list of endpoint + # transformers + transformers.append(subfilter.fromBasicServiceEndpoint) + elif isinstance(subfilter, collections.Callable): + # It's a simple callable, so add it to the list of + # endpoint transformers + transformers.append(subfilter) + else: + raise filter_type_error + else: + filters.append(mkCompoundFilter(subfilter)) + + if transformers: + filters.append(TransformFilterMaker(transformers)) + + if len(filters) == 1: + return filters[0] + else: + return CompoundFilter(filters) diff --git a/flex/lib/python3.6/site-packages/openid/yadis/manager.py b/flex/lib/python3.6/site-packages/openid/yadis/manager.py new file mode 100644 index 0000000..9c9a042 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/manager.py @@ -0,0 +1,195 @@ +class YadisServiceManager(object): + """Holds the state of a list of selected Yadis services, managing + storing it in a session and iterating over the services in order.""" + + def __init__(self, starting_url, yadis_url, services, session_key): + # The URL that was used to initiate the Yadis protocol + self.starting_url = starting_url + + # The URL after following redirects (the identifier) + self.yadis_url = yadis_url + + # List of service elements + self.services = list(services) + + self.session_key = session_key + + # Reference to the current service object + self._current = None + + def __len__(self): + """How many untried services remain?""" + return len(self.services) + + def __iter__(self): + return self + + def __next__(self): + """Return the next service + + self.current() will continue to return that service until the + next call to this method.""" + try: + self._current = self.services.pop(0) + except IndexError: + raise StopIteration + else: + return self._current + + def current(self): + """Return the current service. + + Returns None if there are no services left. + """ + return self._current + + def forURL(self, url): + return url in [self.starting_url, self.yadis_url] + + def started(self): + """Has the first service been returned?""" + return self._current is not None + + def store(self, session): + """Store this object in the session, by its session key.""" + session[self.session_key] = self + + +class Discovery(object): + """State management for discovery. + + High-level usage pattern is to call .getNextService(discover) in + order to find the next available service for this user for this + session. Once a request completes, call .finish() to clean up the + session state. + + @ivar session: a dict-like object that stores state unique to the + requesting user-agent. This object must be able to store + serializable objects. + + @ivar url: the URL that is used to make the discovery request + + @ivar session_key_suffix: The suffix that will be used to identify + this object in the session object. + """ + + DEFAULT_SUFFIX = 'auth' + PREFIX = '_yadis_services_' + + def __init__(self, session, url, session_key_suffix=None): + """Initialize a discovery object""" + self.session = session + self.url = url + if session_key_suffix is None: + session_key_suffix = self.DEFAULT_SUFFIX + + self.session_key_suffix = session_key_suffix + + def getNextService(self, discover): + """Return the next authentication service for the pair of + user_input and session. This function handles fallback. + + + @param discover: a callable that takes a URL and returns a + list of services + + @type discover: str -> [service] + + + @return: the next available service + """ + manager = self.getManager() + if manager is not None and not manager: + self.destroyManager() + + if not manager: + yadis_url, services = discover(self.url) + manager = self.createManager(services, yadis_url) + + if manager: + service = next(manager) + manager.store(self.session) + else: + service = None + + return service + + def cleanup(self, force=False): + """Clean up Yadis-related services in the session and return + the most-recently-attempted service from the manager, if one + exists. + + @param force: True if the manager should be deleted regardless + of whether it's a manager for self.url. + + @return: current service endpoint object or None if there is + no current service + """ + manager = self.getManager(force=force) + if manager is not None: + service = manager.current() + self.destroyManager(force=force) + else: + service = None + + return service + + ### Lower-level methods + + def getSessionKey(self): + """Get the session key for this starting URL and suffix + + @return: The session key + @rtype: str + """ + return self.PREFIX + self.session_key_suffix + + def getManager(self, force=False): + """Extract the YadisServiceManager for this object's URL and + suffix from the session. + + @param force: True if the manager should be returned + regardless of whether it's a manager for self.url. + + @return: The current YadisServiceManager, if it's for this + URL, or else None + """ + manager = self.session.get(self.getSessionKey()) + if (manager is not None and (manager.forURL(self.url) or force)): + return manager + else: + return None + + def createManager(self, services, yadis_url=None): + """Create a new YadisService Manager for this starting URL and + suffix, and store it in the session. + + @raises KeyError: When I already have a manager. + + @return: A new YadisServiceManager or None + """ + key = self.getSessionKey() + if self.getManager(): + raise KeyError('There is already a %r manager for %r' % + (key, self.url)) + + if not services: + return None + + manager = YadisServiceManager(self.url, yadis_url, services, key) + manager.store(self.session) + return manager + + def destroyManager(self, force=False): + """Delete any YadisServiceManager with this starting URL and + suffix from the session. + + If there is no service manager or the service manager is for a + different URL, it silently does nothing. + + @param force: True if the manager should be deleted regardless + of whether it's a manager for self.url. + """ + if self.getManager(force=force) is not None: + key = self.getSessionKey() + del self.session[key] diff --git a/flex/lib/python3.6/site-packages/openid/yadis/parsehtml.py b/flex/lib/python3.6/site-packages/openid/yadis/parsehtml.py new file mode 100644 index 0000000..c787111 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/parsehtml.py @@ -0,0 +1,207 @@ +__all__ = ['findHTMLMeta', 'MetaNotFound'] + +from html.parser import HTMLParser +import html.entities +import re +import sys + +from openid.yadis.constants import YADIS_HEADER_NAME + +# Size of the chunks to search at a time (also the amount that gets +# read at a time) +CHUNK_SIZE = 1024 * 16 # 16 KB + + +class ParseDone(Exception): + """Exception to hold the URI that was located when the parse is + finished. If the parse finishes without finding the URI, set it to + None.""" + + +class MetaNotFound(Exception): + """Exception to hold the content of the page if we did not find + the appropriate tag""" + + +re_flags = re.IGNORECASE | re.UNICODE | re.VERBOSE +ent_pat = r''' +& + +(?: \#x (?P [a-f0-9]+ ) +| \# (?P \d+ ) +| (?P \w+ ) +) + +;''' + +ent_re = re.compile(ent_pat, re_flags) + + +def substituteMO(mo): + if mo.lastgroup == 'hex': + codepoint = int(mo.group('hex'), 16) + elif mo.lastgroup == 'dec': + codepoint = int(mo.group('dec')) + else: + assert mo.lastgroup == 'word' + codepoint = html.entities.name2codepoint.get(mo.group('word')) + + if codepoint is None: + return mo.group() + else: + return chr(codepoint) + + +def substituteEntities(s): + return ent_re.sub(substituteMO, s) + + +class YadisHTMLParser(HTMLParser): + """Parser that finds a meta http-equiv tag in the head of a html + document. + + When feeding in data, if the tag is matched or it will never be + found, the parser will raise ParseDone with the uri as the first + attribute. + + Parsing state diagram + ===================== + + Any unlisted input does not affect the state:: + + 1, 2, 5 8 + +--------------------------+ +-+ + | | | | + 4 | 3 1, 2, 5, 7 v | v + TOP -> HTML -> HEAD ----------> TERMINATED + | | ^ | ^ ^ + | | 3 | | | | + | +------------+ +-> FOUND ------+ | + | 6 8 | + | 1, 2 | + +------------------------------------+ + + 1. any of , , -> TERMINATE + 2. -> TERMINATE + 3. -> HEAD + 4. -> HTML + 5. -> TERMINATE + 6. -> FOUND + 7. -> TERMINATE + 8. Any input -> TERMINATE + """ + TOP = 0 + HTML = 1 + HEAD = 2 + FOUND = 3 + TERMINATED = 4 + + def __init__(self): + if (sys.version_info.minor <= 2): + # Python 3.2 and below actually require the `strict` argument + # to `html.parser.HTMLParser` -- otherwise it's deprecated and + # we don't want to pass it + super(YadisHTMLParser, self).__init__(strict=False) + else: + super(YadisHTMLParser, self).__init__() + self.phase = self.TOP + + def _terminate(self): + self.phase = self.TERMINATED + raise ParseDone(None) + + def handle_endtag(self, tag): + # If we ever see an end of head, body, or html, bail out right away. + # [1] + if tag in ['head', 'body', 'html']: + self._terminate() + + def handle_starttag(self, tag, attrs): + # if we ever see a start body tag, bail out right away, since + # we want to prevent the meta tag from appearing in the body + # [2] + if tag == 'body': + self._terminate() + + if self.phase == self.TOP: + # At the top level, allow a html tag or a head tag to move + # to the head or html phase + if tag == 'head': + # [3] + self.phase = self.HEAD + elif tag == 'html': + # [4] + self.phase = self.HTML + + elif self.phase == self.HTML: + # if we are in the html tag, allow a head tag to move to + # the HEAD phase. If we get another html tag, then bail + # out + if tag == 'head': + # [3] + self.phase = self.HEAD + elif tag == 'html': + # [5] + self._terminate() + + elif self.phase == self.HEAD: + # If we are in the head phase, look for the appropriate + # meta tag. If we get a head or body tag, bail out. + if tag == 'meta': + attrs_d = dict(attrs) + http_equiv = attrs_d.get('http-equiv', '').lower() + if http_equiv == YADIS_HEADER_NAME.lower(): + raw_attr = attrs_d.get('content') + yadis_loc = substituteEntities(raw_attr) + # [6] + self.phase = self.FOUND + raise ParseDone(yadis_loc) + + elif tag in ('head', 'html'): + # [5], [7] + self._terminate() + + def feed(self, chars): + # [8] + if self.phase in (self.TERMINATED, self.FOUND): + self._terminate() + + return super(YadisHTMLParser, self).feed(chars) + + +def findHTMLMeta(stream): + """Look for a meta http-equiv tag with the YADIS header name. + + @param stream: Source of the html text + @type stream: Object that implements a read() method that works + like file.read + + @return: The URI from which to fetch the XRDS document + @rtype: str + + @raises MetaNotFound: raised with the content that was + searched as the first parameter. + """ + parser = YadisHTMLParser() + chunks = [] + + while 1: + chunk = stream.read(CHUNK_SIZE) + if not chunk: + # End of file + break + + chunks.append(chunk) + try: + parser.feed(chunk) + except ParseDone as why: + uri = why.args[0] + if uri is None: + # Parse finished, but we may need the rest of the file + chunks.append(stream.read()) + break + else: + return uri + + content = ''.join(chunks) + raise MetaNotFound(content) diff --git a/flex/lib/python3.6/site-packages/openid/yadis/services.py b/flex/lib/python3.6/site-packages/openid/yadis/services.py new file mode 100644 index 0000000..2092a9c --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/services.py @@ -0,0 +1,56 @@ +# -*- test-case-name: openid.test.test_services -*- + +from openid.yadis.filters import mkFilter +from openid.yadis.discover import discover, DiscoveryFailure +from openid.yadis.etxrd import parseXRDS, iterServices, XRDSError + + +def getServiceEndpoints(input_url, flt=None): + """Perform the Yadis protocol on the input URL and return an + iterable of resulting endpoint objects. + + @param flt: A filter object or something that is convertable to + a filter object (using mkFilter) that will be used to generate + endpoint objects. This defaults to generating BasicEndpoint + objects. + + @param input_url: The URL on which to perform the Yadis protocol + + @return: The normalized identity URL and an iterable of endpoint + objects generated by the filter function. + + @rtype: (str, [endpoint]) + + @raises DiscoveryFailure: when Yadis fails to obtain an XRDS document. + """ + result = discover(input_url) + try: + endpoints = applyFilter(result.normalized_uri, result.response_text, + flt) + except XRDSError as err: + raise DiscoveryFailure(str(err), None) + return (result.normalized_uri, endpoints) + + +def applyFilter(normalized_uri, xrd_data, flt=None): + """Generate an iterable of endpoint objects given this input data, + presumably from the result of performing the Yadis protocol. + + @param normalized_uri: The input URL, after following redirects, + as in the Yadis protocol. + + + @param xrd_data: The XML text the XRDS file fetched from the + normalized URI. + @type xrd_data: str + + """ + flt = mkFilter(flt) + et = parseXRDS(xrd_data) + + endpoints = [] + for service_element in iterServices(et): + endpoints.extend( + flt.getServiceEndpoints(normalized_uri, service_element)) + + return endpoints diff --git a/flex/lib/python3.6/site-packages/openid/yadis/xri.py b/flex/lib/python3.6/site-packages/openid/yadis/xri.py new file mode 100644 index 0000000..a88027f --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/xri.py @@ -0,0 +1,122 @@ +# -*- test-case-name: openid.test.test_xri -*- +"""Utility functions for handling XRIs. + +@see: XRI Syntax v2.0 at the U{OASIS XRI Technical Committee} +""" + +import re +from functools import reduce + +from openid import codecutil # registers 'oid_percent_escape' encoding handler + +XRI_AUTHORITIES = ['!', '=', '@', '+', '$', '('] + + +def identifierScheme(identifier): + """Determine if this identifier is an XRI or URI. + + @returns: C{"XRI"} or C{"URI"} + """ + if identifier.startswith('xri://') or (identifier and + identifier[0] in XRI_AUTHORITIES): + return "XRI" + else: + return "URI" + + +def toIRINormal(xri): + """Transform an XRI to IRI-normal form.""" + if not xri.startswith('xri://'): + xri = 'xri://' + xri + return escapeForIRI(xri) + + +_xref_re = re.compile('\((.*?)\)') + + +def _escape_xref(xref_match): + """Escape things that need to be escaped if they're in a cross-reference. + """ + xref = xref_match.group() + xref = xref.replace('/', '%2F') + xref = xref.replace('?', '%3F') + xref = xref.replace('#', '%23') + return xref + + +def escapeForIRI(xri): + """Escape things that need to be escaped when transforming to an IRI.""" + xri = xri.replace('%', '%25') + xri = _xref_re.sub(_escape_xref, xri) + return xri + + +def toURINormal(xri): + """Transform an XRI to URI normal form.""" + return iriToURI(toIRINormal(xri)) + + +def iriToURI(iri): + """Transform an IRI to a URI by escaping unicode.""" + # According to RFC 3987, section 3.1, "Mapping of IRIs to URIs" + if isinstance(iri, bytes): + iri = str(iri, encoding="utf-8") + return iri.encode('ascii', errors='oid_percent_escape').decode() + + +def providerIsAuthoritative(providerID, canonicalID): + """Is this provider ID authoritative for this XRI? + + @returntype: bool + """ + # XXX: can't use rsplit until we require python >= 2.4. + lastbang = canonicalID.rindex('!') + parent = canonicalID[:lastbang] + return parent == providerID + + +def rootAuthority(xri): + """Return the root authority for an XRI. + + Example:: + + rootAuthority("xri://@example") == "xri://@" + + @type xri: unicode + @returntype: unicode + """ + if xri.startswith('xri://'): + xri = xri[6:] + authority = xri.split('/', 1)[0] + if authority[0] == '(': + # Cross-reference. + # XXX: This is incorrect if someone nests cross-references so there + # is another close-paren in there. Hopefully nobody does that + # before we have a real xriparse function. Hopefully nobody does + # that *ever*. + root = authority[:authority.index(')') + 1] + elif authority[0] in XRI_AUTHORITIES: + # Other XRI reference. + root = authority[0] + else: + # IRI reference. XXX: Can IRI authorities have segments? + segments = authority.split('!') + segments = reduce(list.__add__, [s.split('*') for s in segments]) + root = segments[0] + + return XRI(root) + + +def XRI(xri): + """An XRI object allowing comparison of XRI. + + Ideally, this would do full normalization and provide comparsion + operators as per XRI Syntax. Right now, it just does a bit of + canonicalization by ensuring the xri scheme is present. + + @param xri: an xri string + @type xri: unicode + """ + if not xri.startswith('xri://'): + xri = 'xri://' + xri + return xri diff --git a/flex/lib/python3.6/site-packages/openid/yadis/xrires.py b/flex/lib/python3.6/site-packages/openid/yadis/xrires.py new file mode 100644 index 0000000..a714029 --- /dev/null +++ b/flex/lib/python3.6/site-packages/openid/yadis/xrires.py @@ -0,0 +1,123 @@ +# -*- test-case-name: openid.test.test_xrires -*- +"""XRI resolution. +""" + +from urllib.parse import urlencode +from openid import fetchers +from openid.yadis import etxrd +from openid.yadis.xri import toURINormal +from openid.yadis.services import iterServices + +DEFAULT_PROXY = 'http://proxy.xri.net/' + + +class ProxyResolver(object): + """Python interface to a remote XRI proxy resolver. + """ + + def __init__(self, proxy_url=DEFAULT_PROXY): + self.proxy_url = proxy_url + + def queryURL(self, xri, service_type=None): + """Build a URL to query the proxy resolver. + + @param xri: An XRI to resolve. + @type xri: unicode + + @param service_type: The service type to resolve, if you desire + service endpoint selection. A service type is a URI. + @type service_type: str + + @returns: a URL + @returntype: str + """ + # Trim off the xri:// prefix. The proxy resolver didn't accept it + # when this code was written, but that may (or may not) change for + # XRI Resolution 2.0 Working Draft 11. + qxri = toURINormal(xri)[6:] + hxri = self.proxy_url + qxri + args = { + # XXX: If the proxy resolver will ensure that it doesn't return + # bogus CanonicalIDs (as per Steve's message of 15 Aug 2006 + # 11:13:42), then we could ask for application/xrd+xml instead, + # which would give us a bit less to process. + '_xrd_r': 'application/xrds+xml', + } + if service_type: + args['_xrd_t'] = service_type + else: + # Don't perform service endpoint selection. + args['_xrd_r'] += ';sep=false' + query = _appendArgs(hxri, args) + return query + + def query(self, xri, service_types): + """Resolve some services for an XRI. + + Note: I don't implement any service endpoint selection beyond what + the resolver I'm querying does, so the Services I return may well + include Services that were not of the types you asked for. + + May raise fetchers.HTTPFetchingError or L{etxrd.XRDSError} if + the fetching or parsing don't go so well. + + @param xri: An XRI to resolve. + @type xri: unicode + + @param service_types: A list of services types to query for. Service + types are URIs. + @type service_types: list of str + + @returns: tuple of (CanonicalID, Service elements) + @returntype: (unicode, list of C{ElementTree.Element}s) + """ + # FIXME: No test coverage! + services = [] + # Make a seperate request to the proxy resolver for each service + # type, as, if it is following Refs, it could return a different + # XRDS for each. + + canonicalID = None + + for service_type in service_types: + url = self.queryURL(xri, service_type) + response = fetchers.fetch(url) + if response.status not in (200, 206): + # XXX: sucks to fail silently. + # print "response not OK:", response + continue + et = etxrd.parseXRDS(response.body) + canonicalID = etxrd.getCanonicalID(xri, et) + some_services = list(iterServices(et)) + services.extend(some_services) + # TODO: + # * If we do get hits for multiple service_types, we're almost + # certainly going to have duplicated service entries and + # broken priority ordering. + return canonicalID, services + + +def _appendArgs(url, args): + """Append some arguments to an HTTP query. + """ + # to be merged with oidutil.appendArgs when we combine the projects. + if hasattr(args, 'items'): + args = list(args.items()) + args.sort() + + if len(args) == 0: + return url + + # According to XRI Resolution section "QXRI query parameters": + # + # """If the original QXRI had a null query component (only a leading + # question mark), or a query component consisting of only question + # marks, one additional leading question mark MUST be added when + # adding any XRI resolution parameters.""" + + if '?' in url.rstrip('?'): + sep = '&' + else: + sep = '?' + + return '%s%s%s' % (url, sep, urlencode(args)) diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..166f0fb --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,6 @@ +This is a set of Python packages to support use of +the OpenID decentralized identity system in your application, update to Python +3. Want to enable single sign-on for your web site? Use the openid.consumer +package. Want to run your own OpenID server? Check out openid.server. +Includes example code and support for a variety of storage back-ends. + diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/METADATA new file mode 100644 index 0000000..5e09282 --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/METADATA @@ -0,0 +1,29 @@ +Metadata-Version: 2.0 +Name: python3-openid +Version: 3.1.0 +Summary: OpenID support for modern servers and consumers. +Home-page: http://github.com/necaris/python3-openid +Author: Rami Chowdhury +Author-email: rami.chowdhury@gmail.com +License: UNKNOWN +Download-URL: http://github.com/necaris/python3-openid/tarball/v3.1.0 +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: POSIX +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: System :: Systems Administration :: Authentication/Directory +Requires-Dist: defusedxml + +This is a set of Python packages to support use of +the OpenID decentralized identity system in your application, update to Python +3. Want to enable single sign-on for your web site? Use the openid.consumer +package. Want to run your own OpenID server? Check out openid.server. +Includes example code and support for a variety of storage back-ends. + diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/RECORD new file mode 100644 index 0000000..adbbdcb --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/RECORD @@ -0,0 +1,91 @@ +openid/__init__.py,sha256=JQ69wS_S4X9ijxHj9WZENKYxEqGoIIE4DBbYcGjlTyQ,1371 +openid/association.py,sha256=EtzgGrDU9ZXdKFQ0Eaz0UnjQqUhXij4jMAxmHfUKUSQ,18302 +openid/codecutil.py,sha256=bWgdiLm5OgQieZaUQmjGMo4hQfmqnSGQJG6CE6m4biA,2280 +openid/cryptutil.py,sha256=7Ihf8WZgEHsk_LXnc4220Fcv6YfwCabTH31E0aDUnWk,3722 +openid/dh.py,sha256=GbGSU1O7ANSG-YThasrCpk92n6zKY-4Wife24RTCFpQ,1657 +openid/extension.py,sha256=IA8_5rzFhT9QepV5Me30U_DTDdsTV8iiXtvao34wlAk,1696 +openid/fetchers.py,sha256=vbn5Y-n6hp8F4mcRPajBCWK75CrUhnkDK4QjpaA2078,15881 +openid/kvform.py,sha256=mg1HBlkrc-8llBIzrULqLBjAeMjeuXxmPCVPO7jSfqg,3296 +openid/message.py,sha256=jdtlE43Jc-1oKMiAai5IxNjr6S_BPFEDg_b_qG5u580,22680 +openid/oidutil.py,sha256=3DrCqt1-EcmqaoY40H54M1nyucyqqtD3HKAz4GICb6Q,6651 +openid/sreg.py,sha256=rM-x49cP8eA2mXDmmby43oIjT4bmY5LoS9zkmQAkdrk,195 +openid/urinorm.py,sha256=-5pMOTtI-7PWB6CBNeXY0C21Jp7SBTY5Y4fRXwPatPg,4439 +openid/consumer/__init__.py,sha256=TW-dGFLas2ZO41A9JGvb0vSrU6z5UO7Sq2xr37pGSlo,142 +openid/consumer/consumer.py,sha256=QtR5n6Fz96qy5onS29uVv8tTkRGPWsNQqJiAkL2ORHo,76758 +openid/consumer/discover.py,sha256=ewlnR6Ew44053KMSqalrtsj26g5295R5y5k5KPzle0k,16137 +openid/consumer/html_parse.py,sha256=oqics5256UlovEJoblMr1Z2ZjGihDkh5LgdOn42Gjyw,7996 +openid/extensions/__init__.py,sha256=vkdH77CGmBqhefhsLbq0VYQMZRcYcA8U9IYNxlYVgPA,117 +openid/extensions/ax.py,sha256=xZzE5s_Zwrvc53FUwsabA0zlfUyqEL_zxCDLyxFs9_k,26629 +openid/extensions/sreg.py,sha256=357ha1F3-vcCs-3Z2MCe09i4Y7gnEkQL5t110G_4PtU,17839 +openid/extensions/draft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +openid/extensions/draft/pape2.py,sha256=TDjmQZ_1CyTJ_fbo3vJo1Y3YP4XKMyFTNLwtKQCA-bM,9485 +openid/extensions/draft/pape5.py,sha256=S9oABuh3wq_BG11OoP3LNSVmcK5CpOVocbd-gFG3T7M,16508 +openid/server/__init__.py,sha256=KpYvxlPdF0VZoaDO85sFUjRYt3xeMZKiMO20WdPoOZ4,169 +openid/server/server.py,sha256=ICq3psJgoIkB40QbZa3OEhZm55ZzDYxqSkL3EnbYS1U,65575 +openid/server/trustroot.py,sha256=XdjycABgxzq8z0hZaphV5RdR8PwyCfsccId_0eGT4f4,14387 +openid/store/__init__.py,sha256=ZX1xkoa8XahEYQkUJNIdowhorpaeBlj33aILTfXOUYY,215 +openid/store/filestore.py,sha256=67D5fBHmlnG0taQv7qBkXRVdgL11exnZ9cgHW6HT0SE,12636 +openid/store/interface.py,sha256=yzIDYOFD6RW5rF8GiDq_5LUo_R0wn2SDltBSr5K96pw,7084 +openid/store/memstore.py,sha256=TKm5NgPxnZvj837v77M8u4BJCYqBmkRufoV36WT06_8,3587 +openid/store/nonce.py,sha256=AzsJfqEsjs7bypH2R5UVMTO-4l14SbXMxdXFnITKY9M,2843 +openid/store/sqlstore.py,sha256=VG7meXmIg84o8XEAKJnoR_nG76__WlFgfZrqexCHjMg,16631 +openid/yadis/__init__.py,sha256=1iKQS1NpTLN5twvh_8ARwvTRC7SNIk0xrpJ8uBJz6Ns,252 +openid/yadis/accept.py,sha256=pduXjMLe9Ly-aB82k5rEDUNgM8BthbesU1EvWa_wxco,3747 +openid/yadis/constants.py,sha256=lVqo0FXIPyBUx2x2BB9D_Ly3g6qGm6tlpB-ZixwJY6Y,483 +openid/yadis/discover.py,sha256=qXyysYnxvfV6WVrYyHxoeyFHFwKISzLf5AA5WbEc-sY,6062 +openid/yadis/etxrd.py,sha256=RbO1MAPs9qrVmVN8--EdjV1qxVSMfvd8phxIKYbt45Q,8325 +openid/yadis/filters.py,sha256=oMiBzxr_1CRB7ulHrFYwDsFnabKY85umE-_8t8P2pU4,7187 +openid/yadis/manager.py,sha256=R1thXogdBwbk2hkXWQRdWlbVubzaEa9RRbfkH_dhFkw,6085 +openid/yadis/parsehtml.py,sha256=-fNQ2WCS1r9uQtUfSL7ZzgyHzvayPcxczgRCrLr6E_Q,6061 +openid/yadis/services.py,sha256=u3SKE_1-y6HS8bme07klbMx2PZuojwjNgsOpUNdiRP4,1842 +openid/yadis/xri.py,sha256=_HEfGY5oAAlL78Wb8rfEqvy_PXESo_V5shD1Cj_Yevc,3475 +openid/yadis/xrires.py,sha256=N_QE7wz4QWACY9qSg5zl6x4YeO7y4GbFEJp1NIuy7pE,4276 +python3_openid-3.1.0.dist-info/DESCRIPTION.rst,sha256=8xWWV-FQ1x8BJQFlx3p0TgnbAngLBVZzzxmeaT90DEU,350 +python3_openid-3.1.0.dist-info/METADATA,sha256=oq-SzWcEs-K2UJdsvPuoCP61nXNsKjTI51Uh3dMZADg,1328 +python3_openid-3.1.0.dist-info/RECORD,, +python3_openid-3.1.0.dist-info/WHEEL,sha256=rNo05PbNqwnXiIHFsYm0m22u4Zm6YJtugFG2THx4w3g,92 +python3_openid-3.1.0.dist-info/metadata.json,sha256=rFGBmZez6W0aftF18hJGc69ntbcxCx0Hqb1hJPgzSqg,1101 +python3_openid-3.1.0.dist-info/top_level.txt,sha256=PZKYepgDgIpDlWmOt8aR7aenHJBBVPvrlOmDE_0uOpA,7 +python3_openid-3.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +openid/server/__pycache__/trustroot.cpython-36.pyc,, +openid/server/__pycache__/server.cpython-36.pyc,, +openid/server/__pycache__/__init__.cpython-36.pyc,, +openid/__pycache__/dh.cpython-36.pyc,, +openid/__pycache__/sreg.cpython-36.pyc,, +openid/__pycache__/message.cpython-36.pyc,, +openid/__pycache__/codecutil.cpython-36.pyc,, +openid/__pycache__/kvform.cpython-36.pyc,, +openid/__pycache__/urinorm.cpython-36.pyc,, +openid/__pycache__/association.cpython-36.pyc,, +openid/__pycache__/__init__.cpython-36.pyc,, +openid/__pycache__/extension.cpython-36.pyc,, +openid/__pycache__/cryptutil.cpython-36.pyc,, +openid/__pycache__/fetchers.cpython-36.pyc,, +openid/__pycache__/oidutil.cpython-36.pyc,, +openid/extensions/draft/__pycache__/pape2.cpython-36.pyc,, +openid/extensions/draft/__pycache__/pape5.cpython-36.pyc,, +openid/extensions/draft/__pycache__/__init__.cpython-36.pyc,, +openid/extensions/__pycache__/sreg.cpython-36.pyc,, +openid/extensions/__pycache__/ax.cpython-36.pyc,, +openid/extensions/__pycache__/__init__.cpython-36.pyc,, +openid/consumer/__pycache__/discover.cpython-36.pyc,, +openid/consumer/__pycache__/html_parse.cpython-36.pyc,, +openid/consumer/__pycache__/consumer.cpython-36.pyc,, +openid/consumer/__pycache__/__init__.cpython-36.pyc,, +openid/yadis/__pycache__/services.cpython-36.pyc,, +openid/yadis/__pycache__/constants.cpython-36.pyc,, +openid/yadis/__pycache__/discover.cpython-36.pyc,, +openid/yadis/__pycache__/etxrd.cpython-36.pyc,, +openid/yadis/__pycache__/xrires.cpython-36.pyc,, +openid/yadis/__pycache__/xri.cpython-36.pyc,, +openid/yadis/__pycache__/parsehtml.cpython-36.pyc,, +openid/yadis/__pycache__/filters.cpython-36.pyc,, +openid/yadis/__pycache__/manager.cpython-36.pyc,, +openid/yadis/__pycache__/__init__.cpython-36.pyc,, +openid/yadis/__pycache__/accept.cpython-36.pyc,, +openid/store/__pycache__/memstore.cpython-36.pyc,, +openid/store/__pycache__/filestore.cpython-36.pyc,, +openid/store/__pycache__/nonce.cpython-36.pyc,, +openid/store/__pycache__/interface.cpython-36.pyc,, +openid/store/__pycache__/sqlstore.cpython-36.pyc,, +openid/store/__pycache__/__init__.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/WHEEL new file mode 100644 index 0000000..bb7f7db --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/metadata.json new file mode 100644 index 0000000..55d68e3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 3", "Topic :: Internet :: WWW/HTTP", "Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: System :: Systems Administration :: Authentication/Directory"], "download_url": "http://github.com/necaris/python3-openid/tarball/v3.1.0", "extensions": {"python.details": {"contacts": [{"email": "rami.chowdhury@gmail.com", "name": "Rami Chowdhury", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://github.com/necaris/python3-openid"}}}, "extras": [], "generator": "bdist_wheel (0.29.0)", "metadata_version": "2.0", "name": "python3-openid", "run_requires": [{"requires": ["defusedxml"]}], "summary": "OpenID support for modern servers and consumers.", "version": "3.1.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/top_level.txt new file mode 100644 index 0000000..c2f7f63 --- /dev/null +++ b/flex/lib/python3.6/site-packages/python3_openid-3.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +openid diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..e73fab9 --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,161 @@ +Requests-OAuthlib |build-status| |coverage-status| |docs| +========================================================= + +This project provides first-class OAuth library support for `Requests `_. + +The OAuth 1 workflow +-------------------- + +OAuth 1 can seem overly complicated and it sure has its quirks. Luckily, +requests_oauthlib hides most of these and let you focus at the task at hand. + +Accessing protected resources using requests_oauthlib is as simple as: + +.. code-block:: pycon + + >>> from requests_oauthlib import OAuth1Session + >>> twitter = OAuth1Session('client_key', + client_secret='client_secret', + resource_owner_key='resource_owner_key', + resource_owner_secret='resource_owner_secret') + >>> url = 'https://api.twitter.com/1/account/settings.json' + >>> r = twitter.get(url) + +Before accessing resources you will need to obtain a few credentials from your +provider (i.e. Twitter) and authorization from the user for whom you wish to +retrieve resources for. You can read all about this in the full +`OAuth 1 workflow guide on RTD `_. + +The OAuth 2 workflow +-------------------- + +OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most +common being the Authorization Code Grant, also known as the WebApplication +flow. + +Fetching a protected resource after obtaining an access token can be extremely +simple. However, before accessing resources you will need to obtain a few +credentials from your provider (i.e. Google) and authorization from the user +for whom you wish to retrieve resources for. You can read all about this in the +full `OAuth 2 workflow guide on RTD `_. + +Installation +------------- + +To install requests and requests_oauthlib you can use pip: + +.. code-block:: bash + + $ pip install requests requests_oauthlib + +.. |build-status| image:: https://travis-ci.org/requests/requests-oauthlib.svg?branch=master + :target: https://travis-ci.org/requests/requests-oauthlib +.. |coverage-status| image:: https://img.shields.io/coveralls/requests/requests-oauthlib.svg + :target: https://coveralls.io/r/requests/requests-oauthlib +.. |docs| image:: https://readthedocs.org/projects/requests-oauthlib/badge/?version=latest + :alt: Documentation Status + :scale: 100% + :target: https://readthedocs.org/projects/requests-oauthlib/ + + +History +------- + +v0.8.0 (14 February 2017) ++++++++++++++++++++++++++ + +- Added Fitbit compliance fix. +- Fixed an issue where newlines in the response body for the access token + request would cause errors when trying to extract the token. +- Fixed an issue introduced in v0.7.0 where users passing ``auth`` to several + methods would encounter conflicts with the ``client_id`` and + ``client_secret``-derived auth. The user-supplied ``auth`` argument is now + used in preference to those options. + +v0.7.0 (22 September 2016) +++++++++++++++++++++++++++ + +- Allowed ``OAuth2Session.request`` to take the ``client_id`` and + ``client_secret`` parameters for the purposes of automatic token refresh, + which may need them. + +v0.6.2 (12 July 2016) ++++++++++++++++++++++ + +- Use ``client_id`` and ``client_secret`` for the Authorization header if + provided. +- Allow explicit bypass of the Authorization header by setting ``auth=False``. +- Pass through the ``proxies`` kwarg when refreshing tokens. +- Miscellaneous cleanups. + +v0.6.1 (19 February 2016) ++++++++++++++++++++++++++ + +- Fixed a bug when sending authorization in headers with no username and + password present. +- Make sure we clear the session token before obtaining a new one. +- Some improvements to the Slack compliance fix. +- Avoid timing problems around token refresh. +- Allow passing arbitrary arguments to requests when calling + ``fetch_request_token`` and ``fetch_access_token``. + +v0.6.0 (14 December 2015) ++++++++++++++++++++++++++ + +- Add compliance fix for Slack. +- Add compliance fix for Mailchimp. +- ``TokenRequestDenied`` exceptions now carry the entire response, not just the + status code. +- Pass through keyword arguments when refreshing tokens automatically. +- Send authorization in headers, not just body, to maximize compatibility. +- More getters/setters available for OAuth2 session client values. +- Allow sending custom headers when refreshing tokens, and set some defaults. + + +v0.5.0 (4 May 2015) ++++++++++++++++++++ +- Fix ``TypeError`` being raised instead of ``TokenMissing`` error. +- Raise requests exceptions on 4XX and 5XX responses in the OAuth2 flow. +- Avoid ``AttributeError`` when initializing the ``OAuth2Session`` class + without complete client information. + +v0.4.2 (16 October 2014) +++++++++++++++++++++++++ +- New ``authorized`` property on OAuth1Session and OAuth2Session, which allows + you to easily determine if the session is already authorized with OAuth tokens + or not. +- New ``TokenMissing`` and ``VerifierMissing`` exception classes for OAuth1Session: + this will make it easier to catch and identify these exceptions. + +v0.4.1 (6 June 2014) +++++++++++++++++++++ +- New install target ``[rsa]`` for people using OAuth1 RSA-SHA1 signature + method. +- Fixed bug in OAuth2 where supplied state param was not used in auth url. +- OAuth2 HTTPS checking can be disabled by setting environment variable + ``OAUTHLIB_INSECURE_TRANSPORT``. +- OAuth1 now re-authorize upon redirects. +- OAuth1 token fetching now raise a detailed error message when the + response body is incorrectly encoded or the request was denied. +- Added support for custom OAuth1 clients. +- OAuth2 compliance fix for Sina Weibo. +- Multiple fixes to facebook compliance fix. +- Compliance fixes now re-encode body properly as bytes in Python 3. +- Logging now properly done under ``requests_oauthlib`` namespace instead + of piggybacking on oauthlib namespace. +- Logging introduced for OAuth1 auth and session. + +v0.4.0 (29 September 2013) +++++++++++++++++++++++++++ +- OAuth1Session methods only return unicode strings. #55. +- Renamed requests_oauthlib.core to requests_oauthlib.oauth1_auth for consistency. #79. +- Added Facebook compliance fix and access_token_response hook to OAuth2Session. #63. +- Added LinkedIn compliance fix. +- Added refresh_token_response compliance hook, invoked before parsing the refresh token. +- Correctly limit compliance hooks to running only once! +- Content type guessing should only be done when no content type is given +- OAuth1 now updates r.headers instead of replacing it with non case insensitive dict +- Remove last use of Response.content (in OAuth1Session). #44. +- State param can now be supplied in OAuth2Session.authorize_url + + diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/METADATA new file mode 100644 index 0000000..b77d262 --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/METADATA @@ -0,0 +1,186 @@ +Metadata-Version: 2.0 +Name: requests-oauthlib +Version: 0.8.0 +Summary: OAuthlib authentication support for Requests. +Home-page: https://github.com/requests/requests-oauthlib +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: ISC +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Requires-Dist: oauthlib (>=0.6.2) +Requires-Dist: requests (>=2.0.0) +Provides-Extra: rsa +Requires-Dist: oauthlib[rsa] (>=0.6.2); extra == 'rsa' +Requires-Dist: requests (>=2.0.0); extra == 'rsa' + +Requests-OAuthlib |build-status| |coverage-status| |docs| +========================================================= + +This project provides first-class OAuth library support for `Requests `_. + +The OAuth 1 workflow +-------------------- + +OAuth 1 can seem overly complicated and it sure has its quirks. Luckily, +requests_oauthlib hides most of these and let you focus at the task at hand. + +Accessing protected resources using requests_oauthlib is as simple as: + +.. code-block:: pycon + + >>> from requests_oauthlib import OAuth1Session + >>> twitter = OAuth1Session('client_key', + client_secret='client_secret', + resource_owner_key='resource_owner_key', + resource_owner_secret='resource_owner_secret') + >>> url = 'https://api.twitter.com/1/account/settings.json' + >>> r = twitter.get(url) + +Before accessing resources you will need to obtain a few credentials from your +provider (i.e. Twitter) and authorization from the user for whom you wish to +retrieve resources for. You can read all about this in the full +`OAuth 1 workflow guide on RTD `_. + +The OAuth 2 workflow +-------------------- + +OAuth 2 is generally simpler than OAuth 1 but comes in more flavours. The most +common being the Authorization Code Grant, also known as the WebApplication +flow. + +Fetching a protected resource after obtaining an access token can be extremely +simple. However, before accessing resources you will need to obtain a few +credentials from your provider (i.e. Google) and authorization from the user +for whom you wish to retrieve resources for. You can read all about this in the +full `OAuth 2 workflow guide on RTD `_. + +Installation +------------- + +To install requests and requests_oauthlib you can use pip: + +.. code-block:: bash + + $ pip install requests requests_oauthlib + +.. |build-status| image:: https://travis-ci.org/requests/requests-oauthlib.svg?branch=master + :target: https://travis-ci.org/requests/requests-oauthlib +.. |coverage-status| image:: https://img.shields.io/coveralls/requests/requests-oauthlib.svg + :target: https://coveralls.io/r/requests/requests-oauthlib +.. |docs| image:: https://readthedocs.org/projects/requests-oauthlib/badge/?version=latest + :alt: Documentation Status + :scale: 100% + :target: https://readthedocs.org/projects/requests-oauthlib/ + + +History +------- + +v0.8.0 (14 February 2017) ++++++++++++++++++++++++++ + +- Added Fitbit compliance fix. +- Fixed an issue where newlines in the response body for the access token + request would cause errors when trying to extract the token. +- Fixed an issue introduced in v0.7.0 where users passing ``auth`` to several + methods would encounter conflicts with the ``client_id`` and + ``client_secret``-derived auth. The user-supplied ``auth`` argument is now + used in preference to those options. + +v0.7.0 (22 September 2016) +++++++++++++++++++++++++++ + +- Allowed ``OAuth2Session.request`` to take the ``client_id`` and + ``client_secret`` parameters for the purposes of automatic token refresh, + which may need them. + +v0.6.2 (12 July 2016) ++++++++++++++++++++++ + +- Use ``client_id`` and ``client_secret`` for the Authorization header if + provided. +- Allow explicit bypass of the Authorization header by setting ``auth=False``. +- Pass through the ``proxies`` kwarg when refreshing tokens. +- Miscellaneous cleanups. + +v0.6.1 (19 February 2016) ++++++++++++++++++++++++++ + +- Fixed a bug when sending authorization in headers with no username and + password present. +- Make sure we clear the session token before obtaining a new one. +- Some improvements to the Slack compliance fix. +- Avoid timing problems around token refresh. +- Allow passing arbitrary arguments to requests when calling + ``fetch_request_token`` and ``fetch_access_token``. + +v0.6.0 (14 December 2015) ++++++++++++++++++++++++++ + +- Add compliance fix for Slack. +- Add compliance fix for Mailchimp. +- ``TokenRequestDenied`` exceptions now carry the entire response, not just the + status code. +- Pass through keyword arguments when refreshing tokens automatically. +- Send authorization in headers, not just body, to maximize compatibility. +- More getters/setters available for OAuth2 session client values. +- Allow sending custom headers when refreshing tokens, and set some defaults. + + +v0.5.0 (4 May 2015) ++++++++++++++++++++ +- Fix ``TypeError`` being raised instead of ``TokenMissing`` error. +- Raise requests exceptions on 4XX and 5XX responses in the OAuth2 flow. +- Avoid ``AttributeError`` when initializing the ``OAuth2Session`` class + without complete client information. + +v0.4.2 (16 October 2014) +++++++++++++++++++++++++ +- New ``authorized`` property on OAuth1Session and OAuth2Session, which allows + you to easily determine if the session is already authorized with OAuth tokens + or not. +- New ``TokenMissing`` and ``VerifierMissing`` exception classes for OAuth1Session: + this will make it easier to catch and identify these exceptions. + +v0.4.1 (6 June 2014) +++++++++++++++++++++ +- New install target ``[rsa]`` for people using OAuth1 RSA-SHA1 signature + method. +- Fixed bug in OAuth2 where supplied state param was not used in auth url. +- OAuth2 HTTPS checking can be disabled by setting environment variable + ``OAUTHLIB_INSECURE_TRANSPORT``. +- OAuth1 now re-authorize upon redirects. +- OAuth1 token fetching now raise a detailed error message when the + response body is incorrectly encoded or the request was denied. +- Added support for custom OAuth1 clients. +- OAuth2 compliance fix for Sina Weibo. +- Multiple fixes to facebook compliance fix. +- Compliance fixes now re-encode body properly as bytes in Python 3. +- Logging now properly done under ``requests_oauthlib`` namespace instead + of piggybacking on oauthlib namespace. +- Logging introduced for OAuth1 auth and session. + +v0.4.0 (29 September 2013) +++++++++++++++++++++++++++ +- OAuth1Session methods only return unicode strings. #55. +- Renamed requests_oauthlib.core to requests_oauthlib.oauth1_auth for consistency. #79. +- Added Facebook compliance fix and access_token_response hook to OAuth2Session. #63. +- Added LinkedIn compliance fix. +- Added refresh_token_response compliance hook, invoked before parsing the refresh token. +- Correctly limit compliance hooks to running only once! +- Content type guessing should only be done when no content type is given +- OAuth1 now updates r.headers instead of replacing it with non case insensitive dict +- Remove last use of Response.content (in OAuth1Session). #44. +- State param can now be supplied in OAuth2Session.authorize_url + + diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/RECORD new file mode 100644 index 0000000..3fd1296 --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/RECORD @@ -0,0 +1,33 @@ +requests_oauthlib/__init__.py,sha256=rYQzGhBPUfdyxjRCcGBGKnWEt2RSwJ3Twz9nXsbjOT0,671 +requests_oauthlib/oauth1_auth.py,sha256=XVk2Xs6P1lw-RL98VUNS0CkCuQc6fF5kxGEX-CE6rV0,3549 +requests_oauthlib/oauth1_session.py,sha256=-YUo4CxZ1mWTwGvX-1tz_i_cAVtEN7Eh2VQPOvMcIj4,16474 +requests_oauthlib/oauth2_auth.py,sha256=LcLvn222F5N2-rZezCrbb2LK3ayLt26CEDMOBwEFL0o,1542 +requests_oauthlib/oauth2_session.py,sha256=SuC7_Pd_pvPWoNntS0mQ5D-1E1-hUoXFlf5BnWj39bY,17215 +requests_oauthlib/compliance_fixes/__init__.py,sha256=dkEvznaE2e6eEKwwx4SzNp9NmBp5QLZMFefsDtmsiWI,302 +requests_oauthlib/compliance_fixes/douban.py,sha256=ScsTLV8TffXulTxB5xSui7E7SA_LPakqU3hxZjnYft0,473 +requests_oauthlib/compliance_fixes/facebook.py,sha256=QU0VAMb2Vfz_WqiXq4SkblM3Z5uvqIZzNpaBk-Jt_3I,1119 +requests_oauthlib/compliance_fixes/fitbit.py,sha256=8ejsjFGkDp8s5p4eSut7M6vEfTsyGiYA6Jcx1PYjltI,906 +requests_oauthlib/compliance_fixes/linkedin.py,sha256=e_Am10sByk99x_XbSiEjEgxfiQ3IMKWrt67uc123rVY,824 +requests_oauthlib/compliance_fixes/mailchimp.py,sha256=ZmB1Tka44YejemalNDPpKLoPu2-kr0K4COEl88DO944,756 +requests_oauthlib/compliance_fixes/slack.py,sha256=KpJHuC9DOqwHfKmjZeDpCeWeBrnDznrSdtYPFcIUaLg,1453 +requests_oauthlib/compliance_fixes/weibo.py,sha256=7DytnOaDHGGHa5ECMFxkHOASgfzWEXsvAn-whE6soZI,482 +requests_oauthlib-0.8.0.dist-info/DESCRIPTION.rst,sha256=q5MdpwqG6snoqM9ozQ7SHG94aK1NJee3s8eL8Bqd68M,6759 +requests_oauthlib-0.8.0.dist-info/METADATA,sha256=Q0cCYHCz6lXuCX3ZwcS-p48TK8cRb54AiWCqPbokWVk,7695 +requests_oauthlib-0.8.0.dist-info/RECORD,, +requests_oauthlib-0.8.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +requests_oauthlib-0.8.0.dist-info/metadata.json,sha256=_Y6DjHvjtQHJyAq3iRnTkjTU9wsAMGwsFbtPdnax_Kc,1094 +requests_oauthlib-0.8.0.dist-info/top_level.txt,sha256=vx1R42DWBO64h6iu8Gco0F01TVTPWXSRWBaV1GwVRTQ,18 +requests_oauthlib-0.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +requests_oauthlib/__pycache__/oauth2_session.cpython-36.pyc,, +requests_oauthlib/__pycache__/oauth1_session.cpython-36.pyc,, +requests_oauthlib/__pycache__/__init__.cpython-36.pyc,, +requests_oauthlib/__pycache__/oauth2_auth.cpython-36.pyc,, +requests_oauthlib/__pycache__/oauth1_auth.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/linkedin.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/douban.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/weibo.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/mailchimp.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/facebook.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/slack.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/__init__.cpython-36.pyc,, +requests_oauthlib/compliance_fixes/__pycache__/fitbit.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/WHEEL new file mode 100644 index 0000000..8b6dd1b --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/metadata.json new file mode 100644 index 0000000..fe1dc01 --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4"], "extensions": {"python.details": {"contacts": [{"email": "me@kennethreitz.com", "name": "Kenneth Reitz", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/requests/requests-oauthlib"}}}, "extras": ["rsa"], "generator": "bdist_wheel (0.29.0)", "license": "ISC", "metadata_version": "2.0", "name": "requests-oauthlib", "run_requires": [{"requires": ["oauthlib (>=0.6.2)", "requests (>=2.0.0)"]}, {"extra": "rsa", "requires": ["oauthlib[rsa] (>=0.6.2)", "requests (>=2.0.0)"]}], "summary": "OAuthlib authentication support for Requests.", "test_requires": [{"requires": ["mock", "requests-mock"]}], "version": "0.8.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/top_level.txt new file mode 100644 index 0000000..55d4f90 --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib-0.8.0.dist-info/top_level.txt @@ -0,0 +1 @@ +requests_oauthlib diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__init__.py b/flex/lib/python3.6/site-packages/requests_oauthlib/__init__.py new file mode 100644 index 0000000..56560ba --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib/__init__.py @@ -0,0 +1,22 @@ +from .oauth1_auth import OAuth1 +from .oauth1_session import OAuth1Session +from .oauth2_auth import OAuth2 +from .oauth2_session import OAuth2Session, TokenUpdated + +__version__ = '0.8.0' + +import requests +if requests.__version__ < '2.0.0': + msg = ('You are using requests version %s, which is older than ' + 'requests-oauthlib expects, please upgrade to 2.0.0 or later.') + raise Warning(msg % requests.__version__) + +import logging +try: # Python 2.7+ + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +logging.getLogger('requests_oauthlib').addHandler(NullHandler()) diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5d2f08a5296b8f31d72c98f7302b1c071bd089b3 GIT binary patch literal 1069 zcmYjPO>fgM7`BtNP1B}y8$uj7VIcGe-53qP&)`?TATY$R zKoKSwYitN3GKpDWQ&>@jR0?c~YE&b&$R;+dE2Pe?x9;Se-O@lQ(qc z4qD=GxZt?Ny=((=gd4R;3!@VhKd=zLHFe~7jP&;ku?A~iA=c#Xd3$M+)<+Z@9}ujz zn7hR1+q|`e9pU8K5$d$AF$nMAy!!6>Je%wRa#!exD-|a3rqu5k>r#JR%A3c@l*jK= z7G#_)D!uMOw|5QOp~FR`-|cmK3-vLXdx7NMT!r!2lYBPkDpTGCmwI#WzIy0=o`j=` z7b-6ij7u+@1o4Krn*^Xrgr^?=lJZdo^i=Rb!G`Hr28?@I;uS}EiSz{MkX=yRIGhXd zI*6Iza-;>71Fmm%UAF;nL@-0dV3-+Kh?vX-Lo8`SX{3pI=vh1T0)AydUjhmFt=7CsMq+2%hzcty^IqlGPd4i3LHQ6eKZe(?K zNTZOrNA)dAt%tPM8MS&|($vWnt-qpam)EpDEol`<$|{7sLFxMSfMB~kG&dtP&TA)u zjNzf>HIaaIT3o1ZIJcjyEPAS+ bg1QHxiS^euEW^eQwu~0c25#dPzH9vhe`y=_ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_auth.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_auth.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..324f250549a6a36853d1c06ba9ac626b902df34e GIT binary patch literal 2537 zcmaJ@&2QYs73U1OTrT(1k{ze%2N(kZvO%P^Yo)aUBZwl)u@QS^$SX7^0!%2*?20RX zd1h!u3o4iOIfq`_f1yCH{U>_rnLtnNrT;=teKSjaBnV1k-afv0Z|2SW_|4w$c3to9 z)ccf9FeswrS_B zK|8F!H2vEYzchy3|A1e{5LJUBjlzsF5=WBpAQoc~_Skol2caZ+z=K2#&C1@5BbLgl z_wwnNct+}Ih;HpPxf{m?H#~6>pz+OV*k-A5ZjR;`PqH7 zyUa)$$mk6tLh>k`fy~({BcnU$wt9rmMW*|Vm&Eh z8G)N}q^D>ggHRpq;%Ni(ISla-2s3;{4G2E+O=kHGidQeQSyOW+P;1(z4QjtdzC)YT zd29GB9kNzs?kv%skFs7feX65a7J0=e>L0hX;>$KY6gVJsd9@g`93Y zcpO53=%)vC4aQe6#5X|BI%RWiERFN#AB^;-Wk_`5at|5mePyKhYol!EAC<-&pSR0S zX%Mq)m#D<0Sz2YIw997cl&xutqSB2}+5Hp#0fCl94M;N{R}N(mDizvB)hP0u@v65Y zA=pQSWP6-vyy}EHxg?AOA*wY9WXMRAhH(LPag@=s%3f&~-I^CH z4C$TYrHPCZ&CR(5C2KD6IWBR>;G41mQhSNSr%G$yl+Fp}pUKua zmTrkMk*?ZSuduEbKt>Lqo6ClTdi`{u1fm z!4f=~+xefVskD~XH}FgP=N3>q{Z_5R|NVdL{_(SRAO5@rwSTw;J3vGwT?G>5zR+YY65(`uw+0D;ig+4a`mbX~80UMMtN=_J9K zcf`E9NvJpFS)#2}yLi!5^I?`s0L=|myj~;Phj)PsBvg<=c#UMWxVn<^;8q8GplC% z6imm9{$UO{%BTm9D#>E~LU7E$6E3uHa3NF+frJm&D>Ke!RfDplV#aSk*;dWkqB3Vp za#M*crO(%ac9#qz*_}}XjtjPb)+s2QeP_kms*Wz z3|WzPlmxn2#fp&M!A zZoqe8i1&av$ikM1(ba2dzBABz!*`Vr(?X*#;R=bWCggMm1>^TGU~V(ak|axYI^w4lhIfwyome5CAtp@=&FwV3D-t@8PB73|N{F?Do0kiibR3E9#wWD=qU H!)^QzmZGxq literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_session.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_session.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..61503007e8617535f81966d38e1fc309f9fa0426 GIT binary patch literal 16009 zcmeHOTXWn-b_V9o5lK9@)uH-xBP%S;5AQvOeIhIl<%AddN3D~@|IFb)$T|D zap`V!pFWrGoIZVZZmw+on^UWOY#9Gxq<*uw#xo=MZ=4*%F+HPg)=bmbTfkk$%d|7K zjA?vnbgY~qvO8t@k!jFBew$NopBSCYFX&0u%h&SwoAV0oVy(!f^0+S5X85|`mD|T^ z$J(>C+4fv*j-O?{`P#fwbV}aw_CjqTes-dELY!zVIx{;t`8)AS?WD-JUKOYI&Dv{D z**W&ysFi+dIJ3^&bHkYvr}wk9*Kt4Z9LN1}zCVNe1?L3rPw@R4xL@zV#+* z{K&~ZG%6>*$H5L0zSZ3ew{hjTjnMTww%n8StqlyIj8VnIiE>mt%6q=; z1eHvb+w`41IfsJ(XvhWp`TgInbfx>$4#mn-Tdp*HRFuI=Q?6XyS?$QLuU)_F^qv0o z;M#{f*RHp(zklsAep{WDM=M>odug-hdd{WyIt}?@c=_uUDbURz3@-UL2;jM!`bk|s z;fvLJAc6q>u6Fli9{qR&M^MJe$YjitS-`>18}umoW#fzB0xpD6GaaLr`L)qBiFnUT zwXDcFSts`#WUb|YZRCtv0VK>vi;w&r(RrXccUyE^;WU!A=x=faXK)i5p@|8|U>eMQ z$j|`OQO`OCS~52(xhNm>xh@&U2k9#r?pAax2<@;J)S2~7c?wOb-Vtx8 zF20Vdi#P&eVI$jrYf1;xtrUqW9lI?+m2$n__MM(b*R%Ed*FD=)Z+PHQsVjZZGTf8& zk!UWaP(yfLLM7@)#gwPie`B< zKX<{5ovlBZDda2olKe59XhzkPXB9bL(+}cd3`t96S8)ty|J8JN^t@N{k~mY6kVqPo z`pR2$#jQeMBMW+6(HM@(8wVU%0$P%5YlbBLdIl)P8t8c{%VSs zEwuDLHA#$uZ_@q9H(9)?z^`x`ezTT!GU}hmiF|yWZI)TEb58#GOsyb_PQfX%V3(Xz zPRW^hZq{ZTWk$?e**VQ-#H<}t=A$;N%tmcanF|=Flg_I!ILCSJ7ckD(qdBFfA1X~` z|2qUJ))7|VZgs#AmfdlzhTnNgI-}DNR$D;nI)T-MjD_36Vr6DIHr`dsWedMAx9!k! z1Agnc&8CoCaZ`l5LUgQ=Pjd6>$xl@6vDb|d665VzJ&0^v)p9#9Y#n|~y>r7Lk!t>| zZ$ZbnLCBqPVJXl{Zq?fL!L@NzZu$YM>v)PEPytMU{Y*>gp#{c<*rh_h6g4H>pNo4TCmze=VdReJs=oOxf05M7l4^rzaOsA% zw9^uDJG^rB^7Y=*^3+nG2XP}6_5#OlHMg%{x%%M;*W%*OlEEZ@F{p=t!eB2NRn@xd zdrE0Qsd;W^hlNT{rmi(Qw&Xoa5RK!)#1l0#EdkTnFR3Mdk{b6~;50A$*FU^+HE20a z+`Ow^CUdpMQg#07`uQt&@n6dpu9G5@ymQWawCzegXg!sRC}KX!bl0Vy85s1^sDl!yr_bG%G0!lJIPm6>B{iwW40~4KRmlM}PG& z=J+AJaR6(i`0Z(o2vBnO>NWqv>+gSX_1b&mEHpM4#o%d?y^y|n5%wOR>7#l4%1$fT zcADMmSKO=DY%qM_Omy>{b=&WVs`bS0LE1Z32S%}N?<9sY7Q$dCxG}y>X-w$sagJSG zQN={C((A&Mi7NSCn$xU~*sZG);FfQf)Fs3cRsew4C;RLdNKIe|?i#cH?e?U>^O*^`qk$j^GoVhVITjT#7I|FqehcTQo`b)EqYku2DBAunK&M;qenQ!6gemj9Y9(%- zGULQiH^Ad|J7r97SnKhf6?8=d$g^PqA;3Hn233j4ae;v496KtT9LkiLm7;F;f(4N>=ZJ3zIHCjGt(XIq)*+`qt%WQ{bVbJ@tPfS zKnqC_I#ILiMDv`XEAsHJjA-nMZ)#SmEC1n!^)O*B?NK-aA87-KTH6fKV`X&sXK9Lc z>`s*j2AnT|m%D@kf!n15-I^_?U9cfV+@{JGtJPtwi{75qLG7}`27C*y^TZY9PV{Rn#yz!xoJSJ1Qz50?hQ>j1H_|9%=(IEhiPab zeh8Vd?kEmcVs%s#>uFaC5Wq%|&C4q z9IbB-Ch~ZA^TypzN$5Tm-rfYC$iP-go5Vj3 ze_F(qK^}*{7hl$=BpTe>L8-TkP9@Mc;uQJbaie-d`msNk?|8 z%H8@3rUvlWb`noL)){)nVgP^;3M{DGX?Q(^>xpa8NpIKQ3(x>RWQj<#b!^0RO9$$n zv{q!5NReh;E$+a@ixeW=3h-ISaXE_8*9f_RFu-CxB6)D2b2(cFmh z7%e6yatIBgSI3?y!xYV?Em5>E&O6ck@Cj+Js2Fo?R7!XQO{jwOpbT#ouLxP81oI ze}+?3P}EFaJ& zpq(k?2OAqhT5Fp$MI6zC+#sZ)83QW0yhk2R+0_$THn)>iSbZeiu=%`0E{1U@2ql zgGRQ<45{egq^eCGR3+wnlt;W#tGpb$1 zX^`p8Cd@X-I_5|4lv+85{3LTf3%@BpFbCOIVIK=e>=Dtb%q``4fJc%~D8f+~U=xO> zo51*ZT2&y73}g)2Y)=zL2^Xtp>BX^3Qhx|qKwqkGGZae_wn^YVkJMV#v0nuYdA5UX zzvnraKmCma_$qD3ygt$KWo2%L4heBF{2cU|m_xS(dzosELY~xjlvO=Q-Vq=1OLD_w zrvO!BKTr(4wKzDdxB^WNW5+0kd8zZx&Y9nO+l)~VUVC@YhhKn`BL?emm`Ha!I?ZM2zD^@5Igi^ebi1}zQ z5X|ez4$RggpqJ5p6K4@Zp;$w@MbX|A3|oh8`1&xSp2W@-Re_BW3s*Q_$3lscDeAqv zU^4MAwKg&iiVPhcnog?30p+A6Mm9-6Z6XL9hwU%H#w{V#tL&$;$ZJWoUPV%-w)aDV zw$#8da7%aN!P`TkTjRx3QQU(gTvSPgkLZfXIzkL>g^rBMQuUV-nN`uRKe#uDsr&xg z(xxrri0Mn~*t^s)wR@OEkcf0Ww}DtmTBZEa2*TJ}h~@|#Q6H{W{YhqC9&0M0O*rm_ zS2L&s(J@t-jZXh9a%>PLOxc$6v{Y-6emx&lij1}73IGlRt0?RFTT$K-o4qYb_$SIE zi>e(|atyPgEb7(fM(m#?bTmvt;2aI)Sf%3~q|N^orGf%Z#>GW~U_YJ?!L(x1NQ`oW zvN#xteTs`gM$S8C3xD^`4)O+o%8)Tt&*E>6W7pZ=n$L1!zEyCt`zG?Lk`hIIh1fPQ zTF!omA>QSM9y>t@AP#VRO2E_js;Srd4u)6jqb&iNX-K4z0niFo8x&L_ClB_Pg8QkO zG(m9;kJr@|z#F0~Oep!JP3gB8z5^~mO)>RWzPYr!yL$F zfJ>b{-7oM%65YThAY>fIpTMousiC?YM^Gbp$2tN**14$LUF#@$T2~mx*?kEYQ zZHFn6Y`{;$d>{eDrYwpC5(dZxNTiv<&VXp}eHbCrD>OsEco<>E6eI~J%Y<$P{Iyh= zHwkFyW)Lp%vA%#FrPc#z9z~-^5wL`@u#llA{pBOL5vcLUjJtksS{r>@hZA8)LOwF} zvG{py>jEMK!0AXt;Y5xOtvPiOQxKJErG&gqBzg?hbCie7dTk`psYO**Q}QbGp}a;X ziq9ZRjGbZ1!IC#{isnboWiApB>fOQ|gWA zZS6tdM{A_FDV34}G<5`Q)D=4Z)uV@+Hp(rMC06b$YcuDXs zrM%O|QYBps<@6AhqFefZqE5!w_MrnbVjN#9XJxUHt}#^4N;Qq8I;0z$gr6hXA*qy@ zBr}BhK6atjMzUby)RBaiYw7F;J!cc1)Js=yY|wvT-Us2mgZ;|`^FOFs+7XS(a!3wH zszySmsd*q&?lAXL>5KHFACM114IMl(%4GWA^_)4hz0tQe0Eak2FkFtIpzkInazeXT z``3qBKB0_dhgdt>?1c!i#vwLR^a;YUJ9}-!OQGV2%AG$K6}JR(u|vsuv+~cOGKczo zM749={Udt6MyD}tzBozcMFh43DrTI?L6I*Yw_d6A{%D3=9Nb7xP=mtl^dk&T`urA- zfGkW1ZR}vT!ag>ZgwW>*up?cbLht+LvkbolLPz?%(JByDr$l;=jDC+M?|g&gO5l~0 zW1_JvL*Wqz0{A6PpwbYGBVt-U$Dt@^T~REIu^1|0$vO>}IsUmRNHr%-r$UXyeaVkw zle%jvnyM2mftES=3H2WHNeZlL=(w)HA{O0JqlK|20;D@NI;1}-LFEf3Gk#F=8%@-~ zs^b4#3HFf-C^J@cLE;0`4}#{K856x^y$xtvb3+ofM#C6{-?58$doekxa+?? zO5thKswqp$WQ6EBR`M^nNOUxGaHf@|bt+tehLqulC`lG8R>j)9;-MOW_RNwaQrQB3+-yw!$kpBRqy}h5uk0&kCl|KZQ9d zoHEenu>tV=cjlMI6WUscw|`Uk1#dLub_;_%+YhoOSc>KT&Egqd1Z186%3NfOu@4&j;?`fr$P09&wo@h|siM4=R>dL?{dbIa`Ltk6D3oCzj3!OV$3_ z+qCy@>>Ik=+vkI~`~UQq7HHw!`;iKk@&*wAh?f+{)jS+b=; zbqtTL9jIM~>LK<>n1YmBT18%fRy0Gj66MH3^NuaG2`)$Jl5!pV#(D@Fxnxw0OoQ?%TW6ee2GjM<+GK%IM6Ps3;)BB+zat=}0uL8+H=%OC01@FO3YvAC+x8x-CXNg-!(cvX`BLEy zS`-}ujA(A8;YYMJv8Y-NqAaw3G#hW^VYfnF!iZx75G}-SoMiU_Z&(|nel#l)2gdeJ zuHU6;z?!yU>DKct>DxOKf3tQj&zA!wiG(XX_Fs0J= zTdkD_j61@RW{TM591M4kD?9y$!i?K%*&=YBLO1up@E}DjNJECwh$Spy@jH~pO}uG~ zg)Y*%{euHKxE>xeW^k#MnqYV+3@R1jmyfI$ItnM zR?=c&R%pwx7Vi(S0AEc-HibnFWl~8zQ5w@?_J0^tWh|V5-m(dgD~k(dajwQfat!Mk zH|JKB{T0X}FTR#TeBw%qnaCU&sh)j_^U6TrMJ6l8G0ie=4Dmm&5dM1)3EPNjFx-s@sjLJ8mI5uhHf2f!=|S ze|{O1T3k}g2bWY2CQ34{&0wMj56>SI`r`0tpIx)-BXjuZ{O~A0`ucDm+G#O38I+>j z8&^WIy|0Q)KeGE5g9V4p-mP%eO`{k_e5F!d;3F$&K#>E$OcTtRX(e-wt zlm7CLEOfKb+}nx63*#|3P84V82ALfTLiiN{{iCzkjX`fgZ$nQ&H1X?bjzBhuB+y|L zWD(MBkOf>K98ynu7f|)lsFTcGs2O0F!P|#u{?h?Pi_6<0kKd>4ka|UNzF5ca8bPjQ|5O2#=7@;LA7~ z1W6aQQ5Su_&VlBiUNjnwfZ?#HpgAX`?h=wKR!Qe?5^_;dxtK`^VJag;?*gR$)EP(l zHRJZz2^iOnqf`gQVRze)jd~jnyva47IM$`xOCB%oD=)<+1ui4$)=~m}M{w;RJ=of& YK8EJxkDJJ!0RI~9Z=xNvi8iBu0jX{Z`v3p{ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_session.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_session.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbef40d87e56b5a735cbd4ce06b3ea669a1b54b7 GIT binary patch literal 14163 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 LeHZ$Tez2aO5GAkj literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/douban.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/douban.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fe992ccbd7df59bbba568512f9bffbc89efa6ebd GIT binary patch literal 822 zcmZvaL2ueH6o73f0mPuKCRN&L>oo@mQ&|O4RZZKSOLxj83bAPejvZ{LDhN*Ww12X{ zvVSmNciLarY0rTnCe6`%wx8eo{N8h}yWRHHcN+h|2>nDSgTVSDh`s_v5yc$EZYCTT zBqkV9kNPht_9(tbVekvQ&?LmRZDdl3Jd>1Vz^sR3c_5!b^jlCAQ}Tkgn0gzOpbeU1 z>TmE4s2k*HlVhk0b8_!OZ$KGvh5)j|TfD(@xSkRh2zH0STVjF@oX#86+kt1&tdai2 zWwu}CuuL zEtxDzo~I&XiAzUER94ee^vjh!1H(SMx)Uwe*ET4GJMgvf1zz{|-ca5trGLw1mU4a9 z|Ce9`$VaW>%QVZF)(5s?x|Bk*ki_qjjOWm%pw@G~{uK=)n)7s;N Y?p{sRSF~;mNh{9o+`p_Hp+!30AL8ES6951J literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/facebook.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/facebook.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2eca1c9376bac46630c98c8ff21034757e245ae1 GIT binary patch literal 1099 zcmZ`&y>8nu5GE7N!s5t&4mM9Cx-B!)9N zWBVX|lcJBHwNqZ9Q;%{JxB*InCm!#<aq>ZHSuGx>qvucYLHH!05nDmk~GAYh5-EE(*y7cH4`EZl%Y)UENaolH?qLj zn1g?W<`}Bj;FCF_C}|2hCv!}lF``X)x?`jo$_836Ck|~bfN0RH3APVs8(~qTaTKaJ z=iWr-eAbR~t{7K6bycvLtJtOTiZqP*?GNC645~a%7%PI4G~`Ke5vC=RmCY`TSTO0k ze>XwQ{l*&>3MRCQ3FARs&-!qD(A(>}m3_`a%7mXOQq0D;bn4lt>8 zTbd`}R?q&!!G)k~fI-CRw6V~yaoBBErv7}EZE{MH*8kD6i>>!rkqh-sh+I@{h=GFr uI_|IE>DLE=GQiip)+JZy=!S}#lQYh8TBdAY|BX_s*v15J;753qw2eP}^eof> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/fitbit.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/fitbit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5522fa5d0167a2010989e022707c1823a1ab56cc GIT binary patch literal 1183 zcmZuw%}(1u5Z-m{B(*}z0jhFap{i0OlF&jckPs?DMXJ=&s-zcREXTV^oY=c&c8vgq z6P$RHzDgg#*Pi+cJ$2S0i2kg#quJTn@0&kwEiL)%I}dJag!~{6mV^3R+d zX7K9ML1D(P*q5V2rcESy1e^^n7zk51$y0AgNcX@OA~E27)1CU5ClCU zrqMSQcdqM*1!(B9{y5PrFN|hmrI0s~EKj3YFs*!iX_$(TKTQTKMwlE_EVQsOCy)q3_6%Ukdvof(vF8a)>h_4I2q#YS+3i(MCP!NNvybtvEJ{(SlR^3W*94Ju!2|!3Z++7{TqQ6I{mus<&d03M)b}i=nYlM1?b)o zdRx<%5>B^wH~A&M+|}DV)9u}C_s#Yuu958ZdwG&~21Szc&TARNj@dlzfjBLMHoCKz zk$X#cZ;4fwr%5DZ5e}1cfn3$7ck_$#ahT0p7*^>mV|?02L8wog&I(|TD;|vT$f2^b2h)0`>YVgu-~POMJ1IqSCKBF zN~X=0d5bhI7>}_Dg9VkWMGaxYFeA5Ufxn^zw>NnU!klktiXse}Ft^4_sH(L)HCfqE zi8o#+HE>MgGZ6I>ltwk0;w4^ShHJRs1ZkozNCeN+X4VqdC~|5D@a=Kve&kegY3iME z5$T(f&z;ZbpL#>zl_qy%YksYz1Ew_hG%gngRpUi*Av6~n2*9#&+5|t4?7~NXz6YgD zuc+q16_r6!WQaZN~>N$D}9lrU)G*z?TL%L%u*_19wq5DS3#DF8E2{Jmp9E`l&4CiVglW{ zN9jSC$|!`lu!o=F`QSbTfNdu0pJYW$Gj-bkN1E7hVwMeJ>^%^52#T2&qJ&KF!qUKb zZ7sVLz0raiB5sCfwhwHY6K%klUcFe;k_oLN^ kX~^Nov|;rdn2;6KX4W@I_fGPHRT)1uf4njQJ7mZD3$-jgaR2}S literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/mailchimp.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/mailchimp.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd783344618a92f4c5afd0d6b8f1aa30453070e4 GIT binary patch literal 1072 zcma)4L2uJA7`2_G4PD1T8fe^hTo7%BR;E>&CIp;&8K+#LEOwcj#LjGI8dZ1NuKW=G z3O~VDPTV2Cd2!s&&+mKB2b-I{-ETDcNf7#u)&_i|xA5p)0E#FsQR3dD z#1%;hM%1JJElNC!Pf-;7fGRYJugJu{ukQ)qSc!7(Kdbxnv3y@lbZaxEnHy9)1 zL#;akezOjR9Sf9p7eEzxlO)i&tl2zh%ye)Rh1Q>GDQr;6jB4u}c4e$r%UU@BHlR&a zYujPMb!o#im%=b%B4V+!fsq9hUya}w_2{S=jjQqDXdlKW5r2$pUJuS1UeduEk*g!Ke-SHo(J*cFKxU0OFZtPOC0(uL zvZ_m-iJYZVe#LZLWxUMKc~uYV&vqjfON*G=JOYS_at*2t2QH60*o2wgPK6ZdoGF>M$7F^};r?0k zBy^q}{*NbrQzg%`JZD;`?R`k0jamxLBAo0f_K9o5)Vjy#QWlYCyBfBD5CxqgwY8Gm zif?uA{7a5AVW~arhTT4-s-D7HGTlaTl2+>w>L~EF1ubLBb4Fk+m zk|`x{4VcG1ijA`&Y!Phahsf$13JscQTEdhJtSOn2iB(zyn;Fe6Z5QO8c1o&YO66Wj z?{4Iq5=v6io^=c*RVU1(T}a4;=FjI517j;GBL|Rg&OCawkI1@#(e{*1;10#3N0p<+ zkmp{rvRM*qX{wf{*UA}XKG#(%QAw_Jl*c@9qd4Zu%m?G7>CAH~3&&YC&UnPQ zR0tL6C|E>WSc6D20~~KdVPGQi@9^iVU&!P<(%e6fq~8;%3G{o?-yOcl<*3u$VVCT( zt2+C`PB-iBb#{>YxqsppNwIx8PExl0GLPlH-WmClV;ZH^wur|1EKN@5mT+!~MOLJV z=^*SSpSki?8pXqQaZx=EbCHL)_jmT4ynI#T z%)@8!?SoXrQK}Bx|HTg03n?^@HD}>X{T>M(N#0MC=JGz^nGi#d%4MuWt{Jg{!Xq=o zD(cn7zlvDI8?}G}h`Z$yxsKYA*QmE!@3~rfmlZRpJ0tTo(;LoiMAoB%Sv~!Z5f%>f)$d`0zx&zh45j$ zhow&d2qxYg`izOcMGKT+K#v4lyn~Qi1oYwLKj%y5{2IW3$pY^%xZ^Dljgj$;56&YFiE3#G*?tPT}vu5v##hS{CM@DJM2Wt3A2Hrw?+l9RZgUhsBr3SoE1_S zDhyV(Bvjp$xL!Wl}=_VIa{6zwHl22WJ5M1Js2(rqjL0S z(1$S>$yHLZsynS2C*5;VsG;ev5=B=v)kb$^UYl#qrVlsS!%ZQ}inCl4G@G$os*_KY zO=YiI+h "3": + unicode = str + + +log = logging.getLogger(__name__) + + +def urldecode(body): + """Parse query or json to python dictionary""" + try: + return _urldecode(body) + except: + import json + return json.loads(body) + + +class TokenRequestDenied(ValueError): + + def __init__(self, message, response): + super(TokenRequestDenied, self).__init__(message) + self.response = response + + @property + def status_code(self): + """For backwards-compatibility purposes""" + return self.response.status_code + + +class TokenMissing(ValueError): + def __init__(self, message, response): + super(TokenMissing, self).__init__(message) + self.response = response + + +class VerifierMissing(ValueError): + pass + + +class OAuth1Session(requests.Session): + """Request signing and convenience methods for the oauth dance. + + What is the difference between OAuth1Session and OAuth1? + + OAuth1Session actually uses OAuth1 internally and its purpose is to assist + in the OAuth workflow through convenience methods to prepare authorization + URLs and parse the various token and redirection responses. It also provide + rudimentary validation of responses. + + An example of the OAuth workflow using a basic CLI app and Twitter. + + >>> # Credentials obtained during the registration. + >>> client_key = 'client key' + >>> client_secret = 'secret' + >>> callback_uri = 'https://127.0.0.1/callback' + >>> + >>> # Endpoints found in the OAuth provider API documentation + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + >>> + >>> oauth_session = OAuth1Session(client_key,client_secret=client_secret, callback_uri=callback_uri) + >>> + >>> # First step, fetch the request token. + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'kjerht2309u', + 'oauth_token_secret': 'lsdajfh923874', + } + >>> + >>> # Second step. Follow this link and authorize + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback' + >>> + >>> # Third step. Fetch the access token + >>> redirect_response = raw_input('Paste the full redirect URL here.') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + >>> oauth_session.fetch_access_token(access_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> # Done. You can now make OAuth requests. + >>> status_url = 'http://api.twitter.com/1/statuses/update.json' + >>> new_status = {'status': 'hello world!'} + >>> oauth_session.post(status_url, data=new_status) + + """ + + def __init__(self, client_key, + client_secret=None, + resource_owner_key=None, + resource_owner_secret=None, + callback_uri=None, + signature_method=SIGNATURE_HMAC, + signature_type=SIGNATURE_TYPE_AUTH_HEADER, + rsa_key=None, + verifier=None, + client_class=None, + force_include_body=False, + **kwargs): + """Construct the OAuth 1 session. + + :param client_key: A client specific identifier. + :param client_secret: A client specific secret used to create HMAC and + plaintext signatures. + :param resource_owner_key: A resource owner key, also referred to as + request token or access token depending on + when in the workflow it is used. + :param resource_owner_secret: A resource owner secret obtained with + either a request or access token. Often + referred to as token secret. + :param callback_uri: The URL the user is redirect back to after + authorization. + :param signature_method: Signature methods determine how the OAuth + signature is created. The three options are + oauthlib.oauth1.SIGNATURE_HMAC (default), + oauthlib.oauth1.SIGNATURE_RSA and + oauthlib.oauth1.SIGNATURE_PLAIN. + :param signature_type: Signature type decides where the OAuth + parameters are added. Either in the + Authorization header (default) or to the URL + query parameters or the request body. Defined as + oauthlib.oauth1.SIGNATURE_TYPE_AUTH_HEADER, + oauthlib.oauth1.SIGNATURE_TYPE_QUERY and + oauthlib.oauth1.SIGNATURE_TYPE_BODY + respectively. + :param rsa_key: The private RSA key as a string. Can only be used with + signature_method=oauthlib.oauth1.SIGNATURE_RSA. + :param verifier: A verifier string to prove authorization was granted. + :param client_class: A subclass of `oauthlib.oauth1.Client` to use with + `requests_oauthlib.OAuth1` instead of the default + :param force_include_body: Always include the request body in the + signature creation. + :param **kwargs: Additional keyword arguments passed to `OAuth1` + """ + super(OAuth1Session, self).__init__() + self._client = OAuth1(client_key, + client_secret=client_secret, + resource_owner_key=resource_owner_key, + resource_owner_secret=resource_owner_secret, + callback_uri=callback_uri, + signature_method=signature_method, + signature_type=signature_type, + rsa_key=rsa_key, + verifier=verifier, + client_class=client_class, + force_include_body=force_include_body, + **kwargs) + self.auth = self._client + + @property + def authorized(self): + """Boolean that indicates whether this session has an OAuth token + or not. If `self.authorized` is True, you can reasonably expect + OAuth-protected requests to the resource to succeed. If + `self.authorized` is False, you need the user to go through the OAuth + authentication dance before OAuth-protected requests to the resource + will succeed. + """ + if self._client.client.signature_method == SIGNATURE_RSA: + # RSA only uses resource_owner_key + return bool(self._client.client.resource_owner_key) + else: + # other methods of authentication use all three pieces + return ( + bool(self._client.client.client_secret) and + bool(self._client.client.resource_owner_key) and + bool(self._client.client.resource_owner_secret) + ) + + def authorization_url(self, url, request_token=None, **kwargs): + """Create an authorization URL by appending request_token and optional + kwargs to url. + + This is the second step in the OAuth 1 workflow. The user should be + redirected to this authorization URL, grant access to you, and then + be redirected back to you. The redirection back can either be specified + during client registration or by supplying a callback URI per request. + + :param url: The authorization endpoint URL. + :param request_token: The previously obtained request token. + :param kwargs: Optional parameters to append to the URL. + :returns: The authorization URL with new parameters embedded. + + An example using a registered default callback URI. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf' + >>> oauth_session.authorization_url(authorization_url, foo='bar') + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&foo=bar' + + An example using an explicit callback URI. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> authorization_url = 'https://api.twitter.com/oauth/authorize' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret', callback_uri='https://127.0.0.1/callback') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + >>> oauth_session.authorization_url(authorization_url) + 'https://api.twitter.com/oauth/authorize?oauth_token=sdf0o9823sjdfsdf&oauth_callback=https%3A%2F%2F127.0.0.1%2Fcallback' + """ + kwargs['oauth_token'] = request_token or self._client.client.resource_owner_key + log.debug('Adding parameters %s to url %s', kwargs, url) + return add_params_to_uri(url, kwargs.items()) + + def fetch_request_token(self, url, realm=None, **request_kwargs): + """Fetch a request token. + + This is the first step in the OAuth 1 workflow. A request token is + obtained by making a signed post request to url. The token is then + parsed from the application/x-www-form-urlencoded response and ready + to be used to construct an authorization url. + + :param url: The request token endpoint URL. + :param realm: A list of realms to request access to. + :param \*\*request_kwargs: Optional arguments passed to ''post'' + function in ''requests.Session'' + :returns: The response in dict format. + + Note that a previously set callback_uri will be reset for your + convenience, or else signature creation will be incorrect on + consecutive requests. + + >>> request_token_url = 'https://api.twitter.com/oauth/request_token' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.fetch_request_token(request_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + """ + self._client.client.realm = ' '.join(realm) if realm else None + token = self._fetch_token(url, **request_kwargs) + log.debug('Resetting callback_uri and realm (not needed in next phase).') + self._client.client.callback_uri = None + self._client.client.realm = None + return token + + def fetch_access_token(self, url, verifier=None, **request_kwargs): + """Fetch an access token. + + This is the final step in the OAuth 1 workflow. An access token is + obtained using all previously obtained credentials, including the + verifier from the authorization step. + + Note that a previously set verifier will be reset for your + convenience, or else signature creation will be incorrect on + consecutive requests. + + >>> access_token_url = 'https://api.twitter.com/oauth/access_token' + >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + >>> oauth_session.fetch_access_token(access_token_url) + { + 'oauth_token': 'sdf0o9823sjdfsdf', + 'oauth_token_secret': '2kjshdfp92i34asdasd', + } + """ + if verifier: + self._client.client.verifier = verifier + if not getattr(self._client.client, 'verifier', None): + raise VerifierMissing('No client verifier has been set.') + token = self._fetch_token(url, **request_kwargs) + log.debug('Resetting verifier attribute, should not be used anymore.') + self._client.client.verifier = None + return token + + def parse_authorization_response(self, url): + """Extract parameters from the post authorization redirect response URL. + + :param url: The full URL that resulted from the user being redirected + back from the OAuth provider to you, the client. + :returns: A dict of parameters extracted from the URL. + + >>> redirect_response = 'https://127.0.0.1/callback?oauth_token=kjerht2309uf&oauth_token_secret=lsdajfh923874&oauth_verifier=w34o8967345' + >>> oauth_session = OAuth1Session('client-key', client_secret='secret') + >>> oauth_session.parse_authorization_response(redirect_response) + { + 'oauth_token: 'kjerht2309u', + 'oauth_token_secret: 'lsdajfh923874', + 'oauth_verifier: 'w34o8967345', + } + """ + log.debug('Parsing token from query part of url %s', url) + token = dict(urldecode(urlparse(url).query)) + log.debug('Updating internal client token attribute.') + self._populate_attributes(token) + return token + + def _populate_attributes(self, token): + if 'oauth_token' in token: + self._client.client.resource_owner_key = token['oauth_token'] + else: + raise TokenMissing( + 'Response does not contain a token: {resp}'.format(resp=token), + token, + ) + if 'oauth_token_secret' in token: + self._client.client.resource_owner_secret = ( + token['oauth_token_secret']) + if 'oauth_verifier' in token: + self._client.client.verifier = token['oauth_verifier'] + + def _fetch_token(self, url, **request_kwargs): + log.debug('Fetching token from %s using client %s', url, self._client.client) + r = self.post(url, **request_kwargs) + + if r.status_code >= 400: + error = "Token request failed with code %s, response was '%s'." + raise TokenRequestDenied(error % (r.status_code, r.text), r) + + log.debug('Decoding token from response "%s"', r.text) + try: + token = dict(urldecode(r.text.strip())) + except ValueError as e: + error = ("Unable to decode token from token response. " + "This is commonly caused by an unsuccessful request where" + " a non urlencoded error message is returned. " + "The decoding error was %s""" % e) + raise ValueError(error) + + log.debug('Obtained token %s', token) + log.debug('Updating internal client attributes from token data.') + self._populate_attributes(token) + return token + + def rebuild_auth(self, prepared_request, response): + """ + When being redirected we should always strip Authorization + header, since nonce may not be reused as per OAuth spec. + """ + if 'Authorization' in prepared_request.headers: + # If we get redirected to a new host, we should strip out + # any authentication headers. + prepared_request.headers.pop('Authorization', True) + prepared_request.prepare_auth(self.auth) + return diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_auth.py b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_auth.py new file mode 100644 index 0000000..0ce58cc --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_auth.py @@ -0,0 +1,36 @@ +from __future__ import unicode_literals +from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError +from oauthlib.oauth2 import is_secure_transport +from requests.auth import AuthBase + + +class OAuth2(AuthBase): + """Adds proof of authorization (OAuth2 token) to the request.""" + + def __init__(self, client_id=None, client=None, token=None): + """Construct a new OAuth 2 authorization object. + + :param client_id: Client id obtained during registration + :param client: :class:`oauthlib.oauth2.Client` to be used. Default is + WebApplicationClient which is useful for any + hosted application but not mobile or desktop. + :param token: Token dictionary, must include access_token + and token_type. + """ + self._client = client or WebApplicationClient(client_id, token=token) + if token: + for k, v in token.items(): + setattr(self._client, k, v) + + def __call__(self, r): + """Append an OAuth 2 token to the request. + + Note that currently HTTPS is required for all requests. There may be + a token type that allows for plain HTTP in the future and then this + should be updated to allow plain HTTP on a white list basis. + """ + if not is_secure_transport(r.url): + raise InsecureTransportError() + r.url, r.headers, r.body = self._client.add_token(r.url, + http_method=r.method, body=r.body, headers=r.headers) + return r diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py new file mode 100644 index 0000000..5c46c2d --- /dev/null +++ b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth2_session.py @@ -0,0 +1,376 @@ +from __future__ import unicode_literals + +import logging + +from oauthlib.common import generate_token, urldecode +from oauthlib.oauth2 import WebApplicationClient, InsecureTransportError +from oauthlib.oauth2 import TokenExpiredError, is_secure_transport +import requests + +log = logging.getLogger(__name__) + + +class TokenUpdated(Warning): + def __init__(self, token): + super(TokenUpdated, self).__init__() + self.token = token + + +class OAuth2Session(requests.Session): + """Versatile OAuth 2 extension to :class:`requests.Session`. + + Supports any grant type adhering to :class:`oauthlib.oauth2.Client` spec + including the four core OAuth 2 grants. + + Can be used to create authorization urls, fetch tokens and access protected + resources using the :class:`requests.Session` interface you are used to. + + - :class:`oauthlib.oauth2.WebApplicationClient` (default): Authorization Code Grant + - :class:`oauthlib.oauth2.MobileApplicationClient`: Implicit Grant + - :class:`oauthlib.oauth2.LegacyApplicationClient`: Password Credentials Grant + - :class:`oauthlib.oauth2.BackendApplicationClient`: Client Credentials Grant + + Note that the only time you will be using Implicit Grant from python is if + you are driving a user agent able to obtain URL fragments. + """ + + def __init__(self, client_id=None, client=None, auto_refresh_url=None, + auto_refresh_kwargs=None, scope=None, redirect_uri=None, token=None, + state=None, token_updater=None, **kwargs): + """Construct a new OAuth 2 client session. + + :param client_id: Client id obtained during registration + :param client: :class:`oauthlib.oauth2.Client` to be used. Default is + WebApplicationClient which is useful for any + hosted application but not mobile or desktop. + :param scope: List of scopes you wish to request access to + :param redirect_uri: Redirect URI you registered as callback + :param token: Token dictionary, must include access_token + and token_type. + :param state: State string used to prevent CSRF. This will be given + when creating the authorization url and must be supplied + when parsing the authorization response. + Can be either a string or a no argument callable. + :auto_refresh_url: Refresh token endpoint URL, must be HTTPS. Supply + this if you wish the client to automatically refresh + your access tokens. + :auto_refresh_kwargs: Extra arguments to pass to the refresh token + endpoint. + :token_updater: Method with one argument, token, to be used to update + your token databse on automatic token refresh. If not + set a TokenUpdated warning will be raised when a token + has been refreshed. This warning will carry the token + in its token argument. + :param kwargs: Arguments to pass to the Session constructor. + """ + super(OAuth2Session, self).__init__(**kwargs) + self._client = client or WebApplicationClient(client_id, token=token) + self.token = token or {} + self.scope = scope + self.redirect_uri = redirect_uri + self.state = state or generate_token + self._state = state + self.auto_refresh_url = auto_refresh_url + self.auto_refresh_kwargs = auto_refresh_kwargs or {} + self.token_updater = token_updater + + # Allow customizations for non compliant providers through various + # hooks to adjust requests and responses. + self.compliance_hook = { + 'access_token_response': set([]), + 'refresh_token_response': set([]), + 'protected_request': set([]), + } + + def new_state(self): + """Generates a state string to be used in authorizations.""" + try: + self._state = self.state() + log.debug('Generated new state %s.', self._state) + except TypeError: + self._state = self.state + log.debug('Re-using previously supplied state %s.', self._state) + return self._state + + @property + def client_id(self): + return getattr(self._client, "client_id", None) + + @client_id.setter + def client_id(self, value): + self._client.client_id = value + + @client_id.deleter + def client_id(self): + del self._client.client_id + + @property + def token(self): + return getattr(self._client, "token", None) + + @token.setter + def token(self, value): + self._client.token = value + self._client._populate_attributes(value) + + @property + def access_token(self): + return getattr(self._client, "access_token", None) + + @access_token.setter + def access_token(self, value): + self._client.access_token = value + + @access_token.deleter + def access_token(self): + del self._client.access_token + + @property + def authorized(self): + """Boolean that indicates whether this session has an OAuth token + or not. If `self.authorized` is True, you can reasonably expect + OAuth-protected requests to the resource to succeed. If + `self.authorized` is False, you need the user to go through the OAuth + authentication dance before OAuth-protected requests to the resource + will succeed. + """ + return bool(self.access_token) + + def authorization_url(self, url, state=None, **kwargs): + """Form an authorization URL. + + :param url: Authorization endpoint url, must be HTTPS. + :param state: An optional state string for CSRF protection. If not + given it will be generated for you. + :param kwargs: Extra parameters to include. + :return: authorization_url, state + """ + state = state or self.new_state() + return self._client.prepare_request_uri(url, + redirect_uri=self.redirect_uri, + scope=self.scope, + state=state, + **kwargs), state + + def fetch_token(self, token_url, code=None, authorization_response=None, + body='', auth=None, username=None, password=None, method='POST', + timeout=None, headers=None, verify=True, proxies=None, **kwargs): + """Generic method for fetching an access token from the token endpoint. + + If you are using the MobileApplicationClient you will want to use + token_from_fragment instead of fetch_token. + + :param token_url: Token endpoint URL, must use HTTPS. + :param code: Authorization code (used by WebApplicationClients). + :param authorization_response: Authorization response URL, the callback + URL of the request back to you. Used by + WebApplicationClients instead of code. + :param body: Optional application/x-www-form-urlencoded body to add the + include in the token request. Prefer kwargs over body. + :param auth: An auth tuple or method as accepted by requests. + :param username: Username used by LegacyApplicationClients. + :param password: Password used by LegacyApplicationClients. + :param method: The HTTP method used to make the request. Defaults + to POST, but may also be GET. Other methods should + be added as needed. + :param headers: Dict to default request headers with. + :param timeout: Timeout of the request in seconds. + :param verify: Verify SSL certificate. + :param kwargs: Extra parameters to include in the token request. + :return: A token dict + """ + if not is_secure_transport(token_url): + raise InsecureTransportError() + + if not code and authorization_response: + self._client.parse_request_uri_response(authorization_response, + state=self._state) + code = self._client.code + elif not code and isinstance(self._client, WebApplicationClient): + code = self._client.code + if not code: + raise ValueError('Please supply either code or ' + 'authorization_response parameters.') + + + body = self._client.prepare_request_body(code=code, body=body, + redirect_uri=self.redirect_uri, username=username, + password=password, **kwargs) + + client_id = kwargs.get('client_id', '') + if auth is None: + if client_id: + log.debug('Encoding client_id "%s" with client_secret as Basic auth credentials.', client_id) + client_secret = kwargs.get('client_secret', '') + client_secret = client_secret if client_secret is not None else '' + auth = requests.auth.HTTPBasicAuth(client_id, client_secret) + elif username: + if password is None: + raise ValueError('Username was supplied, but not password.') + log.debug('Encoding username, password as Basic auth credentials.') + auth = requests.auth.HTTPBasicAuth(username, password) + + headers = headers or { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', + } + self.token = {} + if method.upper() == 'POST': + r = self.post(token_url, data=dict(urldecode(body)), + timeout=timeout, headers=headers, auth=auth, + verify=verify, proxies=proxies) + log.debug('Prepared fetch token request body %s', body) + elif method.upper() == 'GET': + # if method is not 'POST', switch body to querystring and GET + r = self.get(token_url, params=dict(urldecode(body)), + timeout=timeout, headers=headers, auth=auth, + verify=verify, proxies=proxies) + log.debug('Prepared fetch token request querystring %s', body) + else: + raise ValueError('The method kwarg must be POST or GET.') + + log.debug('Request to fetch token completed with status %s.', + r.status_code) + log.debug('Request headers were %s', r.request.headers) + log.debug('Request body was %s', r.request.body) + log.debug('Response headers were %s and content %s.', + r.headers, r.text) + log.debug('Invoking %d token response hooks.', + len(self.compliance_hook['access_token_response'])) + for hook in self.compliance_hook['access_token_response']: + log.debug('Invoking hook %s.', hook) + r = hook(r) + + self._client.parse_request_body_response(r.text, scope=self.scope) + self.token = self._client.token + log.debug('Obtained token %s.', self.token) + return self.token + + def token_from_fragment(self, authorization_response): + """Parse token from the URI fragment, used by MobileApplicationClients. + + :param authorization_response: The full URL of the redirect back to you + :return: A token dict + """ + self._client.parse_request_uri_response(authorization_response, + state=self._state) + self.token = self._client.token + return self.token + + def refresh_token(self, token_url, refresh_token=None, body='', auth=None, + timeout=None, headers=None, verify=True, proxies=None, **kwargs): + """Fetch a new access token using a refresh token. + + :param token_url: The token endpoint, must be HTTPS. + :param refresh_token: The refresh_token to use. + :param body: Optional application/x-www-form-urlencoded body to add the + include in the token request. Prefer kwargs over body. + :param auth: An auth tuple or method as accepted by requests. + :param timeout: Timeout of the request in seconds. + :param verify: Verify SSL certificate. + :param kwargs: Extra parameters to include in the token request. + :return: A token dict + """ + if not token_url: + raise ValueError('No token endpoint set for auto_refresh.') + + if not is_secure_transport(token_url): + raise InsecureTransportError() + + refresh_token = refresh_token or self.token.get('refresh_token') + + log.debug('Adding auto refresh key word arguments %s.', + self.auto_refresh_kwargs) + kwargs.update(self.auto_refresh_kwargs) + body = self._client.prepare_refresh_body(body=body, + refresh_token=refresh_token, scope=self.scope, **kwargs) + log.debug('Prepared refresh token request body %s', body) + + if headers is None: + headers = { + 'Accept': 'application/json', + 'Content-Type': ( + 'application/x-www-form-urlencoded;charset=UTF-8' + ), + } + + r = self.post(token_url, data=dict(urldecode(body)), auth=auth, + timeout=timeout, headers=headers, verify=verify, withhold_token=True, proxies=proxies) + log.debug('Request to refresh token completed with status %s.', + r.status_code) + log.debug('Response headers were %s and content %s.', + r.headers, r.text) + log.debug('Invoking %d token response hooks.', + len(self.compliance_hook['refresh_token_response'])) + for hook in self.compliance_hook['refresh_token_response']: + log.debug('Invoking hook %s.', hook) + r = hook(r) + + self.token = self._client.parse_request_body_response(r.text, scope=self.scope) + if not 'refresh_token' in self.token: + log.debug('No new refresh token given. Re-using old.') + self.token['refresh_token'] = refresh_token + return self.token + + def request(self, method, url, data=None, headers=None, withhold_token=False, + client_id=None, client_secret=None, **kwargs): + """Intercept all requests and add the OAuth 2 token if present.""" + if not is_secure_transport(url): + raise InsecureTransportError() + if self.token and not withhold_token: + log.debug('Invoking %d protected resource request hooks.', + len(self.compliance_hook['protected_request'])) + for hook in self.compliance_hook['protected_request']: + log.debug('Invoking hook %s.', hook) + url, headers, data = hook(url, headers, data) + + log.debug('Adding token %s to request.', self.token) + try: + url, headers, data = self._client.add_token(url, + http_method=method, body=data, headers=headers) + # Attempt to retrieve and save new access token if expired + except TokenExpiredError: + if self.auto_refresh_url: + log.debug('Auto refresh is set, attempting to refresh at %s.', + self.auto_refresh_url) + + # We mustn't pass auth twice. + auth = kwargs.pop('auth', None) + if client_id and client_secret and (auth is None): + log.debug('Encoding client_id "%s" with client_secret as Basic auth credentials.', client_id) + auth = requests.auth.HTTPBasicAuth(client_id, client_secret) + token = self.refresh_token( + self.auto_refresh_url, auth=auth, **kwargs + ) + if self.token_updater: + log.debug('Updating token to %s using %s.', + token, self.token_updater) + self.token_updater(token) + url, headers, data = self._client.add_token(url, + http_method=method, body=data, headers=headers) + else: + raise TokenUpdated(token) + else: + raise + + log.debug('Requesting url %s using method %s.', url, method) + log.debug('Supplying headers %s and data %s', headers, data) + log.debug('Passing through key word arguments %s.', kwargs) + return super(OAuth2Session, self).request(method, url, + headers=headers, data=data, **kwargs) + + def register_compliance_hook(self, hook_type, hook): + """Register a hook for request/response tweaking. + + Available hooks are: + access_token_response invoked before token parsing. + refresh_token_response invoked before refresh token parsing. + protected_request invoked before making a request. + + If you find a new hook is needed please send a GitHub PR request + or open an issue. + """ + if hook_type not in self.compliance_hook: + raise ValueError('Hook type %s is not in %s.', + hook_type, self.compliance_hook) + self.compliance_hook[hook_type].add(hook) diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..09c2c99 --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,27 @@ +.. image:: http://img.shields.io/pypi/v/six.svg + :target: https://pypi.python.org/pypi/six + +.. image:: https://travis-ci.org/benjaminp/six.svg?branch=master + :target: https://travis-ci.org/benjaminp/six + +.. image:: http://img.shields.io/badge/license-MIT-green.svg + :target: https://github.com/benjaminp/six/blob/master/LICENSE + +Six is a Python 2 and 3 compatibility library. It provides utility functions +for smoothing over the differences between the Python versions with the goal of +writing Python code that is compatible on both Python versions. See the +documentation for more information on what is provided. + +Six supports every Python version since 2.6. It is contained in only one Python +file, so it can be easily copied into your project. (The copyright and license +notice must be retained.) + +Online documentation is at http://six.rtfd.org. + +Bugs can be reported to https://github.com/benjaminp/six. The code can also +be found there. + +For questions about six or porting in general, email the python-porting mailing +list: https://mail.python.org/mailman/listinfo/python-porting + + diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/METADATA new file mode 100644 index 0000000..04e93dc --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/METADATA @@ -0,0 +1,43 @@ +Metadata-Version: 2.0 +Name: six +Version: 1.11.0 +Summary: Python 2 and 3 compatibility utilities +Home-page: http://pypi.python.org/pypi/six/ +Author: Benjamin Peterson +Author-email: benjamin@python.org +License: MIT +Platform: UNKNOWN +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 3 +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities + +.. image:: http://img.shields.io/pypi/v/six.svg + :target: https://pypi.python.org/pypi/six + +.. image:: https://travis-ci.org/benjaminp/six.svg?branch=master + :target: https://travis-ci.org/benjaminp/six + +.. image:: http://img.shields.io/badge/license-MIT-green.svg + :target: https://github.com/benjaminp/six/blob/master/LICENSE + +Six is a Python 2 and 3 compatibility library. It provides utility functions +for smoothing over the differences between the Python versions with the goal of +writing Python code that is compatible on both Python versions. See the +documentation for more information on what is provided. + +Six supports every Python version since 2.6. It is contained in only one Python +file, so it can be easily copied into your project. (The copyright and license +notice must be retained.) + +Online documentation is at http://six.rtfd.org. + +Bugs can be reported to https://github.com/benjaminp/six. The code can also +be found there. + +For questions about six or porting in general, email the python-porting mailing +list: https://mail.python.org/mailman/listinfo/python-porting + + diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/RECORD new file mode 100644 index 0000000..c870eca --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/RECORD @@ -0,0 +1,9 @@ +six.py,sha256=A08MPb-Gi9FfInI3IW7HimXFmEH2T2IPzHgDvdhZPRA,30888 +six-1.11.0.dist-info/DESCRIPTION.rst,sha256=gPBoq1Ruc1QDWyLeXPlieL3F-XZz1_WXB-5gctCfg-A,1098 +six-1.11.0.dist-info/METADATA,sha256=06nZXaDYN3vnC-pmUjhkECYFH_a--ywvcPIpUdNeH1o,1607 +six-1.11.0.dist-info/RECORD,, +six-1.11.0.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110 +six-1.11.0.dist-info/metadata.json,sha256=ac3f4f7MpSHSnZ1SqhHCwsL7FGWMG0gBEb0hhS2eSSM,703 +six-1.11.0.dist-info/top_level.txt,sha256=_iVH_iYEtEXnD8nYGQYpYFUvkUW9sEO1GYbkeKSAais,4 +six-1.11.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +__pycache__/six.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/WHEEL new file mode 100644 index 0000000..8b6dd1b --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/metadata.json new file mode 100644 index 0000000..2c7fcea --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries", "Topic :: Utilities"], "extensions": {"python.details": {"contacts": [{"email": "benjamin@python.org", "name": "Benjamin Peterson", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://pypi.python.org/pypi/six/"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT", "metadata_version": "2.0", "name": "six", "summary": "Python 2 and 3 compatibility utilities", "test_requires": [{"requires": ["pytest"]}], "version": "1.11.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/top_level.txt new file mode 100644 index 0000000..ffe2fce --- /dev/null +++ b/flex/lib/python3.6/site-packages/six-1.11.0.dist-info/top_level.txt @@ -0,0 +1 @@ +six diff --git a/flex/lib/python3.6/site-packages/six.py b/flex/lib/python3.6/site-packages/six.py new file mode 100644 index 0000000..6bf4fd3 --- /dev/null +++ b/flex/lib/python3.6/site-packages/six.py @@ -0,0 +1,891 @@ +# Copyright (c) 2010-2017 Benjamin Peterson +# +# 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. + +"""Utilities for writing code that runs on Python 2 and 3""" + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson " +__version__ = "1.11.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("getoutput", "commands", "subprocess"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("splitvalue", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), + MovedAttribute("parse_http_list", "urllib2", "urllib.request"), + MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + try: + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None + tb = None + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + try: + raise tp, value, tb + finally: + tb = None +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + try: + if from_value is None: + raise value + raise value from from_value + finally: + value = None +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + try: + raise value from from_value + finally: + value = None +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(type): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + + @classmethod + def __prepare__(cls, name, this_bases): + return meta.__prepare__(name, bases) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..0fe9b07 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,53 @@ +# Python Social Auth - Django + +[![Build Status](https://travis-ci.org/python-social-auth/social-app-django.svg?branch=master)](https://travis-ci.org/python-social-auth/social-app-django) +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + +Python Social Auth is an easy to setup social authentication/registration +mechanism with support for several frameworks and auth providers. + +## Description + +This is the [Django](https://www.djangoproject.com/) component of the +[python-social-auth ecosystem](https://github.com/python-social-auth/social-core), +it implements the needed functionality to integrate +[social-auth-core](https://github.com/python-social-auth/social-core) +in a Django based project. + +## Django version + +This project will focus on the currently supported Django releases as +stated on the [Django Project Supported Versions table](https://www.djangoproject.com/download/#supported-versions). + +Backward compatibility with unsupported versions won't be enforced. + +## Documentation + +Project documentation is available at http://python-social-auth.readthedocs.org/. + +## Setup + +```shell +$ pip install social-auth-app-django +``` + +## Contributing + +See the [CONTRIBUTING.md](CONTRIBUTING.md) document for details. + +## Versioning + +This project follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). + +## License + +This project follows the BSD license. See the [LICENSE](LICENSE) for details. + +## Donations + +This project is maintened on my spare time, consider donating to keep +it improving. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + + diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/METADATA new file mode 100644 index 0000000..13d422c --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/METADATA @@ -0,0 +1,75 @@ +Metadata-Version: 2.0 +Name: social-auth-app-django +Version: 2.1.0 +Summary: Python Social Authentication, Django integration. +Home-page: https://github.com/python-social-auth/social-app-django +Author: Matias Aguirre +Author-email: matiasaguirre@gmail.com +License: BSD +Description-Content-Type: UNKNOWN +Keywords: django,social auth +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Topic :: Internet +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Environment :: Web Environment +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Requires-Dist: six +Requires-Dist: social-auth-core (>=1.2.0) + +# Python Social Auth - Django + +[![Build Status](https://travis-ci.org/python-social-auth/social-app-django.svg?branch=master)](https://travis-ci.org/python-social-auth/social-app-django) +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + +Python Social Auth is an easy to setup social authentication/registration +mechanism with support for several frameworks and auth providers. + +## Description + +This is the [Django](https://www.djangoproject.com/) component of the +[python-social-auth ecosystem](https://github.com/python-social-auth/social-core), +it implements the needed functionality to integrate +[social-auth-core](https://github.com/python-social-auth/social-core) +in a Django based project. + +## Django version + +This project will focus on the currently supported Django releases as +stated on the [Django Project Supported Versions table](https://www.djangoproject.com/download/#supported-versions). + +Backward compatibility with unsupported versions won't be enforced. + +## Documentation + +Project documentation is available at http://python-social-auth.readthedocs.org/. + +## Setup + +```shell +$ pip install social-auth-app-django +``` + +## Contributing + +See the [CONTRIBUTING.md](CONTRIBUTING.md) document for details. + +## Versioning + +This project follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). + +## License + +This project follows the BSD license. See the [LICENSE](LICENSE) for details. + +## Donations + +This project is maintened on my spare time, consider donating to keep +it improving. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + + diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/RECORD new file mode 100644 index 0000000..6c2bd6f --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/RECORD @@ -0,0 +1,53 @@ +social_auth_app_django-2.1.0.dist-info/DESCRIPTION.rst,sha256=IF-3FkE9BcTOP6oqKld6EsFS4gGsQMdWcmVhmhbX66A,2031 +social_auth_app_django-2.1.0.dist-info/METADATA,sha256=IkXDXtvPZOZ3pu3M7YA9gEUrz05gvNcFMPaQUsKRx4o,2792 +social_auth_app_django-2.1.0.dist-info/RECORD,, +social_auth_app_django-2.1.0.dist-info/WHEEL,sha256=8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA,92 +social_auth_app_django-2.1.0.dist-info/metadata.json,sha256=X6BWijVoy6EDJk8wlCEQ2Q8x6HHM4PqU3aDrvWSWl6w,934 +social_auth_app_django-2.1.0.dist-info/top_level.txt,sha256=8t6G3xz_UTQ6eV8mCJ3VHJ7AC9jJeykRbymQ_GLLKWI,14 +social_django/__init__.py,sha256=atefbGMyenEV9ioMZjD0yzVeVF7kWQAWTSwvrEr31IE,801 +social_django/admin.py,sha256=Z5NYwI8Q4VFObuuqZNtZm3aFWs3RXU8PgnbRBjJFl1Y,2231 +social_django/compat.py,sha256=v59lghLf4RDVCT5a5G2tpMs4w6j_54s0hOSknIrKBCk,819 +social_django/config.py,sha256=CT_ydYB_LPsj8GbZ4d0YCx_52n_z7PnD4-TRrha2w7s,373 +social_django/context_processors.py,sha256=N4vl3hRAsruZSAR-xBLn9aSRrur1K9UKIwM9Hg_GOls,1635 +social_django/fields.py,sha256=Iu2NH9zak8OGWCzdEkCECYmvnbHPo5UFdBG1Ha7ht6w,3027 +social_django/managers.py,sha256=l_Z4TW9pP8nPGw-cuHobqCqdS70JD7-JXt4XvJSK-f8,438 +social_django/middleware.py,sha256=Ln77bsETDXWAs2JeQwZqXhMnSR00ev-q-EQZabWvw9I,2402 +social_django/models.py,sha256=d9YI1bqAmGXFIC2qfOMf633uz-zOHOviuuhk6TDkQLc,4554 +social_django/storage.py,sha256=XW_zQWnH4R1nefqhS_7Sll-ghdX5o3K1URa6fzPPBm0,6839 +social_django/strategy.py,sha256=wd0I_pNp2qLwsluGSk8dEXvEqc6MwW-BCIJoiSWJ-Nc,5548 +social_django/urls.py,sha256=sQGqyarF4c868gmtKos5brdY9WiypvR_f1f7wj5BvTI,765 +social_django/utils.py,sha256=tmJZji4tkA7XccoN1Z3R1YfJIUz-2Z3Vr4Bx-viEpAQ,1779 +social_django/views.py,sha256=PECnj19_t-k3hfvkIC25BQvWS2MBo0ihhpIWaCyFBCM,5645 +social_django/migrations/0001_initial.py,sha256=lrbW2wxvNtHIONMYF_-Wl3TgtH827PhfTt3plUT2xTQ,4369 +social_django/migrations/0002_add_related_name.py,sha256=SnjW14Shrp2YYaaJY5M1EAlje2Z3cdKVHskueTBxuog,840 +social_django/migrations/0003_alter_email_max_length.py,sha256=LdjLzwdMtcAfLIJrcj_EyJ0StgFfXt8zUtsCq0LPIRU,690 +social_django/migrations/0004_auto_20160423_0400.py,sha256=vZkKyquq8Ut15kV3qn7503Tcs2SMTS5lYYXDo0I0k-U,569 +social_django/migrations/0005_auto_20160727_2333.py,sha256=q9NSyIEWxveJtAsKRiCQO_TVbVLBa7p9CORq_Ld9boo,513 +social_django/migrations/0006_partial.py,sha256=cgFmo1RBe9eW2tUbggF4JyOiIIN7iRrd5aELRuFmpAQ,1032 +social_django/migrations/0007_code_timestamp.py,sha256=199XJmySQhJbHWFNHrwyaGJgS6uAiOyEp3pOqmCMMC4,637 +social_django/migrations/0008_partial_timestamp.py,sha256=ajODqXdxPZBjDUCY7EQ5On3GGRS4G3mbolWC81mHihU,648 +social_django/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +social_auth_app_django-2.1.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +social_django/migrations/__pycache__/0004_auto_20160423_0400.cpython-36.pyc,, +social_django/migrations/__pycache__/0001_initial.cpython-36.pyc,, +social_django/migrations/__pycache__/0006_partial.cpython-36.pyc,, +social_django/migrations/__pycache__/0008_partial_timestamp.cpython-36.pyc,, +social_django/migrations/__pycache__/0003_alter_email_max_length.cpython-36.pyc,, +social_django/migrations/__pycache__/0005_auto_20160727_2333.cpython-36.pyc,, +social_django/migrations/__pycache__/__init__.cpython-36.pyc,, +social_django/migrations/__pycache__/0007_code_timestamp.cpython-36.pyc,, +social_django/migrations/__pycache__/0002_add_related_name.cpython-36.pyc,, +social_django/__pycache__/context_processors.cpython-36.pyc,, +social_django/__pycache__/managers.cpython-36.pyc,, +social_django/__pycache__/admin.cpython-36.pyc,, +social_django/__pycache__/storage.cpython-36.pyc,, +social_django/__pycache__/config.cpython-36.pyc,, +social_django/__pycache__/strategy.cpython-36.pyc,, +social_django/__pycache__/models.cpython-36.pyc,, +social_django/__pycache__/views.cpython-36.pyc,, +social_django/__pycache__/compat.cpython-36.pyc,, +social_django/__pycache__/middleware.cpython-36.pyc,, +social_django/__pycache__/utils.cpython-36.pyc,, +social_django/__pycache__/fields.cpython-36.pyc,, +social_django/__pycache__/__init__.cpython-36.pyc,, +social_django/__pycache__/urls.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/WHEEL new file mode 100644 index 0000000..6261a26 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/metadata.json new file mode 100644 index 0000000..d780d3d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"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.7", "Programming Language :: Python :: 3"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "matiasaguirre@gmail.com", "name": "Matias Aguirre", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/python-social-auth/social-app-django"}}}, "extras": [], "generator": "bdist_wheel (0.30.0)", "keywords": ["django", "social", "auth"], "license": "BSD", "metadata_version": "2.0", "name": "social-auth-app-django", "run_requires": [{"requires": ["six", "social-auth-core (>=1.2.0)"]}], "summary": "Python Social Authentication, Django integration.", "version": "2.1.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/top_level.txt new file mode 100644 index 0000000..0bfdf7e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_app_django-2.1.0.dist-info/top_level.txt @@ -0,0 +1 @@ +social_django diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/DESCRIPTION.rst new file mode 100644 index 0000000..b9000e1 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/DESCRIPTION.rst @@ -0,0 +1,45 @@ +# Python Social Auth - Core + +[![Build Status](https://travis-ci.org/python-social-auth/social-core.svg?branch=master)](https://travis-ci.org/python-social-auth/social-core) +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + +Python Social Auth is an easy to setup social authentication/registration +mechanism with support for several frameworks and auth providers. + +## Description + +This is the core component of the python-social-auth ecosystem, it +implements the common interface to define new authentication backends +to third parties services, implement integrations with web frameworks +and storage solutions. + +## Documentation + +Project documentation is available at http://python-social-auth.readthedocs.org/. + +## Setup + +```shell +$ pip install social-auth-core +``` + +## Contributing + +See the [CONTRIBUTING.md](CONTRIBUTING.md) document for details. + +## Versioning + +This project follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). + +## License + +This project follows the BSD license. See the [LICENSE](LICENSE) for details. + +## Donations + +This project is maintened on my spare time, consider donating to keep +it improving. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + + diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/INSTALLER b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/METADATA b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/METADATA new file mode 100644 index 0000000..3e34d67 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/METADATA @@ -0,0 +1,82 @@ +Metadata-Version: 2.0 +Name: social-auth-core +Version: 1.6.0 +Summary: Python social authentication made simple. +Home-page: https://github.com/python-social-auth/social-core +Author: Matias Aguirre +Author-email: matiasaguirre@gmail.com +License: BSD +Description-Content-Type: UNKNOWN +Keywords: openid,oauth,saml,social auth +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Topic :: Internet +Classifier: License :: OSI Approved :: BSD License +Classifier: Intended Audience :: Developers +Classifier: Environment :: Web Environment +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Requires-Dist: PyJWT (>=1.4.0) +Requires-Dist: defusedxml (>=0.5.0rc1) +Requires-Dist: oauthlib (>=1.0.3) +Requires-Dist: python3-openid (>=3.0.10) +Requires-Dist: requests-oauthlib (>=0.6.1) +Requires-Dist: requests (>=2.9.1) +Requires-Dist: six (>=1.10.0) +Provides-Extra: all +Requires-Dist: cryptography (>=2.1.1); extra == 'all' +Requires-Dist: pyjwkest (>=1.0.1); extra == 'all' +Requires-Dist: python3-saml (>=1.2.1); extra == 'all' +Provides-Extra: azuread +Requires-Dist: cryptography (>=2.1.1); extra == 'azuread' +Provides-Extra: openidconnect +Requires-Dist: pyjwkest (>=1.0.1); extra == 'openidconnect' +Provides-Extra: saml +Requires-Dist: python3-saml (>=1.2.1); extra == 'saml' + +# Python Social Auth - Core + +[![Build Status](https://travis-ci.org/python-social-auth/social-core.svg?branch=master)](https://travis-ci.org/python-social-auth/social-core) +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + +Python Social Auth is an easy to setup social authentication/registration +mechanism with support for several frameworks and auth providers. + +## Description + +This is the core component of the python-social-auth ecosystem, it +implements the common interface to define new authentication backends +to third parties services, implement integrations with web frameworks +and storage solutions. + +## Documentation + +Project documentation is available at http://python-social-auth.readthedocs.org/. + +## Setup + +```shell +$ pip install social-auth-core +``` + +## Contributing + +See the [CONTRIBUTING.md](CONTRIBUTING.md) document for details. + +## Versioning + +This project follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). + +## License + +This project follows the BSD license. See the [LICENSE](LICENSE) for details. + +## Donations + +This project is maintened on my spare time, consider donating to keep +it improving. + +[![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) + + diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/RECORD b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/RECORD new file mode 100644 index 0000000..73cc40d --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/RECORD @@ -0,0 +1,550 @@ +social_auth_core-1.6.0.dist-info/DESCRIPTION.rst,sha256=Ec2yjm0izUzPh9UWAoeI8FZye4qGm0-xYuXi_0LqMzk,1663 +social_auth_core-1.6.0.dist-info/METADATA,sha256=ubin9Bsg2HGGGzV4bDR6DcIGY-Cn6yrsOoZ4pVy3vXA,3032 +social_auth_core-1.6.0.dist-info/RECORD,, +social_auth_core-1.6.0.dist-info/WHEEL,sha256=8Lm45v9gcYRm70DrgFGVe4WsUtUMi1_0Tso1hqPGMjA,92 +social_auth_core-1.6.0.dist-info/metadata.json,sha256=V6bfDy0y7jLT9rLeMul6xxz8ULe3uqRuILGBM_pZe0E,1612 +social_auth_core-1.6.0.dist-info/top_level.txt,sha256=CuEK6eKXcMTo6BpFhFEZetBiCqD8OjooJ-Hrr_MjUDE,12 +social_core/__init__.py,sha256=bYN93DTWr8alkTnO3wj31CU5gqTshxIcsbXUcJcUAc8,22 +social_core/actions.py,sha256=h3os-e1jPyNlp9jMFFqTfpLHg2MXOW70ihnYhzdZT3I,5413 +social_core/exceptions.py,sha256=LHqeSxCbvgIBfgFUkHUozWb1wWLIR8XWncu-POiHIxs,3434 +social_core/storage.py,sha256=GexUIeIcE0nsgWi3LJsqJTAH7uuAZFNfMj694frRwUU,10325 +social_core/store.py,sha256=-6EU09rygTA_14ea3KjfLNdSxjBrRx4a-hrB2S0FTAI,2744 +social_core/strategy.py,sha256=6h2OE0Adp90POE4szWpVPG5xbwcRhSX0a_ZLuQhIDIU,8084 +social_core/utils.py,sha256=telCQyDgckj6KYsMxZPdUhqIcnjPpYpz8F-J0AT5zUM,9554 +social_core/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +social_core/backends/amazon.py,sha256=zFX0j4KL_rJuNYv3_N81eKFwknGbk_DTMu7kDZxzWxM,1572 +social_core/backends/angel.py,sha256=bC_yIfqUGFC6Mcqj4F7tv0Jh1hsqb3QWj8QZUw5ZIbw,1068 +social_core/backends/aol.py,sha256=IaamM7aBIbQMWWy2QcZFJvjeoKtodofpdsTkPtx4jUs,225 +social_core/backends/appsfuel.py,sha256=rcNQwCEvCNqzIU8g6GJDTFU4ZcEHmlgersl-ZzuIofU,1505 +social_core/backends/arcgis.py,sha256=iFZ7wrxVxGvRmJtv2vmr2CperGQF5MoJFj6VCxLZCC8,1059 +social_core/backends/asana.py,sha256=szEa_8GTX-8nVX75ctxfeUpC7aRUFv-WJO0ajw0h3k0,1513 +social_core/backends/azuread.py,sha256=CS5pwqxc3xK1ntAR06nRX4z60Sh-VdI_HqRDLgv8X1c,4696 +social_core/backends/azuread_tenant.py,sha256=IMZg9U-uGdafwS93nYtnu4LJYUh7bQOJZuvYbPEyzhA,4174 +social_core/backends/base.py,sha256=ezzRwONXAAn3_zYHI6BqvVTznk-svVQxzjYTVqfVg-4,10025 +social_core/backends/battlenet.py,sha256=PWJfgJZ1TaUD8BM77SKPZr1GKfOse8wMvF-XI3RdK-I,1803 +social_core/backends/beats.py,sha256=xC1bUVdQiUSe0aCrqZoCeS7h6Q1GQtAMp3JIqwZRTto,2245 +social_core/backends/behance.py,sha256=egVsG76UfeH75kT3T1dsIMH5wE6jqg2telI_OpcdXV8,1584 +social_core/backends/belgiumeid.py,sha256=QvUDzUABjX2jSfkV55EYWMCkO9NIfWrcustVqGaHvbw,342 +social_core/backends/bitbucket.py,sha256=GJTWaKEhMUEdd9f_yIODxCpygzD-PIdgul-aNNGY5Lo,3548 +social_core/backends/box.py,sha256=4hPVGWp-hpWoPU_yt98-oIMdJi8KTvNhaNZWtyPDXTc,2196 +social_core/backends/bungie.py,sha256=ySe6fVsWAYBDZ3wVAfnTmqEyH6W3kI6j4tjhEMJj6Ag,3524 +social_core/backends/changetip.py,sha256=Nk7MYNePtt_ot-684hTVck-OO7lcdIc4pBog5wTRzZM,871 +social_core/backends/chatwork.py,sha256=_Arx3mX4oY-wtIjgen-Hgx9__iadKDhwzWGRJkclKsQ,2189 +social_core/backends/classlink.py,sha256=FVzod6bg4zwYevzeZlRn52t2zOa_aiRnXLhRurqHYds,1472 +social_core/backends/clef.py,sha256=j2l7aAD3T2qAZhXKgxbKBrReU694O-WOw1pmDiYlRpc,1753 +social_core/backends/coding.py,sha256=MrGx13izt99zkfG646aQ7NK_nCfY4Vyve8HK5mIKOUk,1480 +social_core/backends/coinbase.py,sha256=z96ZG1ilLI8A0WxZR2GngQx5g8ml98nsLCwdlSbBoO8,1353 +social_core/backends/coursera.py,sha256=5st7PU5_QIas5JqefVpjip6o10gI-0ZrUCkVCwLpCms,1460 +social_core/backends/dailymotion.py,sha256=_N875sdD3M54NHrinqqinCTrSu4V1epIgofOQYrD7Vg,892 +social_core/backends/deezer.py,sha256=1350IUUqyLeQQ4u5pq-ZRnY2rJWV3lCNzZXgQELXX3E,1659 +social_core/backends/digitalocean.py,sha256=T9mdVsu84Y-8BCe6LCS0fX1U84XdjrajkRb7m05IvSk,1425 +social_core/backends/disqus.py,sha256=Ntr1cm5Bq_Wzy9dC6Prv9f_iADbXw5kDxns1txIMtIk,1841 +social_core/backends/docker.py,sha256=63eDcosnTSam71r1l5Kgqkek_PQyasYiZsl-M5ouJqs,1618 +social_core/backends/douban.py,sha256=WU1oWiQKQxyTrCtd8p40fpAKKZEtbUj6qECK8IbJgE0,2103 +social_core/backends/dribbble.py,sha256=2PpnpNtq1yJOVlSA77VLtwqMjCR8JiY4BReMVJYU04s,2220 +social_core/backends/drip.py,sha256=HaJeVqZmANk3Vf4UlYBDM5-oFif3gj1qm-KE2HRlSYQ,845 +social_core/backends/dropbox.py,sha256=P-BH9_WbL-ohHInYLndUbIwpEU8EJ884wHDFyTnHhVk,4091 +social_core/backends/echosign.py,sha256=17mq8On2s_gt26GKX5rK69Zhc18YJLq548lXYnA_jdo,844 +social_core/backends/edmodo.py,sha256=uF7RMwRPCwtq06iPDC6vB0wCOhqXSRCrTGK7RfJEG4M,1124 +social_core/backends/email.py,sha256=Ws2NaprarrCycYtGU56WYgx4dNWwWPDz4N3fbe1X7Rs,281 +social_core/backends/eventbrite.py,sha256=Fmd0Av-V3UszPNcmIo78DMl7EcrPROxlRMmzal2Gud0,1055 +social_core/backends/eveonline.py,sha256=QQ7xPUAmH273YjiiQz7JKAf8UYaztwV70fkeuIbGEwk,1432 +social_core/backends/evernote.py,sha256=LDsHftCGn3pKi15nmWExsJFPX3mqEqt0TRBC8vjbo_Q,2726 +social_core/backends/exacttarget.py,sha256=TyuQ-GBO9oyKVTtWLq1KSUx1lYpQ24Fchy8dOoKjKdc,4009 +social_core/backends/facebook.py,sha256=9TUGt3KV-Pc4P1usbv2ND02h3lb0RWVMpHYwwUxPFsc,8963 +social_core/backends/fedora.py,sha256=UG2TmGoFYt44PKyoTBjlqSRxDLKwrJfcCfWlYu0K49o,274 +social_core/backends/fitbit.py,sha256=cD55u6VaoEra4g-CZO9tnUYwJevZpgB5u9L1YLpV_5g,2379 +social_core/backends/five_hundred_px.py,sha256=JU-bb_eippQiXqy4mNnur-ixPfFPwWyn66HAHDQ8bo8,1172 +social_core/backends/flickr.py,sha256=s-1Zclfn4So-QPgFY62xdEyRSzKAgqG6kKbNUy72D5w,1499 +social_core/backends/foursquare.py,sha256=AxM3n10-yfuHPQGv-wJtR7_Dy3kHDvEIkMGBokSnUAE,1379 +social_core/backends/gae.py,sha256=SH2z69B6abxVw56kRFQfhdzUa0YTXBJjduRTtDM8x0A,1272 +social_core/backends/github.py,sha256=2mfF_AlZ8YOZQa4I7EwKN1pkidqRdzQLSpc3DJavJ0w,3944 +social_core/backends/github_enterprise.py,sha256=Vob9sR4LFyrI6GRtxIPUXd6jN_800hS01uZC2xPfQkY,1344 +social_core/backends/gitlab.py,sha256=TrXXqdWx3A9z6Zj2msF7CVJtCFQlssJu3kRMgU1HeNI,1962 +social_core/backends/goclio.py,sha256=AanAR4vrwGggK13hrIL11XrAT0OR5h17BkbShTRduro,1232 +social_core/backends/goclioeu.py,sha256=slhfuievhZ9hgo3imZ8tMQ-F9LMuehedQVacFV8_GR8,467 +social_core/backends/google.py,sha256=9rAX7twu1B19dbcoeWgAW2DrvpLN8_byO-sIo87SpTE,7033 +social_core/backends/google_openidconnect.py,sha256=I_nEqFiIm3HqfN8YG-5RnfI_--aiVdpi4GIGTDH-bOE,785 +social_core/backends/instagram.py,sha256=eNtEkBpWBRuCRXW5rTmN3bDZ212pyyLEpQSzdsyMYMk,2072 +social_core/backends/itembase.py,sha256=cIjg96m7qmtL28lhIWpDYJkcGXC8yec8Hz0Rs7ITm5E,3481 +social_core/backends/jawbone.py,sha256=UajskF2y93nFj2qrjNVDrtTqUvr3X3CE8HGUHtJWmsc,2787 +social_core/backends/justgiving.py,sha256=f3NbYzcvT04D22sZQh-mGRLw7IAzWXJjjNR12AlVtxA,2133 +social_core/backends/kakao.py,sha256=j-UlphJQcR2l8OUmQc_L9Zip0r6bKxRK3axU1ymhaAE,1288 +social_core/backends/khanacademy.py,sha256=P5Oba1tikFJ_GIH3pfxzzYMfIWdwUFBiQ9xx_cXGNf4,5052 +social_core/backends/lastfm.py,sha256=4_E2R56pjITbi4b5ULMEu6C8tFPT3QUkIAGu0mZjreg,1868 +social_core/backends/launchpad.py,sha256=SOP7tLkTEkTnQf8z3VwyjiQ2Mx3VDNu3D9mRe6_eMyk,196 +social_core/backends/legacy.py,sha256=zH36VJR4pV1KwxNtLMSvdNP_AElIlcaRmuzJhuAHjmc,1499 +social_core/backends/line.py,sha256=g_aMH-rYWntAnVbYLnK55qk8B5gUPXBjQHRHH4znPTo,3130 +social_core/backends/linkedin.py,sha256=XtR7LjqEmD7M_N9jREXBmidpCj-WIQMliC84Wr26fTs,3845 +social_core/backends/live.py,sha256=1fyYSOlHVie-V-TcZLU5RU8euBqeWUExhqs_GVxCd1U,1568 +social_core/backends/livejournal.py,sha256=xqE4qwcrXMV6Q3ApHjDUKHECl7eOCCUNrQgBqYqB46A,1003 +social_core/backends/loginradius.py,sha256=xeIvOdIkaN8vAgG_B1cfDkm6zAhbzotsfpEqjx-6adk,2620 +social_core/backends/lyft.py,sha256=wQ_kM93jnOd_DE2a5B4DsJjZfk2qNiReHI-qvqBPiyU,1839 +social_core/backends/mailchimp.py,sha256=XYTTndc4ddss5EOFXTGGoLZ1t9XexAVqYGUmLTEACac,1104 +social_core/backends/mailru.py,sha256=aCBXbT4Fp-524lyBToyKFLSwezZCsGMXtutdVDQdOLg,1710 +social_core/backends/mapmyfitness.py,sha256=hhnoDhxR3OVgezMpvGPxvCxOFLTIRou2QKVRfpbuokA,1528 +social_core/backends/mediawiki.py,sha256=qlLDDHNwcyKzQFJTuqnBS1OkRMZnjKlj5ckQZxQ55DE,5765 +social_core/backends/meetup.py,sha256=86O_XH15hwVfaki0CwDlgmvQ2Bw5QGU7MIcdSVKQic4,1197 +social_core/backends/mendeley.py,sha256=TmiQQOTipjK_A76CZWzDPi2MqOXmE3GXDp43g7pOb14,2248 +social_core/backends/microsoft.py,sha256=DuDB4kYL4zPCVFlUyarOfSlf-RM5iSYyH1uwBNinZE4,2845 +social_core/backends/mineid.py,sha256=Vc0fNmylpshe_36sKo1mcCNB9uw82GWbYb5wyHexOL4,1242 +social_core/backends/mixcloud.py,sha256=B8XJgsZ6hB5cZ7MENPQYQ9-ulfEcKv6typPfHyUPcZE,964 +social_core/backends/monzo.py,sha256=tGrfE4l_40rHqw9XOTAueMVutM2zjMQn2O-Nfa2acbE,891 +social_core/backends/moves.py,sha256=XTtgowcyorvHZYsPRohLwLQ5WDi8MPuqKKN4KewlQi8,1001 +social_core/backends/nationbuilder.py,sha256=m1rMydKrIOVUq6SX39jjBs3ABX2zjJhoxX87NyNW6KI,1621 +social_core/backends/naver.py,sha256=W4tbscMd3VfrewrifCOBhlA2dBCxtE_mwMkK4Kuorxk,2001 +social_core/backends/ngpvan.py,sha256=9lqCV_pdkkTMQlQnwFKV_g-snaKZz-P0mn_Oov9UIKg,2097 +social_core/backends/nk.py,sha256=qgSi6h12dBBSNHXO70OUOSCZg1uIYSy54aQldq_SoPc,2775 +social_core/backends/oauth.py,sha256=YfU5XpulEx6S7m6ZNp_FSd3Zla0mRRCyIvak64CBYSg,17299 +social_core/backends/odnoklassniki.py,sha256=CJw0tt_7YEbrIYb2S4lR-0XreKUB5cVWvJ8ya0-FYmU,7024 +social_core/backends/open_id.py,sha256=HItOQ9Be76ZuW1LogNVKc9UQrf8mqyqqT12aZVWn40E,10634 +social_core/backends/open_id_connect.py,sha256=BpzoKfGgylCvm9BNauJGuSNrUgXqGZrwxlOSNbeixg0,6850 +social_core/backends/openshift.py,sha256=VlxRCxQWd0gj-vJLsz0HTHkZBxkTzKtVJfW7gQntxGQ,1132 +social_core/backends/openstreetmap.py,sha256=MDvKFdEto4a49O4sdvH4zvT7Pp2fDvxoKASgV4_EBVU,1905 +social_core/backends/orbi.py,sha256=3-EYfcBh-P_35Nys7N3eAT6s-kXJBEGHDQrkBvMFp4A,1268 +social_core/backends/orcid.py,sha256=e5_L49pfF6t5uTh4mK9KGInHy4gnRR03j_b99soHpuI,2782 +social_core/backends/patreon.py,sha256=XQzCHRfKYLXYX0ebegdnGRgTGqn4Q3Casa2XTcXOzgI,1339 +social_core/backends/persona.py,sha256=D-6ChrbZJRhKi4tgbqaUcUfN5ZbF6TZXJFk-Zk6d4zk,1838 +social_core/backends/pinterest.py,sha256=J-CczQF5hZ8hmlezZEyxzC4UsKLTVPxN75plh5GDKCs,1500 +social_core/backends/pixelpin.py,sha256=8L2Q4jlByS2ooQbdZmTTf3MBKr1uIZ4UNiZSU3107Fw,1178 +social_core/backends/pocket.py,sha256=6vNDfTtHFlMhMHmstAaVFjxft4ie6aCVbI0-_-JTgVA,1696 +social_core/backends/podio.py,sha256=oQG41Z-C2CPym8ODuWAQJyfyQ6VygNOQMqTowqgz_54,1249 +social_core/backends/professionali.py,sha256=J-KmDh8-Jfd99QjVv7ZDqCmpsD-0JShOdgdaoyMgrNU,1887 +social_core/backends/pushbullet.py,sha256=JVYFGmj9SWYJPmYsJc95hG_cAo2u2dx9um8fskWREi8,830 +social_core/backends/qiita.py,sha256=Yat_06OOV-BAqk1pibo3xsKuYJ30JjFmiG7po3oEknE,2206 +social_core/backends/qq.py,sha256=wP4aRrZtjhaeSGDzLSq4XnxM0EgVel_nokyBRzwaIe4,2198 +social_core/backends/quizlet.py,sha256=yzDXYamlDrmhttdwH1msduGtdF8cyJjXByZ8zLhZ7m0,684 +social_core/backends/rdio.py,sha256=ZVDIOTABY47GeguapMB-MxPoICFdZ7yMqRg24MzbzD4,2471 +social_core/backends/readability.py,sha256=9wQz7wOJOtEqQE-ZrWWQMg-YApVQ2JHBo1Kinl-2ES4,1354 +social_core/backends/reddit.py,sha256=m0rZlCnweE7C_m5ytCVsLdWIQQvwttvu81ED83nfRkg,1846 +social_core/backends/runkeeper.py,sha256=rxEzWGEeUigfJSXO_WY36bAhGV3qJY9OIHJ1P_STc5c,1810 +social_core/backends/salesforce.py,sha256=TmnCNm0rwoHXCAP1aGh28BiErXYLjEwcxC7dvi7ikN8,1840 +social_core/backends/saml.py,sha256=KiZY8V1rdKgbVOHea7bkiH2JzlG15qVjkxlitkwGE0w,12876 +social_core/backends/shimmering.py,sha256=RJC6Uw5W_rdazYayp2sO_0dUWHJuOGnE2QwcRPC8854,1197 +social_core/backends/shopify.py,sha256=SIvO1T5CLZizegzLGZaERtxo2a7A1InDBO2RInT1rk4,3373 +social_core/backends/sketchfab.py,sha256=5Cz8uosHIzlyvqFv48rTIkBFTN63ZqvdYdk_n9_1Lfc,1361 +social_core/backends/skyrock.py,sha256=1RXrR5Vr6dyM31QFb1RR5bJCV9OOWZGMMF9KNM8lG0w,1198 +social_core/backends/slack.py,sha256=Gwkn4lp_HlGNsjgO4osvmDE0fzMjo3A_M3BHMTNQ-V4,2052 +social_core/backends/soundcloud.py,sha256=ooVaapcWEPF9HAg4C_ZhggVp1oQHBYl0dsvKC3mOSZw,2173 +social_core/backends/spotify.py,sha256=oUJ-dzEZarmGp_ePXTBSAJIa3jW1sQsrdXwY3x3c3BM,1551 +social_core/backends/stackoverflow.py,sha256=o9-IY_MSTBKdYIWCTlSKrPxyTzm6s6JYlBDIqix1w3c,1459 +social_core/backends/steam.py,sha256=n_oSiYRE5GD_7FnVTZ289iDR8cYbzOkhtq0N8loVvU4,1551 +social_core/backends/stocktwits.py,sha256=OsN2AVPGje670hQ0DDotQnfj8yIUi7G0KM2Z-P4A8vc,1360 +social_core/backends/strava.py,sha256=IeEmhksrGXNyITrK3tmRsCEtmTaf7lMthkA-Io_KXoc,1853 +social_core/backends/stripe.py,sha256=fNqP7dejuejDMyfDnc8BxAD_V6_eMjJR_Fhxk_pp3zA,1642 +social_core/backends/suse.py,sha256=P543KViqLFIA-s5sashscMDH6g_DqabpchiAY_OOI34,462 +social_core/backends/taobao.py,sha256=SwC9LOqRlUymVH6oWkYxRVvoN4iK1v-jMRmKiCukMyg,877 +social_core/backends/thisismyjam.py,sha256=y5j5hByUxITD_P52QjSULRQSGorF1yL5dY-MewLs0xE,1222 +social_core/backends/trello.py,sha256=j0LOAHXpUbJOaM6nFtCNb90T3ZroskrEiGVniG6IMLI,1516 +social_core/backends/tripit.py,sha256=iTLl6q0Tv0v13AHdtXN8X7XMHppVn-6FaeGD6n7bEKw,1779 +social_core/backends/tumblr.py,sha256=nPGdGaKZONo7JTKuRWB-y6dTOGDkWciz9H-VgoYViCE,1106 +social_core/backends/twilio.py,sha256=bGCSG4qRUTmo3E0zsZhRAy1UwarJj5VGm3zWohgWjyo,1401 +social_core/backends/twitch.py,sha256=yZzRHkH5N3cb6rJkkZlXTixjDYb9MGo6F5lAvE4P720,913 +social_core/backends/twitter.py,sha256=ulCYyn-IjWm-DbZhHS1YMTxKLyh_JVrBM9uT5n5ibCU,1503 +social_core/backends/uber.py,sha256=Qu2CPeakuskY2f8ltEE2iCKamjLZ6YoOjKaD2iOXEhc,1335 +social_core/backends/ubuntu.py,sha256=jCb5qIdpjUA6qBTruZHvqOoOVioGAOHMSe0PnbHFW5M,370 +social_core/backends/udata.py,sha256=NNqAByrsymR13BDXGAZA4QOd2P-VzeqrWZTJIzpKNCY,1145 +social_core/backends/untappd.py,sha256=Y0SusXlY5zcGkEgjLxSEfp6qMVAr9e1rpd_ijDP_vQA,3791 +social_core/backends/upwork.py,sha256=2nwrjh-ayOYjbVAX91OOhCsECJzzhC035KjB_iXQY2A,1401 +social_core/backends/username.py,sha256=YleeUvxUomL8N8NVDFU5rnH65NHlfnyy0smDfeZK0Lw,262 +social_core/backends/utils.py,sha256=VTQr-4le6ZlgbQ51e9B2ls_ADhwMmKCkBdo5jJuD0ZE,3176 +social_core/backends/vend.py,sha256=zulnCi87WP8O1KS59cLzXbKkRSfAbvRgv7L4UbDtGcs,1227 +social_core/backends/vimeo.py,sha256=BesDJet5qIDA7Vaj68oEUQcE1_wqcG1qs61U3ChBBBM,2805 +social_core/backends/vk.py,sha256=yDNgXjGfuERGjC29KEw4NInBInVaf0Q0Jyhka7o33Xk,7153 +social_core/backends/weibo.py,sha256=twJ3uxxKCz_6m1Gwzn8bA8ktgC5d35vCQzrlwR0oN80,2199 +social_core/backends/weixin.py,sha256=FumJ5pht3vnyzKotuwSB0Neec2T_dfh6KMx310sjHVE,6262 +social_core/backends/withings.py,sha256=XwX_oUWs21ie2HSdJVTLGw78L6Ch4npKabxhUCVyvfQ,542 +social_core/backends/wunderlist.py,sha256=7fxamn4JvQxzCGmPbqRmIgeHaKfsuC_HJA-YtVJBQMM,1082 +social_core/backends/xing.py,sha256=8dVstBpK9q1AZmdSJZIigFnUisIcmjr-rcwSt-M9_-0,2676 +social_core/backends/yahoo.py,sha256=rHs9UJ8s6uREmhzTbS4WV7VwtMvbWvS7UCsiPtDYogM,5983 +social_core/backends/yammer.py,sha256=aaNB8DX-JLIn7JdsMTrZ38Govad9mG14YkDA070YSyE,1539 +social_core/backends/yandex.py,sha256=_e4lFkdkoJjOZ6-YuzXHhJVsjxDisTbMWZE4vgW-7kg,2986 +social_core/backends/zotero.py,sha256=TOFAkkfeQB-uhd6ASjJabWpeDCMXjU8eXaEYfExpUbU,944 +social_core/pipeline/__init__.py,sha256=60bbFVwxAIFxwMSsiliARpopdVF9mn_upf4fWuFg29I,2462 +social_core/pipeline/debug.py,sha256=xPJkSuvckkzbEh87RA6exqJc-YcQWxFdR5s29_fzivM,252 +social_core/pipeline/disconnect.py,sha256=Tr28-grFZ7SqXyzgVtC6huwCkzIOAt-CRAJcW_hs9ac,1077 +social_core/pipeline/mail.py,sha256=UVQniridPpBFva_-ImC7jeIKGP7zFSghbjkdc9lCM5E,1336 +social_core/pipeline/partial.py,sha256=3isT-HKXGmuE4qqZ1R7DxGD1s4HA4qwXwd-DvzcNtg4,1693 +social_core/pipeline/social_auth.py,sha256=BtiKyFeo7ccqiih4HW6qkmBWBUsmhr1tkEFpiNrqGNw,3334 +social_core/pipeline/user.py,sha256=AhcxPRBsUHJGstmoDGf0tW9qthQZBx8qNTtobQrC7Bs,3725 +social_core/pipeline/utils.py,sha256=flMBP1z3AXgP7LCxCQJjsJh5s5SnoNP7CZ55jnxlpg8,2542 +social_core/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +social_core/tests/models.py,sha256=JlqFs7hFeC1RR7x958hqGOLvmSONP9_7KS6iOhviLDc,6016 +social_core/tests/pipeline.py,sha256=M09csDIgwNb_wDv8MmG335Tb6ABI8Sg-eNbE6Li3TOs,1198 +social_core/tests/requirements-base.txt,sha256=s3jHhEnP_8ReEgNGeT8wC2uJdrLoqnchPYgEXaNCiiQ,58 +social_core/tests/requirements-pypy.txt,sha256=eIO1H5z0_xT_pJiHkqNEERXAXl12VGco0E7A-S_qSBY,54 +social_core/tests/requirements-python2.txt,sha256=eIO1H5z0_xT_pJiHkqNEERXAXl12VGco0E7A-S_qSBY,54 +social_core/tests/requirements-python3.txt,sha256=eXbFIiMw6EdJI0uzVFO8rHWcg-Qx1Hdok6acTDDR19M,46 +social_core/tests/requirements.txt,sha256=Z2_j5RMin6PzIhfodSQRKan2rE6RrN6kcdWtX42s-yM,28 +social_core/tests/strategy.py,sha256=znJ4c-kn_qsnXng1nmbc9zn0hAIl6eTsEUQVm4VlGLw,3801 +social_core/tests/test_exceptions.py,sha256=IObJJVBaCZ_6BJcmHGlJDwmlQIfqTrGbPe4wU485gmM,3810 +social_core/tests/test_partial.py,sha256=LLu2NeeP49nlKjxKdlmuN55EugXA_B9G5oPu3n357Ho,3887 +social_core/tests/test_pipeline.py,sha256=TvjtsA5OHExBNuWr15eHHYdPeMIXHzHxFUJ8EtxFjQ0,7328 +social_core/tests/test_storage.py,sha256=bbxsJXkma-ULqnOkdXpsXZTzuuzK5fwy-KgyLbhgwGo,6389 +social_core/tests/test_utils.py,sha256=1a-wra_Mx-kVRZFx0pVZKckOmVPqnrsEc_oIHHXi8zk,6920 +social_core/tests/testkey.pem,sha256=INCfQP5gVXVTH0rvT2UpERTo6k3PPzXAhFDXVuYds5k,1679 +social_core/tests/actions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +social_core/tests/actions/actions.py,sha256=oZygJvLvCUyfkQRQjBvOh_dlvSnxsDaNuA8Dra0TNj4,8513 +social_core/tests/actions/test_associate.py,sha256=pBtLpXzjAUYHUWaCH8fShDOY_ZxdrkvtJcTfIw64o-s,2897 +social_core/tests/actions/test_disconnect.py,sha256=gfbG-BKiZZkoam20EpTV9mEsOZAUWaFdhCUP2ip5B3g,2577 +social_core/tests/actions/test_login.py,sha256=Wq13VfS9FJjFGW2T9zL-qOeAqcOC7-6uhy3lgZm6Xlo,2680 +social_core/tests/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +social_core/tests/backends/base.py,sha256=qZRpxX_Kj2AVTfo2E29PAPQ4yIY09Nd7FtqGsReagSQ,5946 +social_core/tests/backends/legacy.py,sha256=veFBQMIpbKTxGPspfjJHsJj_BL9RaKXHbm0eSgdw_eU,1481 +social_core/tests/backends/oauth.py,sha256=TVSZ3DGKWpr6K9ZDSTLb11d6wYzimmfTeD3DvShFEeE,4656 +social_core/tests/backends/open_id.py,sha256=vxDPZs8AQWuBrVbuctTVfqdi0R3uwn4_Bxgh68yhdpk,3339 +social_core/tests/backends/open_id_connect.py,sha256=SdjIIyoPh6e8OA05POgGheXl_N8eGLHj6cDg_ws1vNc,6166 +social_core/tests/backends/test_amazon.py,sha256=xXepdkQtMrxT21x1i9eaPJ2F4kkmkCyPcyktYvFrlcU,1306 +social_core/tests/backends/test_angel.py,sha256=22AnD5R6tgDjJapK_pHKjctmgIjuadjc6T2S7i3VqAQ,1089 +social_core/tests/backends/test_arcgis.py,sha256=9xHzGl1D6XlMFKSoUviV-afriLPsXmaCsxfvoi8XBS4,870 +social_core/tests/backends/test_asana.py,sha256=XF_GRQe3CilagcBvB8E_eucRw-77iNRN7PE3yTvhGkM,895 +social_core/tests/backends/test_azuread.py,sha256=sv0HsxlNef2fQKNCDM18zWIVjE2DV2WY-ZAgB-VSrJg,3164 +social_core/tests/backends/test_behance.py,sha256=5yWheMRBa9PUBc5fIlVTsygzDLORE38ZbvCF9Bs0NfE,1684 +social_core/tests/backends/test_bitbucket.py,sha256=w14m7h9kGuoUX0Ti_9n7mYDuYiowV9FgaTv1LxTx15M,5712 +social_core/tests/backends/test_box.py,sha256=HM_HDJnbS9kS0-YkUNaelgWox0KSCHb_AEF8EiNWnJI,2278 +social_core/tests/backends/test_broken.py,sha256=_7f4yxzYbGKBjAh3C4wDS3zBNS648oQjxlaO8wmLax4,1162 +social_core/tests/backends/test_chatwork.py,sha256=pKyVP9jVME1VEP3xVfaHBHwTt-ts1y877bqijxwaXFw,1285 +social_core/tests/backends/test_clef.py,sha256=rII-V5bUaeWF0XO_Y6yBYKLhns-Z3eIsQEohzA9RU3Y,626 +social_core/tests/backends/test_coinbase.py,sha256=rqBVnpJhvvzvn6WbDgCyZCpxvqw183oK7tkywwo-FeQ,934 +social_core/tests/backends/test_coursera.py,sha256=W_xbxs4fL1JeFzEfDI5c3yNL0E-mirB1iQf7sOkeh2k,1107 +social_core/tests/backends/test_dailymotion.py,sha256=G2IAxg-GUo2QTSom3o5de3yAjIjR97XdfRWiu49K8WE,576 +social_core/tests/backends/test_deezer.py,sha256=0AzoC574bVW6di8gBC2WjNFlPJlLsPJbbzjgsJNeR1o,1215 +social_core/tests/backends/test_digitalocean.py,sha256=dpprYY5DhOHtrBamEmDf6ML7-3ODPEseRyWzX7SKXig,977 +social_core/tests/backends/test_disqus.py,sha256=NMpijPiuvlYWiivtMxYXAXJrVeVcNj8tDz6f6QuuoH4,2150 +social_core/tests/backends/test_dribbble.py,sha256=ifk4P2HIA901CLgpehkgmXouKmTEOIfK7Gk8BMlcWyQ,593 +social_core/tests/backends/test_drip.py,sha256=bCLxKkp_Pc_wmBYwceiooGjqiUucJnzcPjizASr4P58,677 +social_core/tests/backends/test_dropbox.py,sha256=7pTzXh0jSxhx8I8jnLLh_vN-PAJ94q7s56ousCIOQBk,1040 +social_core/tests/backends/test_dummy.py,sha256=svwXdhjadEo0mn7sek8lEjRaqtldPtlg2DvNOyZvOCg,4734 +social_core/tests/backends/test_edmodo.py,sha256=kWRc8sZxZ47vGaXXiAC-ubMhyuk-9-13Wp--5t5NQ8A,1323 +social_core/tests/backends/test_email.py,sha256=l_BqJ8zi4HWcFyAge3AwmUpaqBi4o6thcYRYscAqcxw,468 +social_core/tests/backends/test_evernote.py,sha256=fD4BVXAGVmqrl3biglSHjCBh7KyeALnRSpv_eINmY8U,1648 +social_core/tests/backends/test_facebook.py,sha256=mDEjb9QgoBaDYbt0d4YDEhkMoQTYnq1LJFcyn2dfZew,1980 +social_core/tests/backends/test_fitbit.py,sha256=JAGIV_H4yXE8h1DqOxAXN9RTsBzz390wJO_Js4dHtI8,1555 +social_core/tests/backends/test_five_hundred_px.py,sha256=jFlOp1e5TrQBKgmB3UJmuGaDtWPiSPnZfug_w3ZvBTw,3943 +social_core/tests/backends/test_flickr.py,sha256=OMkJTW94WX-9792M_KoSCRuaSiYaB4QYMmb4CLGMhNc,683 +social_core/tests/backends/test_foursquare.py,sha256=2RpEH63DUbynKrbRmdYhClNFXpSzrmU0MUyhXRIEmUQ,4092 +social_core/tests/backends/test_github.py,sha256=ngVPkEH3c11sK_LHdDMKwrynjtKNWa9FDMxYfgFn_og,6442 +social_core/tests/backends/test_github_enterprise.py,sha256=U-fRuiq6KMU0aWbcW6_401oEUtUTiuOMn3bBxEShMGI,10066 +social_core/tests/backends/test_gitlab.py,sha256=ov7IfFIk1kFJ-DkSh0K1zkxUHS4wL4hJSAKGEpfVCeE,3324 +social_core/tests/backends/test_google.py,sha256=jDfXi5ixr5paRV5FMdiflvVcgCMhrd-IRCDsUSq1cl0,10806 +social_core/tests/backends/test_instagram.py,sha256=E5cXkXQjLzuHmqCeJf22oBO_US1OmddICcMoPY9UWiw,1771 +social_core/tests/backends/test_itembase.py,sha256=lYUik_yaixrP55TXIyoBJrmn8deeOj0zUEoEQH7GSc8,1364 +social_core/tests/backends/test_kakao.py,sha256=rAr7EQks61eLn7sdTMIxFDUhHlXM1AczT8Rp4kjnZbc,847 +social_core/tests/backends/test_khanacademy.py,sha256=40K8VE5OTjioKtZbLOdxFQbjvy6-W4eA_WjpLp40eck,847 +social_core/tests/backends/test_linkedin.py,sha256=1rqJv5GEqRrJKSopScvGdHaZO2bdk487UydpTlpfLiM,1053 +social_core/tests/backends/test_live.py,sha256=-lewoW6OJuPa88x94xaPEZIYxfmqfNDxAuXs852IVTA,937 +social_core/tests/backends/test_livejournal.py,sha256=Ewu5JqjfXqW0GQhje3I2mZNqJZfRYWGA95Afyll62oo,3699 +social_core/tests/backends/test_lyft.py,sha256=Eaq1M767ugsoXUMJrA_RlJ5kZkkNGQOmVouT1M_x32c,691 +social_core/tests/backends/test_mapmyfitness.py,sha256=hAaW93ZMFIOInmkBZ03r0FyWDyYMWs6z7zJTnE3rD-g,4891 +social_core/tests/backends/test_mineid.py,sha256=tkBxMI5UWB4gAmZbfLOyVButphVmXBWb4aSm2cMRJbA,574 +social_core/tests/backends/test_mixcloud.py,sha256=jDGiKBQx00HzmOQ29pVAInXvycwR4Ald2BcP-JQTQ1s,2418 +social_core/tests/backends/test_nationbuilder.py,sha256=0bU_ny4Aw1fZvLcfkTDOyhOyihc1NhJggNs40r3Iyu4,8473 +social_core/tests/backends/test_naver.py,sha256=Inka4ZF-elkFcP3ReghdzU6l2ViWgKuv4aKDevf3Sc8,1270 +social_core/tests/backends/test_ngpvan.py,sha256=HoJOqVykJ0UhCKxSzBJskHniuAct7cvLU9eP7sN9IdY,8522 +social_core/tests/backends/test_orbi.py,sha256=IFCcMTQo7-Kp0CXEcUDCt-UtmdDZ90HTsMjowrXc0I8,770 +social_core/tests/backends/test_patreon.py,sha256=v4fqRxZLiHTd5RQBvaC-w-nefgRNomtPhcJczAdACfs,2093 +social_core/tests/backends/test_pinterest.py,sha256=kqSghSSj-aXg6UnYixQ_M7okC4s5U5YnEkUdbsA0Jo4,1313 +social_core/tests/backends/test_podio.py,sha256=NbswbL7d_ubp3p0cl__wuS1ewKfHSrRHX72jRNN1Yj4,1673 +social_core/tests/backends/test_qiita.py,sha256=fjWVy4_EDxBAivcYYsXPHhz86T80vPRSLFWluM-jruc,558 +social_core/tests/backends/test_quizlet.py,sha256=hsfRMRHBaMkeVXHYA0mXj4FfFlJ2b-MUMGEQDaQCxuU,522 +social_core/tests/backends/test_readability.py,sha256=-QOG4_BW94hVToE9S1euOfggQX135pb6JRg66hoOHAs,1312 +social_core/tests/backends/test_reddit.py,sha256=-n6zYeiYoD3F9aZpQSSJktPb2NGD83A5sIvIR0usFE4,1738 +social_core/tests/backends/test_saml.py,sha256=whAn1WdbkgvPr_xTQnnkempElmfCYhfdspmTdECzDoo,4937 +social_core/tests/backends/test_sketchfab.py,sha256=eKBZAFAK83ESIKvsSIK2ACpppqcu1XEWxy4-AX7vIaY,658 +social_core/tests/backends/test_skyrock.py,sha256=WM2lGEvR5yCEaXzdOjKqHHwYSiTUvGJm--lO2xtOLaA,1341 +social_core/tests/backends/test_slack.py,sha256=RViurF0GAfTTqQnLU6OG_VQXUzG2jhJsH2_CR8dOIJA,764 +social_core/tests/backends/test_soundcloud.py,sha256=RV297FC7XoGPS0E_-YnQVZ03nxJYlm3YEZw-ZfOMTDk,1615 +social_core/tests/backends/test_spotify.py,sha256=BnSu3mjVvoZP9YMg6i31Gg_JSwDaPvzi_mDJrFPFcQw,888 +social_core/tests/backends/test_stackoverflow.py,sha256=Pvwp0wrHUFs0LQHzsfsrBuEGoGRMjStvxX3pr_BWizo,1675 +social_core/tests/backends/test_steam.py,sha256=XhxvCZiU44HtpKgyWN29H602btKOjrDFbrGJu4C4BB4,5406 +social_core/tests/backends/test_stocktwits.py,sha256=pZqr54lMLssd8zWe7rQ1RHREB37zbvo4543mwxgV-wg,1642 +social_core/tests/backends/test_strava.py,sha256=OMA9r78AWRfpPRKWqbVoszEK0ButcSejuJyYbxokaE8,1866 +social_core/tests/backends/test_stripe.py,sha256=PGDLC9klGGBEF9cpvS6Yjnf7VMNVEPBajeCUA93nPUI,602 +social_core/tests/backends/test_taobao.py,sha256=VNrZfdQJTMbBdGyGwz6n3TRl0g5MdW1vuiQ-9yZwQ_4,748 +social_core/tests/backends/test_thisismyjam.py,sha256=-RRwIsk5weBZKqb_KWEfjt-UdgNW0BEZ3KgEEw9tAd8,860 +social_core/tests/backends/test_tripit.py,sha256=k6Iz6wR2HCjOHTH6PyTvkSMDGMxUym7LUPgA_4FEFmk,4216 +social_core/tests/backends/test_tumblr.py,sha256=kllsEnypv4C-QaAEV5qk3TiMrJPtC6UVZ-JTACL4uxE,1741 +social_core/tests/backends/test_twitch.py,sha256=GHXnzbsiQSxjVyDoNqurV_rA_a0B9RH8B70nV-t2zXM,1034 +social_core/tests/backends/test_twitter.py,sha256=cdFEQMiYYrtM5KjWXnFYoLLH3Iv0suCJI-ETtMUUrsM,9388 +social_core/tests/backends/test_uber.py,sha256=BS2f8czyeDsgafjKe6RdC4FvVLmN-3-CXzV4ZqEYkIo,997 +social_core/tests/backends/test_udata.py,sha256=BpY2c7R6XayTcs4qtn9f_G0AcYcchkqbIj_TNgRKkNQ,793 +social_core/tests/backends/test_upwork.py,sha256=cDruJnVMliKBL-Bz0F2NlAwZqQ4DmE7B9sxneycqqLA,1528 +social_core/tests/backends/test_username.py,sha256=IPHuxFF9moWL68E_QEKkbHfiAyU0Zf-iXqTeRi8nSUY,481 +social_core/tests/backends/test_utils.py,sha256=xDg9h8BWOkKIaT2Smbs14jg8TQaFotZ8Ut7ra07K0hw,1767 +social_core/tests/backends/test_vk.py,sha256=pb-jgqpUbka0w5NR3_NaKk_buUKA0XngsWmLV1woOcg,831 +social_core/tests/backends/test_wunderlist.py,sha256=VmkEo_FFczVvTl41oPr2s78WMx6C5lA1ew10JF2-rTo,741 +social_core/tests/backends/test_xing.py,sha256=iqiFYR5eWyEdK-wnd5czWq0UPwZNIvPppmK7cVfWg3o,6277 +social_core/tests/backends/test_yahoo.py,sha256=afkgOjkVLxQH97ye9YDaXP-SykGAE5zLG9Pcejrdbfo,2533 +social_core/tests/backends/test_yammer.py,sha256=hO0Muz8THARKFKVPxkLkBc73FzEXbQy32T8qpuMTnXk,3990 +social_core/tests/backends/test_yandex.py,sha256=kwU7NR1uAHopvN0Y3GicszYquDSYOsuNHnHTq-1eqVc,701 +social_core/tests/backends/test_zotero.py,sha256=SzYtE-8G-8TfcpX7LfyXchRI1NxUQ0UbHHbuauewpzA,697 +social_core/tests/backends/data/saml_response.txt,sha256=K2pUL1chyWM1ICWhjsks1GRrC8YaVnPcD29FAmfkmw8,17495 +social_auth_core-1.6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +social_core/pipeline/__pycache__/debug.cpython-36.pyc,, +social_core/pipeline/__pycache__/mail.cpython-36.pyc,, +social_core/pipeline/__pycache__/social_auth.cpython-36.pyc,, +social_core/pipeline/__pycache__/partial.cpython-36.pyc,, +social_core/pipeline/__pycache__/disconnect.cpython-36.pyc,, +social_core/pipeline/__pycache__/utils.cpython-36.pyc,, +social_core/pipeline/__pycache__/user.cpython-36.pyc,, +social_core/pipeline/__pycache__/__init__.cpython-36.pyc,, +social_core/backends/__pycache__/disqus.cpython-36.pyc,, +social_core/backends/__pycache__/persona.cpython-36.pyc,, +social_core/backends/__pycache__/open_id_connect.cpython-36.pyc,, +social_core/backends/__pycache__/sketchfab.cpython-36.pyc,, +social_core/backends/__pycache__/google_openidconnect.cpython-36.pyc,, +social_core/backends/__pycache__/username.cpython-36.pyc,, +social_core/backends/__pycache__/goclioeu.cpython-36.pyc,, +social_core/backends/__pycache__/stripe.cpython-36.pyc,, +social_core/backends/__pycache__/wunderlist.cpython-36.pyc,, +social_core/backends/__pycache__/live.cpython-36.pyc,, +social_core/backends/__pycache__/echosign.cpython-36.pyc,, +social_core/backends/__pycache__/changetip.cpython-36.pyc,, +social_core/backends/__pycache__/asana.cpython-36.pyc,, +social_core/backends/__pycache__/suse.cpython-36.pyc,, +social_core/backends/__pycache__/coinbase.cpython-36.pyc,, +social_core/backends/__pycache__/tumblr.cpython-36.pyc,, +social_core/backends/__pycache__/linkedin.cpython-36.pyc,, +social_core/backends/__pycache__/kakao.cpython-36.pyc,, +social_core/backends/__pycache__/khanacademy.cpython-36.pyc,, +social_core/backends/__pycache__/beats.cpython-36.pyc,, +social_core/backends/__pycache__/untappd.cpython-36.pyc,, +social_core/backends/__pycache__/mixcloud.cpython-36.pyc,, +social_core/backends/__pycache__/angel.cpython-36.pyc,, +social_core/backends/__pycache__/bitbucket.cpython-36.pyc,, +social_core/backends/__pycache__/vend.cpython-36.pyc,, +social_core/backends/__pycache__/rdio.cpython-36.pyc,, +social_core/backends/__pycache__/moves.cpython-36.pyc,, +social_core/backends/__pycache__/spotify.cpython-36.pyc,, +social_core/backends/__pycache__/five_hundred_px.cpython-36.pyc,, +social_core/backends/__pycache__/loginradius.cpython-36.pyc,, +social_core/backends/__pycache__/shimmering.cpython-36.pyc,, +social_core/backends/__pycache__/pixelpin.cpython-36.pyc,, +social_core/backends/__pycache__/reddit.cpython-36.pyc,, +social_core/backends/__pycache__/edmodo.cpython-36.pyc,, +social_core/backends/__pycache__/github_enterprise.cpython-36.pyc,, +social_core/backends/__pycache__/xing.cpython-36.pyc,, +social_core/backends/__pycache__/battlenet.cpython-36.pyc,, +social_core/backends/__pycache__/yammer.cpython-36.pyc,, +social_core/backends/__pycache__/appsfuel.cpython-36.pyc,, +social_core/backends/__pycache__/gitlab.cpython-36.pyc,, +social_core/backends/__pycache__/douban.cpython-36.pyc,, +social_core/backends/__pycache__/stocktwits.cpython-36.pyc,, +social_core/backends/__pycache__/evernote.cpython-36.pyc,, +social_core/backends/__pycache__/box.cpython-36.pyc,, +social_core/backends/__pycache__/itembase.cpython-36.pyc,, +social_core/backends/__pycache__/ngpvan.cpython-36.pyc,, +social_core/backends/__pycache__/justgiving.cpython-36.pyc,, +social_core/backends/__pycache__/withings.cpython-36.pyc,, +social_core/backends/__pycache__/ubuntu.cpython-36.pyc,, +social_core/backends/__pycache__/meetup.cpython-36.pyc,, +social_core/backends/__pycache__/lyft.cpython-36.pyc,, +social_core/backends/__pycache__/patreon.cpython-36.pyc,, +social_core/backends/__pycache__/podio.cpython-36.pyc,, +social_core/backends/__pycache__/foursquare.cpython-36.pyc,, +social_core/backends/__pycache__/shopify.cpython-36.pyc,, +social_core/backends/__pycache__/digitalocean.cpython-36.pyc,, +social_core/backends/__pycache__/vk.cpython-36.pyc,, +social_core/backends/__pycache__/steam.cpython-36.pyc,, +social_core/backends/__pycache__/skyrock.cpython-36.pyc,, +social_core/backends/__pycache__/vimeo.cpython-36.pyc,, +social_core/backends/__pycache__/github.cpython-36.pyc,, +social_core/backends/__pycache__/aol.cpython-36.pyc,, +social_core/backends/__pycache__/readability.cpython-36.pyc,, +social_core/backends/__pycache__/weibo.cpython-36.pyc,, +social_core/backends/__pycache__/pinterest.cpython-36.pyc,, +social_core/backends/__pycache__/launchpad.cpython-36.pyc,, +social_core/backends/__pycache__/chatwork.cpython-36.pyc,, +social_core/backends/__pycache__/nationbuilder.cpython-36.pyc,, +social_core/backends/__pycache__/bungie.cpython-36.pyc,, +social_core/backends/__pycache__/orbi.cpython-36.pyc,, +social_core/backends/__pycache__/qiita.cpython-36.pyc,, +social_core/backends/__pycache__/docker.cpython-36.pyc,, +social_core/backends/__pycache__/flickr.cpython-36.pyc,, +social_core/backends/__pycache__/pocket.cpython-36.pyc,, +social_core/backends/__pycache__/mendeley.cpython-36.pyc,, +social_core/backends/__pycache__/mailchimp.cpython-36.pyc,, +social_core/backends/__pycache__/taobao.cpython-36.pyc,, +social_core/backends/__pycache__/livejournal.cpython-36.pyc,, +social_core/backends/__pycache__/openstreetmap.cpython-36.pyc,, +social_core/backends/__pycache__/openshift.cpython-36.pyc,, +social_core/backends/__pycache__/pushbullet.cpython-36.pyc,, +social_core/backends/__pycache__/google.cpython-36.pyc,, +social_core/backends/__pycache__/gae.cpython-36.pyc,, +social_core/backends/__pycache__/zotero.cpython-36.pyc,, +social_core/backends/__pycache__/mediawiki.cpython-36.pyc,, +social_core/backends/__pycache__/naver.cpython-36.pyc,, +social_core/backends/__pycache__/qq.cpython-36.pyc,, +social_core/backends/__pycache__/dailymotion.cpython-36.pyc,, +social_core/backends/__pycache__/open_id.cpython-36.pyc,, +social_core/backends/__pycache__/instagram.cpython-36.pyc,, +social_core/backends/__pycache__/stackoverflow.cpython-36.pyc,, +social_core/backends/__pycache__/coursera.cpython-36.pyc,, +social_core/backends/__pycache__/microsoft.cpython-36.pyc,, +social_core/backends/__pycache__/base.cpython-36.pyc,, +social_core/backends/__pycache__/twitch.cpython-36.pyc,, +social_core/backends/__pycache__/saml.cpython-36.pyc,, +social_core/backends/__pycache__/twilio.cpython-36.pyc,, +social_core/backends/__pycache__/yandex.cpython-36.pyc,, +social_core/backends/__pycache__/uber.cpython-36.pyc,, +social_core/backends/__pycache__/dropbox.cpython-36.pyc,, +social_core/backends/__pycache__/facebook.cpython-36.pyc,, +social_core/backends/__pycache__/amazon.cpython-36.pyc,, +social_core/backends/__pycache__/classlink.cpython-36.pyc,, +social_core/backends/__pycache__/twitter.cpython-36.pyc,, +social_core/backends/__pycache__/soundcloud.cpython-36.pyc,, +social_core/backends/__pycache__/runkeeper.cpython-36.pyc,, +social_core/backends/__pycache__/utils.cpython-36.pyc,, +social_core/backends/__pycache__/arcgis.cpython-36.pyc,, +social_core/backends/__pycache__/mailru.cpython-36.pyc,, +social_core/backends/__pycache__/fedora.cpython-36.pyc,, +social_core/backends/__pycache__/lastfm.cpython-36.pyc,, +social_core/backends/__pycache__/nk.cpython-36.pyc,, +social_core/backends/__pycache__/upwork.cpython-36.pyc,, +social_core/backends/__pycache__/slack.cpython-36.pyc,, +social_core/backends/__pycache__/thisismyjam.cpython-36.pyc,, +social_core/backends/__pycache__/coding.cpython-36.pyc,, +social_core/backends/__pycache__/strava.cpython-36.pyc,, +social_core/backends/__pycache__/salesforce.cpython-36.pyc,, +social_core/backends/__pycache__/tripit.cpython-36.pyc,, +social_core/backends/__pycache__/odnoklassniki.cpython-36.pyc,, +social_core/backends/__pycache__/mineid.cpython-36.pyc,, +social_core/backends/__pycache__/monzo.cpython-36.pyc,, +social_core/backends/__pycache__/legacy.cpython-36.pyc,, +social_core/backends/__pycache__/weixin.cpython-36.pyc,, +social_core/backends/__pycache__/azuread.cpython-36.pyc,, +social_core/backends/__pycache__/eventbrite.cpython-36.pyc,, +social_core/backends/__pycache__/orcid.cpython-36.pyc,, +social_core/backends/__pycache__/jawbone.cpython-36.pyc,, +social_core/backends/__pycache__/line.cpython-36.pyc,, +social_core/backends/__pycache__/quizlet.cpython-36.pyc,, +social_core/backends/__pycache__/mapmyfitness.cpython-36.pyc,, +social_core/backends/__pycache__/drip.cpython-36.pyc,, +social_core/backends/__pycache__/udata.cpython-36.pyc,, +social_core/backends/__pycache__/__init__.cpython-36.pyc,, +social_core/backends/__pycache__/exacttarget.cpython-36.pyc,, +social_core/backends/__pycache__/clef.cpython-36.pyc,, +social_core/backends/__pycache__/deezer.cpython-36.pyc,, +social_core/backends/__pycache__/dribbble.cpython-36.pyc,, +social_core/backends/__pycache__/eveonline.cpython-36.pyc,, +social_core/backends/__pycache__/email.cpython-36.pyc,, +social_core/backends/__pycache__/behance.cpython-36.pyc,, +social_core/backends/__pycache__/belgiumeid.cpython-36.pyc,, +social_core/backends/__pycache__/fitbit.cpython-36.pyc,, +social_core/backends/__pycache__/yahoo.cpython-36.pyc,, +social_core/backends/__pycache__/oauth.cpython-36.pyc,, +social_core/backends/__pycache__/trello.cpython-36.pyc,, +social_core/backends/__pycache__/professionali.cpython-36.pyc,, +social_core/backends/__pycache__/azuread_tenant.cpython-36.pyc,, +social_core/backends/__pycache__/goclio.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_patreon.cpython-36.pyc,, +social_core/tests/backends/__pycache__/open_id_connect.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_naver.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_soundcloud.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_facebook.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_mineid.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_drip.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_upwork.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_broken.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_readability.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_strava.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_tripit.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_mapmyfitness.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_clef.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_fitbit.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_stackoverflow.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_utils.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_deezer.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_coursera.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_skyrock.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_dribbble.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_instagram.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_slack.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_dummy.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_udata.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_google.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_zotero.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_uber.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_khanacademy.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_spotify.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_saml.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_arcgis.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_five_hundred_px.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_yandex.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_yahoo.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_amazon.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_twitch.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_email.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_mixcloud.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_asana.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_quizlet.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_ngpvan.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_azuread.cpython-36.pyc,, +social_core/tests/backends/__pycache__/open_id.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_kakao.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_lyft.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_digitalocean.cpython-36.pyc,, +social_core/tests/backends/__pycache__/base.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_flickr.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_username.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_taobao.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_angel.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_behance.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_pinterest.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_orbi.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_vk.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_linkedin.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_github_enterprise.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_github.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_thisismyjam.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_coinbase.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_wunderlist.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_live.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_nationbuilder.cpython-36.pyc,, +social_core/tests/backends/__pycache__/legacy.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_tumblr.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_podio.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_chatwork.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_disqus.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_sketchfab.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_stripe.cpython-36.pyc,, +social_core/tests/backends/__pycache__/__init__.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_reddit.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_edmodo.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_yammer.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_gitlab.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_box.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_steam.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_twitter.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_foursquare.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_evernote.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_xing.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_qiita.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_livejournal.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_dropbox.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_bitbucket.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_itembase.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_stocktwits.cpython-36.pyc,, +social_core/tests/backends/__pycache__/oauth.cpython-36.pyc,, +social_core/tests/backends/__pycache__/test_dailymotion.cpython-36.pyc,, +social_core/tests/__pycache__/test_pipeline.cpython-36.pyc,, +social_core/tests/__pycache__/test_utils.cpython-36.pyc,, +social_core/tests/__pycache__/strategy.cpython-36.pyc,, +social_core/tests/__pycache__/models.cpython-36.pyc,, +social_core/tests/__pycache__/test_storage.cpython-36.pyc,, +social_core/tests/__pycache__/test_exceptions.cpython-36.pyc,, +social_core/tests/__pycache__/pipeline.cpython-36.pyc,, +social_core/tests/__pycache__/__init__.cpython-36.pyc,, +social_core/tests/__pycache__/test_partial.cpython-36.pyc,, +social_core/tests/actions/__pycache__/test_disconnect.cpython-36.pyc,, +social_core/tests/actions/__pycache__/test_login.cpython-36.pyc,, +social_core/tests/actions/__pycache__/actions.cpython-36.pyc,, +social_core/tests/actions/__pycache__/test_associate.cpython-36.pyc,, +social_core/tests/actions/__pycache__/__init__.cpython-36.pyc,, +social_core/__pycache__/exceptions.cpython-36.pyc,, +social_core/__pycache__/storage.cpython-36.pyc,, +social_core/__pycache__/strategy.cpython-36.pyc,, +social_core/__pycache__/store.cpython-36.pyc,, +social_core/__pycache__/actions.cpython-36.pyc,, +social_core/__pycache__/utils.cpython-36.pyc,, +social_core/__pycache__/__init__.cpython-36.pyc,, diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/WHEEL b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/WHEEL new file mode 100644 index 0000000..6261a26 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.30.0) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/metadata.json b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/metadata.json new file mode 100644 index 0000000..d8b398a --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"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.7", "Programming Language :: Python :: 3"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "matiasaguirre@gmail.com", "name": "Matias Aguirre", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/python-social-auth/social-core"}}}, "extras": ["all", "azuread", "openidconnect", "saml"], "generator": "bdist_wheel (0.30.0)", "keywords": ["openid", "oauth", "saml", "social", "auth"], "license": "BSD", "metadata_version": "2.0", "name": "social-auth-core", "run_requires": [{"requires": ["PyJWT (>=1.4.0)", "defusedxml (>=0.5.0rc1)", "oauthlib (>=1.0.3)", "python3-openid (>=3.0.10)", "requests (>=2.9.1)", "requests-oauthlib (>=0.6.1)", "six (>=1.10.0)"]}, {"extra": "all", "requires": ["cryptography (>=2.1.1)", "pyjwkest (>=1.0.1)", "python3-saml (>=1.2.1)"]}, {"extra": "azuread", "requires": ["cryptography (>=2.1.1)"]}, {"extra": "openidconnect", "requires": ["pyjwkest (>=1.0.1)"]}, {"extra": "saml", "requires": ["python3-saml (>=1.2.1)"]}], "summary": "Python social authentication made simple.", "test_requires": [{"requires": ["coverage (>=3.6)", "cryptography (>=2.1.1)", "httpretty (==0.6.5)", "nose (>=1.2.1)", "pyjwkest (>=1.0.1)", "python3-saml (>=1.2.1)", "rednose (>=0.4.1)", "unittest2py3k (==0.5.1)"]}], "version": "1.6.0"} \ No newline at end of file diff --git a/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/top_level.txt b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/top_level.txt new file mode 100644 index 0000000..3d5ecb0 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_auth_core-1.6.0.dist-info/top_level.txt @@ -0,0 +1 @@ +social_core diff --git a/flex/lib/python3.6/site-packages/social_core/__init__.py b/flex/lib/python3.6/site-packages/social_core/__init__.py new file mode 100644 index 0000000..bcd8d54 --- /dev/null +++ b/flex/lib/python3.6/site-packages/social_core/__init__.py @@ -0,0 +1 @@ +__version__ = '1.6.0' diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65790a6595675cfd901560e9b3d550b8e9f247d3 GIT binary patch literal 220 zcmX|*F$%&k7==?o5T&bwH|UV2#ab+gcmfxly8Of@YG_T`BrVt*c@>Y~>Mfj%B0l&Y zkH;I{-1o<`JIkjCp{L_7&!vsSjGQr)Bj)W;FzAPIn1<0d@E#L@j%y>71h^qRaJ#R) zEUC5VsO6-injBTd+>9JGnU}HDO_D{dXMJXpWtn7`EKMS(lO%gmiy9wVQ8B!bggtuYhj`5K>rxu)aPF`@Kfsm!4udcRON%;Srw*q((lkHaLG#j_}h(%JVoK z1#_7cRvNv^hn!Z%&&E~4b%ZPX9D_2(#+V5v_@CK=zdFDie{y6j`4i(0_ZOxz6+g4( zo{rNqW!}Y8W}NRmHx$!P|EjoRca4Rmvn^3ta;da!Qze~iEsYV|H5R43q)N(Kavg1q zP1LYvZ*;V?WK{LE5Bh`NN&89A=?49Fzu)O~i$?#j z+ZlBHc7O++llIY|KPYw%kB@uL5qRkfp7r{Je$hDYeTdvE??)%cO{Z|PT;hmFpA=OY zozJ5zUmc~hK1w>%EQ_aU0LQm6A5W2I;TVfjmg8MC?ZSBx4$q=g6t&B@8KmJPDlGkK zywf;Jg!cGYY4y7Cp5mewCdu?=Bm%0L{lk!VQTA=tn#uSAJ8NBpvUNI5L?pA;scgM> z_HHWAA3lB{UW?a{vxkq)9zLEte*fVE{6=YO&;lg(U(Dk~?EfMi%18Nw^Hw$;0xbK4 zsr}(pMr%i)?GTWfrrE*llfn^G4LpZHvWLOob+f@-w!>ZSvMTdf9hc2G+vM_lc-|e- zA<%CF7Xhq?EBguv*Ds$fjfI_a#g}a1C`aB?Jmtbz@Z3~};-_3$UvO3Wj4fQ{$`6oZ zDHpLky$u_15dd1uROxRVF)KjX$`pLbXNj`M&dOet^=j_8Jod1nr?`BRR{&0L$*YEZ zqj=xI>i<+c`+Hu+%4K0L%|#`xuV)tK7Uo|l{)WkCd5v}q>rP%*ocN9Cs^a5~MGdgU z&Q)z`YQP%fP2s58cuNDgiX4w>3RkZ|HLEvPB%(D$85L=zN8hcfv!NcMs&}Nb^$hz} zW#oLO%Bpf^l9k_5Wt(gfHLG3t?bU^^dbh+PR8GQNHprtD$Gwz~=7zjYAtim3 zdVPtOW)xN^M_FN?y`;O`A=l=2dWy5|A%6X`UT_g6^GN=X;!U&=c^{U1hlV>ekT9)= zd$5WM)Z+47C^Gva27`N~6uiMKjJK^itK!;Z7PsK3bJ7kM3gYo9#wuc6_$+wghvo7* z`4QvtD&NGt0j(j`K_!gx0Bc-{j)%#W==>Lx9I~U>HCeHhEuTRS-iKH)bU3zT5Coc; zD`{Dxy#wGDJSB>O%sAEs>aGf4PC(<*R?c4MnS`Jx2T7i zMi9w&5Q`>XA)tk0kl)AP?>_1Dk9yrM`r-{%Y2sez!G;FD{3&JrfHG_NbU-T$RtUmZ zOq_ufDZWr-lY)MP0gWxjoEYR+!xz~!ndcF8v=yH;g0Da;e}b^4r}27in}pU?MZNaV zs4U=}&Crk{xld33AM$EI-nUzd5SCHCp;1T3^LPVzYjEmS4WOn9w5@uVM>8gWj_4+n zWxDb1&3nb)$>LWBlj%j29e@T&{2~BXupv#z(maooOjB_s8pP_=0cw&{Thu7ki(Dg( crjKTh`zr{n5Bim=;s5{u literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/exceptions.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/exceptions.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2b50311c73eb95bc7dfed1d4a36955e87f887cf2 GIT binary patch literal 6411 zcmbtY-E!N;6$VI=-uG4d2)ONA^s3)E_$5V3wnPmee2c~I+3)=9Is0+FKC}EE(K@bc z+JCje&%pC8u3$mew68Rw3H?W0QhQr&>A)pX1};k-xCCs73UEc1fXl#DF$Fxu?+xIZ zm{Dr3(TruC`l8pN=UK~o-LpH%n<>i@zGGSc z)>IW@@a=zRTV3Uz*`eGzv(;AH?+B@at+v|w_~cifdi``~Tl7VLCwThl$Oq-zZ{)u7FIbrW-o9 z43SpCn_cR%id#;aDE+8`xUw+tABF`2tI_QD%J&Yxu$>d>35UL9PLY}dB!XSf4K0gMz!T{Gd&OBd(;g13i+=P! zn|>BR=*QYXJJ#Ro5TLZ*|L|)M)F|nMCUGXc&~kzC8<)7Li zi`LW?tz}d~s5c0eWzjokRRgR2g%J_C%YL(>NHA5V8)-n@q8r@2I4oNUtyk4>Ba~>X zR_2!R+FC%ri`v41jcD$VZV+t|)v=_~_fb6=*^!-2Iy!L-_at&TY_O2a=}a8v zNbDO?Jwu{?X}cXM`j3ZrZgV5=6M=s!O(pHX_cpV5MAlEF(-l0%E!08;M9^J&tNox4 zbfKS=)H3Z!q6`(pamv_l)nR2&q$Q;1V%80swGgaQJ)hfw>DV3!L8s-BPO)&GnFwf5 z*d5HSmgvSFMh*-=CN)Oo)8H`WFt=Iiwt*|4P^9T|I+5AGlXHiB&_lR^7 zHVKmD5w6@PsAasOeu+1eeFFYL=}xUxVgIPYKd?WMpwCin)(K_qQKZd0cw_bu0CJ_J z3S&uLpT}5^n6aRpo$cUTe z*mdqw0|oAslbm?!Mhi}SWe&SuaV?pvjJGlqUyfRtTE#w?SXZ3??8k;Y#-vhnV!P?D z(ae|hN)3zGH)>ewK4iJLKI*oOD>E!ZpTV{{vPRw?no|>_L2SmC*cXZP3z~+QCEJ}f z6t#`FFcWVkTUZu#8zki`@5J-Zz2{2#s=tyT&L=VpL$ve@CN&fXh?QXt<=UYf<=1+0X7|qqV%Jjh@Lr`uHylD1}cI=-l4bkLHS>$fsw^hR0>8kolP2t&=2r! z96~F3_Mz6O^@?En75e0f$3rri(WPDts#lg0t0_4NC7HPFkV%S6;voWZw!{%W5AzfT z|3H$Fp=x@6b%IO|40XgKl70?IxE+iE4^0Qe$A`I;=h z|B#WHFCUq-n^sx5I})FCJ)=n|85K!Mw)z5z4pm@hcBd!FtYAu~aoMuTxx*y{eoLbz z1akN)ik76RXt>mgPG3uXaWRjB|jash77w1bX)R(?Ga0L>&n={50X2Mj? z`=NU#y(<~}p4OEx#u?+VaM$LHQn^<&cE%aC3~@&4-=!`S3N%i0_Bu$S#hbl=%dHT3 z?cN?rv36T3+^@ zyVT`fe1)U`ck&Edl<04Asi|Vb@+NtHyo0%38bi-qoC>{z^Q;vHq{@_vKg4E=#609x z{&PUfp!FaCvHujsy|yiEV7$NK$~mN#YjMbBZtglw3X+9N0d2kZv$Uf-EZP=oL8RT4RjF*Jkr)7`!vYCbf)b^O7^P*{?lpO|E!;ifaFAj#ju|%m!H# z$l%W@L|sKkP@rR>IBm|sg+r%_yV!>qOr-M&I^G-DLXWD2vf94}#K3U}BG(|~T ze9S|&IG0O`YYI+-IRi841%-CCPPYxZk&!CmmgnF_7H(ASN44L@XXKxgEAkmvqtckc URcTB$rWV!Z literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/storage.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/storage.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ca4f974fce0bc4e9b4192a9eaab8d48091ed795 GIT binary patch literal 12004 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/store.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/store.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3226275e57c0f56f7f2868f021d7a0af617dc242 GIT binary patch literal 3121 zcmZ`*+in}j8J?Lv@*r7t6IW3jfGLuubSlxQRo5u(cPJb83fjF*Ec?edjCC88XQmUS zwTmBB7{?iA#BmjxQYWQ4zK8--AGB)WoZ~c0%4}Rzp6yg#F)mUSZHqZ`_HFYF86;|r20X5_pE2nlkX?f@?<}SL6?HKZj*@*CKo!y zF}6_*c_0(p+~*;0;+?%^ouRhK!3ul2@_>~cKBu`iQ9;XW$YBGq5O6|rEHV4g+Ot!r zA3C*yNI#uy(2-+hijT^UdXbs3-xa!rC7^EWd5+$qUehRju5>)rxkkJ&ePdni(ElAf z`p>Tw{|iWsbrvzZy?OdK2d+_&VLcR!;sA}>qlRJ#KO!Uxc+-L{_@y&<^c`Rn@F{1( z*9dQZtw!T>wS;Hw-|zaBn*&$n$}BBwXznqcr z0=_N}bIL0g3rE}@N_;Sd)p5 zj0y>7XM0vA^xoWs%&TS3*xc0vyP7+9Nd}Yaxg+^IfG?qjt~1;HT9s2>bgW0o4X4&% z4REMut*vjNW06*VG6APby$eY#wnTaOfCC&0S%C>NM8)de)spYFvQwjun=u5?Fnihmhh|)U4F2bM`(Bv)!H}vFy zXb~g`2O?B4yZ?nM6cJ-*T&T{}s7^5*Jy*H|&+h12Z+Ei6oepUChs1PJtuR>rwd++* z@*KfYW+SD^&YH+5a@oJDNEG65?MCQ^LFhMk?7tM^^+UR#$J|G?PPffvx^<*W>DH!R zBwMsSBv?weJo0U74PcjcwL-6q0sB7F2PGwBP%2~;P0+uu15DRZNlPo=jDY63ghDAY|wWRX$aUxB3nIl{>QRST{`4l&nTF+MTR@wVl_c2P% zj(n$1Y@KBsT+s(I+6Ex`;2Pr9NrUcS6cK$( z3IIu&MfXruo}7**<|=BlLcIi~KcV5jMRgs~A*4@H*6i8lv;T)@z3yuwB_Kq~pVJb= z!WZEjporjHggUO5x9jUA8HlcxwV8MoK2;ZwwI1uuGgg6AgJgO=5FM!UE82pHn{h!b z#Amxdsdo%T`u`Z?G-;T;BGeyJ_g7SXM3vPa8B-H}{d=mqRQ+^}kvshf#n7!G6pXu0 z+i!c;&AM$(xT_%j%6eFBme!NeWUNbzFuGL-!hz-ZgRCf(9we#KbhC(V{3S8U0(o>q zmzC(|mC^Q|S92KAsD7I&I?wB))-8L32|ukF=Z};;42eP^K-$|N?KW>aJNNzvI;G!* literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/strategy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/strategy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d162fa53146d9d8925b62a1441edb1aedbe20474 GIT binary patch literal 9685 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/utils.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..471bab8641426088f7cbba5dc993cb1a1f2cc424 GIT binary patch literal 9110 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/angel.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/angel.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec09fd553daa69fa3b09553fbe3de0ee8ac30114 GIT binary patch literal 1367 zcmaJ>&2HO95Z>jVC|Y*i{2XlLzyfUn0g={e+#(K&B8sE7MpLVhlnYT{vEi;P+7wCe zQZ7PBC+FHX=nM2!`Ut%Clvl{5on0w{nnQ<}Z)bmIcK()+cXxxszj*Y}LFjL^MKDLt zfYs*!97Tv@Zl!o;MHWUsAZ~N#E#i)_ZtckC?g+KLDh+zsg-F9eZ(dHn3SYDMN@Vuxe)H^1YmEp)W7gt|=aDL@SoD)_gB-hRp4Y^DzL2Boh!Dw84_CJTq95O?` zkF}GU8o(a#DX@ADu)rK2pi9K9Tg?3fw6qrZ5_5;Ux7N~Lpi7u@?*QH6U+tx{beG=3 zS~v@L;Vo?5_(RvDS$#PaarTnQO+QL-de;Y8-M(8Lz9IL_x;8SR>I(nOVXF4Omz zNS)i-6*JJNQSD@GCfaW8YeQPD{mDE{jTB5033^6urfe-r9n8CbfZBHLsv=Fw+P)BF zvn0gXo99umw}dcG_(NzMDh*{?<-Qs!Vi!-LA2 zm$twO=JxLv)X}LPyv!L_>oT&Et!fClx=CVD9d7!pNV=QGd_-o@i7hCxP>PydmI98wUptVXCy>$ntP?e>>F8 zne%o~`_z<~(mJ4Ymh*WkKyFcbJ!htUD3v}~i9YSU8vi&Lo<+U!+2EYM8orF~_D)ax zqY)hse(ImopZl=?BHI7YqCU1a?7ui0_D{!jH13W2@*bFz2O4xbw?Ky=Z2*0t+S3Ad uK75wHWA@J5PTims{y>yQu6fu}zfrTfWz5(%N%6m3#r2&|hslY9TlT-DBzDdK literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/aol.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/aol.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4972fd33372c0d51a102370192b77df4d063108a GIT binary patch literal 569 zcmY+B!A=`75J2tSg(NGul}k^1=p_;xry{KgLI_S35&{Y6WiOW5G2Ky7P1C0F*SUQjm@p?8XEzn}q<5ugw#S_N4^ z$qxb{L{9`F89atr2wOL#zr85pv#X2bs*&|MBzGd8$Qt$&(78>7JBkyO!nwvCG1fe| zLf5I)c_~yXX08|*DZmxdKOdBu$(kwQq;+gv$ugnUpm3Fn|51eM(>7l9I>YoFBOCG8 zr>OQ58c8yW0|Y=3G{_5x<0i@0IK7vcJ`KIj=<}Id5am zD-AQ{{T)iF+J?MH2;6I#*}j$gUy9u_gaB8mfah6F!@@W>1aHJ z1uVvPbT}D}tMT{I5an0RZdp?{>D{bUkbbLkbLfUs_HJ2eu8mv=_|pHdq~^hd$k6pG w@E_Tw5nV)83ahrzx^b<-k`E;c5NqzrqD_(X zF6GpObaJjmU!X70SLq|{t>`JQkW*(U$*`03XNmc^KeM~@%?yW6u3f9$`-Ay^Ry6G| z?NT$LzX2lG0GQ@$OlKt4N5m((_Kn62X1>&z$;r9l8!)ag3&s{VU~IB#Pjl_!*IGNv zn*%_sbJ93<5smR?&4ogGn(EM4=fi}$Z zOg{6x>?|LqNmHg_MB^rf(^`U4mJd0qx1!YJi5JtH%iNpaB0W04zctK9aqX23TwGn% zzNV7T3<_c9q7mqAfRvoM=Ig+lFx@v80ZxWrVWw}fieF`xZ?h`1UuymmTVgeM*Vr;} zuB+<{%Cqx*dtriK9ZN2Ph!vkAC1322d{J( z&uJ7(XCTs%vv8C{!!R8uxvKCHtSarbzVQmo(EK`!WERu2AfY4fR+Np0U|-pTaU7Su z+8`1#FE!LsOy^wz>8!biG7dSM3x`Cxe;xWk83WuX-KsJrj|a*YTxMwkN8={3giu3R zMyLb)u8EuQdOh_r5uHMcd8br(gEVGbNN*s#C&!Nyak93)$_iGj%eAM+YwM%+FViQks z)NdO^pFPEcg?ndt*m`sCMfmYUN9lsd+W-(;6rJf4J%W$^7XpDaiu*e$WpW-zn$uYj z!E2{c$WjnMp^G-SA-?}pM zeFt`Ii92ABxP@>R0n_CYAO~Rp*dP+aW<#_t9%A1pSFaQ!tW_JRejnhUixI@eQtw}N zLvL1Aiw2#SzvjHq&Poqf(|47Vxsn$S(^T@p`?oT((7jZBiiiJa14d}qt%`fVN_>cb z{KQ8HD+n0inLFR*Lp*g%btN?m~g61_AsGg21nob_pzTrDscxO93g5E;Gyi Ta+VoiVNOY`gQi|L8Y_PTXp!{M literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/arcgis.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/arcgis.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccfb711654adbce23397db5898c0029b50856c7e GIT binary patch literal 1328 zcmb7D&2AJq5N^AFrl&JugIIw;i!{tG@bte|0su^$#aMJC^myx-{V5 zdID430l_U|al~|=H&IgM-0-xK&=I+!Qc-4(yB%^1$)Ba>s7KiuxZ&-9B zG9G+J5MzMq;3-q0*^6N1VqmbIz*G-G2#X-N4|4=@F>$y}T<#E$yQIT=3q*Y0;r<7U zba|HtpaZ@Fx3AW{=JF;!@tBMjv8w%237N4}RKvrfSgK#c@bvU_quJev@-$S3Ovc$F zltL9@&UEIXmbr{8QEh(5sUnARYv<);I;#Wm=Q4(nG|nPjpB`F!Ft=f<4G?GOE#P{N zjK;0DTkW*kJxAR8s1fc|clSk6%B-)8>T^-BI8pt%%+r30Jd2{d%nGU7SGytw2jb7m zB-sN9NOvAfRT$GxSYr)b<19P+gmKkqk-)f8ZP#7xYfNaOy%5C!*Nzg&y!N5OWu7VV zwrpO6yFiWIl1qhcE0^!;HAszWXlM(~RY*w% zAS~>o=BsWuNHsLs`2UHEUTFYbm=l=lM-XS0?$sH*0&aiA-1!SbN8IYC7dhjqZ3rt^ z(+$YsB#uON?_1adBuvXJF5ZO(Q|GP9GeFpF=!E6~y0>JKrD_1|*BKiRN3K9xqz5e* zO3{{lOfFRI9i3`zTGEIs;97SZY#_^3$ixa-X2e< zbT)ZDZdR^+werUpPVA6i{A#v8qC2D6NdBO6xUPk!*(IS1lQsy=U$q9pN6vpA_H5rb pyL$tt?oi6}h*IL3S1$E)G@F;gndk;A|8Y~^Z#uZrbZ}75{ul1uY_$LY literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/asana.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/asana.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55ff487e832958ef2c3b4f3f29d491cf279e681c GIT binary patch literal 1746 zcmaJ?&2Aev5GMCWTFH`Q$N7QN77bdUsSfr!ja$S;P*ic0#!Vvyk_-ey0fJdl_R6bW znOr5UDxG8?r@ld7ps&(L;I*f`LQkDp%ZXbQ<$}W@IV5Mk`9``|uUBvVA=z7>kUz<# z=7Ih#bn`g~NhD1OBhry>LPrjBDEW>^Px@y>`pQ|j%$2l98l|^jP1p!BWAk+wit%{6SlW)V zQMeUm0(|5sG96Eq^`3Wn{pqLwZ=Po-Dt$ct?7ywVI1cY_?}QVhv`#yBD3t~nk-6V5oMuueONN1BQ=#TE)oqKa1 z=I$o0qI75e%#+@4K0x8OttUk;bOmm(z8Z-*Y4~~tX90SLZ`xomNs>aVB*NspP^*Jj zFZBjxt4Av5c%axcjgs|@N(MH70LEDg8&$Yd*HK(Su?pgMqOZd6*EAgK_?5_2_)6$- zkR=jf8R+od$=y_+?(Xf#shsYa-TNoId!xOtc6Xp1rC~oD$K$P+lQ@xEU#5}1pYNQ8 zCW~T`Y+=5(qD-sh0x$HJB7(d~6P_n{{Igwq=eSfkL~pKvM+UtkG@#dL2|fqD>4%p% zv;FTw@6v+og>oSs=;juPA{}H1$&t>`nb8G}sdRsJ0DP})>&SnkmB`~PomL(yp_RV< z%Nz=%e5?cl(QsLS?+s0s+WKLy)#Z*%HJ`0iyLVYU@s7sn_w&@{$;O6J{E7dd_9U4ZNyw9zj(Y&flY} zKZdVr&0c zM6>m7>oI`;WNC*>tKHTkNXUMl_xg|@eG^}(wqEqR&4P~_3rBx|0;9L}B`%Rlk_&9G z+8b2gK_g^gku;3DD3ySEmp=!~uH*)lz%T58ib}!CfVm97iTx^2}=fHe} M|274?1E=Qx4K;zoL;wH) literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c482d44186ed0409cc692954821adfc728b5df2 GIT binary patch literal 3490 zcmb7G-E!N;6~-?KQa_e$HU5c{pl;HHi6yGB(ps&XDJ#*jC#mG2sIjv*D2!cFkU)T1 zfOf>8dQo~NGo4AFpws)lN*@8Yz49yMs^3|Fl;lY-3eFxJ?w(!j`Sv^K?ER&s`t5&n z_WYWr{YSg-SkV7~mOh5y8rLI@(c6ghfx(P{$;^SpY+d6fx1!3R%Bs+9UWuGRjn#DR zD~(sV^IGGMs7*~)=lZ_ZsJ+2z+HS+l7j{IDaM6}Bk@-sdyJ08=-w*q-pN*u*D_jk~ zF<{=%^X1kkJM1RMB32XL=tXz^kA5olXzcTV7M?xzQc3>?MQBXt8Z)@gOl~lXo6P1G zt8klDd4)N&O>?y9W20`BG~P_gXu}rpL=Z z{q^};;*%3U>h5^!Fw2JNZ=201>4)+9APi)ZCcP|)qc9fhK{9Bf4U)K7%xTihM22HA z{^I|gnh~P8^>lB)`-oMNp@>7yszaId!bq@dqf|&Q91Mj_6P!z!#QCBh1R_nnVx|-F zQd^RuCq;T#{;dmjps5$ed9~E@c_-GX$4TbB5WNIvUGIl4MeN1?0D3Q!X;$b9J%12J zr^TOIf>H^IuwCfo;1CQE8A!(9sk5Q0%@m2I%B+gkP3G+j9ouPQ|}8`5xB$anc;h@TH%L=1X5Tdr8CrOS31N z_m6)S%ahHm4L;`Mt#tFj@#fZG>(`qbc=~a(+Z=|&wHKo>;%i^Tfqal{oHWxU2>ob{ zj&3ag24#n*^cQ}BW8rDDB**&jG+)3bz2ZFh@5x7URx`%8F7avof>od_U}U3J&=fRZ zLYe3U4L7ES(hwA-W|3wB$1~&1JhLXnxINXOnG=gw{$T^o>bTJn2-4W4{kSDyZcioy zSMA1a?YN*xGK#a@Qm3lP>-etx8Ht+^4Lf&evKq9-0Ep*KZxlt;Ay>)l9WvA9EoCil zQ`a35ACj0`sG}*w`xwYq<^Yu2YN0n>fzZslUe{L*Lw9rozw!J31G#eI96kqgX@Nbo zltE0)f>{8_f@VzhGy8NU(uLvkFiWY$ERRHw#Y z^=I0*luXh8m+EsZHlAyf3ZS)j3n!QGnMa^z<)x5e@3dhn9P;|ntIR7BqomW7#Cl0* z+i(hq7NO^sFZ*d;Ieta&^97NQR8HmIJZcSan8TdZYz)*%< zp!3xIs@-*mz68rMMDKd;blUrS2c7MSujd=5o<`!`X z={V#CTi>M_pO9!k_k+trBY2r zAVsFESiLWOj8e5K*--dyDaPcsDrV>2z8#ONmkO(=$eSMG^95@RXO_?2V$DxYn?peE zQ-p5QI*%d9UAV3PprZ0fKSBXO1=SI$#x0F9U1nwWkv-L>`mru|U|*S-+=RW2yl272 zoS2ls#%W2W3^7>{3Y0-Tb5ZME>QvQU)sB0v-$NX_B2GsV(S%_?bHi-Tt3%XlTok}V zbv?Z2Vu{i*G7n+gbJJuU5mr@s2n@OkcR-fk;3X*?Hmb~-RRy+C6kb(H)u>`oZiZB< zvJl0GPPS1gvgsDBu{m2vt<0{);#G0U@n$!Kw~uU@KBuBqIevl#iacsA;;d&&btQbAAx{fc{Pn}(#QmsFk5PmUMf8xps9O$qd?(n}Q+x~jmcv>NgT z*^p@HyN&C)COzC}! literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread_tenant.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread_tenant.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b646653639fc8283098c15201d884ea105e98a6 GIT binary patch literal 3074 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/beats.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/beats.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e11789854d5f4acae7fb92aa2963e2a2deda2b4e GIT binary patch literal 2391 zcma)8&2Aev5a#|#D_L@ECux3~wu_=oD?CGv7Be)SY_0di^gJ{OJ<% zH<@{C7~h8`?}A_?Ak1K9Vgv@Wn4Oq?E3gd099Bx~z7seG`HrwMb6*hVa%XG>C1&oC zMy1%UKHy<4z5Osc;wii3u`H5am~U1+cn+%>;hwjT1%vz0WCsK%-~91Djca!)!3v3CgS#xU3vhn9C|JNKj=}HV6G2udy1dzaV2HsIz%?0p{k}0$YUs z0$Tz{FRD51=j8Cf(Qdpkyr4FeT+k>>bN)+StiN@55Q;nncf&z^-sO|qeiot&KFUnQ z1uy*f*f-sjXMnS^pS1Qm-$v*-KG-9C+tB1Zh!MDdjpU>U63jPuRY`C;O!9^)O0cd< zI}hoj=8p|a*^(z+Rc1U7<3uVK^afcf`5#2!m);cqK*T3u&i#{6_}wgFTu8qw{5wat zQ*peuvC0ZoY{<2{M{67XjrFxvcn(s(;}7D&^8PSR*z(tDB<|*`$G*&>I82t&^W`WL zd^!OD!gv}((%0y&44$dE1D;cjaLg{FZ|Ed3iYqf@tjy>{7%2E)??BUldK)l6~_k8uVD1+F1e*3iHs8Tl* z{V-RheTc^OHMKYti441(?ys-$G|Ctcme8gn{)|E>Q^_O2L4C5;FvJ|#qp_Wc3-D9b z5E6RGLqzu~%t*{oVpfgf>a4S~YG>Y}Dze&wCI=wEKWyNcfxU_TLbIVc=rQ!AF|L%E z@zOrUH8Qfaw$*bPU^F&GdxT@ipRuVmDkFLHn4oXR{*BaYF>!aP*vspG2D=Rg1p$~~E- zVi4_$3b+I4MEEJHgJ0BPs!%Y#w8<3GHSEh^{v=FdFo;vIFX!R|Jp1B1n}29_zHdEL77z^OB%}D9hOH@8 zb|?;{Djofdy|(f^%k_7T{RBQQ@eE?s3>)5>Q8KD#)vUp&W)#={L;PEVBb^7Z5n~*g z*KxiHgOP!We~JvlxCc>>@%@u^3LKu=dIosby$!uNvPL#@esh4sQgO3AL(;S$cwLe8 zJ#C2xpk~7~*PH}37Rx~rKBH;a=gN$ka(Ev~2xN$hEaSAm)KhMEm?T=O+KmN}qsNsb zoDLgiFn^ZBxF*F%u&?1vG6m@tpP~31#Z44gnP#$Q;cWZV>Qz{m3m}N;0ZY+K9sCSrpE+b{h-IrJ5c}Rh80y#)fbeQ&pq%co=FD;g^jh1sC?VTThyFulZ!F zz13;8MHN0DEN(r83~BEKTb-SjPB!r&%!?biINxqQ+-WzrJ9MwJ)oBW3NN1VA8%%g8 zzCiIMihoJgBdnA9^1}M5Ya~(&FSs=6UCS-k7TucGYLr!(Quu>GskjI`lrzlXg3~7j fj=oI=QmmE#?=0osoB00?MuUWZP{cM|Oit?`V?ub& literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/behance.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/behance.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..acd1f81f6e154eddf74b81a48617c7ece1ea438b GIT binary patch literal 1683 zcmah}QE%f!5Z<-diQ^_|?>NvE0SeLyVTr`$uAuAbgvhl4Dj=znoP<_LE7!Ye+&Xr; z>$Eg-pIYhO_yve}{t7?AuRQS=?ul6^ZBrq{T01*Cp7GB2&3K>HYR>zAa=KMQ=r6P~ z238T(@Es~jUhP~=guo%iN%=#SZ;>~p@%vEArQp?#NZebHYnjHwK$e;&VRVQQOZ?ws$5wUv6)~ zIEuZYHw~xF!&w;d<`;1wpQl@=-byPpgG7p@fff3{T3~vSuN{e$>6BT>A8Je*9K!R> zdxdGOcm;(AI6=b~v!LQ7Y%;8=xHK|!tZ4yCK$?Xlo9N2?*}OC_$)yEW3#?{FGK-gZ z`8VDC_WaYnNM|x`=xiEG#u_7;Od2;`;Riu7i&I&K!=R}`HI4iWrVEriqfn}Jp;sb* zsY;z=j$CV@*rQn#X?M8}cDbQnT4DmFaWSpmV$>eWElEc^=^7UYN=?sQ;9Uh}O@C+O zHhh`-yI@uN2xQop;17%vHi~Ed(SLBe4D1G$j)Gys)Vj`AZG*_nV|0Zs%USstXQpQR z3S*QSshOcGu|^4FVAx z*YP7fe|#VM%Be#pZEE2_y@?l3k<_$(5jR~b8<`9%?7S7r=uI#7tN8GX=x zJ^ZfM->2>2ey_`3_kW-p?cLqZV8DjG?>h_2JNGO(T`elnb&1rpQLDJXr2g587c2c%+#N;J*)JEwe5LPcr3JxN^B>H3 BwnqQ} literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/belgiumeid.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/belgiumeid.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..180cc5fafd9508f6844abc4bee63711728cde502 GIT binary patch literal 695 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bungie.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bungie.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc613ace82ee018c67c653914a39a741a136cdd8 GIT binary patch literal 3440 zcmZ`*PjlPG6~})`7I zcWFf^bW*O#wHHtB{T@B_BjDOozd}!aZ$VL{M*(KvF7_Yx{eJJgeP1*h^{sys?_Ui? z`HwR97|{L(3f}=i6i*>4(PDM1d76rCJ=VvDXQ;}aLJVU5s1TECm%3*X>p*GN@_Y3s zlVlW8XTLoW$Dcb#L3m0NQvWY(r!>{F{v_bE8iOyJQz4>c#6ujudMs#s1BE9bsN$(` z4vnauPBhOT_%Fk=pwx)z*~Ide$Oq&;c|blS&dBuYq(N4GRJ>c{Hd%#wnXHj@s2k)C z*?@Y5{D$0x`Zl>o-h+CTY?3Xg*U0;z!AIqCr9gE?&pMLol#MYRA5q4Sqs)&;{>iZr z8UM<4&(6-at0~(F6)sKOGyuh26e){x>gGGYGB=9@F-+Om4YF*yN~8dmTNrY9@9*LdTE>FWKh+7C( zD@+-kcKEVAH6HFx5zfxbC7kIWi*dY(YG@!7Ex$X@+4gM8g6byBl&Y^oLntRIQ9)`% zBl=qdfG_|ELb&sNtNp6mdeQlt6C}jhXVEB1@{fR{I0^#+sJK70fb|wq*P5`HCSgh_ z$yev6obWV(9hYXC<;%NiB4{F7gY%4fR<&fnS5XeHir$p+7Sr{jJVd6{-60vb% z71oJasD(kSpR|iwQ9H3sb5+A)sl~4PmXr0Xtrvr z%kb8rUV)N33q0Pic;*G@0W~Q5P&ferG*(nrlxjeU4t-dROB^u^{ZfS*$O2iD(kALp z=7j;@3Q{kPVF`%UHkF}TsK1~Y7?E0N|ImKM0Au8z@22A{rh;-OPQja;jHMXJK6Sol8`oy(m>KHGz~#;JfAt_;mJ3 zq)7kO3iGtao`LV0MBh}9W@3D;z#14S;QD{7O^rZ5F$%dOkZ$FFeikJWKX$}1b?{_Y zq&q3lTlLP}X3kl_(GXZf-%wHkBfZM*PkAm)V01#8nj9=^)4QQ*mP;5Wd}%H3fuig# z2+zhVP0-P~C&M5T1<{)}ttwRF-t6}%kPm4xH?hIc@%{>hqiZVKI=CD<-738K#sd0N zvS+yCA7;2ef{#KcYK7kgBr$+9IuOQ0{ZhyHNDLS?p;(&=Mo4g}+FK209(Ly z@iNDMN^)n&VhEcd`#bn3zX>dp9_ZJN8-8DT(8W0L82Jj(m8pQyd1g~y}z(QMcfdbmMupdQp zy|jI)k?)sv-yf%B5>sd|`~K@m5TmZ}Wnk@D-KR1KS;NExk`Avu=O$zl zp0$4GS-*4ed>Rd*iy=$iO26~8+wbfS{DVPz(DCY>?+5+1|Fk`5v)gzqyn3Zgu|Hz# zV-#p445Q#2hSHAEAsLzhYRnON8#0@@S7L}bTDoo6w!Lhx*mc{MYkEzqtoc4kL*Mtd zsvK0NoZD9^=eA7a8AefXgOeZ`rQ2be3}v1s_qgdmrTuk<<6khH<1+T*ZLR+w?lXWh literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/changetip.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/changetip.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5c470ee28d07c7c105cfab9afbe1ce1b97fa506f GIT binary patch literal 1240 zcmaJ>&2AGh5Vm(W=_YMy`H>J6hZP5;Ahj+uAc_zoga|62sgm^Ki)GeMy6yhe-XvmG zaiLeLf_G)<$(SiM0)@N z2^^vbQ;g9oBrIW{BVkMY$)=WY`lwO)fx)eTi~0+$Ww$lXjvn}6Su6{*=ODTXAQ8m^ z!BI?YVNplev?3f@6&1Q9s?-%r!aYZ{CTgM%dtEF;h?QckeZ=FD90chsKz^_=c%{YT z5Ld=o;PWg3W-fSIZS*o89GM!Gh{PI&-kZ$=&M0gsMPPDeL=Xd|@o)8l# zQ9&+Ae&>IFSpo@=!a3;n2N&4$4Gm_Qy#mq((Hj6~Sm0ZL$0fU;fMIx?FD1A6y`Id* zD)z=&Do@Ca2chIp;j%Gr}Q-N#M)e`_*+Aoxg@@@_qTVhNkJ}8bc(5tP(!Ru~skG2MT-F*5` zeloB07w`xASNaq5+9&=(pL)((_9Q9t?9tKL>-U|@`rh<(<@(=@zOxnOALZ1egZ>Cw z`Yi~iP=yhuhJ+HPF+Ehf8r6vMv%(B!zEzmXwV_6JrtT?qE5BCRIPkKQSRB=M8olgb zwYKm1M?7Mc53rcB{egh2lcMt2OZjMoj~(Mj1LYC4Gz7sF*i)e@BUEE5?4ePE>C|Kf zwU|k5X3;5TcL=SpDSCljXE!=3t+Hu0^H!nL>>`_m_YAwq=GnqqLNBsKb_p!A>@vFo z?>TmrU4!>Ln}f4|Qp}8xJ~~%^XZ$^aQu6>0kFvn`vLKGez^Lt)i1QlVmAYvVbx=j^O323iaK`OaRe zm0#+mT%^m1h}%KPm%IF-;2tX~p6|!KD02hQ`5#FjcC@EqEJj2d-SrSAleV z&&h3um|rEya{j$Slb(k7HsKBg^G^ zeGF@S@NGbo%+)}_+{u75rU7Hhp*B?hR1Wo_G9*`&fQ)#X#!QJtkjubIO_&Dh5%{&- z0wNy*lXaso_aS5MuN71K_t$vj$G}7t2GaQs&)g&a+VvvlrrZ}C$Q=!+6EOorB$XpE z51&O98E_9EK!D7fU>U(vZIa(O5AW2-?MZlGm>?q(Brd=R1_*#uAs`T@3PC}b{$2xs z#x|YD9VY;RaK;N=N(BO-(h(lSX!bhc0@E^fv4vNmf*qR7rmOW1!E?nP9hIGCD|Ne+ddA?-Rfd zh==5*Hc$uJK$jL35@uCukS3+356QrgHUn%=CcRIXIWYT1-|VY>t* zJrUI~!E20XUJ$0Wwurm6@u_QNk{5d9bveD@m*ohOo>pWnal0@};He8;s@Fh;kg{=6 zoKzSb?Sl7#*r)?bwlz(tKcD?rlTTT=$`1Lg?V&>Z}AnZc%GH15hJks571j?Cj{!% zbHFFVCWsNO22CQydfSJ%Wa@jwrfrKUu+&xg5=gS#xUQEOB<}!GM?1RV{HYet+!bXm zJ%)iP)&u4LdGxXIusddufd3qmlgLw%?fODtyE3X=x2U*oH)g$%gI;yrS3OV0C45<~ z%VOVk#VuS1G19rli`FkYyIZu;+S=L18;H&}Ha41jdv0szX>(**IAi&>2{Sh7%x-gY zYqz=4a`#$|R+CO|Hh*focm|&vJI|Zc+}d=XHh-s;=5MXthP&BlHKb$372Nw%6knqF z2E`HzytPgVthWN4f*9pZY7_m;&m^+mPcSbnfEZMG(}G*S|o=%Fa_8 X$o*thx4$0|_9G6HkX$$5i zU5A%$l&x;$3lW9!M7^@Os@u?3Mq62JAHIggSs3_Pn8fy(A52(G+jZrdRI>0HLoF&Fx7{UTE2XsF|fRa`?1D54H zwsXO_oyXx-&g_ud5M~xqM(wj}8&+mvz)0m3v_twUa`e&=EE<-A=E|wR4tv@>pI-AIXzT z2#{7hSy4bSpncCRP%hvFgh%ou3NgvbIl4r1e2FQ(#cI}WG1cE{Kj}BxjecV&n{kWN zZKat@FR;wDxjr{&9`j^+kC}cDBzc^X+Cj)gcI;0Xv5x$O=4fSo zEX`gF_Ob>{T&TqCP2ny`^(Z+H<3sA0r6o_V+)O6dN^6`)ku>lIoQXF(k$xZ?5AC+_}#V1FJPsM`Z z)X)3{+JKBCb@ACY6iHmxfAFK6^*B$sQ&D)9<}z7*elE(&ruM-23a=UwH#A_F{CJrAlFD2Ai} zd9QOi`nEqfB%RS=|JXYn9FdJqx7!;Iy;1+W-ooL_%k1PIOR=gR3r+Hi%jzY6I70HuG7Ws! mD^k-e!&+B9#}51{R2Ahijg{@+0+!ZS3lzSVza>=_8v0*fA?N!5 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/clef.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/clef.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2fa51c6b1e8f9aa7c328d634166fe9f0d18d38b3 GIT binary patch literal 2026 zcmZ`)&2HO95ayB;MbVP;+oVm}f+>m?0%|KiZV?wj5mi;wAg&cy%ArJo1;t(2Yf~h> zOFD=^ofIIa^a=U`eU-jJ*<(+6g`7G=+KS?$BzAUoXE;0a&Ft`AtL5DIlX-txn)Z*j z@|eIsgrPnM!Zc4~gy|uf>Yh%tM;bGj`BGyh*B6Fou-Z^-TeH8NUdSizpqr%!uF8@m zmg%PBj1GiygE&g17-y-7BiCn4ohi7JSh}myO-Nrc!szUB)LO;>>+6z^C+pJ12gaapmrd6CSza z7uC^zl7u4gOXnot7b=z2B9U>xmD+UP5Wt~L3g>%Y@iM}@0qiUv1!di%Je;z0P}270s+ z#FC$G(2_sRgsQMlUdTGQe-)9lfoSA1S%-Ia4RcxLb#o=O;H;K#)?xHusEa^y{SfZq zf&kSQ1Q0cL3sXdsxX=ZIaE*PQwhh?;)xwrsC2^#9QD;2$MX2OD>{kS{Sge6I%@miA zg&VM;@GR|pw^mewQVbdx1CG+I1ehEh0=MRRt{p-#RPL2C6aX_{>%SUvV{XoC_omfXp$Lme-|LT8PSw0S*pN(~2W)$@98I)(LD;R?mwP(B6`V z5^Meq3{74_Q8PJ+BTl1iI_9!%6*eAwkKH_mdm9z`7sC}5tm`|)|VKfl7fnc zUX!-%GEMRdsFY61Pmz0P0&UArkeo}~#ihN|xKT;riJI;Zli1{{(S-T3?ie~bduKOS zitW6GWoq%3;~2nD=shH~?EEo5-Ta^aQI=v8pof-8(-^u@o5&`ZX%o-J zLl0FPwaqH>Td*%3Bx^{{McF6|lliH?1yY4hX~ZJ4t1DqFRod%?P0RFBS~!$WW0r*+ z_$H;tnO|lHR&`2Q8~|ftCcI1dHyP|b_PV3TgI)S;Z^ygX?e+S@Asr2#^sAjK@9q5D zhcABcE)08vr+qr?Kke>yM}xice#;Lq3>V2~K#JCB?+JJqQu;1FfTV>=cn8bAh5#w- y4mMTP2*n@DG`4F literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coding.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coding.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8f7de39afd4714879199469610c4c15e895501b GIT binary patch literal 1827 zcmZux-EP}96c!~~Rvg=LldS2wA9yRU!d+u!YgRPHFw{%kE?5=^wsS)a5EM~Pl*p1p z%GstmSI+I;U@x#Y8TJUe-Bn+qS38Gt+BgUW9v<=>{(K)l@kympy8ka{{}eRsKkZtf z;J<(>H^6X>X`FC9BtxC)MB@ghp)sV46799d3*5ZaxarfW!3wQ4)A=62#FJAZB_BXS#NrBqRCUw)1{7@)O$~ z1$O%ImOzY)IcQ=Lq<-@_8HQ=&mPA}ZCEKp&`BFMb3^ZnRztii#BabsWmcDr9A$3@= z#kvhunqVf{i3Z5X9unQ!-*FDQyWmpkhl9U0u?Q*e-q<4%oV$r{pS!{y#3A>Evxvt;AUma!MO;W|QF z_hRAC1{~$jL?Ev?8vT(K0igp5_y|v|gV9L3di@rfvjk5CAr4B_5SqgH6dc4jAt!{B zDSW>g7y5;9L9a$st53=35jQ6K#F$V{|0)0{h4f+9PsSpuBZqbFCvFhR`as0P`s*M$ z9v{|$^LQL38I9bbpB4NeWMnWivv3LyiX}KeW(~$+sB%h!K*+>V{zB-^+=kAoM}Feq zEESWP0sTfnl+<+L%TXLba2dK{1yo zI5R-~H=v69kn=TEX@Sw;N=;JJ)3rI6A6UiTyOvpwl9uBD;m$DT^Ns*SBVpP zMaO~n*Kq`jMl66~cUlMipE}(g*6Qzc_V9gRwbu4_yVrC2ouAwJ$jaQvFKw9dk}dVN zJNs>?*WPb+Tm4R#RbIAVwGMXsjta19xBYUb+unwLztwLmM#LI!{5cxLcn!DGI?7z& z4~|EZm|+nL+WzpF(6$d)2z>J~0Ft$Wln+;7d40Ro64c5ki?d*aWATS~wE0HCK zlrvP2uhQGS!CqjmvPaposd(;r)3@n3iFZU2ge2M_uw$1qLenu*=DT6yvDk}9%0%jw zo`e^aN9>N%N#y>EAiNfm?n^jzacBTDtEPkY0=jqwf@v556rCanS;wXVaiL?2TDU?h z*rqmisDrDtie2i`jdvQ}pfy^Du|^wo6UI8-0;t=vu|&E!6FbYW-lK1%ITMUy`D6~JzurICqZ!Q(#Qw9Z{e#KD5Bq!2E@E%!O~dKV z`7Dg+&i8S^pQU@(-dgf^f`qej!Y}j>lRkYbH!fJ}D~A1$exg7rbQ;PYbxKMWDcM~! z3knmkQXneO3o?#C$&n&suIKtCP(qDceQp3jCd`?|oOO%lR<2#5+`L7<8S~2Ao;$gf zS8_Xda)Va>RCnLbT78zzc-&NhHB0uIBc4o}%dnax2$ETxavRQ-7MlRUQj=y(CQLeN zpVC?uSD>Ubnnh8ulvQoqov5h+!^305s#8UPbFR#=slXjQojt6XJURtu_Bj*BgP zTdk-g_&p^)RpK)xRte^H_?25LpCuY)B$TnymUUHw z)Z-#)n8ca=k`WHcfofroq0GRgZ6WL^n3p1nTV_ETu0fiZ5PtFIq)GUNkd>?7)p&;@ zejlR$O2JT=G`)hd2X|OlaevZkNXIXd==;+3{YgS+5d*#E``0s4q}un3?BLBor+3=* z2i?>h+ItdwASC@z46N@m9Oj=?(_|u=h*1oauLe zg`c&Vd;ha}(*+-gxY6$(9{0N)@I7n~ySxEN_yZ*rx;4bb^-wp1tBT@4FS6$#6TXF1 tnyEX6Q!i{zTBfvpA8NVpW2>llTs_2M@nfE)^W%b+pA?}5t0t-${{heR$}0c> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coursera.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coursera.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fe00d8f3137e4659cbd0c57265735ef4d3563c5 GIT binary patch literal 1997 zcma)7TTdHD6y8}c_F`S?^k4(W+z3W#@9{`@VC|_-VCTzW=A7e@q$1U&gg! z!T1GK{V6oUpoSpA?2*1nO=5g&h=Q=*8^V(2Re=`7RL7_l;~&af!BAo_<0uOCrt3!1J6`d!BP)SAa(Nim!yb3pmV?Omc;p9z zoIbU4l8HS8c?P^MK0VJ3e5#M0S_M;SLUVp?%{>GZTx)i5(K;`oEFvMMFCdLE7CE} z7Jr0j05J-Ci7k7w4{UW}`U3n@IFeD#N-QmVhl#DE4ub)(ErI!@l&{ zKO1=Jd9-@w>cI1PuZ}|1y+Fxqf~Dgu1ht#{U?uz|dB6ZG%YDHPRnTWS;3d?g42?l5 zq)cowYsODyb?S99ew16yNKEea5G@w$@k|;W z6&PT20CqqQxJmph!_8CV7|l+oWXP2ijz4fF?K)gLLx>PlO@pnZxU-%L^-`mTHzVMh1haq_+WQJ(3Q^R;izzUoWJiqf&<7~D0J+Q{Za z2ZLk|M;Nbcv|6)9VpPE2QfroFF;_=ADW1N?e$v&`7&S});>Sp^2#sM{#3b=Y6XJ|F zj-|OJG=+IBG$FaG4L}ztKTcn=xcpLrdzJI+>LqY0;`_{_JQDB0)f4d4U6^|Ub>uM; zKb!zHA;4a3A+Z_5SYj+GGu98pu!rLcV`oF&%T`K^!CwhXTR5Q>;0C(TIOu-YYVXiS zcc-<-4%)kPzOl8{>~vVS^}0ElS-LUveG^V>)7f@&d#Byp>atF^(QVSX&Q@!`$vVyb zM!V5%wP|&``KocS3+uR$rk|Qew)jY>kFi0b*T|;{iMgmSi)^U+6C4n0;#V|n+qU3W kN_Xx-2w;TMsr2Pn=%RGtHrt8)7-Kl2h_3`8UO$Q literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dailymotion.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dailymotion.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..34ba6333f4e6f158ef50346060ea518eabefbdea GIT binary patch literal 1312 zcmah|&2HO95ayDUOw&>jx6L8Rg$)X{LMzd2+#n8uAgV2-MjES;WT4&4V#8Tmj46`d zrJR`Xp+Iu&gY;GU2)y>xSI8+dr0c3bXqVX8;p}i`zHf$iyIud*ufQ)H%lge)SuX02 zahp$2fW<9fU?*&0bDLRDEO5YmYk{lmHx758HMM%}^0mJwWBvjEBo&{#Eco zMkgwTF9OIS6UgF$AK;o7MQ$F1VSZlBv$SurD3(cI;uiz0BouQ+?t?fBRT?I;P^Jhk zBbg8=%c9OQm=}x0|D9oxJy!YONTZs(5n8U6joM?}<`D{IafX0wU|6`#UFaS&-U64m zp~csr%{|=fu*QAxVEwJdH^7GtJUh@qnAfV!8fBBeNmhQgVrqZ~ghi!A9LbuE%i=NE zpT$>p43)PuN~dz6%Fq8GMdtBf#cdE}i!h^jA&E@Kr7A!Df9E1Y;w$(2@pKju1GMPG zR$IHc%{3IjF4#*8>^CfCh|W3CEwpmqL|Uno>Ox+fV^#F*$~7uEsywYso~1_pVrd_J z-~ANkIzGc5g=bQSM_B?&oA5}7cTc`d_38G`7L-u#nC<%~+dGS$d)r&Mj?-`!=5gMC zv5XVwf1O79ezA2Lu1rEd%Cx#Ph+6;G3CPc@>sX&49tEfZ`86L9yc#nr@2vEuY1iQI z?pO5C)YBf*_Y~y9LUM5OFOVK^ezuV!x4bz~#ZspMWeh+TGRSpy7K4KFh>VH?ObZPFg?aSs_Ui6(w?h5Os=~qDp%@bQ?*ZC(OEOzhz+`2Z%|_mH}KU^ z?UvnUju59ynOxqi34uQ(nq4w*fAqs_ zG8B8m*^syQ_r%lDk9>1@IQw=y+2_OA{`f!~PQK%tlhO0T(R3zebbYwHJDN^c zEFb>G@@#}b_H>8p+ay6Fwjv0%rU@p3%txd!7oT=5Npo$_sXY#mMq-7x>Yqo`Jk%E} V;p*r$litGy`Xi#ICemdc=RJLOZYcl& literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/deezer.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/deezer.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0bd5d9fa4d23eed97b5eb0e401afc3d13464bd1c GIT binary patch literal 2020 zcma)7UvJws5Ems`ksaqx(=6M%b#s9YgMr3M*KBBtVyKsRThPu9Z1-VA0YVX(M2RdZ zq@1O$^OU@9pP*l0Uu7RbuY2lO=+o{fCvbuQ8x0=G;~mBO-S6nfb90q@e{=fQF^qqV zsb<6cIdruKf*aJ}gqsl=nA9W&x40cy1Do2!_}Snk?pzq$5%#%7OMIqh)ZNi(WlM;W zkhNVuI1({`SmQ~cYJT>#QiFDoWvP1Vd0d=`C`pA>4X*7QK{D|CH1raGm>v92Tks!* zN`*w8g{JbOuSBN_$9@~2(Pgt>@*KKqgAfKK5U$AywYW)b zjv?99;Uzl59qRHKTIMdT@Fia1vloQU@+zOZFld#}^92~^_#(dp<2)||5O?!xfkW}# z(N_~xie(LB6mb>?eg>Dn#0tKqvB>S%ABa)?609JJV-aN2NNJQjJf@Kzq@zdwZ5jkZ zDV8OW6m-e$oldX+fjs=F9heY5Hq1TtP3WoyVr(25=f-RE%sjKk=GcNYIRuE#ffMg6 z2-9lkc93vUx2WT%DGNDulnA8AWEuQHy|gbfb|l^~Kjy61%5Cmver~}|-Og<#qP@JN zGCvcv97G}Dg6GUlriIU6hMb23KfLu)8J>Wtcj8NLFNru%$lH_NUVJdP9{ zj9G$n5Ndp*$0XDc1AzAE^OuRw)dW8%wgNl|<0K5k=svy{@Cd_mJN>3$m>wWmo#q6* zs7^ujI-xFJ>rf@}gl+OmxPV+nfn{(r`mzQNi+vS#3uc)P8LeCk^~wktzL2|2L(N!T zVQi4_VI*K)W$bw9>-@lKhB5e8VN7R~I@??9Me8?ur}?V?OQ*X{oBi!ho4xA3q>IhX z%~r3+`kfc8LUZ?;=GPW@Y|(|@W@o3xdaa#ix7qJ>>3p}fwcTxP_F1pr?6)LlOn!{w z3lx~esbKMCl2}#}3jryjJ^|qp`})tUxUTJ1wN<G3natwvXoSvjV0c@aqamZo^Ylwf+U3$|7t4 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/digitalocean.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/digitalocean.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1afebfed79ef059ce270229c6df53b565def266 GIT binary patch literal 1761 zcma)-&2HO95P-S-ktj;CohEhBKY$2O6gtSX9jCB^B8Y4YZP3&*B&7(50tC%j*|aH= z-X+zTkPn4(?V zaIL&|7@mfiAN2z2$8yKwwPj;lp10)-+sAlloP~j(g-L9m_`wB@VZ)lOPXhkLKFhL{ zKXDwO6B;EcWqbo>8zf?bjUX92kOcV{{YWoPBD^}%1qQXWw%EFL7DP!7@A{c+&5ebJ zgoOny+W*%iOEC0OKkf~NZ;4%6^mQ6C%DpfSG(;~nJVh$Z8@IlCYp`(s)vtcrV=1|1{l zmQkAGi^LoDftWW(c@)Wo)+l5=^Q2yj{JGjv-Ic}HiFcgU(5_`L3kTK^;(-t!3b8JP zIKPDwoiSAUnsVc^+FsL-$_i4KJOiQFZ(atwa&po zcQEjVz3;lSnfrHUzVD*Np}RUb=pA>xLHD@R?+kl=*Xq6+_B-BTXV_t@A}En7%ogIG ztdg2YEhcU%@khf&2AYOW7LQudOx>)@6-O=ne~`B=6SXPl-si3~zn*>XQ}JgctG%K9 E4UXL9^8f$< literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/disqus.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/disqus.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..576df0f3d582f50177fc07d38c6819a9a19b36a3 GIT binary patch literal 2127 zcmZ`*TW{Mo6c#D@66Ml#y{2nd_p)}v8Y^wHp(%!8UgE9Ay11}iU?2t%sz}F5WXU1r zuJxR!6zJ3X3yQt(uk0uAbx->XecCya>n6o2fk&SYk4T=6ACG)@anZf-w_vv`mi3Qy zCndT29SlscAI9w+Wrr1wX<)@qEh{+-&> zAl#FQxK$HrsB1xX->t#b&$6Mu@B70SSwBrybs9!Nyc)n8btMCl^(BVaqtus)9|xJ# znLiy#`y!v9-p>ZH`!9i|dBnKi2U-?WhTxf9^HZdI0HnnTq(KFNhvaY~$iX)#xv5oh5!FK)cDdPJNTD=+8Rg18_) z1@Gc3%dyxJkZ{v16dV-mf${SNkH37E^)!Jn9r(d8@>9&)zf)iH5u_>_OMeXM6%>6u zI1HuMJWGLJ-#Cw3o$e2B$t_b3o&{N;%zT(8i43zSO|+?uv{XD2rX&YJ6dN~x@P45C zW-iAM5LV>#VWgxs)i@0UeAPGunFXc_&LIpWjgtfeX{tCLqR^f)j86(}6PjKCGXb7g zEJ0T+;KbfmC%|WDB>sY5#?dnFnTmk1qgdyC>J$Xz=I!tPP(_f1^q&RF@1?PjO8Y(K z-`%^NsQvYgH8B?B4ZVJEZ+&C1@%8!|Tsw*1^@q`L_30>z#p+i{sP1KJ`~DGet%j+R zQwPuShoTz3F!MVy<5RjWVltSOMaJikZfgBVP?!fm3{p^rmQ!#az=Z6=nTf1JdO#-> zFctP8&AlK#KiA|`W+Roqc#cBK-s4OE zPs;R-?XZO5aN)WT<7D~{n9Q2kJu0{uU1vfjbW)i(J!ew-lTOO7 z2~a`{4S6Nwh$zg|3JhaPdNhJUQbad&V9`qM+#v#yNr7(Wboj z8$Q`03Y@>wH`J0j&cir>qs0qKkPUSSj0a8Q23zDbStjFi$C7_P`AiTPi&?jz=^B`c zwQC((ztRKepad9lhfGU?z5!wUK_N$HeDP5l2tD0($fVeE$l_TP%JJ$)%EDc!9+)ar z$KpuUcXgV0)GUBXW@Uo8P;a=Fp@QuvraTN(Fwh=lpWt5a$rn6G#I&DP(U*@>E`)Lo z5>S|hx&p@75YNiRU>|B{E;zC~7U0Oz7c z131?<8o;PUg%ut!R-D`bUPO!r@DgHF9bh~X#kruxxxmBWk!|mUsdA3hjC131K1jtV zM!dlJ{wRoxp8QRAvhk$*pw)iJ8r_GjZT_VFh%GlZH=CUf@3www7M@ebJU=(#jV-p+ zZf-qnH#fVy(`|H{>_lg?^|;A9&Bu*)quXjTxA{xA-QZh|ZbO~JrCvaT!c#ZEn8m3s zLm9{Kw23v-BkC#!{ia~*Tl}M-(Kuwm{ttXq9H0OI literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/docker.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/docker.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db99dbf07da2b6742d5ab779a0abab1b7421e245 GIT binary patch literal 1719 zcmah}-EP}96xN?@*|mdYY11Wb0by8&8f~m(&4#8JhB}Gc1nuI%R)JL*ASgO?tVEU+ zQpym`xk_*M277_M${vBYyV@)4YKN4QCBcAF;Ni!kL-PE5u~ zfsdLPo>`Ct%<{9B4GY^i%To5zw$rn0oW!dv2|_Iaq5+AZL5$2sRu}N%4enwbk z&vs%q{=!2#<7^T&{#Mbww#pk@J|pFk0cx&V9nmZ7>>UcCxGILCfr>6`t^vAR0mH3g zuR+B%q3YJ*9xTCq_#BpD1s=e|L(^@*JS==v+&l0YEaKRNyBO{x-YlanPsLrNTfCMt zLcI`*XO}^a^MlUJ=OCHbej3`R>z#Ewu|>cM4GUryPd;s(C74N@8$b4k&THPHWJC!Y zds6N23Yqv}#OtFxioDpL5N--sq9_UcEKFjq=QQFqbR+fSGd_1n{Zl{lsV5f)8YW4! z3icZIZR~6d#Z);`rs|Oj>V=xFNTdk_`i1(ley*MC=f5i2tPHgt z`7>DBY3|%1$vjyKv7T`q=j{qN7>P#Q#KfgZjGK#{=)4dMDE?6BJske|)lO-Mi6Qo> zPwi0>0inzuQTzGvdQ49?HrJqlVv}vWINsQtY<|13hV3x69Xk!v)q^~YVD+0gpf9qu z6Z;yuSA&F-nZlF!HmTDyK7Zx;EJqK}B_`rlG()v?Q`O{GJi5-m&h1qtbn$xV9~I!faO}ky9|}7x}=0ERDaWu;I*RjhWZC(xl)>p!iZR5JWA+9a_M&I zI`l4XE(=t&KgDKI8D>9bHgAb{LRV|)zIu&uhLi0_CcF>+uL z&vBc`6v9|Ze{T5nkn!sAFJdf7A}T4%NtY1ux+DW2+n?Z+34SSBQ#I78S}fhjJxO8D;|5$_)rkBh($ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/douban.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/douban.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16befb0d0bab54b67b3bfa8f290eb142975c7eb7 GIT binary patch literal 2564 zcma)7-E!MR6qZ)9ttfVy^p}(tL|d3KG?ANz(sZEIO$>B^1RQsU(J-S?wCh-nEIGS! zGF2UJ(p%nu7vNQRguUg8SKx|s){diQN++w?qodukKj%B=e7aw&+1LM~-WQgp{jC+6 z3HlT0;vopCc^cIziS<75NZ;@bUHev}1~p%6)MUo7;hFGVqGk9lGXmcwTG`g>R-W4% z>2N1VoUP_C>)msLgpSn}XD0~vSVF~Jho+%$g6xs)Kqaa=i zVD&O*0nK_0&6lIpWr-UH855a%vXx8KVavU&AKU-vaP>No_R~Nxb^J;}zZb&nv=Pvr zKo^feFwN7!WkPk&pu{uj+^!A|nO>Qeyb3LQ7Ohb0wdT#x8EV7Zrn9sP?^#*{r!UCa zvCrzv{B{ul2mN447KK3;rO62fu(P_jBoWQ;A_4%w;o;%(#Gf$jyMpn9C}b|~phU`} zoaK#kSaY^N1ep9R1;BIn94mq_WI~J=ga%-O4c`dVI&@J5F#?DdHA)sWaNF3-%XH__ z5Ii7vvOi%!nu5h$S)nWoqF6|aGciaLaHI^2mH79SJK)g)MA1D6xZ6!*%D8a5+`Yed zFX8*EYYm#yd`+xA+*@7iuRU08K-*2+jys43OFP3Trc0kEA%B=P_T56REQKj&lL24p zXLDrmQqJzO%pbyi{D{t@Q|O(h=hq7HTQ1myY937Rjc6z&E`l=BPmsW>_=5l*rToJ- z%Z5C0@C0W9g45+`-x=>;#7knPT4V#L|1U`de3LLlLQ_fE>H`)FY zj0&t>jo5mAjWB#&fjm{|^y8sctB&!AZR?bCad5tO}TTlcB8b{>FI5O23F#a(ak&!VnDS=WhovW1jb=a39Cvpcwy)?d&)g8tvgQVS!I54nE zo|!n9sC*8Mi8nW`V>A%bfN$92wC-==&bLwAL2;TZDorqnK7%fBJB^t7WvG6lp8RGp zhNooqHWEOf6bWDvjY$9?Fvc_x>C{w2P?HP1(B)G8}nEH1B@P*9RL6T literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dribbble.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dribbble.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7cf750cf8679025e15cd5e900b8701e85c49755 GIT binary patch literal 2226 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/echosign.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/echosign.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..249b1f1d479146208a568cb6d5d1d4c1f0ff5417 GIT binary patch literal 1377 zcmah}&2rO75Y|YFtvF5ygslQD>plpt!XxOqCthJsbWaj5PVqCYo*wmd_jG^V?ak%o#+{EK_8rUm*ZN|)cz%kc zf5r_gV5v1`f-&n)3v6)STi{6c&Ji}a1FN<0iJ@AHRgFI)Eqk45G5R$|$!ytpdWxiX zag&x{V4*1!4%os4M=T)Kkm}$H4;I8C)I zKOW^eIn2)A6rQ~JeqOmRdxN07KPrle{*7}j{D=IluQjfm>`ngXfPtj{k0MQ~&SneQcD#97VL|ONP)v_yB%XC#@%dR%OraGl3Vl(q?#cAP7DAqdU+Ac_$L(0qpE+vZU@v}I7Ic4jHrGsCOnGtNatD0 zHF=3Px# literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/edmodo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/edmodo.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f6b5d8ab60d651dd54b1fa3e2dd5513424ad318 GIT binary patch literal 1363 zcmaJ>UvK0z5Rda`H=AoqffGWY6A?n7NbI_B1zk@kbfr{q;^?C8-iw74`C@N3Z4#%p zx9Gz5srSk^;0y3o_y~FBiLbyDV`p#Ko&=6&Y|q%^nfcA+{`PkC-XDxG-*NtQHU{jS z$3W^MV9X&7L(DBu=@J(?-#W}={!54X+`aLL$AXEI^qQ;an3aNwczn3f^Uvc+epU_h zDt=D03tq8LVAN%QfmaA=*6R^DZz#KHuG4HGhNc=O@^ zp3?#n)%rhs7Bxrrbi&+~=u4++% zc3{k`ANt*q+aUP+7hKEy3TgpgQHf`wU|cFZllcC{XO+C%KiFdpYYx=@gNyxx^5Dz; zJs4*dp5i*MhtC&z!G>Q{nS7x4F7Zai4KpG6N|4(1>y=i&Xm{71ud?R;0GkX2r8d}q2hQ>hZEno&((`ZYSTSx&gzj^?DU zu)r~J;vQ_3sR0u^MEuUJh}XwXlBf++3w4^dQJTX4VNq~cZ>8zwg4+6kT|Z5s+hO6G zf+24nKAV0wK7B$Cr%%Qw>9f!R%e#s$g+HR@z@ge$xUBs}SSbEzvjH>ls;DSs-LJlO_O&NCI<@?<3o@iGu)QU~0e`vF*LOjVx~ zQf*CI7K6G>GOh-EW746PT$oha?Zd1jvLK2ZSsSv?R1;|v4^xvX{}(VuecbHt3?V@R zk-uh_FQM1Z0VJXrif{x5vY2|pp&fyjn0AE=)V=1grEHTlo%WK&tbfq-7%O-#8EbsT z@=|P+^^X`^Z@Aj8cy^@S_=>%YzS5I<^zlK8&aAb2yE!!bSXHM*Zl^VjeP> zh2N1r1XeBl-jPaY_fROg=bGG=N=RLkJ55ekuL`{$Pex)Vc9VL1wi-|J$(!*AY*~;6 zsj_NtyUCOoye<-bW=3oBM9x7{YPk=v*8fk&YO7sf(X%31=wYvIZFB~r954oN0Tp*u f`>xutr0to1^dCJRVomSjQKGl@@6;ANeB}NC(Zi#S literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eventbrite.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eventbrite.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08811a152d01c93e8d303bb0c5a10679879300a2 GIT binary patch literal 1526 zcmahJ%Wm67aF?VcN|qA`Es)BWvX`PNVAHAH)-DVqjIB0CQ!9{|%U*~zcV#o?L*Aw2 zgiz6geeECg2l^}h1YUdUFZ9$IN|l^8K$e)<;q1)Ldw18@Tbq9f_Nhh4pXA}}BoQ|skMZz|LLTt)f9 zQt`!#b)<~)kEGg4XN@YK1NqXdow z&$*+d9%reRA(jsp?FK-++3Q2Gx1c8xRE7^^7Z>h14XY5GQ?yA}HT7Z;AEJeTg2 zD|eK|0)reWclYW^s;>7Ac0?hH1HHF@wReyle6zO$>mqf1cN~qk&L>eUw!Th7wV&@? zyE+Ra9&cgtx57-x1tX~FMIp6&uVCZ1rF|Ilmp zp8%SX*9cWZ&FEtiK}-E=Kv}Kg+ZP!ZdeJl36g(9Uc1H;F1AP>wqf909F{*kABA-T~ zEZ4oocvNRaWUqCWmhGQg^{ZBS9z3kn|s3{Im3n_BOF hhP_rvoOR*RtX?UF^IGxVcO6W|Jm@1FZH=;R^B*|+q{RRL literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eveonline.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eveonline.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7fcc4c24d137f7b7a4ed35edf87d3cfc1f8fb3b1 GIT binary patch literal 1661 zcmah}&2HO95aypGinbitPU9perfAU!g+w=Ti?}F?$hMFeNzKAiik2uqY`80lF-6L| zD<@{{UYu)>1$yWU^i}!@d+n*O&{JnfRZ)EDE_UYI;p}quo7sKXXgGKOq`_|$P5Vn* zY8K$fFvLe7s1|5cr$(YrjKI*fCmJ=W^;V-6Gp@|Qq?Nwbs^-5sz3+OipC)n2+%tK?WgYje&27oes?Cv58Rg|I$Lo-nAw3~7FHR|Y(;3T2;{2_{30k^x_K09*01;Rg ztqSNd4DlQU(*hk%Yfv3dWds&Ag9@uqYfB5Npevwjv>Mp77S!oY`T@N~w?OV* zRtr|>I^6)hN;iR#+v)}oQu0y$IR9jcp~B=6U<4?LqmqOtrL4NN)R+4NKr=7)z zPLxf&4Ae$Es5Ta^ z{_B+Sr$mgElOYVpfCs9<*$~!@!(!{m0H&}ZbvX86hzB4pv{%}NPW3z5m40c!OusOw zfwLdYOY_pYtWfg;yLADi{*Gl+$v-`2a>i44CKz`qlO#@rJLK8KT_TG_Q8r7ZS_Qy_ zC<6%^i@=Y_gellfRoDbJP&V$CqR}4ClB7V+Fy=yr1+FJ#4qJw*qjN}!B*EH<$*`EB zn`l*)C0H_4HXMJNrQo*-=j2Tk>nJur{Gst%p#J&UoAUS!s?a+l+#6;IWn6ee?mayD zEaj&=yW2FU`L5V`bh5KM+5KW?8`>!K2HrHDwqMTTgtk9VBmPKkpL!yTVv@8mvF#}1 zY;Fh(JTDXR?xu96=c>5~eoo^SL?tj*%~;bdy=fYH*>mJ}xiUM~moAcwF9Ey`nh!(V z27yV&*MORkUj0gsb!z=$6si3136t(3CoVGjE|b9B8RPl=|1D<^+~VQ6YINsCU0jXa z*<*x5iTt#E3D>zchWiinwv52W*k6e(Rn=j}Cq%X^eiH)at00s`_(-Vg$!pX_o=Sn- z6v_M;fWR`=3=8PV?_8&5Q5{DugCiXry#dz{+lWnGN9=$Yz+?p{C_FlK&3tt>R57xUc22cIt!=O7j^p6Ttv%A0F z>-WQf|EyP9s)q;Rv)&KE=6heh>A}i_!0CNIIPQiA-9eXQ`pN>vdc97D2{t^9xQhVl z1-lI$v{BYg)wI_*`W*qs_do}g;sxUPBorT(HZA^vG%Ne7^4wn|djf@}ubTe?SN+nV literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/evernote.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/evernote.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d22dedf0f46ed778a85379e2d8c7de9104254b0 GIT binary patch literal 2878 zcma)8UvC@75#Rem@<>s#oH&l{w!tB-pdYQVOSM{LiJhBU&fd=a=C`w3pP#SX z`3Lp>Rw3k{3)$D^HJZ1^CKRS)%ku8FR z9;7eqeoC1whJ!)MrE|}wX(;SKwkkHXBPj=><+_72d6Xv0A`PP;UJl@EE1U&X9x*gu ziBgv(ZX8G^q&rzfxNNfg%8~5HmDdm0_@O|ML=bp93b zDs+KfgXbK*{R8P~^g4a#RoSc3cj*lno&SND#9ILT?qu^*_>>oc0p2Q1ro%a^HiE$t z8HIt2(qxM9N=4Z|O@)XKVrG9G#6u=>PyyFJE-@PPefDAyaVC~p_V1Qz_4^Oj*VfmX z8|#hQ(jV@r`Iv*;6!W+zQ_l8zjP|p?mB}Vc2bAQ+B&8r+eWe4p+1YSPntQWmdwm;9kOF^fIt1nX^?Azh)ZHiiJ zf3wEwVXf8Ktkv25`}BaZ#(HZD-X5&>)`D;i-kxsOn(M9hX8+7b*IP@7)~qc(-fYxr zjYd6OXU&7P)n4;Kt;uM;xfawmXd__t<^`nA6=penp>^ zAVhkMKKP$zD&VXdgdr0m4?LT@fI@a%Eq9RzSy^S%AM!Y>UL;gjnlLNGErt!B2J|iH zVgbZhquOmkb!z-Y2fCXhXP3z#Pi&0Grc4G=EbJan`}U0R;9ELOB)4FGMjSn}&^QVD zEGw`+O!zwvnu5i>%mQ8y(ggm02dtI&_qjXZ(Fs%u_axwMFO4bV!tHUleq2lV^Tx(1 z9nsN-Xf%%-8~u&@ja6ueiQ9Dt(O~&t7{zpXJqdYJu0D4!)S~4OI6WEgmHu`O8k}W| zhfFFO_>=HHLNKrd6TPU7KA6UtC+xqCXHX)Hi_jI6zX#>)=9oMq&^74#SbGW3{s^F& zL>k8x{pu6og{{{=TJhV-AlQhv&2^0!#=^<0+rRoEff723aF#;8{pWsQ}V81959ie3CW}6 zFk5VIZFTnc{O-<|oo#=A_bacGvkYf*j3|+gm4{~{r!!+1(Hw`;ge(9rKNMN<_*6YjoQTJn?}u>!JJ!#^P&oVw z7TAC;;Jhb9ThtZ|OS_?sZe7OUjR!MwA<%dN?m!oa##nnsUJ?jJ8{NLNLt%M)z|)h6 zGK$F$)4`czsu)xr_=hl?{~EKwe;o;)=p$75?}Fx}u>qL51V!wo~Sxz!QtSs!2lC4ct;88{j!7RK@TL i$_HKqTLqq)ebr)_^)M&Nr?>&swlTFT6ppH~`0ziUJoAVE literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/exacttarget.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/exacttarget.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78dd4c08e706c3c819c32c2f37380719d39d94f3 GIT binary patch literal 3848 zcmaJ^-EP~+73Prop=jB0oNP8pvst3s!ciLAisPh7ur0E6lHF`K4zPBxEm44AI3t-h zMJh8?5@{)*Smk#61dHA;(2Jsvv5&ypUgZ^f)$fdC*^Y!z;F%xJ%sKO&U)@?6snoF-Bu6wmgfMcsVY0%3irs@hX_F@M>J` z*q+UdFAZ+<+FOIyggvsn8n zc7yORXbZR7KX{!v?{F2PVHUM4>Ig34EGTRqWP*k}@67Q>hZsK&qFC^vPW|m52?a*) z7%gCh^QS-wE%atc2Od2ZIuG!wZ$X6NF`#C0282v*y*0cNA%(U~2%)WvvGJ0Q`%->x--?YpoPj2^9#H6(w3$l=TUPO@k!p2*7AT_YJEkDG|2{8wYpO1YheA z_h&<{p!wh5H@Y%<1qE%q3gkvBjk%C&qa`gG|W1)^{>tbzn)Rd`uiJ0QV2b#l(UQp4&^{WaIX;?F#vA`*RjZP zC(pJNfWQr4`9iu+9*$js)beQWXl~tou3+vHAzc~ZvxJs{gj6*>ii=jzi!=8K?Hg_Y zbtZ9ND|GBnk#d7qxN7hs7H*VG*RCI(K0OU9JH2qtZx7NIKzu9cK+lbXNh}}KN>t5SQ-G@Oxj@tW~yBoYh#BQw4sCzie zVpYcpy2w2BpE_YU9SyASo5RA4I6`}+mC8<#Nu)D4k|h5ssuQ=61gVIg^tIn>&v+iw4sYyw7tHcCLJ6EBHg0ZPHfF zl?^_f<$7xE2lyJ`%XU7*)+X!qdosR#g5%{`jz`Pp=aY489_N!0lh{LHwdOGlu?%wJCsDaDoRMifh?mtQJcJizWfyt zN(N)1RkqlbX^2GR>%;EiqpE-y%wZSiLU?NZ^j1}Dp-BD=ui_w(-xwKxGl!*Nxql&J z2WHOlGG}ds<{zzLgWj2um2zXmeqet!67w%cZXJ}ll~*E`8@%)r zP+@dx<)E6Ix%Ce+jq;9H{%Uvg_MHavoFHzeGRpQlN;(jrVQLdCs*`lou)P&BPt-I% z${b&bP$I=zh*U*|i!kM)Sa_)0M+C1V7>Y`-OGS-mD>!5(+I_E18lBWqqEQ{&Brz;X zC>&H#Iee|#GVgUdeXla%1HT~_{C)hZJoV|sKi~wl1Y)o{GlBgwTVm(g;L@x~O?FJp zmpp|Mp~hZY6tE}j(KU&cTj~ng5bO#z1OzeDVmbS#nVT9#Yp}VUQhLac_2RURJT+1B z4df(684i&(8K=i6Q3%wbke`C%;G-{&!Jxu&Tz8umskllInSw2?br#)?CBwMBpqs4{*6vvqQY&&bPIFRtu<*& zleRo!yz*0dSk+&v+#Z>{cF`ExWWpmOvvQj|KeCUFVJ)v^j4z2H#_Y24OJ1OtQ)UW&g%U%#qzLcny=SjMYL@p7byeU5+LQW&E5uv{wC&bnv zoT9g^fl$y;G-b1rY1izF5ZtoM=vDRihEr61pQoYkO9EMgtKGN2)kC2(hZ_4S`HNT9 g_9Dsc^a1lY7xw4lOZ}1DO_3*;*s^Jx%dBqw9|Rk|b^rhX literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/facebook.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/facebook.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8696be89bb7e3dcd5d1aff607d204ea05e5be908 GIT binary patch literal 7606 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fedora.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fedora.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..456c02578779a5a2c6499e74143d9048ff3d9967 GIT binary patch literal 621 zcmZ8f&2AGh5cd8gjh6Jn2~`~Sz$Fs94r)M6g;0A42mwKA=>cCXcgJm(?AjaKsnl?U zm*G`-guQa&6*w`@2B;Xz<4@nnGvADTxWAtqd|r-1ZSVAdEl&VHMON z3j^|sKm_qu0CblN4>?Tw{+Z zZEoE{tIX=66jdhHZjl=)z%4LGVe?W`sc0pfw2tmFS$efCkS|`@ z1u}olhfi?Z2k0bW0k#oBfO10?L&SOzqwHQg>{NGI+J0AJ^YD+(67r5}jQ%7Gmuqv) z2C6KUO4M>b>bC>VJ>_2ZludTI zE-T2Ms=^$(@rvFVY*uI^cLd+!{|DOKnh0eK{J8oa1Tn5HHy_3A9_RQdoU=WRPs>X% cp%?A1{FC3Id&sqe=iL}y_-|yri-TeG8;-}Twg3PC literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fitbit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fitbit.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd3b08096e8861fb718d01406ec17fdb0f46cafd GIT binary patch literal 2779 zcmb_e-BQ~|6xN?*VQet@At67l+cZht2^Kgc6EbNh4w!~Xic{NkTHR=bb`1hqGP`mp z3S5!f_6hm|eU(1K-u5c5(5s#!*Q-htE7#yc5gSrNHs4yVa?7f1A} zi$=ps1?4FW{saV-AWq3rh-|4uu`~j45e;I=SXrW52AL+sZN-`(IWqZHvT`I(3NTNS zDTsAOFdcL69T02QMCfd{X}cbhcov!H|zmk5m<&*!BM3%jL3Uf}y- zKZqD0P!e!lEQd;lcEPeN3w4`DaE2PEen%LL@?IEl`lrP3d;h#Kd(3+cNHAYJ%$b0oz|%ULY}vm-*78y`|;u^24PCxV8hcZT7t0{8r!d$^3)BWsjnTeRBkF z^KQuKV8KrG_i@%c6h&~&#<=W3T$jKlj+G-Rh8*8a159C1dEfRliD-|b0fS!!aU@|9 zLCQx-Du07y&Pho!Z@dg0!Uwr@A}38Gc<|bDX?*V^=e%g5ljc0+D(rYZt?cqJfYfRT zC!ygcWY|CtamUIfV6$kD-3>|KM?G)b`+dhB>||{lhyj*0 zTo5z0&Gz#~bKR=7*Bcx5X7i;r)2#ovS#P!NcH>1oSrWzC%1XV}8d=fTSM3+|Uqr6{ zbGuoyS8MGWyMV4>AR}QS@)*X-G9VDH98cbmwUM79>xz*{EXIfiA9;$8`~>2YM56)G z=-V>TOcp4{0D8GXu97)&ZCkVQq)29fath=Exd`(VxkN6*JWXZ+DA&g*s5(wT)sIjR z)4`$m?hr%g@lSPxfNl*9&m*9!nJ|ygUNu^6D?7kla>i>SM}O^kP{_6yi0nWY1x7ot zwv+NuJ3+!34o-{Ze`g#v2~iJ7hviY1Ss`6Q;InBI$$_7pI$~l0#`&dQ{dlS4*`JpB zXK;f}^+=wTJo#8l%3eLvhz13pNeW(%KUwgiP#g4vq?g1K$IWpDHUOa<8;=&wMX z41o+OxQtUKseLvJI=g}b*-1sfBQAkQ2>H+FtyzZ-&)4F%DLE|-c ztW{Vb&`6L0Po&?aV>wcGRcI~RiF6_#11s)kPGA+A3|Ik2dNQ)(Mx1@KaQyhq;xYRg zUIFQFxaXE&t*k?T4O!QTtP99;d-TwD0%CLOG8zee3;rKGToN;V=5wb*?X8DPX&)5& za3&P{A^;_!Lp_1_2h^7zNe%xNd`Q_(o`RpD#B(F;SFMcNA_3k8dv*szS!H)oN706j z^O>>8uA$L&6gN=ZM1i1UpQ9*&ur7{q+z)l2#Z~0OwPwBb{EYdm!fJi3w)wJcw^kaj z>g)@AcB)xlU2oP`V6k0m*V&i&)-yU>#M`sNIIE6Zuz@e&B@pABs3-iW047p)Gz^B; rTB?7&I|E?`y(%lJWk literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/five_hundred_px.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/five_hundred_px.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a33c1e9440339795fd55100d5785f2f821885c85 GIT binary patch literal 1475 zcmah}&2HQ_5EdzEtyOF{F>*+YAQ0N3$fDWR8gYU+2#UCd(*y~;t-Zbw0R%}Q5CkdBwZk*^E=;7jQ z;oQV2pCCE04y+S$K!D8fdu^Z6Q~T605;`O0UBlTkVpa!NdSahA;Qldh`FA^9RU!{6 z$wdHM#c3vkktim?yg(R4QRS-fMpc&O@q{;?HZ>egp>g>Hk91t20}~6HXyi5iC>5Bg zv1@1XIoqLZd7rCDyD&Q0*tqYwjU#zBY8UFvH=x}X)vi@+bk7GX%hhdqP-8WU5LH9wPh)1e_6kb7X zHz@=^7et2tBwEY2&GpOh&x`d1#wM4MSkxga_6qR`#jpP6|Dfyr_v{B<)nyxKXLpe3 zt^z@deVgIK{f%NzodXKkNyM~MfB8Y*VigCaD5fcJsPB9dSGu|qADW{fD;Dvdj?FwB z)qyN><{LamM(Jy%E3V~ zJ1^MA@Wo&|+S_@>ZtQIRvbVK6jD~}kTXWI%jm^!i-QD*Rq5CIpYVtk-!|5!9jYNlT rI_weW;-lV@(QS7eaSQd#HMJA^nHt5%yfxmFc`iTbUQ10JvS|Mcte2dM literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/flickr.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/flickr.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa95a8cce30d58426e6943f4f5a647922d2f8414 GIT binary patch literal 1753 zcma)6-EQ1O6rLH|8^_sAlc<1-f>;5ftVH6JgsQ13#1aCOR*6V9Qe{=LT+eLctp74& zC#~6BZKS>gFTktt2m&So7wDioDcut-k%mBf03DH z!uVV0>N7B$c!X2VVwy0IQSu|<1~;z=HwBv*p24j{(rZ9DWuk;weww|L=!Ru&rL%J*woKp1o0@qV4Om_%rm*+S={tm-16r5 zExyE;j}6b}^W3>2-aKF6ZFoDc$&m66U%axth1bL+UK@a})C&zx9kcp$7IYVJh_r~p zpop^cngXxYBwo4K=r1lV)~8^?EOC{P=TRt>o8bm7noLHOs5akiffQ#Y5akycFkXH2 zZi|8tBC2Lh%_3ggCFGF?iKyqq%RG`og-laO2TgG(%DO?sC8sx6VgLOF*{XBbJqt`nb+gXyrxyv1Wt_!vk3Q?&u#J*zgAh=;We z@bWB$zqpBX&@6!Yoyc48`sY_Sm(e-s&pi*MJIrD(BnVKto1=TFJlopd;1#d7)z*X2 z)^@Uee`^EUaq14-Jj&Nzlu^vrzD`5=px8KbXH>oxW>QQYz8=4=?)+7~bd&j%q1%v> zS_DIwNmtkkW%{XB8`88C*WX@rQ$6GkN!*97R>6#+Zg!>&C;SLuznM`auRrtHAWYyU@aW+0DM-5OosTXWu+ zC74<_F*wBj7(NwBj~Ff5qSbq|h;=47E5yqH8w%E#cJ(<73R2K9nXpqfA^B27E&=+O zj^^YioFNU5fB*n(?0Hr$WTI*luc4k%TdD+y#Lep4cp`oAvXFrf>6Qtcan)lQR^7@2 z86*m>qIusBL4eBl-$0Qczy^1rE2N0f6>7s%P1W8M?tga2vS~&kl0E}yV2o^N)9ci> zuf^^Awd4Cq#>-g1xb6FAWuQ+Fe9iej+^H}y@r-!O{iDH8`v;G`{^0Tco_}=k)LTB- z{pD!)aNrO2pX@fSrT)&&?&0B#$Ju>0IOzM|_XmBsj47ZfHN3hr1s;kxz6Ot{2n28k zCt*!BlgNR3AB;`S>xZG1TZX-0+j`C(=;b4r;#vAOlvq;zV@4#nx!$$5A2q1ILADec IleUe20Wg2n1^@s6 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/foursquare.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/foursquare.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd3c60ff75aa8ff56f7ef8b74746656d6801faf7 GIT binary patch literal 1673 zcmah}UvJws5U2iHmYpo^h877rKm+zLceCZV-jEc-P%q22U|n3q?jD2!0xQz75?OLc z*+UH*dq`jV4f+N4RrV41x~F}GKJ89+>})GA3Ot^8NAm9XBk$4Hmb>={3nGhXPiW3% z?yq)|{$PDbW0;Q;_b&}XtZB;qmP)=nHiDUT(?NRzT^@koB+wuoooNuZ9+=DsEM^8a zvw{+{S?M(i9OkexjAiDs3XCqRLV8o}o2SYV zT#mC;a%D~=7fO%W@1$lZQ}Se>N{r_;PNV?xiEwVjyWjk=h^G*!KcxcTP8b&eyYL@f z?x*6aeb{6(HanE_U$UlVJ+;r#me`|+=gz>t+_onnEMv5du8_d8P6vo z^)OIx1*JC-*~r_3>Cq_4CTYHe7^709C`)r1<;vnCKtCuCVj=TWI^qiMUP708O;>JV z2vf9_alTOBX>kP)y zg)Z-bKvI}C*Imsw$mlC*d#0XT6i|Y0WH93#vfpt*1%F zxm5P$uQ+br&l0k7aeo;Gim4&>M7IH-+1`K9S-YOrD#{59p2JYNVK~azB;lY}!w~9f zK{WiCNN8}U^Su9K_v|F-^dZLZ`PtK8r*m|4-0Ow??oY?dnGgRl^YbyR_)hFWFrtd$ zK8lY~Y$z`@o5$g0$70$u`s@GPvv~QOzIn? KUZOCys_{1x_r>b~ literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gae.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gae.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48173266f6b517741d47bb79028bab656119b562 GIT binary patch literal 1784 zcmaJ>-HzNe6tC_HAfW}d^d>h^HuePX=5Ot! zbfe*h=`~N#7vNQR1mAMCZ-BVs94EV7s)C~&pV-Io`M&Sid)wRNcmH7FUk)LEk}D0q z$z6EWH((eE38T!)Xl{iTMc>Zs+zB1<9X7}Yxf{CRyKI;Zb1(ELc}SSYM$ZWwad%^f zBQ}0ag01%U_+eS58TSvW%70L#qTs%2s;ZQ=-zZU}{u9NefAGV%<8#=V1XN#(k5rj8 zHIGD&aWFJ!AV{6lF2K7ND}K<_Kb>3Mz&3t7c$V<07G;s(e=ZtE+=W*y!Eh2%z-BQT z+RO?aW;5qG2?uPzT<8G}^PnFx$R2u+Z2^Xfeyf832s!|`Tl?|lAn#DcFv^QsBrzu8 zAH~TDFIYQG4gOhNRb60WLnhdeazu0=UiA)`njF&&`Ndw_a%XKHTO0BisD~7+_QoS# zH?r`PMoQQOr~}djn;!B!R(i;I9g9q9PjXe2h2q-5 znBR%K2EX|G*`ku-X#Y!&rPChH->2iOUwXEG&%R48_{e1t+At|$@l)N8^jNfyk7HQ1q)tTPH zkx7mqFF8iD0|vy~LVp|08)#67&L9t+s)m?eySHIfO~4>KE<6@I?MMI1QNJ|7TEm7t zl4gj)e<4w80wiIr(e{ITD5U`|`>HGRD&sZxpFH|*-d9{WO(ch6iLw$7b=1f#oJh`u z|d>c zZ9_!TXZ|6A_rPEQcyvnJTmMPk#hSxlq`k2Wn}6S&#Ys4t~c@=GjkY>=SeOC=!Vz947 z-NT*K+>GPhXdZ_P*+HeT{{p@Q^ulV!=Zv_CGn# B+tL64 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e6c943b9c404ccdbd19408bdf75dfb432ffdf778 GIT binary patch literal 4270 zcmb_fTW=f38Qs|%FQO#+?j*KLq!))yL@J5n+NtZBu~o-tEEy8rI13kx)y|4qX}L?! zEM1Exiy}0TJR}ABUi7_xrRXo%*FO0#_^IccrD#eP5(DWHGduU$`Of*yeEfKRzWnyT zxwE~fY5&z`9uwu8Nb)H%t~nZKT#r~+cXUQ&BQmILR9D$og&lj zXuQZv&oy2O?6KjLP%ra2)aQZ{>Sfd`d>-}rpp5z)>I-}k^+i>$@al$EKQ@TVcfxeL zzgb&r_0#Q-YMY+F8^rvB8c%##^U_btH9XsC+LNC)o4vhsJBgQM;)h|6eJumc06Mfz9y)X*6PfP4SQ!}>F5@-&CDRs^qgX@mT4aef9V{^+XaN8;Jf>Yur z`6+(70|W6nUU{xLb9|mJ;9cR1yo&ccKf{mn6VI8mz>mR1XR}Ifq5OjeKcBT%jZ6^4 zY3O@tn8f?mbL{-ggERYv^Lk-pVh2CzHV5zRcg!?KNhge(2`$;AED_-#7`*>uBYi)R z(oGYSDuwWB=aB)3I`y#IOWgu!iNWno_+Y(8)mVZFa^ZleBru zD=X4G)pL@1id+3CqH0#&3WZEvRW3!|q*<g-(#VG)wi#%s{U53C7hgcDTtKGjCOfVdS%vBN4bC61;^A3Sa~3g?PPT?5zeARr zbIf2uGdHUIcCVVZa(%3itS`0rf~lo?YNY0tv0E0OF|dwl9c^TNrN#O~ZD@&ahZZ;9 z(ndC^+;g3aXG=r zOT8Q*nCVIA2ZAg=C^Qu)QV{j0zCe5-u52L5Um@$@Xn(peVky{?vaw##Qmt0vv~FT#pjpi{uRy_9u!{v_l~)=S-ntYEwZ6%F2KAqJE&{H7m9E zdU;WtN1q@j)QdTtD&X#nczB{CGkf>X>TS}Rjy*9Nnb2979NC>T9wJ?7`o(pOmb8(k zmsuG+tFn`<4CWQ}1p_(SD#!vx@2`|jE* zNw`yO-MY21vEjDY{;-m_oH)>OZv``MJBu5)*6y#k8!Pu)>#g?Mx>H_x*j{hBw_EL& zGLJY%yAY`Az%xcr3?Ri3`wAgxY>}C>Kb@q;Hi|}Zu6SCF^f74q?1L@eL(}XX{*AVU zpokmKi@3{`KseSn;Dck6GH1tgQ{M}^n}Nvh0R{9_wdew%FC-+TK$dDL8){=6++ka6 zsQ(jE|2JsZ-Z{k|Z3k*LhQ=rs>E9y~gNj{+idoRFh(WoSHaIu5cI+Lf)2<;NrNtp4 zw6Xp*`%?Q#;}*9@)?ciLxxSH5*gv98IVlBk`^A5d4K6+aXAyk`GQODBHiIaMJGC^a zABg|9qWCamC?j!ih%3Wm@x-^v}2)a76Ifzu+m$+wf zdl%4Bw_nj*ko<{@)3pq&40kEHu8-?Uf^4tQCNA7TlB5gdtpPve!%>IKojV9}&(8Ch116L-=tQxV3U+#hJ!Y<^-Q zKZv4zGtu^SCa%!}%0URE+ej+nf`jOlV+yzfu0Xc9NrP(0W?kV`I7{~=LRxZxLUwkd zHZ#1KgsE5Zp8c@?=s2|o**icgIVg8jw7vwYy)hLJz&VY&et{z#kZVXZ$u(|MMCNca z$nqM*N+_w)_7e-NIJ8wA}%usdcqJulPc-ywYl^1ll<*!>5gI z@+6QAh;|fiHV~)41Bl{|rT!HPid3_5@YD}_^mkJ#4ObUxeg_bf1Z{E@`?BKoJes&c W5tSr?GFe5(k5!E7ud8RPi~j?VXbwmK literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github_enterprise.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github_enterprise.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b5df19bdd365cb5984a30bdb5c71fadba97e9e3 GIT binary patch literal 2243 zcmbVN&2HO95GF;6q-jc#+t_v878QDEARrRm#4RGDC~Tx|P#{%;#JTW7P~5ddn}6P2 z83~b_jlN4?rH{aCPkx1-I(D_3URPj{sLJhrerFa^80KPi1 z58rylw`Y(w;LI*JtDA2b`y+ZAr6Jmf4Zj-nr_gy5giziqrz%k|;RhGg2A+BN@^}Bu ztLL2>(_uqtaw=1$N%G^{9S1V zdE|q08VCugQ`F*{r7ZNOJ3n9pPe?Xw7yuU#?o%d;%{UuHsheQ{xhOIg6%={E_c|(D zha-}VN}vVh#%!bXH*Rc!dS=*z6^2bOzd7@rRDo+kWk+*ZQoBaGKq2Q`2P7Mh^rNbLC?@7Yy$<7 zH1{e-HbXzMH4r%C76?nVH2g_z2#TAA33ULSBkf6j%om0FSqVmUFCeytMm)y zt&JfBbtnpDJj{w+u`!Y*s(V|a2Aw*?huJ0yWU4fMABr5n))DYhXkWm-9q1grW~sW$ zP_rBs?puU4QlaqZ0Iptsw*YRT+!aSFfIeD*w0)8>Sjkr~u|A~OrYdRy*`{wFZf z7Vvk$i>*Z#aN*-9=+`)*#+f2NVhl|_VR#n zD$}{FPec@Rnbn?A8OV@DW}?6pDvfM-N<$%2NCuqbR=%;!pwjxe%HUrxN;zh%t~xbG Oy|1^LpVb}JF#iL0$1j-x literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gitlab.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gitlab.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6b121b88f66450a854518116392ea01ae2336421 GIT binary patch literal 2486 zcma)8TT|Oc6xPkMEDQmXkXw_g$#fF8wM81537JkOE^z`=3Jf^Yj?#|0)~;=VZtTjC zDDYD9+JDeL(EgPE1ikhtf1yu3M=})XWh#x%p1qwt`+eW8{a|v^y#5okfrAmHE8XvL~Bex zQk@z#x1`1d`B9c6^SPg{lAg8R9;L%itse1qSVTXuXzU4#q>oJto?)6M;<0TfuhU^1 zHAU?CBxn+t(c+BIbjWac%a3gq*#SwJNbPYXVGsN?AidTw4Fl8c4oS2ltTeV>J`uzZ zUVne@I5>*^Vhx=IbqgKRV2N1;GFQ+%l zT{L6dbQ3!K$ZoXEWmwCLDMW{m#2Nq^sl{2JaTa;dI%JWxAM+hQ8enQE=5}@yE%P0a zDmCQ1w%qM*bU2SWRH`E$Y{!1|4k*G-^GhOF$_IXbO4RjNA%t%4eif$W&{D!oRp4^(G@PSUBjl2fM_=roj*bcW7C zIYlqhOHf{*m+2go)AS0c=tf>IDJYZ6?u-d3surRJbopLM*jN*eUZR=Jp5h)g0^Db} z|K4U7sM{DU=6=SqTmL#HjX}P-wz0m|edg3qobE_4o-01hP4+7BITNlQ<<+sDPXb%e zHgt=b9%74+I{ucR6t}?~NZWAKj^tztJo!+e%C>T#Y^zi~k`Lq~1)$K@@@jiy#eK24 zn(1%m58vEB{6n@Ino|`lO+f;D3IgRi$TlQtu5*!cKWV7BCRotVwS=U@yh;+^1@eE9 z_+=RV#~VB0{vHU}-Xq-Z#{uY5*nMt4*ts9^-G#+@n$c`gEIis-SPT~*F3iI-i0rPN z_(`)j@&nrZEb{oHbbi-9b#R+r%-OiXE&THbHhIl2K?WbdCs0$8JS}IJPLtgl=jx$I z@xc-iEKoDSIYtf8QP#5Q^F%h}VhJc_VQ{hG(#%0MpE)MH5D$X#AvBAEB00NtHj}Y7 zPKN&9oa*D83i|&$pK0QGOo&n`n9jXZ>D)Y<&bg5%x~jkw9lipghu|PW2lBRz+B%ZI zQxBCx^-wEX6jT=UfWnfGsCJ+ns0VPt|6Bny_3Z8@OGiAiP~H}0DO?1?>hm}}z7iqZ zcoe0%7LkzU297Jd3JYOuE;TCrIwZhvfXR*iCYD}mY<3&`5w#aHi+5YTd<4k zqJElcxPJA7a1shY!N6R$MI4pN)k@{wfu(be@PWwnoqa6N@4SX_T?*?W>*aw`>T))B zMmHye)*1@O15_CBC(y5h0e=#GG>w_ua0@nEH#c23jOi#~;MZMucSH(4p;mQW_<43+ zehnug*v?G*MR$39bH!b9FHOX9(XLle-eVjU#>ETe@ZSlJ};sDq6WhR@}kLg1zx$X{s~nxrTPE> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclio.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclio.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7b4ee24a6b054b36b10355445d4ea35077200b8d GIT binary patch literal 1465 zcmaJ>&2Aev5GMCWTFG+kB!Ch02M}5W!2-3{P23_bf+DJ-rbbgKkd(__2xdvy8?Ur$ za^;{~`Q%*t27Q6PN>4rW+EZVlr_NBSDhFt}nBj0Z+?{Xc<38HnZhr8ah=2D8`Gaf> zSd*t9x(!MYK{GO?F{R`=5sq-*5#dUD?Z%GqMkMtAq!1QT)BJ{OIq1yGvxfD~Ac3ff`a2RgL?9Y4H$_uy!Mr6}@TqOndY*T) zG+%xtuASxytf~iRWm)L2qKFs8!P0k-$=1G#zXQlBl!Z@ku8(&T# z%rqyd(j}YmsWgp@uguE7uiN)^=$imf*xg_<&oZRAtbx_bm_f(c)U_33ei3ya{ zMLvULK87^^iQ71~1-@U2+JWh>pQ1vgm%NnGC0EfT&xBMunyBc}`R6lraddnrDp4Kl zqsQk*$J664j}Bp+&Z2Qtq{aU0d76p+FJ_5)TpnITI!{ub?PK%ylU&J_gIW7EPoPOc zN2_KRKbl=goFPKCs#*(D(tDtY)1vq^=`L+K4qZOg$D33-c>nae{dgHr1|a$ls0G3H z3ui%7D%_tPXq#7k^djd%ue!!dzHAv}bD1Wx`s_al$BQ(&d>CQYI(mDSGd^XhX+evn z*31$ST2d>x;!_>cIKV95=zJD>Y8z5fn2+(eI@P9j{uXE3iKW`b&Bn4MkkW^+)JSCF zcu<3C?*{&LSmI&`44hFc?=5iTy5&7`{+a@Cx4&ctzBZ%^OCGv5HB(=;LEQr9`&n(R z%bki(U|-wIMyq=_a@q*$hbX)YJh45;#N= zrWm7RBpl)1AmK{<*`o^V+fMhk+XP(qz8x19vA5SVy2mK#3?82%Ir> zg+o2z(vt8*=?2lVC_}(|`GZZQLAr@WD>LOoWSM5q=SdQqH-zvwZf)3>Oo`%_lpBHdD+ub#f7P-M(VoY39nDJ zit6yU4t$xt_;)@Y2V}iZtdk*EbH#YXg1p9kUmC-TI$&_+m}?%H28Kpu!Y++kHoV+3 zGMs8#SnhE>GkJM&W!JAr?*RGh6Nz=O=7}V0uE|t|!X}w&aUnEi#dHP3LgY z=|FdL!y&^{W0S}vYf=yp(c}uXuzTiF#Oc?U#8d~wk ze8)D$>?CY8rVM}_4?8$}{P*p(?hX#&YAcK_c_bOjtBgfTq@lHIjIB~0-tKsIr4M`O zllP;u6WW`cjE3y|?3C{H`u)Lp%qF7`gM!ibA!{*?EvQ<~1+cR-6@Uu%?B^8hu;J43 drW0+SKtXP~+jg{#kyqYs3VdfDxUpwz?r(e~-ah~U literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..29e62bbfe30fef69bec37812af4ea38c70e775f1 GIT binary patch literal 6547 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google_openidconnect.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google_openidconnect.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15ef6c49c10fb1fad73b0441784097600fe13c62 GIT binary patch literal 1034 zcmZuw%}(1u5cba|gftOFaB5Y_*Bp}NC#?voDi9zPRZ0-hi!ZCiyCf#I*X*u|u(-9C z9{L7-fxb!~!PlPp3O#k!4k%O|X~vVuYUcaCo!xD>o3DN{f8DjLU)GYPjbrilrs^iUMMh6a6~m%8eh?1#uKLSq-@)p z>NsltwgFzpF4mt7IfO<~Y*hfp=I2fT2cOJ=>;PPO5V+;rz|di~?=r_n%mof5C%;~C zDK-|JmYL+66_BogYRH0$U;g@$8w~=HC0g|scmomV8~-T=28=NTZe$;Wt4#rF|9r)%4Da9;@g0@;T11RXg|8*vooy%bwm;ZnIm-vCzc=d- z;=#NA7T6@gmpBd6?o}2>tot?z z*Qer8G;SWol*xR>4&RLc>Nja*j#WZ7}t)U}foRf`6VirCDLliERA#C41&XlfU+g91bfg5jzDsx!IDSF-{jP= zfPV;6t^;8tAdE62r2&Q2OpTryn3OP!*{RjD1Dlc`2y>YGmN1vwBQtQAu}4~7S=66o zvWPk&>NR&a2SxXG^JNqt@{HYRvOJc}sJLHm!sr%7U*7lq{+ptkXDc#~lPFz@;KZun z5i7bJ)mM|;=b4{I1($_Cxk>uT$v0NJMK7(t1DnZCiz-KUquY1yy`!*)ZTTY(oVO%!naey_Jyv6NSZi#CHDIl?S#Wqx zU6}ei{!mbx8dtVXoQ(R(>QuZM=RH41Pkki0NJ?Jb_+QmH=2C`54p=Jd=iR;i*aYiw zVC>MPEtv8PAeC_d@f*?pqUH?b5;8O^6XfQIg5lPVvIde1WwEG;l#wvN%k1!?WhqPY zw5?pmizrE@@&uRtJOgDH?*5aAIavPr&F_ok7~94#TBq>|Do5fw5V587~GRYoevi38Kxm$a~GHmCY!Q z^FdZV);e1Zg{Jth}`Q}fe{Cf+Q8R9*r?tR?Ev2DRuV z!=U4*eDCJCJl+Rma{Nq0FowH$A9f)?nT*H_z!dej(kZ|C?^-;*bxIo> zPCtT9CtGH)fVdp;H(`{qQ1V#tLd}L9o^b(162iTKhbjj$&rZ-^1}~KriH=mx;VZqG za0Rq6eO_FF_u?{=b3v%-#K+3L4sT@x2r(Lf{v1q)mdn%RnxH<_k9C=VIwt?L03O4p zqC@NT^#Yp*9gz`zM1EZw+K|c=lG>>3Wzsel$v0$Z9WDGq3gZCVi8UgXg|vWx95^Ea z@Ebd&Q(cqaUXv{sT**bjnXgCGf6bB(^!S#e6EpDO zkI~N)s0s~26ST%ie1_avIK)+u-GV8xcnBom0);;C>3I;&L}RBlqr6bpOc<(q81`~D zNICG0FgzMWy0TzrVZ8(wH=pl6-hK8Y*xY}zyAwWt_B5E^+}hgS+Y9%1f7~8RJ~|`$ zX&X*F67yh@z#0*sBSBM7nXetex)L@JjHAQ@9{8Xi!*jfOucqGunr;}vUoi~Dm!PEF mZX~;D@={|M*!q_uFuq#-LtjzegRz_6p_+!vqMp$(8s@)@&m)-t literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/itembase.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/itembase.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1fe3fd02bb06f1f96b88967503a43b4a6fa1eff9 GIT binary patch literal 3441 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/justgiving.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/justgiving.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d437c7e2eb270303e753cdce681bdb808540d324 GIT binary patch literal 2229 zcma)7OLH4V5Z<>|isZ zW$+>u2jv>BTvFWmEBu7La>`%eME9<2l;pyyrl+T;dwQn7?$+H#qjv2tMhyFdI*IFrEIdmfyaC(@g3FSh>LcUhZtr{rvJ}Q6V;Th+j3Csf+ zat{bsh{_bAF_q{{BL>rn$qevqk_xkk%_^kIY*uA8c8S$TCaJLoTY908OYAah!d&MK zw#-&uC^MBTu`6)n)x7!rSf=0kr+zdl{b#pO(kex*Le_HOVmwNH&rSU}YCU(oV;(V5 zI|9v7VN0yBZEig6 z?+>!i&W+r}f7`|B9mn;&cpRm~iW46BEU%mkDv5}Pe!z2+hpr!x>Tnz};rfz$2oru7 zbPL8740#{OL^*<#n5ai8(`MzY&(xXzOrL5~eQFdt#(W27ZK6*gSzehCVk^77$J4Qh zS{R`g<0-h6t)Ym+)?%dR7t9s7k}qVs>xP`zfm?E-2KQ*2xs8WJuvG|hdpHgPT;{dH zh!%VmtqQiSk);t&DOwc*nd`8IgAB=oVQvd9lQ@Fi2AnUJkX#1xry{Pv^v|zOBK%V~ z<<6-qoM9X=E~GOQ&fViXkvQ4dTxS`}Hs!{>wFn` z;$FIb;>g(Z-JpZOcf45exque@Jg20ykS@tt-n_W|JaA38mRtd%Xojk(wz{I(KsESf zH!+@zxxDta^##U=Zv{plhP({~F`g;Uw5c&QfmdNbp&jWHbq1(UOu$orWiaEV2AG=J z^(V2*nkimkd#6}tV$r}gzy*;|! z9dzlz-jm!K#v*jnwk4L~y?GrHj!Q}BMdgZ-%&p^}idlyty&JE1v+zL6M$;3w>5}2CpOJwQP z4SZ1K6zoz?8uO#ezAtW~>W4tcs^BMM4&}NC2Wc*@=vV|~6-EP}96xOdJ%TAZ39l8$L;QnBPf!RveY-oyMXqLKL(9SMm_l7qDE7FN#SyD(k zLxk)qx!oJ|1@FTQg`Cp89u3`LbEHwxG z7trNnFw7tZGnti`Q;S%p@w35f=3E%e;nulLY~~J)cCASK*K|U&)?jC*4!>=^r_qF` z?3)(LBH5zqx!;0zs8lYW2SI+S4zsi)vnZxXhr)`k;FPIDj^W)n3wRnNRB@?-`JZGk z!T)y;)im)xnvhuAEd8IT-V5mR6&P+16F^wZgk&w^Fq^o{AvNZb6;>l2 zTVdXXLF%l|d>DP!U`-esY>nMwpIn%v$yNd4?XsZ}R@)U1m*BP#0Z&yNQ59$DC3xcM zl#8z~VNW#D3P+SpgACsTG?|DCUOf8$K$QWdrSo<$9KDJVA~qZ?=)2IR2j&cb-Zvm& zt6y3%`_pLKr6YMVE>{>=G)`pc2`=+2m0Z+dfj0jXK`!DGpgcICA{b{0<3a{w5j>qd zNyYKj_9iP>u`RcrO}4hD+uv_(LOV!6Dl7htk8P`pxB2 zAhYzwvn0U@;*Vn?Rj5DK6FPS_yKP&nKz5>zrio@jyzt?*Tw9=5-WWHp<3YIVS0d%X+G*zKG1s!|_Q=}dxG1NFn6blM7Ep5O_))DN4pN2GL zq2!U^s%>f5r7IN_)ODzLaevr$nkQWGuwv~IY#~t;2Ap=&EGj`(cTM-E1#Pq6UM;;) z>n#jRKMbcCnWDsw7S+dLxuK7JbWz|$86gF0?#VdZU6uP literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/khanacademy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/khanacademy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5e91c4156225ca4832b28994104f71f1c20cd32 GIT binary patch literal 4985 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/legacy.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/legacy.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..914698b5a06a3a865fce2305c322d0fddff60353 GIT binary patch literal 1844 zcmaJ?-EQMV6rLa3aonU7TB@>(AY5=^X}c+EL9Mhxh2>XlyQ{KX5FsqrGs(Jj9CyZ9 zXmD@H-og{?3-BsD!f?q|UtzB}XPkzl64;tEXFO-ld~?2Y#=GryaOY3a`^zQdZ?f<> zpdUih_dp0DXhw##M=23Z@Qe+4k5lp`5sq+Q5aCKXE2%v9>714s;7i~bXYJx{{5GR+oXTrf9KG@?P525KT5EVTm6*;3*T5MMAMu+wE zIAn$oWNEywKa0Nk{QHm_N6V~l8bX$FnrY)Jsf$srgf@};AN2kAHFUXM0;@A zTTu`VIQp}4n5oym&3^!FS^#L(2BRB<8@Can!4kxIcs6Gc5!`6Q28vB-i&&6q6=v1z z_4faRq75|ZNJ)_@nUrgYq$Vq%ldT0TOJ;LC7%nt_jO+G6APQ*oyrH&AR@w2C{YoL9 zIexEz%$Ia5OA3C_~-JMgt(WQ>alQwZ7LGj&<2lvK*_tgK5=C zb6v)HB15K-UelN*Vd$v_jvWz~u|j0V8y7H;Y8?i>7RFrWWtzaJklVyDPOJvnc;`Q1 ze>;uZmZf9{4YNXHwh`8XbbezwMoXrNO*ybcKv)Det#VMX&hZ|ZvMXkFZuL4oIOpKl z&bV{ULv6}`;8)I-d*xM3{aEo90m&de;r5v_(R_{Mj-?FKMpLiiU0rr literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/line.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/line.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c62fce614604e2f9d0f52ca10512424abb560903 GIT binary patch literal 3064 zcmZuzTW{OQ73T0Jik2M3_GWYImfaR@1jJD`>lT}!D5}`n#zB^1h6K-?IXrVYm+#DcUaz}1|HbH| znx_3*TYF64e+!>{0)%Od#2PJILJ8BE5$j`v8bo6zvtn~>QA_c5Y>z9nLbTs$tiqf( z8gsa@FsQ>wUu#z1!E9Rh9Rb=Th5I;={HQ(6&K|temm=(a8ALH>ps;loBrN9sS(Z(F zE<`G1h%>x@R=dnV!Zp~LMs-H0!E|aegIdg_Hn*Aeu|_M*hFw%>6+V|abc5Ywx7h7d zgVxxO*e1L3hR`}|u&Zx0y2-DwEw=qeTM*h{JM1HnU1itU$Ix!E>+A-!+pG>2eNwp3 zqGV}AVaCAE?&5akqgE>jLoVe#6y6*27K?&7J#D1{nArM;pFk%{W_MQFlW3*-`m?24 zImIA7=gDmQ1Ddmq7U)hyIs}&$=J8Q~Fer?1#0nign}0ir1TSiW4+WQJYM!FL?D-i^ z+YQn6_@FoO+lNnn0wmW)+CuwUpX+lY*P%xiC<70_HNbYg+cb)57)LzG{D@Ilg~4iJ z=1)aLVHG(^6UqJTV!}al>y&5yIlu5BroQB%;2CY8?DDW)r+<9Z?RWga%i~VdEKJGc zVPVNE$hf!yyDI9-CCfSfsfits{^tU`5WNPa*6TpDhH1>WkgcI;eSZE~B3|u1yw7HA z_E7G9alZF({P0(M_u)BBT7%Xkn(UrTqnPdfG6}^O+5J~7nT8OoU5wOjmG`eXcL`KLb16mJ-vNYQBQh4Nf_&mkRH@4fWG!T!zski_)a27Y0O8Sg`Y^DXv0Om@LQw z>Xxb@PF*kgaXG1qSV=xY{W>HtLZ!s`ry!CIAR2Mk-UgXnUvuT9At6>2e9-ea^awur z4vm8 zq1)DBW1ADuweNr`_hJK-Y;INUkHf1%&P_C-=)y zW!0dK^&S8|F1FkI`<;H@9~?dHbp5Y7gC|D^P<2xulOC5*Ta-oh$zX8Y!D3#RBbg?J z6{i7{;>WO9Q3=x|gL=|*mYEcYQ(4&O-(aiYFSc=Kw~?q324vR%<&(l$xOC`U(3Lnq zGim@zgry0Y?CO2F!JXjhOjupX&fgg-&o4gj~K7%1OUWU44S%fgtGO2%~aJBGXmh4 z!I>!!U{lNDV-kln^x3U7#9*ym*AB7j;Daq1rz#NK1hiZS!xT8c2r>&@Z~64fEDXG9 z5TmT;MrMAvz0o8)WxYbfB=t)r3mnKmp=pIfQ!m^#ew_aYGAmI33)bQoHvj+t literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/linkedin.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/linkedin.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d53b056c05f82c6044df6c36f734a7f8c3be6b5 GIT binary patch literal 3757 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/livejournal.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/livejournal.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c580d78c9f45d210dcc5434d07125aebc82f34b4 GIT binary patch literal 1384 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 lHk1qdir z>oJH9Kyi!PnU&kbwyp0h?r`^&#a&_l;u42@6RY3&%Z9MNUAMnsT8yGad44|u&$>0h zdJLjZKnaW3+=8h#ak)c0?ve)gNRu~6i#LhSTVw;I&3&@TZ}U6+9e#J_kq+3MC&sdrb&eMFp+_?H^?Fg2hfI&i{DAR3Lbs^_O9o$yr+Ltq1lJnTHbR-w#nsl~$tq_${JK342tZyhUU#O2%2qYVT|`iN7VS zg%*mYyxwF8f8g(?EvE#Qztn(1JwWFD{p5j4OAuagUu&~sTptPcy5w%M7Q%o+mW z>N~ZMOQB0`K+NwJxI8VgY3;8kZ^D2=_4m)=T%}j66yX(9;Z$Z^C>>5!_~7ERLR}sl z?(>RQhx*{r#lc~I`0!vK+N=oUaGuV0Uo6s$?|xn+>QT9W8EToNEZfBj?Iu!*m4h04 zRc;;rTh;kb_4Z7Z)F8b&`(4ONBkwJ@ZC3_@8?4%UGklFYPCAHFGBle~u zOSJ`|^~Mqx$m;_>kaTAnv+-cuY2!c^yH_=7~b_;D(U?X{q>i24&8vamP zw<*mfUt|L84yBh1mLZMcuSF@B2^bza$gSvk{A4sfCQ*DmI>pnG?PxF>m~4%QN5|vgAf{%7G>?zy)8P-KJ^Vf%M-(Ac zJxm$NaRVhoV}wfKeM5ZohoF4hd-HP~8^5OOw@mD5A6`52peVeq#`k{<*Zx;aNWR9W JRby<&{ReGKjj;d# literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailru.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailru.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6313f0e5075d2c242c8d9cca327f2907d489d6f2 GIT binary patch literal 2026 zcmaJ?-EI>{6rP>^@p@xJC~Xj^MOG`dwN!fpgo>aP;gYD0S`w6)svS|SmYuP^$^JMq zvjn#GRgg+AcmrObH+_&k!rba2F=?3r_B&iT&I-e@+xD}Ph+mxIth z=&fM^eHUJ_0TV?8QG?>dNHM{Nrp?4mEn)#}Q9H3yhd2iM4pE2Jo+Dag&WTBC)ZItz zdYOCo!#L^kqP5#AaqhFsPeRFr^yeoDKZP53(H+S&@m_<;sBI`Sr8i-z71>ddOZM8BvAu6Y z!Dg03TJm(5K-`75a+x7wfZLcF#H5&5)Fd{wh(m2~%ONg3PnU<7)Tu|8o+IMX25rK+ zM9+cC7gR%g%!}DA<<(RFwKFV}aTLlp&rWeDn=|V>x!%97%p#`c7pHJUd6uzAcC`#( z=vP9%mdtrvvhuV47nC_bt*i&T`+e2qY`~cq1v93{o{nSq4&qGJ=5&PTaM-XyQlNVp zkxiH;=m1G$Vo>8UIx#8=Yl@mL@gsAJr{>hExYlCd*3<+~%!x&vS2iG1E5F=hvfx>( z5RA7dli-zT4S1fmPC?@AsDRiiZxC}KgDgy$swd%`Y-8R4Yj_hTVr6l3sBEUOY1_)x zhiZ9#4tAA0D3YZ5Txhg0IX7f7(1O*;g6pt@t|eG9P%gkR&NJ9u)|V{w{zUvDFy8#; zk9qtAa?gJfa(|E~lyTt?xPRmDTE>so)>mms%XP7K>u_y7UH^J*6~-|0`~El{cODmU zLOVCJh~JW{NB&zG-idO~=8T}C-_7jtGqt?1e2&`+m?X5J2wR3{tY8mj3{Uxq2Jkbvl_OYGa2Dxe*F)#CZe};& z6<1-JpkG&}I*-vMFmM9dd(|76mJ-4gMh< z^QOKrWEoTqzth%7&+aMPXMnHLOzU+wk~|6%ai_b`QmC{GIq2L{rih2LGKTJVe09uSAe>T9iv=%KUzCb!qU}GobAovhpk`8h2F#d_q%)Bq}Six z-3cD<{YaL3_wH@&?+5+eAGT(ki)T3Zw?Jc)cw0aB_j;OQ9{MRjt f*j3}_x?HO>-gmunH)fFB(J3!nm@K1#;W7UO|6?pS literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mapmyfitness.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mapmyfitness.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d80cd7cb2be239f401732d4a21b4ed3828fa135 GIT binary patch literal 1929 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 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/meetup.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/meetup.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9422f42803c6780a356ae075220ea059547de9fc GIT binary patch literal 1477 zcmah}&2Aev5MJ(&yiz2^P1*oO8&n4afr3cuI&KgLMG-|-TBD9-Nb-d!K+sFdT6_OY zQZBNUPtLV(&==^TFVaWYYfpWJoI2c<1v!^;F~cD_9L_gC`@GSpef2lPf7yodkFhpj zZM+7OTfmrs4ThK*qR7N1GJZ6e#q19Tv$=U=VT(Cqqg~13+B?qGJZ%kob2Zy(ol^gT z$Lv{)CBAG?^{Uo_F;gm)uRJe(uVzWyk%=GButVWSS8&SIjBE97ka#@yLaMk_-qMov zqT+$>OhsYsUj(kTQC@pPB`=@!VP`c=kX{4HH^8`o5x_7R0(VVpGYdP+#uet^4OTfr z*kv2I%3NGyReX=t*u4)1uCqF8z}#R>0C+#Im-x!JvaP#lH3A;1z^5um;#=^zQo5Wy zy~QoLKNq~aMB*orm*}fIp5}=NGM+v8e_h|_Qs(yYa6HMKQz`@hBYO5Wccg-d`5L+& z4p0TC4ifw&R=FM1i07`>DZOGR)6 zk?^jl@TN(~xRBmdc+W4sjm72e-ZslvwkLOAUhM8gdoOmkVVuR@#7l#;b2<+~*7+{> z#Y?q)>8(|0$4>-bZjeI$v@+B8`R2;=Wt<*@OH#XMm^OM~xu}jz__D`k=*n{HetK(_ zMBR#-hCouo(@E6rxIh77*6$|Nrk#C#m{2B{`JsxI$$ySh!E20wUTj;DxN(GH%8@fY#cP^8-ZHrBCL3{x$x2ZUjdFA3)Jr|KLnFgF! zi{vqI-PuaF_{d19+W!|@9exAz>R&}*dM&@og7ZcxIa7| z;QPfy#)ISDs5cpoaC0=+KNt=ApfKr8w1S?9J_ZYek@y_m7hh_obF#)XdSL<69?C~r vK=$gZl#=Pz3!Nhf=wnd9PVpBIx^s(zHG_ryBBt*5WmJC9(JVr0qq_AUL&lpO literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mendeley.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mendeley.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..50bc7fbe91cd61832223012b22005deeae6796f0 GIT binary patch literal 2851 zcmai0-BQ~|6xN?@Szs_p(=-jusQ+;j5J_k!WICBR;IvE$PK}xA=td*7YY@ni*p+Ee zotu)|zCmB0Z_?=_>}{|53c2bz>mTeiP-*sPwY%Eg^X+%OqX(6Wb@MOc{%I=8-^#>Q zfcg_CdYF*vcRpmQ{=)`!f5Q7?Lx?6zv0x80Kkrt?SX1GOYDG?J| zOlZ-er9{d*O3lid^_<3pM)cHf)_d8(J$ug&j-ctTO_G4ye)h<+;X25&ls|Hu^fWt2 z;w7F0p&u>z@WnEtKFJO!4qpxvhsI9iXOw5oa3=0_hf6FUWZlU6M+G-(ny{YwoDLS9 zsNWBucYGO8djf?&0z(y71@|?gx;mL7^Q5}3yT-s(w=i(fEs`>^UMt|>44H+eNh;vz zf~buAd>+0G<2ULZVWlkTgc0>ZB8=GYQlamKNua|@c;QonvJQpAktlsw_=Wmvf>qRlk5t#*-%F0yE(nJDNMFuB6+rGpe`wZ2I`UtXli9)dXg`mCoIqFCZq?E?1@>= zJMQ@rGRgDY`JMIVi-xz;cu{ZF+s&4+8b7sLb#J5IuCsZVhaoK(9;n>o#J*(!Bf<OU0 z&aAHxZApB2IQf`e63vOAh9KFlnG6Gk-9)p92Dv2`>bvdlo2^Z^-rj6(d%LYIccInz zaksJ4_S(%K8nQ`L>+9={ot^WRi&=9EW<>^YBoeYl-WtR9DZY%e`(x<2m@oS(q_m=I z((<82DwzKE_3Mgj5sS<~nuBBq*jL5eD6*vQ_w)G?YnCLQ_j3o&1~WNOYS?xuvj$Z8 z{||vk;3ZiN!x1q7V2L zs>F~CE98q?iBI@2!Jxv*>;mcLBn)W&`GkwpzKy8|tP_>`FnatfOyb;nN__?i>z9>R zK*-Vo{MA6B2GOx$Q=-^OSl7>L#sn3wK?i#u%{bdjO_rp<(;TWXDn`CIeK2pcQe$7> zL}*~iFR_jW>bJ3urZ)R7=CY5m{}V9o)hTv9Zvde;+{#AdS$%h_?MW$g=UR=8%~oR_ z-rMzdL&_KX2&X`eo^a$`|2~C|dr&yKI%cw3l#IqKYU&KfKxm1Q=fQsk$Zy`YpfJB3$oXTWg#7g~)T&-xyi)lWi9p7H literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/microsoft.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/microsoft.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b131e0b2130b32bd45390de9c80ecdef915c8360 GIT binary patch literal 2865 zcmbVOOLODI7FM?;OLpwU^C%!ZZi#}8U~JC}1I17jg-HxdF(gyArvPnK8g<)IqKBuu zoe-770<-0A?}lQ}U*RWc%WD1tt9wq{lUK1qu0H)br~CAIe8(TOT8%gUWWie-hVhqi z>9b(`IW)Nogc*#aMu0s{Nk#*jnSn_RW-=?aGCQzgY_nQg%bdU=##aV&nEQ*tT<$E* zz-44)`1KcXlCkI0YGaqj1>*xD3Q@HOKh6@t*(jOjk(vwsf&!aQs?Gjf9gK@3o@>Sz zWVPJ*B9eR`_wL7d;L8uCK8GfUK)4YQW(1Uxz+^PAm>Jm23Tn&_999E`oGA(F%w_do zjG)09YyJON(iech5PBJWX=mjf<=YEi3X~5#bwp$M?H;dpImaQu6YXeDEGtUr}pbcZ}*G1?428=3=cADU!97RDhqvLpn1x-`yA=&@N#T(KACl0aY6Ge=YK zqzvRTR%m!7tVHxot7rM%;1 zbE!PR!Qr_#2YtOHmnzC*?pu|`A{AB60la_Wb zH-gss@Q`C*35aO$-EovAa2yZebj5?)fgslv;r1ntYSa4yo|Hr^S6r?mFrED}*B zgQi|x>lJKY<%uDPd9IQumDR2O&dy*o3dj534EDlr2jj2zcOmkM5Jh~uvLsI@@Gvoz zRqg0R_qNthv_b7Z4DkwdfB)2*iR2jc?j1*>Hz`uIM{go}9~|Aw#q$Rb@3NAW59Nc8 zj~+bC9)9%TE_~CxH}1`nS?Ae2Nm=K^JQg3TyU%;Fh(WgwYS4)b!Pg7$NEknhVu&Cn zd*=b$ot;*#D=*vzl_ctA&?a%IMQX%_RwE6wL7Jp|>so$vuSmxxO2UUdChHC~*#NSj zhk!3jj2K#a5WK5^0G|}|TzLueBI(WZ1RMvGiY5%u!JKmCo8l(OR}SMUN>Zt9r!BW4 zuj1wgFoqamVZz?SEAeiFmTz3w_7Y#Ld5{en=8e|%BN!|Q>dNSn>JjG1)fLEw^`4C* zR{PC{cRA&~;iXt#>+4NKk$LBOcq@$X)(;0fO5`k!PWPgWS4L%J@W!A%O^$i4mmCSn z1A7opED=F(3n_87UWeZA-|cd#++ZxkzpB%C_14> z4e^*5XBIKawgPwp;GsX0?~EUyxG?w3`pNp<$m#bm12fl4fV~SyK^XC#oq7;B;xx&p z7s`5IuHz);w}F7~HSl4IUP^$v4t}%+2C&48F(@S#p+G5V zP?z2&<@R|Pt{r(Ub?(6$2%-u)8NP;rE(8RM0y(3Q#N?1d5y2j6?l$;rVJS=5hxXD~ zk|QE^v79VSW&*#xfO-r(<^p`}+ofFlO&xHIe*kz-1H1=@_Ha>$y#|ImUNli)Se`@t zqgT~I1Sp{z-ugQLxX26T@zo!TJ02V%_yj7&0kS8+Jn>}F1r%}Cz)&?-%rY!;Shn@u z0?q3?D=#2pu(85W4JUdR<&=m*U9ij~SCyp_NV?YgzEzd-Yb$pl{8Sd6q=m)%Fb{6# z{A9%zzN}@}A#4-FK!d5)YXD*rL&PuvhXHxp&|Fgf=ei58itYJZl^bdx2*auohFQVp zDTi@044=;<4Hq!8G;C~*cJ`kP!qMPKf7l=I4}%;1r%=U)kAnXA(f(feboe-EU#W7i zB5vS|Hiv`VN5jF+I2?`p<3Z5c9emk;`WU8oq<9T@#G6RoMdBm5hvXt|^$5{hxIkLa zBucpuv=-Gr7}cnI!?j%3-O^lsUG#t_Fb_}EMY^DaFxwz=rB0o|*8jT%qPmv*r)w+s U)0J^P!#_|Gm2hbrs$SFiKTRwg+W-In literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mineid.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mineid.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d0f99de5f84a29c713aaa394601f7d151a68bcd6 GIT binary patch literal 1835 zcmb7F-EP}96ecCvQ5@&LP4|NZMzH`futeFK4b3nN^%7$Vw%UR10t0UdMPwW$wxp19 zgKF88bGtX#3+z?)2)y0ZUZGbzM{=_`4K|Dh4=L(9;&;AtNI#pOuYCN6V8$WjPjaWh zGkF1B*T4uO=zt6~)ly&{J=WT4B#X7BPFGe>HO{~I9W}E*C3j(P%Phcp#+G-l_7Q zU3?y?%iX;lk%??i?>@iS-5c(GwYvkYA9+1536rgpQ8*A=Uq*p?p6*Zs(cd00tSzTMl6?RpWIE@_I>QFa)?Lu+R$|c%r!X& z^~My`qAe`J$sT^{*Bvzr2vhZ^lHseqHsy;e9CKX?Q$^@(K@gl*af3WxG+UeEb_;0* zm_uyH3VWWyvAgW{9?n5UPGl{(~t`LSIn!^%GMvQ9zO{^+(0eDS0J2n;wyis zA*YW~lR{2DQUhMRfa3zo=Q@ri(dLfEN&NK8*nqnip+eQM;tJ(ym1a+-qWed_01q`b z98rlpI`q#qho~^VLU+ro@W!j&VY_q08oi@-i@)mps2%~fPV*$(qhXfcLw^L|LU1cv zpQ7|0^#3ti=8gURX1B|G?H`)|No?sp7}NQV*L0EIM^4eYEeDJX1rtVxHkXK2~Doa24MIh-awjKyex^D5_;BY!Y?GsC%v16X!&K|O&z z)Q4c$>Xa-mn@|)7Y_YrFK5p`E^SIGz^x7R(Y5vsfH26WI*HH85yMhLb%eaY(6RFZS z>H}Ou{qkV$ln~x#3JB0Sn18V{dlI{EIzH zV6-{Gwcns$fbYSHkKk)h{R%zxc{Uqu=%F6HpWpb|&+qr%o4K>L7Jc_cvd=z3f1#y8 zKz|HkegKe&A&I4z;>=?nM!z8GOY#v(qP!12^JTD)x~*DA&ysg>T1;hUxIL{Vw>z&z ze5i8yQ%4rD>4@q<)PXUns?t25w0vJpio9ovI1y=2z=^(ALRJ&yw)c~Qs+^{xQl_G( zo(#=Sujo&zERFug5MCGC=x1Tn{L&a!&V~otV-T|epb*1A#giC9_85^q3nXDJ8L$=E zk}Dq(3uP$VFt=p{lrGry1*5rd7A~&0;nXOdi%iuw7krM7kNXRIKQ1y_I8M|-p_5wG zKmOm&I9A5+s(_?f^80Xq6gvWL1RyPt9T0O9V20kH8J74u`hZ{gC*FyFLg3xI4%M1@ zGk*s3{&aOA14~?-+Lg2$G?NWfCX&>K<7t{Yqm9OiHWhF5b}CNQE@tZos^X4oV>8{r z5~I?w4Ye|5k%J)wzq;)p0{DdV8chG3&{8LFp?LJI&~#j+QfWiSn%+6QmFuI;tqobr zddqCyKiu5Pw(f0iz&Ob1h?Ys&dp%83*}I#^`hK->M3-8m7Z+NcF7QVGZ)=qA?WMD@ zrK*6cxd=g-RRDyq;uiLrr~dwYefwXZI?KOHo~ywS#9Re%`Ip#b9P;c*@`nex4hHp& zMU6$7obQHAQA^&KBJX;3RVL@%%S-X?tg9cYmLchEI3rD_72~Jllm6PqeFm z{>Fi8kR@>OYxn^n*Ga~`g_tZJFAN*opiAru=kRCXoCVF7rQOHT7?+$izMG7M59fRy Mx!M^A1h4x40!=1Zpa1{> literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/monzo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/monzo.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bc85948019c3b7c9094fa3973e4b35381ea3e9c5 GIT binary patch literal 1232 zcmZux!EPHj5G8k4(rPWI4uYTwY7|0mI#}x_Y7rMj5yeqkqqS66%7qB9V0I{bt<_Q{ zS1G!YPs+7_&>!fp^b>gPDZkKDhm@+iKuK^oI~)%AW*+zH{(khyAHd%Eg#1bFv;g(z zICL8Y5TH4kQ${KIj(`XLdjh_oYoB=#j7g{VfudW7n&<`BV$@%jS5H#3?3#z#a~!&d zLJ&rQ;8x0f@K^vot3kl(P-6|Kvk)2(z9*~+O^9%gU=NRInZ0vaRB{u%>PBsB&eGnP z+8j?6MVY0%%w*Ai!_%24VB^SYbGx}Zyei9u{wj{GbN5n|Xa?C$Wp_D?tIu|;i>&K5 z;L1Ff*7-?XN{nZMpGM=!x2XkWTWvc|adzNVB338%|E zcUm;flr9seH*>yKJ3hOI028;@u4&`zQW+nOotg=>$fqV$LN8>2F+)66HLZwH{7%$I zIQ><{3zfa)rHJ2h6;EXjLg{#_;-|CE3w6^wIfe?V6Wx0@>z&L`zU&=iyDZ{KyvPCtoJ>YP3^tYr|FN78 zaK}q_?_%)s>|$^_Nyd}@WT5Wj1=Yt^SO(n@7LLOI46(Iv#SHzx!;WyUdtuA14w1Mu d_vDm)WX^UciVNy86Mnsc_Kp4bXzSbZ{|21DOkw~4 literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/moves.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/moves.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89703b0c71db8cf0f06ad67999d5d0833effda89 GIT binary patch literal 1407 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 K6~}B^P5U3uE|P!% literal 0 HcmV?d00001 diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nk.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nk.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ebfc0a8eb8248dc0a6ac61fa68b75f0ae9e6c9f2 GIT binary patch literal 2611 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