-
+
+
{% 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 0000000..9de6f60
Binary files /dev/null and b/flex/lib/python3.6/site-packages/__pycache__/six.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/DESCRIPTION.rst b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000..a4c5491
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/defusedxml-0.5.0.dist-info/DESCRIPTION.rst
@@ -0,0 +1,851 @@
+===================================================
+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/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 0000000..e9ef314
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/ElementTree.cpython-36.pyc differ
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 0000000..4facc5a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/cElementTree.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/cElementTree.cpython-36.pyc
new file mode 100644
index 0000000..0334a74
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/cElementTree.cpython-36.pyc differ
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 0000000..89471de
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/common.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatbuilder.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatbuilder.cpython-36.pyc
new file mode 100644
index 0000000..3213d8a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatbuilder.cpython-36.pyc differ
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 0000000..a6146ae
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/expatreader.cpython-36.pyc differ
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 0000000..c6ebd92
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/lxml.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/defusedxml/__pycache__/minidom.cpython-36.pyc b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/minidom.cpython-36.pyc
new file mode 100644
index 0000000..17dff0f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/minidom.cpython-36.pyc differ
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 0000000..ab71987
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/pulldom.cpython-36.pyc differ
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 0000000..412321f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/sax.cpython-36.pyc differ
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 0000000..9b536eb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/defusedxml/__pycache__/xmlrpc.cpython-36.pyc differ
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 e00a254..774984f 100644
Binary files a/flex/lib/python3.6/site-packages/django/conf/urls/__pycache__/__init__.cpython-36.pyc and b/flex/lib/python3.6/site-packages/django/conf/urls/__pycache__/__init__.cpython-36.pyc differ
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 ef1df9b..7b8c180 100644
Binary files a/flex/lib/python3.6/site-packages/django/middleware/__pycache__/http.cpython-36.pyc and b/flex/lib/python3.6/site-packages/django/middleware/__pycache__/http.cpython-36.pyc differ
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 7203f75..c86d3eb 100644
Binary files a/flex/lib/python3.6/site-packages/django/views/__pycache__/defaults.cpython-36.pyc and b/flex/lib/python3.6/site-packages/django/views/__pycache__/defaults.cpython-36.pyc differ
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 8cc7b06..ce503a2 100644
Binary files a/flex/lib/python3.6/site-packages/django/views/decorators/__pycache__/http.cpython-36.pyc and b/flex/lib/python3.6/site-packages/django/views/decorators/__pycache__/http.cpython-36.pyc differ
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 0000000..fee578c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/jwt/__pycache__/__main__.cpython-36.pyc b/flex/lib/python3.6/site-packages/jwt/__pycache__/__main__.cpython-36.pyc
new file mode 100644
index 0000000..052bf60
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/__main__.cpython-36.pyc differ
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 0000000..32e28dd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/algorithms.cpython-36.pyc differ
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 0000000..7d638a8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jws.cpython-36.pyc differ
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 0000000..7dcc6cd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/api_jwt.cpython-36.pyc differ
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 0000000..2929735
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/compat.cpython-36.pyc differ
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 0000000..af13e18
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/exceptions.cpython-36.pyc differ
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 0000000..f9155e1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/__pycache__/utils.cpython-36.pyc differ
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 0000000..1d08d6e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/contrib/__pycache__/__init__.cpython-36.pyc differ
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 0000000..00da806
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/__init__.cpython-36.pyc differ
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 0000000..c8d5588
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/py_ecdsa.cpython-36.pyc differ
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 0000000..a696fe1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/jwt/contrib/algorithms/__pycache__/pycrypto.cpython-36.pyc differ
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 0000000..d0d27ec
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/__init__.cpython-36.pyc differ
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 0000000..1f982ea
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/common.cpython-36.pyc differ
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 0000000..57d27f3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/signals.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/__pycache__/uri_validate.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/uri_validate.cpython-36.pyc
new file mode 100644
index 0000000..294a15b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/__pycache__/uri_validate.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/common.py b/flex/lib/python3.6/site-packages/oauthlib/common.py
new file mode 100644
index 0000000..705cbd2
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/oauthlib/common.py
@@ -0,0 +1,460 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.common
+~~~~~~~~~~~~~~
+
+This module provides data structures and utilities common
+to all implementations of OAuth.
+"""
+from __future__ import absolute_import, unicode_literals
+
+import collections
+import datetime
+import logging
+import random
+import re
+import sys
+import time
+
+try:
+ from urllib import quote as _quote
+ from urllib import unquote as _unquote
+ from urllib import urlencode as _urlencode
+except ImportError:
+ from urllib.parse import quote as _quote
+ from urllib.parse import unquote as _unquote
+ from urllib.parse import urlencode as _urlencode
+try:
+ import urlparse
+except ImportError:
+ import urllib.parse as urlparse
+
+UNICODE_ASCII_CHARACTER_SET = ('abcdefghijklmnopqrstuvwxyz'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ '0123456789')
+
+CLIENT_ID_CHARACTER_SET = (r' !"#$%&\'()*+,-./0123456789:;<=>?@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 0000000..104848e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/__pycache__/__init__.cpython-36.pyc differ
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 0000000..0d9bde2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/__init__.cpython-36.pyc differ
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 0000000..ecc1b34
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/errors.cpython-36.pyc differ
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 0000000..b993fb7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/parameters.cpython-36.pyc differ
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 0000000..d8f12ad
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/request_validator.cpython-36.pyc differ
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 0000000..05fb90c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/signature.cpython-36.pyc differ
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 0000000..c367672
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/__pycache__/utils.cpython-36.pyc differ
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 0000000..b495fb9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/access_token.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/access_token.cpython-36.pyc
new file mode 100644
index 0000000..fe055b6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/access_token.cpython-36.pyc differ
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 0000000..598d588
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/authorization.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/base.cpython-36.pyc
new file mode 100644
index 0000000..ec4f0de
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/base.cpython-36.pyc differ
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 0000000..cbe9728
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/pre_configured.cpython-36.pyc differ
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 0000000..7677faf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/request_token.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/resource.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/resource.cpython-36.pyc
new file mode 100644
index 0000000..0ae8490
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/resource.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/signature_only.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/signature_only.cpython-36.pyc
new file mode 100644
index 0000000..da860cd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/__pycache__/signature_only.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
new file mode 100644
index 0000000..12b901c
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/oauthlib/oauth1/rfc5849/endpoints/access_token.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+"""
+oauthlib.oauth1.rfc5849.endpoints.access_token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This module is an implementation of the access token provider logic of
+OAuth 1.0 RFC 5849. It validates the correctness of access 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 AccessTokenEndpoint(BaseEndpoint):
+
+ """An endpoint responsible for providing OAuth 1 access tokens.
+
+ Typical use is to instantiate with a request validator and invoke the
+ ``create_access_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_access_token(self, request, credentials):
+ """Create and save a new access token.
+
+ Similar to OAuth 2, indication of granted scopes will be included as a
+ space separated list in ``oauth_authorized_realms``.
+
+ :param request: An oauthlib.common.Request object.
+ :returns: The token as an urlencoded string.
+ """
+ request.realms = self.request_validator.get_realms(
+ request.resource_owner_key, request)
+ token = {
+ 'oauth_token': self.token_generator(),
+ 'oauth_token_secret': self.token_generator(),
+ # Backport the authorized scopes indication used in OAuth2
+ 'oauth_authorized_realms': ' '.join(request.realms)
+ }
+ token.update(credentials)
+ self.request_validator.save_access_token(token, request)
+ return urlencode(token.items())
+
+ def create_access_token_response(self, uri, http_method='GET', body=None,
+ headers=None, credentials=None):
+ """Create an access 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 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 0000000..60bfaaa
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/__pycache__/__init__.cpython-36.pyc differ
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 0000000..a88a6d9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/errors.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/errors.cpython-36.pyc
new file mode 100644
index 0000000..4039a7f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/errors.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/parameters.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/parameters.cpython-36.pyc
new file mode 100644
index 0000000..1e026c2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/parameters.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/request_validator.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/request_validator.cpython-36.pyc
new file mode 100644
index 0000000..d427c71
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/request_validator.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/tokens.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/tokens.cpython-36.pyc
new file mode 100644
index 0000000..4b1c0e7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/tokens.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/utils.cpython-36.pyc
new file mode 100644
index 0000000..cd50203
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/__pycache__/utils.cpython-36.pyc differ
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 0000000..a6b26f5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/__init__.cpython-36.pyc differ
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 0000000..bb2bca2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/backend_application.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/base.cpython-36.pyc
new file mode 100644
index 0000000..d2f62c4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/base.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/legacy_application.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/legacy_application.cpython-36.pyc
new file mode 100644
index 0000000..cca6653
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/legacy_application.cpython-36.pyc differ
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 0000000..3dbbf7e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/mobile_application.cpython-36.pyc differ
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 0000000..a43f262
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/service_application.cpython-36.pyc differ
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 0000000..9f0be6a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/clients/__pycache__/web_application.cpython-36.pyc differ
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 0000000..baabb75
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/__init__.cpython-36.pyc differ
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 0000000..7163b3e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/authorization.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/base.cpython-36.pyc
new file mode 100644
index 0000000..8f5abf3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/base.cpython-36.pyc differ
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 0000000..b9781cc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/pre_configured.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/resource.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/resource.cpython-36.pyc
new file mode 100644
index 0000000..b0db0cf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/resource.cpython-36.pyc differ
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 0000000..3bc5ece
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/revocation.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/token.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/token.cpython-36.pyc
new file mode 100644
index 0000000..6283ffd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/endpoints/__pycache__/token.cpython-36.pyc differ
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 0000000..27aae96
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/__init__.cpython-36.pyc differ
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 0000000..927905d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/authorization_code.cpython-36.pyc differ
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 0000000..451bd1a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/base.cpython-36.pyc differ
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 0000000..25874e1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/client_credentials.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/implicit.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/implicit.cpython-36.pyc
new file mode 100644
index 0000000..afe3f56
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/implicit.cpython-36.pyc differ
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 0000000..ceff648
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/openid_connect.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/refresh_token.cpython-36.pyc b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/refresh_token.cpython-36.pyc
new file mode 100644
index 0000000..a807cf5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/refresh_token.cpython-36.pyc differ
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 0000000..7bb75ad
Binary files /dev/null and b/flex/lib/python3.6/site-packages/oauthlib/oauth2/rfc6749/grant_types/__pycache__/resource_owner_password_credentials.cpython-36.pyc differ
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 0000000..b719b7f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/__init__.cpython-36.pyc differ
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 0000000..6613249
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/association.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/codecutil.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/codecutil.cpython-36.pyc
new file mode 100644
index 0000000..aa5f3b2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/codecutil.cpython-36.pyc differ
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 0000000..67e5f6a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/cryptutil.cpython-36.pyc differ
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 0000000..dd19ab5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/dh.cpython-36.pyc differ
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 0000000..764a211
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/extension.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/fetchers.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/fetchers.cpython-36.pyc
new file mode 100644
index 0000000..90a7ae4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/fetchers.cpython-36.pyc differ
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 0000000..115484b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/kvform.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/message.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/message.cpython-36.pyc
new file mode 100644
index 0000000..91b434b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/message.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/oidutil.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/oidutil.cpython-36.pyc
new file mode 100644
index 0000000..ee7dbbc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/oidutil.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/__pycache__/sreg.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/__pycache__/sreg.cpython-36.pyc
new file mode 100644
index 0000000..a15152f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/sreg.cpython-36.pyc differ
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 0000000..b45e85d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/__pycache__/urinorm.cpython-36.pyc differ
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 0000000..f2da2ec
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/__init__.cpython-36.pyc differ
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 0000000..2c14e8c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/consumer.cpython-36.pyc differ
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 0000000..3f6af6c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/discover.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/html_parse.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/html_parse.cpython-36.pyc
new file mode 100644
index 0000000..480bf25
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/consumer/__pycache__/html_parse.cpython-36.pyc differ
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
+ ?%(closers)s\s*>
+
+ # 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 0000000..52831de
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/__init__.cpython-36.pyc differ
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 0000000..7c30ed6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/ax.cpython-36.pyc differ
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 0000000..137d9f0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/__pycache__/sreg.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/extensions/ax.py b/flex/lib/python3.6/site-packages/openid/extensions/ax.py
new file mode 100644
index 0000000..d36fd00
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/openid/extensions/ax.py
@@ -0,0 +1,781 @@
+# -*- test-case-name: openid.test.test_ax -*-
+"""Implements the OpenID Attribute Exchange specification, version 1.0.
+
+@since: 2.1.0
+"""
+
+__all__ = [
+ 'AttributeRequest',
+ 'FetchRequest',
+ 'FetchResponse',
+ 'StoreRequest',
+ 'StoreResponse',
+]
+
+from openid import extension
+from openid.server.trustroot import TrustRoot
+from openid.message import NamespaceMap, OPENID_NS
+
+# Use this as the 'count' value for an attribute in a FetchRequest to
+# ask for as many values as the OP can provide.
+UNLIMITED_VALUES = "unlimited"
+
+# Minimum supported alias length in characters. Here for
+# completeness.
+MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
+
+
+def checkAlias(alias):
+ """
+ Check an alias for invalid characters; raise AXError if any are
+ found. Return None if the alias is valid.
+ """
+ if ',' in alias:
+ raise AXError("Alias %r must not contain comma" % (alias, ))
+ if '.' in alias:
+ raise AXError("Alias %r must not contain period" % (alias, ))
+
+
+class AXError(ValueError):
+ """Results from data that does not meet the attribute exchange 1.0
+ specification"""
+
+
+class NotAXMessage(AXError):
+ """Raised when there is no Attribute Exchange mode in the message."""
+
+ def __repr__(self):
+ return self.__class__.__name__
+
+ def __str__(self):
+ return self.__class__.__name__
+
+
+class AXMessage(extension.Extension):
+ """Abstract class containing common code for attribute exchange messages
+
+ @cvar ns_alias: The preferred namespace alias for attribute
+ exchange messages
+
+ @cvar mode: The type of this attribute exchange message. This must
+ be overridden in subclasses.
+ """
+
+ # This class is abstract, so it's OK that it doesn't override the
+ # abstract method in Extension:
+ #
+ #pylint:disable-msg=W0223
+
+ ns_alias = 'ax'
+ ns_uri = 'http://openid.net/srv/ax/1.0'
+ mode = None # NOTE mode is only ever set to a str value, see below
+
+ def _checkMode(self, ax_args):
+ """Raise an exception if the mode in the attribute exchange
+ arguments does not match what is expected for this class.
+
+ @raises NotAXMessage: When there is no mode value in ax_args at all.
+
+ @raises AXError: When mode does not match.
+ """
+ mode = ax_args.get('mode')
+ if isinstance(mode, bytes):
+ mode = str(mode, encoding="utf-8")
+ if mode != self.mode:
+ if not mode:
+ raise NotAXMessage()
+ else:
+ raise AXError('Expected mode %r; got %r' % (self.mode, mode))
+
+ def _newArgs(self):
+ """Return a set of attribute exchange arguments containing the
+ basic information that must be in every attribute exchange
+ message.
+ """
+ return {'mode': self.mode}
+
+
+class AttrInfo(object):
+ """Represents a single attribute in an attribute exchange
+ request. This should be added to an AXRequest object in order to
+ request the attribute.
+
+ @ivar required: Whether the attribute will be marked as required
+ when presented to the subject of the attribute exchange
+ request.
+ @type required: bool
+
+ @ivar count: How many values of this type to request from the
+ subject. Defaults to one.
+ @type count: int
+
+ @ivar type_uri: The identifier that determines what the attribute
+ represents and how it is serialized. For example, one type URI
+ representing dates could represent a Unix timestamp in base 10
+ and another could represent a human-readable string.
+ @type type_uri: str
+
+ @ivar alias: The name that should be given to this alias in the
+ request. If it is not supplied, a generic name will be
+ assigned. For example, if you want to call a Unix timestamp
+ value 'tstamp', set its alias to that value. If two attributes
+ in the same message request to use the same alias, the request
+ will fail to be generated.
+ @type alias: str or NoneType
+ """
+
+ # It's OK that this class doesn't have public methods (it's just a
+ # holder for a bunch of attributes):
+ #
+ #pylint:disable-msg=R0903
+
+ def __init__(self, type_uri, count=1, required=False, alias=None):
+ self.required = required
+ self.count = count
+ self.type_uri = type_uri
+ self.alias = alias
+
+ if self.alias is not None:
+ checkAlias(self.alias)
+
+ def wantsUnlimitedValues(self):
+ """
+ When processing a request for this attribute, the OP should
+ call this method to determine whether all available attribute
+ values were requested. If self.count == UNLIMITED_VALUES,
+ this returns True. Otherwise this returns False, in which
+ case self.count is an integer.
+ """
+ return self.count == UNLIMITED_VALUES
+
+
+def toTypeURIs(namespace_map, alias_list_s):
+ """Given a namespace mapping and a string containing a
+ comma-separated list of namespace aliases, return a list of type
+ URIs that correspond to those aliases.
+
+ @param namespace_map: The mapping from namespace URI to alias
+ @type namespace_map: openid.message.NamespaceMap
+
+ @param alias_list_s: The string containing the comma-separated
+ list of aliases. May also be None for convenience.
+ @type alias_list_s: str or NoneType
+
+ @returns: The list of namespace URIs that corresponds to the
+ supplied list of aliases. If the string was zero-length or
+ None, an empty list will be returned.
+
+ @raise KeyError: If an alias is present in the list of aliases but
+ is not present in the namespace map.
+ """
+ uris = []
+
+ if alias_list_s:
+ for alias in alias_list_s.split(','):
+ type_uri = namespace_map.getNamespaceURI(alias)
+ if type_uri is None:
+ raise KeyError('No type is defined for attribute name %r' %
+ (alias, ))
+ else:
+ uris.append(type_uri)
+
+ return uris
+
+
+class FetchRequest(AXMessage):
+ """An attribute exchange 'fetch_request' message. This message is
+ sent by a relying party when it wishes to obtain attributes about
+ the subject of an OpenID authentication request.
+
+ @ivar requested_attributes: The attributes that have been
+ requested thus far, indexed by the type URI.
+ @type requested_attributes: {str:AttrInfo}
+
+ @ivar update_url: A URL that will accept responses for this
+ attribute exchange request, even in the absence of the user
+ who made this request.
+ """
+ mode = 'fetch_request'
+
+ def __init__(self, update_url=None):
+ AXMessage.__init__(self)
+ self.requested_attributes = {}
+ self.update_url = update_url
+
+ def add(self, attribute):
+ """Add an attribute to this attribute exchange request.
+
+ @param attribute: The attribute that is being requested
+ @type attribute: C{L{AttrInfo}}
+
+ @returns: None
+
+ @raise KeyError: when the requested attribute is already
+ present in this fetch request.
+ """
+ if attribute.type_uri in self.requested_attributes:
+ raise KeyError('The attribute %r has already been requested' %
+ (attribute.type_uri, ))
+
+ self.requested_attributes[attribute.type_uri] = attribute
+
+ def getExtensionArgs(self):
+ """Get the serialized form of this attribute fetch request.
+
+ @returns: The fetch request message parameters
+ @rtype: {unicode:unicode}
+ """
+ aliases = NamespaceMap()
+
+ required = []
+ if_available = []
+
+ ax_args = self._newArgs()
+
+ for type_uri, attribute in self.requested_attributes.items():
+ if attribute.alias is None:
+ alias = aliases.add(type_uri)
+ else:
+ # This will raise an exception when the second
+ # attribute with the same alias is added. I think it
+ # would be better to complain at the time that the
+ # attribute is added to this object so that the code
+ # that is adding it is identified in the stack trace,
+ # but it's more work to do so, and it won't be 100%
+ # accurate anyway, since the attributes are
+ # mutable. So for now, just live with the fact that
+ # we'll learn about the error later.
+ #
+ # The other possible approach is to hide the error and
+ # generate a new alias on the fly. I think that would
+ # probably be bad.
+ alias = aliases.addAlias(type_uri, attribute.alias)
+
+ if attribute.required:
+ required.append(alias)
+ else:
+ if_available.append(alias)
+
+ if attribute.count != 1:
+ ax_args['count.' + alias] = str(attribute.count)
+
+ ax_args['type.' + alias] = type_uri
+
+ if required:
+ ax_args['required'] = ','.join(required)
+
+ if if_available:
+ ax_args['if_available'] = ','.join(if_available)
+
+ return ax_args
+
+ def getRequiredAttrs(self):
+ """Get the type URIs for all attributes that have been marked
+ as required.
+
+ @returns: A list of the type URIs for attributes that have
+ been marked as required.
+ @rtype: [str]
+ """
+ required = []
+ for type_uri, attribute in self.requested_attributes.items():
+ if attribute.required:
+ required.append(type_uri)
+
+ return required
+
+ def fromOpenIDRequest(cls, openid_request):
+ """Extract a FetchRequest from an OpenID message
+
+ @param openid_request: The OpenID authentication request
+ containing the attribute fetch request
+ @type openid_request: C{L{openid.server.server.CheckIDRequest}}
+
+ @rtype: C{L{FetchRequest}} or C{None}
+ @returns: The FetchRequest extracted from the message or None, if
+ the message contained no AX extension.
+
+ @raises KeyError: if the AuthRequest is not consistent in its use
+ of namespace aliases.
+
+ @raises AXError: When parseExtensionArgs would raise same.
+
+ @see: L{parseExtensionArgs}
+ """
+ message = openid_request.message
+ ax_args = message.getArgs(cls.ns_uri)
+ self = cls()
+ try:
+ self.parseExtensionArgs(ax_args)
+ except NotAXMessage as err:
+ return None
+
+ if self.update_url:
+ # Update URL must match the openid.realm of the underlying
+ # OpenID 2 message.
+ realm = message.getArg(OPENID_NS, 'realm',
+ message.getArg(OPENID_NS, 'return_to'))
+
+ if not realm:
+ raise AXError(
+ ("Cannot validate update_url %r " + "against absent realm")
+ % (self.update_url, ))
+
+ tr = TrustRoot.parse(realm)
+ if not tr.validateURL(self.update_url):
+ raise AXError(
+ "Update URL %r failed validation against realm %r" %
+ (self.update_url, realm, ))
+
+ return self
+
+ fromOpenIDRequest = classmethod(fromOpenIDRequest)
+
+ def parseExtensionArgs(self, ax_args):
+ """Given attribute exchange arguments, populate this FetchRequest.
+
+ @param ax_args: Attribute Exchange arguments from the request.
+ As returned from L{Message.getArgs}.
+ @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 0000000..274a933
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/__init__.cpython-36.pyc differ
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 0000000..aa88a18
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape2.cpython-36.pyc differ
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 0000000..794ec36
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/extensions/draft/__pycache__/pape5.cpython-36.pyc differ
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 0000000..5da49cb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/server/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/server/__pycache__/server.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/server/__pycache__/server.cpython-36.pyc
new file mode 100644
index 0000000..e91b011
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/server/__pycache__/server.cpython-36.pyc differ
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 0000000..a9a4daa
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/server/__pycache__/trustroot.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/server/server.py b/flex/lib/python3.6/site-packages/openid/server/server.py
new file mode 100644
index 0000000..a9e7325
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/openid/server/server.py
@@ -0,0 +1,1822 @@
+# -*- test-case-name: openid.test.test_server -*-
+"""OpenID server protocol and logic.
+
+Overview
+========
+
+ An OpenID server must perform three tasks:
+
+ 1. Examine the incoming request to determine its nature and validity.
+
+ 2. Make a decision about how to respond to this request.
+
+ 3. Format the response according to the protocol.
+
+ The first and last of these tasks may performed by
+ the L{decodeRequest} 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 0000000..8710ee1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/__init__.cpython-36.pyc differ
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 0000000..2283f60
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/filestore.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/interface.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/interface.cpython-36.pyc
new file mode 100644
index 0000000..9c11dc5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/interface.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/store/__pycache__/memstore.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/store/__pycache__/memstore.cpython-36.pyc
new file mode 100644
index 0000000..f963646
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/memstore.cpython-36.pyc differ
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 0000000..62f2fd1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/nonce.cpython-36.pyc differ
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 0000000..0ed7de2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/store/__pycache__/sqlstore.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/store/filestore.py b/flex/lib/python3.6/site-packages/openid/store/filestore.py
new file mode 100644
index 0000000..99ee485
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/openid/store/filestore.py
@@ -0,0 +1,397 @@
+"""
+This module contains an C{L{OpenIDStore}} implementation backed by
+flat files.
+"""
+
+import string
+import os
+import os.path
+import time
+import logging
+
+from errno import EEXIST, ENOENT
+
+from tempfile import mkstemp
+
+from openid.association import Association
+from openid.store.interface import OpenIDStore
+from openid.store import nonce
+from openid import cryptutil, oidutil
+
+_filename_allowed = string.ascii_letters + string.digits + '.'
+_isFilenameSafe = set(_filename_allowed).__contains__
+
+
+def _safe64(s):
+ h64 = oidutil.toBase64(cryptutil.sha1(s))
+ # to be able to manipulate it, make it a bytearray
+ h64 = bytearray(h64)
+ h64 = h64.replace(b'+', b'_')
+ h64 = h64.replace(b'/', b'.')
+ h64 = h64.replace(b'=', b'')
+ return bytes(h64)
+
+
+def _filenameEscape(s):
+ filename_chunks = []
+ for c in s:
+ if _isFilenameSafe(c):
+ filename_chunks.append(c)
+ else:
+ filename_chunks.append('_%02X' % ord(c))
+ return ''.join(filename_chunks)
+
+
+def _removeIfPresent(filename):
+ """Attempt to remove a file, returning whether the file existed at
+ the time of the call.
+
+ str -> 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 0000000..317887a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/__init__.cpython-36.pyc differ
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 0000000..02976ca
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/accept.cpython-36.pyc differ
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 0000000..8b000e1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/constants.cpython-36.pyc differ
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 0000000..4d6351e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/discover.cpython-36.pyc differ
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 0000000..e7736dd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/etxrd.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/filters.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/filters.cpython-36.pyc
new file mode 100644
index 0000000..aa5cfc5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/filters.cpython-36.pyc differ
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 0000000..c89fe5b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/manager.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/parsehtml.cpython-36.pyc b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/parsehtml.cpython-36.pyc
new file mode 100644
index 0000000..68d0937
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/parsehtml.cpython-36.pyc differ
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 0000000..bc30e93
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/services.cpython-36.pyc differ
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 0000000..bdee016
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xri.cpython-36.pyc differ
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 0000000..600bdda
Binary files /dev/null and b/flex/lib/python3.6/site-packages/openid/yadis/__pycache__/xrires.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/openid/yadis/accept.py b/flex/lib/python3.6/site-packages/openid/yadis/accept.py
new file mode 100644
index 0000000..2f18aa6
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/openid/yadis/accept.py
@@ -0,0 +1,137 @@
+"""Functions for generating and parsing HTTP Accept: headers for
+supporting server-directed content negotiation.
+"""
+
+
+def generateAcceptHeader(*elements):
+ """Generate an accept header value
+
+ [str or (str, float)] -> 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 0000000..5d2f08a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/__init__.cpython-36.pyc differ
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 0000000..324f250
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_auth.cpython-36.pyc differ
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 0000000..6150300
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth1_session.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_auth.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_auth.cpython-36.pyc
new file mode 100644
index 0000000..b535726
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_auth.cpython-36.pyc differ
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 0000000..cbef40d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/__pycache__/oauth2_session.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__init__.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__init__.py
new file mode 100644
index 0000000..65e49c1
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__init__.py
@@ -0,0 +1,8 @@
+from __future__ import absolute_import
+
+from .facebook import facebook_compliance_fix
+from .fitbit import fitbit_compliance_fix
+from .linkedin import linkedin_compliance_fix
+from .slack import slack_compliance_fix
+from .mailchimp import mailchimp_compliance_fix
+from .weibo import weibo_compliance_fix
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..7ba8cc2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/__init__.cpython-36.pyc differ
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 0000000..fe992cc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/douban.cpython-36.pyc differ
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 0000000..2eca1c9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/facebook.cpython-36.pyc differ
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 0000000..5522fa5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/fitbit.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/linkedin.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/linkedin.cpython-36.pyc
new file mode 100644
index 0000000..dc669fa
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/linkedin.cpython-36.pyc differ
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 0000000..cd78334
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/mailchimp.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/slack.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/slack.cpython-36.pyc
new file mode 100644
index 0000000..17c2b58
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/slack.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/weibo.cpython-36.pyc b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/weibo.cpython-36.pyc
new file mode 100644
index 0000000..3b520f6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/__pycache__/weibo.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/douban.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/douban.py
new file mode 100644
index 0000000..2e45b3b
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/douban.py
@@ -0,0 +1,18 @@
+import json
+
+from oauthlib.common import to_unicode
+
+
+def douban_compliance_fix(session):
+
+ def fix_token_type(r):
+ token = json.loads(r.text)
+ token.setdefault('token_type', 'Bearer')
+ fixed_token = json.dumps(token)
+ r._content = to_unicode(fixed_token).encode('utf-8')
+ return r
+
+ session._client_default_token_placement = 'query'
+ session.register_compliance_hook('access_token_response', fix_token_type)
+
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/facebook.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/facebook.py
new file mode 100644
index 0000000..07181c3
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/facebook.py
@@ -0,0 +1,33 @@
+from json import dumps
+try:
+ from urlparse import parse_qsl
+except ImportError:
+ from urllib.parse import parse_qsl
+
+from oauthlib.common import to_unicode
+
+
+def facebook_compliance_fix(session):
+
+ def _compliance_fix(r):
+ # if Facebook claims to be sending us json, let's trust them.
+ if 'application/json' in r.headers.get('content-type', {}):
+ return r
+
+ # Facebook returns a content-type of text/plain when sending their
+ # x-www-form-urlencoded responses, along with a 200. If not, let's
+ # assume we're getting JSON and bail on the fix.
+ if 'text/plain' in r.headers.get('content-type', {}) and r.status_code == 200:
+ token = dict(parse_qsl(r.text, keep_blank_values=True))
+ else:
+ return r
+
+ expires = token.get('expires')
+ if expires is not None:
+ token['expires_in'] = expires
+ token['token_type'] = 'Bearer'
+ r._content = to_unicode(dumps(token)).encode('UTF-8')
+ return r
+
+ session.register_compliance_hook('access_token_response', _compliance_fix)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/fitbit.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/fitbit.py
new file mode 100644
index 0000000..cec533b
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/fitbit.py
@@ -0,0 +1,26 @@
+"""
+The Fitbit API breaks from the OAuth2 RFC standard by returning an "errors"
+object list, rather than a single "error" string. This puts hooks in place so
+that oauthlib can process an error in the results from access token and refresh
+token responses. This is necessary to prevent getting the generic red herring
+MissingTokenError.
+"""
+
+from json import loads, dumps
+
+from oauthlib.common import to_unicode
+
+
+def fitbit_compliance_fix(session):
+
+ def _missing_error(r):
+ token = loads(r.text)
+ if 'errors' in token:
+ # Set the error to the first one we have
+ token['error'] = token['errors'][0]['errorType']
+ r._content = to_unicode(dumps(token)).encode('UTF-8')
+ return r
+
+ session.register_compliance_hook('access_token_response', _missing_error)
+ session.register_compliance_hook('refresh_token_response', _missing_error)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/linkedin.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/linkedin.py
new file mode 100644
index 0000000..e697ced
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/linkedin.py
@@ -0,0 +1,24 @@
+from json import loads, dumps
+
+from oauthlib.common import add_params_to_uri, to_unicode
+
+
+def linkedin_compliance_fix(session):
+
+ def _missing_token_type(r):
+ token = loads(r.text)
+ token['token_type'] = 'Bearer'
+ r._content = to_unicode(dumps(token)).encode('UTF-8')
+ return r
+
+ def _non_compliant_param_name(url, headers, data):
+ token = [('oauth2_access_token', session.access_token)]
+ url = add_params_to_uri(url, token)
+ return url, headers, data
+
+ session._client.default_token_placement = 'query'
+ session.register_compliance_hook('access_token_response',
+ _missing_token_type)
+ session.register_compliance_hook('protected_request',
+ _non_compliant_param_name)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/mailchimp.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/mailchimp.py
new file mode 100644
index 0000000..ee9bc94
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/mailchimp.py
@@ -0,0 +1,22 @@
+import json
+
+from oauthlib.common import to_unicode
+
+def mailchimp_compliance_fix(session):
+ def _null_scope(r):
+ token = json.loads(r.text)
+ if 'scope' in token and token['scope'] is None:
+ token.pop('scope')
+ r._content = to_unicode(json.dumps(token)).encode('utf-8')
+ return r
+
+ def _non_zero_expiration(r):
+ token = json.loads(r.text)
+ if 'expires_in' in token and token['expires_in'] == 0:
+ token['expires_in'] = 3600
+ r._content = to_unicode(json.dumps(token)).encode('utf-8')
+ return r
+
+ session.register_compliance_hook('access_token_response', _null_scope)
+ session.register_compliance_hook('access_token_response', _non_zero_expiration)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/slack.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/slack.py
new file mode 100644
index 0000000..ab2d938
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/slack.py
@@ -0,0 +1,37 @@
+try:
+ from urlparse import urlparse, parse_qs
+except ImportError:
+ from urllib.parse import urlparse, parse_qs
+
+from oauthlib.common import add_params_to_uri
+
+
+def slack_compliance_fix(session):
+ def _non_compliant_param_name(url, headers, data):
+ # If the user has already specified the token, either in the URL
+ # or in a data dictionary, then there's nothing to do.
+ # If the specified token is different from ``session.access_token``,
+ # we assume the user intends to override the access token.
+ url_query = dict(parse_qs(urlparse(url).query))
+ token = url_query.get("token")
+ if not token and isinstance(data, dict):
+ token = data.get("token")
+
+ if token:
+ # Nothing to do, just return.
+ return url, headers, data
+
+ if not data:
+ data = {"token": session.access_token}
+ elif isinstance(data, dict):
+ data["token"] = session.access_token
+ else:
+ # ``data`` is something other than a dict: maybe a stream,
+ # maybe a file object, maybe something else. We can't easily
+ # modify it, so we'll set the token by modifying the URL instead.
+ token = [('token', session.access_token)]
+ url = add_params_to_uri(url, token)
+ return url, headers, data
+
+ session.register_compliance_hook('protected_request', _non_compliant_param_name)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/weibo.py b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/weibo.py
new file mode 100644
index 0000000..28aca32
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/compliance_fixes/weibo.py
@@ -0,0 +1,17 @@
+from json import loads, dumps
+
+from oauthlib.common import to_unicode
+
+
+def weibo_compliance_fix(session):
+
+ def _missing_token_type(r):
+ token = loads(r.text)
+ token['token_type'] = 'Bearer'
+ r._content = to_unicode(dumps(token)).encode('UTF-8')
+ return r
+
+ session._client.default_token_placement = 'query'
+ session.register_compliance_hook('access_token_response',
+ _missing_token_type)
+ return session
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_auth.py b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_auth.py
new file mode 100644
index 0000000..4626384
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_auth.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import logging
+
+from oauthlib.common import extract_params
+from oauthlib.oauth1 import Client, SIGNATURE_HMAC, SIGNATURE_TYPE_AUTH_HEADER
+from oauthlib.oauth1 import SIGNATURE_TYPE_BODY
+from requests.compat import is_py3
+from requests.utils import to_native_string
+from requests.auth import AuthBase
+
+CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded'
+CONTENT_TYPE_MULTI_PART = 'multipart/form-data'
+
+if is_py3:
+ unicode = str
+
+log = logging.getLogger(__name__)
+
+# OBS!: Correct signing of requests are conditional on invoking OAuth1
+# as the last step of preparing a request, or at least having the
+# content-type set properly.
+class OAuth1(AuthBase):
+ """Signs the request using OAuth 1 (RFC5849)"""
+
+ client_class = Client
+
+ 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,
+ decoding='utf-8',
+ client_class=None,
+ force_include_body=False,
+ **kwargs):
+
+ try:
+ signature_type = signature_type.upper()
+ except AttributeError:
+ pass
+
+ client_class = client_class or self.client_class
+
+ self.force_include_body = force_include_body
+
+ self.client = client_class(client_key, client_secret, resource_owner_key,
+ resource_owner_secret, callback_uri, signature_method,
+ signature_type, rsa_key, verifier, decoding=decoding, **kwargs)
+
+ def __call__(self, r):
+ """Add OAuth parameters to the request.
+
+ Parameters may be included from the body if the content-type is
+ urlencoded, if no content type is set a guess is made.
+ """
+ # Overwriting url is safe here as request will not modify it past
+ # this point.
+ log.debug('Signing request %s using client %s', r, self.client)
+
+ content_type = r.headers.get('Content-Type', '')
+ if (not content_type and extract_params(r.body)
+ or self.client.signature_type == SIGNATURE_TYPE_BODY):
+ content_type = CONTENT_TYPE_FORM_URLENCODED
+ if not isinstance(content_type, unicode):
+ content_type = content_type.decode('utf-8')
+
+ is_form_encoded = (CONTENT_TYPE_FORM_URLENCODED in content_type)
+
+ log.debug('Including body in call to sign: %s',
+ is_form_encoded or self.force_include_body)
+
+ if is_form_encoded:
+ r.headers['Content-Type'] = CONTENT_TYPE_FORM_URLENCODED
+ r.url, headers, r.body = self.client.sign(
+ unicode(r.url), unicode(r.method), r.body or '', r.headers)
+ elif self.force_include_body:
+ # To allow custom clients to work on non form encoded bodies.
+ r.url, headers, r.body = self.client.sign(
+ unicode(r.url), unicode(r.method), r.body or '', r.headers)
+ else:
+ # Omit body data in the signing of non form-encoded requests
+ r.url, headers, _ = self.client.sign(
+ unicode(r.url), unicode(r.method), None, r.headers)
+
+ r.prepare_headers(headers)
+ r.url = to_native_string(r.url)
+ log.debug('Updated url: %s', r.url)
+ log.debug('Updated headers: %s', headers)
+ log.debug('Updated body: %r', r.body)
+ return r
diff --git a/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_session.py b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_session.py
new file mode 100644
index 0000000..61e4c41
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/requests_oauthlib/oauth1_session.py
@@ -0,0 +1,378 @@
+from __future__ import unicode_literals
+
+try:
+ from urlparse import urlparse
+except ImportError:
+ from urllib.parse import urlparse
+
+import logging
+
+from oauthlib.common import add_params_to_uri
+from oauthlib.common import urldecode as _urldecode
+from oauthlib.oauth1 import (
+ SIGNATURE_HMAC, SIGNATURE_RSA, SIGNATURE_TYPE_AUTH_HEADER
+)
+import requests
+
+from . import OAuth1
+
+import sys
+if sys.version > "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
+
+[](https://travis-ci.org/python-social-auth/social-app-django)
+[](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.
+
+[](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
+
+[](https://travis-ci.org/python-social-auth/social-app-django)
+[](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.
+
+[](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
+
+[](https://travis-ci.org/python-social-auth/social-core)
+[](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.
+
+[](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
+
+[](https://travis-ci.org/python-social-auth/social-core)
+[](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.
+
+[](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 0000000..65790a6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/__pycache__/actions.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/__pycache__/actions.cpython-36.pyc
new file mode 100644
index 0000000..5f5cc10
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/actions.cpython-36.pyc differ
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 0000000..2b50311
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/exceptions.cpython-36.pyc differ
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 0000000..7ca4f97
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/storage.cpython-36.pyc differ
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 0000000..3226275
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/store.cpython-36.pyc differ
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 0000000..d162fa5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/strategy.cpython-36.pyc differ
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 0000000..471bab8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/__pycache__/utils.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/actions.py b/flex/lib/python3.6/site-packages/social_core/actions.py
new file mode 100644
index 0000000..8cc4862
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/actions.py
@@ -0,0 +1,127 @@
+from six.moves.urllib_parse import quote
+
+from .utils import sanitize_redirect, user_is_authenticated, \
+ user_is_active, partial_pipeline_data, setting_url
+
+
+def do_auth(backend, redirect_name='next'):
+ # Save any defined next value into session
+ data = backend.strategy.request_data(merge=False)
+
+ # Save extra data into session.
+ for field_name in backend.setting('FIELDS_STORED_IN_SESSION', []):
+ if field_name in data:
+ backend.strategy.session_set(field_name, data[field_name])
+
+ if redirect_name in data:
+ # Check and sanitize a user-defined GET/POST next field value
+ redirect_uri = data[redirect_name]
+ if backend.setting('SANITIZE_REDIRECTS', True):
+ allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \
+ [backend.strategy.request_host()]
+ redirect_uri = sanitize_redirect(allowed_hosts, redirect_uri)
+ backend.strategy.session_set(
+ redirect_name,
+ redirect_uri or backend.setting('LOGIN_REDIRECT_URL')
+ )
+ return backend.start()
+
+
+def do_complete(backend, login, user=None, redirect_name='next',
+ *args, **kwargs):
+ data = backend.strategy.request_data()
+
+ is_authenticated = user_is_authenticated(user)
+ user = user if is_authenticated else None
+
+ partial = partial_pipeline_data(backend, user, *args, **kwargs)
+ if partial:
+ user = backend.continue_pipeline(partial)
+ else:
+ user = backend.complete(user=user, *args, **kwargs)
+
+ # pop redirect value before the session is trashed on login(), but after
+ # the pipeline so that the pipeline can change the redirect if needed
+ redirect_value = backend.strategy.session_get(redirect_name, '') or \
+ data.get(redirect_name, '')
+
+ # check if the output value is something else than a user and just
+ # return it to the client
+ user_model = backend.strategy.storage.user.user_model()
+ if user and not isinstance(user, user_model):
+ return user
+
+ if is_authenticated:
+ if not user:
+ url = setting_url(backend, redirect_value, 'LOGIN_REDIRECT_URL')
+ else:
+ url = setting_url(backend, redirect_value,
+ 'NEW_ASSOCIATION_REDIRECT_URL',
+ 'LOGIN_REDIRECT_URL')
+ elif user:
+ if user_is_active(user):
+ # catch is_new/social_user in case login() resets the instance
+ is_new = getattr(user, 'is_new', False)
+ social_user = user.social_user
+ login(backend, user, social_user)
+ # store last login backend name in session
+ backend.strategy.session_set('social_auth_last_login_backend',
+ social_user.provider)
+
+ if is_new:
+ url = setting_url(backend,
+ 'NEW_USER_REDIRECT_URL',
+ redirect_value,
+ 'LOGIN_REDIRECT_URL')
+ else:
+ url = setting_url(backend, redirect_value,
+ 'LOGIN_REDIRECT_URL')
+ else:
+ if backend.setting('INACTIVE_USER_LOGIN', False):
+ social_user = user.social_user
+ login(backend, user, social_user)
+ url = setting_url(backend, 'INACTIVE_USER_URL', 'LOGIN_ERROR_URL',
+ 'LOGIN_URL')
+ else:
+ url = setting_url(backend, 'LOGIN_ERROR_URL', 'LOGIN_URL')
+
+ if redirect_value and redirect_value != url:
+ redirect_value = quote(redirect_value)
+ url += ('&' if '?' in url else '?') + \
+ '{0}={1}'.format(redirect_name, redirect_value)
+
+ if backend.setting('SANITIZE_REDIRECTS', True):
+ allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \
+ [backend.strategy.request_host()]
+ url = sanitize_redirect(allowed_hosts, url) or \
+ backend.setting('LOGIN_REDIRECT_URL')
+ return backend.strategy.redirect(url)
+
+
+def do_disconnect(backend, user, association_id=None, redirect_name='next',
+ *args, **kwargs):
+ partial = partial_pipeline_data(backend, user, *args, **kwargs)
+ if partial:
+ if association_id and not partial.kwargs.get('association_id'):
+ partial.extend_kwargs({
+ 'association_id': association_id
+ })
+ response = backend.disconnect(*partial.args, **partial.kwargs)
+ else:
+ response = backend.disconnect(user=user, association_id=association_id,
+ *args, **kwargs)
+
+ if isinstance(response, dict):
+ url = backend.strategy.absolute_uri(
+ backend.strategy.request_data().get(redirect_name, '') or
+ backend.setting('DISCONNECT_REDIRECT_URL') or
+ backend.setting('LOGIN_REDIRECT_URL')
+ )
+ if backend.setting('SANITIZE_REDIRECTS', True):
+ allowed_hosts = backend.setting('ALLOWED_REDIRECT_HOSTS', []) + \
+ [backend.strategy.request_host()]
+ url = sanitize_redirect(allowed_hosts, url) or \
+ backend.setting('DISCONNECT_REDIRECT_URL') or \
+ backend.setting('LOGIN_REDIRECT_URL')
+ response = backend.strategy.redirect(url)
+ return response
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__init__.py b/flex/lib/python3.6/site-packages/social_core/backends/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/__init__.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..3495a49
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/amazon.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/amazon.cpython-36.pyc
new file mode 100644
index 0000000..410e912
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/amazon.cpython-36.pyc differ
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 0000000..ec09fd5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/angel.cpython-36.pyc differ
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 0000000..4972fd3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/aol.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/appsfuel.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/appsfuel.cpython-36.pyc
new file mode 100644
index 0000000..924417e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/appsfuel.cpython-36.pyc differ
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 0000000..ccfb711
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/arcgis.cpython-36.pyc differ
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 0000000..55ff487
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/asana.cpython-36.pyc differ
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 0000000..8c482d4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread.cpython-36.pyc differ
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 0000000..5b64665
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/azuread_tenant.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/base.cpython-36.pyc
new file mode 100644
index 0000000..a7f0e98
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/base.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/battlenet.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/battlenet.cpython-36.pyc
new file mode 100644
index 0000000..8f9c4a6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/battlenet.cpython-36.pyc differ
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 0000000..e117898
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/beats.cpython-36.pyc differ
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 0000000..acd1f81
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/behance.cpython-36.pyc differ
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 0000000..180cc5f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/belgiumeid.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bitbucket.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bitbucket.cpython-36.pyc
new file mode 100644
index 0000000..177c4e0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bitbucket.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/box.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/box.cpython-36.pyc
new file mode 100644
index 0000000..f41101c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/box.cpython-36.pyc differ
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 0000000..cc613ac
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/bungie.cpython-36.pyc differ
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 0000000..5c470ee
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/changetip.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/chatwork.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/chatwork.cpython-36.pyc
new file mode 100644
index 0000000..9d823fb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/chatwork.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/classlink.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/classlink.cpython-36.pyc
new file mode 100644
index 0000000..1432231
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/classlink.cpython-36.pyc differ
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 0000000..2fa51c6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/clef.cpython-36.pyc differ
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 0000000..b8f7de3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coding.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coinbase.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coinbase.cpython-36.pyc
new file mode 100644
index 0000000..dc0caa3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coinbase.cpython-36.pyc differ
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 0000000..1fe00d8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/coursera.cpython-36.pyc differ
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 0000000..34ba633
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dailymotion.cpython-36.pyc differ
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 0000000..0bd5d9f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/deezer.cpython-36.pyc differ
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 0000000..e1afebf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/digitalocean.cpython-36.pyc differ
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 0000000..576df0f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/disqus.cpython-36.pyc differ
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 0000000..db99dbf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/docker.cpython-36.pyc differ
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 0000000..16befb0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/douban.cpython-36.pyc differ
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 0000000..c7cf750
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dribbble.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/drip.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/drip.cpython-36.pyc
new file mode 100644
index 0000000..e4ad4f5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/drip.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dropbox.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dropbox.cpython-36.pyc
new file mode 100644
index 0000000..0134ab2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/dropbox.cpython-36.pyc differ
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 0000000..249b1f1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/echosign.cpython-36.pyc differ
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 0000000..9f6b5d8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/edmodo.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/email.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/email.cpython-36.pyc
new file mode 100644
index 0000000..d7abf48
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/email.cpython-36.pyc differ
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 0000000..08811a1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eventbrite.cpython-36.pyc differ
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 0000000..7fcc4c2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/eveonline.cpython-36.pyc differ
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 0000000..0d22ded
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/evernote.cpython-36.pyc differ
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 0000000..78dd4c0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/exacttarget.cpython-36.pyc differ
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 0000000..8696be8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/facebook.cpython-36.pyc differ
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 0000000..456c025
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fedora.cpython-36.pyc differ
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 0000000..bd3b080
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/fitbit.cpython-36.pyc differ
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 0000000..a33c1e9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/five_hundred_px.cpython-36.pyc differ
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 0000000..fa95a8c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/flickr.cpython-36.pyc differ
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 0000000..dd3c60f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/foursquare.cpython-36.pyc differ
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 0000000..4817326
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gae.cpython-36.pyc differ
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 0000000..e6c943b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github.cpython-36.pyc differ
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 0000000..8b5df19
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/github_enterprise.cpython-36.pyc differ
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 0000000..6b121b8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/gitlab.cpython-36.pyc differ
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 0000000..7b4ee24
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclio.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclioeu.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclioeu.cpython-36.pyc
new file mode 100644
index 0000000..12900f3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/goclioeu.cpython-36.pyc differ
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 0000000..29e62bb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google.cpython-36.pyc differ
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 0000000..15ef6c4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/google_openidconnect.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/instagram.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/instagram.cpython-36.pyc
new file mode 100644
index 0000000..1c86209
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/instagram.cpython-36.pyc differ
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 0000000..1fe3fd0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/itembase.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/jawbone.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/jawbone.cpython-36.pyc
new file mode 100644
index 0000000..8cd7201
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/jawbone.cpython-36.pyc differ
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 0000000..d437c7e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/justgiving.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/kakao.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/kakao.cpython-36.pyc
new file mode 100644
index 0000000..db77c42
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/kakao.cpython-36.pyc differ
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 0000000..a5e91c4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/khanacademy.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lastfm.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lastfm.cpython-36.pyc
new file mode 100644
index 0000000..1ca99b7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lastfm.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/launchpad.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/launchpad.cpython-36.pyc
new file mode 100644
index 0000000..b329c00
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/launchpad.cpython-36.pyc differ
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 0000000..914698b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/legacy.cpython-36.pyc differ
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 0000000..c62fce6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/line.cpython-36.pyc differ
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 0000000..1d53b05
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/linkedin.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/live.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/live.cpython-36.pyc
new file mode 100644
index 0000000..674a0ec
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/live.cpython-36.pyc differ
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 0000000..c580d78
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/livejournal.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/loginradius.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/loginradius.cpython-36.pyc
new file mode 100644
index 0000000..66cc76b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/loginradius.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lyft.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lyft.cpython-36.pyc
new file mode 100644
index 0000000..3878fa4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/lyft.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailchimp.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailchimp.cpython-36.pyc
new file mode 100644
index 0000000..b789fbc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailchimp.cpython-36.pyc differ
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 0000000..6313f0e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mailru.cpython-36.pyc differ
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 0000000..2d80cd7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mapmyfitness.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mediawiki.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mediawiki.cpython-36.pyc
new file mode 100644
index 0000000..aa2e774
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mediawiki.cpython-36.pyc differ
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 0000000..9422f42
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/meetup.cpython-36.pyc differ
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 0000000..50bc7fb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mendeley.cpython-36.pyc differ
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 0000000..b131e0b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/microsoft.cpython-36.pyc differ
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 0000000..d0f99de
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mineid.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mixcloud.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mixcloud.cpython-36.pyc
new file mode 100644
index 0000000..eee11a1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/mixcloud.cpython-36.pyc differ
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 0000000..bc85948
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/monzo.cpython-36.pyc differ
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 0000000..89703b0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/moves.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nationbuilder.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nationbuilder.cpython-36.pyc
new file mode 100644
index 0000000..98bf918
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nationbuilder.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/naver.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/naver.cpython-36.pyc
new file mode 100644
index 0000000..17e4eaf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/naver.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ngpvan.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ngpvan.cpython-36.pyc
new file mode 100644
index 0000000..ff2227a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ngpvan.cpython-36.pyc differ
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 0000000..ebfc0a8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/nk.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/oauth.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/oauth.cpython-36.pyc
new file mode 100644
index 0000000..bb51b1d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/oauth.cpython-36.pyc differ
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 0000000..c292d22
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/odnoklassniki.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id.cpython-36.pyc
new file mode 100644
index 0000000..9edb86f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id.cpython-36.pyc differ
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 0000000..38dc0d5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/open_id_connect.cpython-36.pyc differ
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 0000000..8fada90
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openshift.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openstreetmap.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openstreetmap.cpython-36.pyc
new file mode 100644
index 0000000..1b0b5a0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/openstreetmap.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orbi.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orbi.cpython-36.pyc
new file mode 100644
index 0000000..e2eda3c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orbi.cpython-36.pyc differ
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 0000000..f9d4f38
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/orcid.cpython-36.pyc differ
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 0000000..211fe9e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/patreon.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/persona.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/persona.cpython-36.pyc
new file mode 100644
index 0000000..26420da
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/persona.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pinterest.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pinterest.cpython-36.pyc
new file mode 100644
index 0000000..bb4cd58
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pinterest.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pixelpin.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pixelpin.cpython-36.pyc
new file mode 100644
index 0000000..4d7ba40
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pixelpin.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pocket.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pocket.cpython-36.pyc
new file mode 100644
index 0000000..ed33ee4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pocket.cpython-36.pyc differ
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 0000000..5f49cd7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/podio.cpython-36.pyc differ
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 0000000..6984ec7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/professionali.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pushbullet.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pushbullet.cpython-36.pyc
new file mode 100644
index 0000000..517d405
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/pushbullet.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qiita.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qiita.cpython-36.pyc
new file mode 100644
index 0000000..d45c2e5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qiita.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qq.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qq.cpython-36.pyc
new file mode 100644
index 0000000..6ea6bf7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/qq.cpython-36.pyc differ
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 0000000..dea14bc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/quizlet.cpython-36.pyc differ
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 0000000..cfcd480
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/rdio.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/readability.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/readability.cpython-36.pyc
new file mode 100644
index 0000000..8e028ef
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/readability.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/reddit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/reddit.cpython-36.pyc
new file mode 100644
index 0000000..53ef82e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/reddit.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/runkeeper.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/runkeeper.cpython-36.pyc
new file mode 100644
index 0000000..e2a587e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/runkeeper.cpython-36.pyc differ
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 0000000..a709a23
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/salesforce.cpython-36.pyc differ
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 0000000..f0f37d0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/saml.cpython-36.pyc differ
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 0000000..1cf2547
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shimmering.cpython-36.pyc differ
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 0000000..d46fb87
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/shopify.cpython-36.pyc differ
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 0000000..f1aeaa4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/sketchfab.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/skyrock.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/skyrock.cpython-36.pyc
new file mode 100644
index 0000000..9cb1e7f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/skyrock.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/slack.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/slack.cpython-36.pyc
new file mode 100644
index 0000000..55f81de
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/slack.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/soundcloud.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/soundcloud.cpython-36.pyc
new file mode 100644
index 0000000..5627926
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/soundcloud.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/spotify.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/spotify.cpython-36.pyc
new file mode 100644
index 0000000..6503c30
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/spotify.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stackoverflow.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stackoverflow.cpython-36.pyc
new file mode 100644
index 0000000..eafc6be
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stackoverflow.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/steam.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/steam.cpython-36.pyc
new file mode 100644
index 0000000..779d828
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/steam.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stocktwits.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stocktwits.cpython-36.pyc
new file mode 100644
index 0000000..c4e30d4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stocktwits.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/strava.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/strava.cpython-36.pyc
new file mode 100644
index 0000000..e15938f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/strava.cpython-36.pyc differ
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 0000000..43388b5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/stripe.cpython-36.pyc differ
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 0000000..540e3c1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/suse.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/taobao.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/taobao.cpython-36.pyc
new file mode 100644
index 0000000..77e00be
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/taobao.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/thisismyjam.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/thisismyjam.cpython-36.pyc
new file mode 100644
index 0000000..2d06d0d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/thisismyjam.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/trello.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/trello.cpython-36.pyc
new file mode 100644
index 0000000..3b20d52
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/trello.cpython-36.pyc differ
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 0000000..6b3128f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tripit.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tumblr.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tumblr.cpython-36.pyc
new file mode 100644
index 0000000..d2e167e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/tumblr.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twilio.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twilio.cpython-36.pyc
new file mode 100644
index 0000000..d02003d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twilio.cpython-36.pyc differ
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 0000000..46e15b8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitch.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitter.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitter.cpython-36.pyc
new file mode 100644
index 0000000..ecf3bfa
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/twitter.cpython-36.pyc differ
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 0000000..6e95230
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/uber.cpython-36.pyc differ
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 0000000..91dd3b7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/ubuntu.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/udata.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/udata.cpython-36.pyc
new file mode 100644
index 0000000..2d269d7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/udata.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/untappd.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/untappd.cpython-36.pyc
new file mode 100644
index 0000000..9c88741
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/untappd.cpython-36.pyc differ
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 0000000..8eea9ad
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/upwork.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/username.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/username.cpython-36.pyc
new file mode 100644
index 0000000..a7f03a5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/username.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/utils.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/utils.cpython-36.pyc
new file mode 100644
index 0000000..2d318b0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/utils.cpython-36.pyc differ
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 0000000..139dd60
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vend.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vimeo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vimeo.cpython-36.pyc
new file mode 100644
index 0000000..71472bb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vimeo.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vk.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vk.cpython-36.pyc
new file mode 100644
index 0000000..2cf0de9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/vk.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weibo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weibo.cpython-36.pyc
new file mode 100644
index 0000000..d540d35
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weibo.cpython-36.pyc differ
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 0000000..3cdae11
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/weixin.cpython-36.pyc differ
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 0000000..9cefa4a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/withings.cpython-36.pyc differ
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 0000000..fbdb135
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/wunderlist.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/xing.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/xing.cpython-36.pyc
new file mode 100644
index 0000000..6ef97fb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/xing.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yahoo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yahoo.cpython-36.pyc
new file mode 100644
index 0000000..3074f86
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yahoo.cpython-36.pyc differ
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 0000000..bbbc4e6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yammer.cpython-36.pyc differ
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 0000000..c8d5b0f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/yandex.cpython-36.pyc differ
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 0000000..25887cb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/backends/__pycache__/zotero.cpython-36.pyc differ
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 0000000..938c84b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/debug.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/debug.cpython-36.pyc
new file mode 100644
index 0000000..f1c45df
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/debug.cpython-36.pyc differ
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 0000000..9722833
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/disconnect.cpython-36.pyc differ
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 0000000..928a407
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/mail.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/partial.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/partial.cpython-36.pyc
new file mode 100644
index 0000000..ff8be64
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/partial.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/social_auth.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/social_auth.cpython-36.pyc
new file mode 100644
index 0000000..f874de9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/social_auth.cpython-36.pyc differ
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 0000000..bd9d142
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/user.cpython-36.pyc differ
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 0000000..d5bcb39
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/pipeline/__pycache__/utils.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/debug.py b/flex/lib/python3.6/site-packages/social_core/pipeline/debug.py
new file mode 100644
index 0000000..7ef1022
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/pipeline/debug.py
@@ -0,0 +1,13 @@
+from pprint import pprint
+
+
+def debug(response, details, *args, **kwargs):
+ print('=' * 80)
+ pprint(response)
+ print('=' * 80)
+ pprint(details)
+ print('=' * 80)
+ pprint(args)
+ print('=' * 80)
+ pprint(kwargs)
+ print('=' * 80)
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/disconnect.py b/flex/lib/python3.6/site-packages/social_core/pipeline/disconnect.py
new file mode 100644
index 0000000..5d482eb
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/pipeline/disconnect.py
@@ -0,0 +1,31 @@
+from ..exceptions import NotAllowedToDisconnect
+
+
+def allowed_to_disconnect(strategy, user, name, user_storage,
+ association_id=None, *args, **kwargs):
+ if not user_storage.allowed_to_disconnect(user, name, association_id):
+ raise NotAllowedToDisconnect()
+
+
+def get_entries(strategy, user, name, user_storage, association_id=None,
+ *args, **kwargs):
+ return {
+ 'entries': user_storage.get_social_auth_for_user(
+ user, name, association_id
+ )
+ }
+
+
+def revoke_tokens(strategy, entries, *args, **kwargs):
+ revoke_tokens = strategy.setting('REVOKE_TOKENS_ON_DISCONNECT', False)
+ if revoke_tokens:
+ for entry in entries:
+ if 'access_token' in entry.extra_data:
+ backend = entry.get_backend(strategy)(strategy)
+ backend.revoke_token(entry.extra_data['access_token'],
+ entry.uid)
+
+
+def disconnect(strategy, entries, user_storage, *args, **kwargs):
+ for entry in entries:
+ user_storage.disconnect(entry)
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/mail.py b/flex/lib/python3.6/site-packages/social_core/pipeline/mail.py
new file mode 100644
index 0000000..67601f5
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/pipeline/mail.py
@@ -0,0 +1,27 @@
+from ..exceptions import InvalidEmail
+from .partial import partial
+
+
+@partial
+def mail_validation(backend, details, is_new=False, *args, **kwargs):
+ requires_validation = backend.REQUIRES_EMAIL_VALIDATION or \
+ backend.setting('FORCE_EMAIL_VALIDATION', False)
+ send_validation = details.get('email') and \
+ (is_new or backend.setting('PASSWORDLESS', False))
+ if requires_validation and send_validation:
+ data = backend.strategy.request_data()
+ if 'verification_code' in data:
+ backend.strategy.session_pop('email_validation_address')
+ if not backend.strategy.validate_email(details['email'],
+ data['verification_code']):
+ raise InvalidEmail(backend)
+ else:
+ current_partial = kwargs.get('current_partial')
+ backend.strategy.send_email_validation(backend,
+ details['email'],
+ current_partial.token)
+ backend.strategy.session_set('email_validation_address',
+ details['email'])
+ return backend.strategy.redirect(
+ backend.strategy.setting('EMAIL_VALIDATION_URL')
+ )
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/partial.py b/flex/lib/python3.6/site-packages/social_core/pipeline/partial.py
new file mode 100644
index 0000000..42343ef
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/pipeline/partial.py
@@ -0,0 +1,44 @@
+from functools import wraps
+
+from .utils import partial_prepare
+
+
+def partial_step(save_to_session):
+ """Wraps func to behave like a partial pipeline step, any output
+ that's not None or {} will be considered a response object and
+ will be returned to user.
+
+ The pipeline function will receive a current_partial object, it
+ contains the partial pipeline data and a token that is used to
+ identify it when it's continued, this is useful to build links
+ with the token.
+
+ The default value for this parameter is partial_token, but can be
+ overridden by SOCIAL_AUTH_PARTIAL_PIPELINE_TOKEN_NAME setting.
+
+ The token is also stored in the session under the
+ partial_pipeline_token key when the save_to_session parameter is True.
+ """
+ def decorator(func):
+ @wraps(func)
+ def wrapper(strategy, backend, pipeline_index, *args, **kwargs):
+ current_partial = partial_prepare(strategy, backend, pipeline_index,
+ *args, **kwargs)
+
+ out = func(strategy=strategy,
+ backend=backend,
+ pipeline_index=pipeline_index,
+ current_partial=current_partial,
+ *args, **kwargs) or {}
+
+ if not isinstance(out, dict):
+ strategy.storage.partial.store(current_partial)
+ if save_to_session:
+ strategy.session_set('partial_pipeline_token', current_partial.token)
+ return out
+ return wrapper
+ return decorator
+
+
+# Backward compatible partial decorator, that stores the token in the session
+partial = partial_step(save_to_session=True)
diff --git a/flex/lib/python3.6/site-packages/social_core/pipeline/social_auth.py b/flex/lib/python3.6/site-packages/social_core/pipeline/social_auth.py
new file mode 100644
index 0000000..4603307
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/pipeline/social_auth.py
@@ -0,0 +1,88 @@
+from ..exceptions import AuthAlreadyAssociated, AuthException, AuthForbidden
+
+
+def social_details(backend, details, response, *args, **kwargs):
+ return {'details': dict(backend.get_user_details(response), **details)}
+
+
+def social_uid(backend, details, response, *args, **kwargs):
+ return {'uid': backend.get_user_id(details, response)}
+
+
+def auth_allowed(backend, details, response, *args, **kwargs):
+ if not backend.auth_allowed(response, details):
+ raise AuthForbidden(backend)
+
+
+def social_user(backend, uid, user=None, *args, **kwargs):
+ provider = backend.name
+ social = backend.strategy.storage.user.get_social_auth(provider, uid)
+ if social:
+ if user and social.user != user:
+ msg = 'This {0} account is already in use.'.format(provider)
+ raise AuthAlreadyAssociated(backend, msg)
+ elif not user:
+ user = social.user
+ return {'social': social,
+ 'user': user,
+ 'is_new': user is None,
+ 'new_association': social is None}
+
+
+def associate_user(backend, uid, user=None, social=None, *args, **kwargs):
+ if user and not social:
+ try:
+ social = backend.strategy.storage.user.create_social_auth(
+ user, uid, backend.name
+ )
+ except Exception as err:
+ if not backend.strategy.storage.is_integrity_error(err):
+ raise
+ # Protect for possible race condition, those bastard with FTL
+ # clicking capabilities, check issue #131:
+ # https://github.com/omab/django-social-auth/issues/131
+ return social_user(backend, uid, user, *args, **kwargs)
+ else:
+ return {'social': social,
+ 'user': social.user,
+ 'new_association': True}
+
+
+def associate_by_email(backend, details, user=None, *args, **kwargs):
+ """
+ Associate current auth with a user with the same email address in the DB.
+
+ This pipeline entry is not 100% secure unless you know that the providers
+ enabled enforce email verification on their side, otherwise a user can
+ attempt to take over another user account by using the same (not validated)
+ email address on some provider. This pipeline entry is disabled by
+ default.
+ """
+ if user:
+ return None
+
+ email = details.get('email')
+ if email:
+ # Try to associate accounts registered with the same email address,
+ # only if it's a single object. AuthException is raised if multiple
+ # objects are returned.
+ users = list(backend.strategy.storage.user.get_users_by_email(email))
+ if len(users) == 0:
+ return None
+ elif len(users) > 1:
+ raise AuthException(
+ backend,
+ 'The given email address is associated with another account'
+ )
+ else:
+ return {'user': users[0],
+ '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 0000000..a666d50
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/__init__.cpython-36.pyc differ
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 0000000..70a2ed7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/models.cpython-36.pyc differ
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 0000000..4c4515a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/pipeline.cpython-36.pyc differ
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 0000000..a630b70
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/strategy.cpython-36.pyc differ
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 0000000..91e51af
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_exceptions.cpython-36.pyc differ
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 0000000..5ff2dfc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_partial.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_pipeline.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_pipeline.cpython-36.pyc
new file mode 100644
index 0000000..6762c9d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_pipeline.cpython-36.pyc differ
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 0000000..b6899a0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_storage.cpython-36.pyc differ
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 0000000..e0b9d85
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/__pycache__/test_utils.cpython-36.pyc differ
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 0000000..0d5c3f3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/__init__.cpython-36.pyc differ
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 0000000..8df58f7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/actions.cpython-36.pyc differ
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 0000000..adbb966
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_associate.cpython-36.pyc differ
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 0000000..b90d4c1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_disconnect.cpython-36.pyc differ
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 0000000..1ed579e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/actions/__pycache__/test_login.cpython-36.pyc differ
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 0000000..6e492bd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/__init__.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/base.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/base.cpython-36.pyc
new file mode 100644
index 0000000..b603951
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/base.cpython-36.pyc differ
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 0000000..795e0d4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/legacy.cpython-36.pyc differ
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 0000000..8448331
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/oauth.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id.cpython-36.pyc
new file mode 100644
index 0000000..aaf7c4d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id.cpython-36.pyc differ
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 0000000..7abadde
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/open_id_connect.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_amazon.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_amazon.cpython-36.pyc
new file mode 100644
index 0000000..deff93f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_amazon.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_angel.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_angel.cpython-36.pyc
new file mode 100644
index 0000000..146b6bb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_angel.cpython-36.pyc differ
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 0000000..dc34d73
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_arcgis.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_asana.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_asana.cpython-36.pyc
new file mode 100644
index 0000000..fb31fa7
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_asana.cpython-36.pyc differ
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 0000000..ec1a503
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_azuread.cpython-36.pyc differ
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 0000000..c61ef11
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_behance.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_bitbucket.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_bitbucket.cpython-36.pyc
new file mode 100644
index 0000000..3c71e76
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_bitbucket.cpython-36.pyc differ
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 0000000..a7b5a64
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_box.cpython-36.pyc differ
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 0000000..e24488a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_broken.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_chatwork.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_chatwork.cpython-36.pyc
new file mode 100644
index 0000000..e502474
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_chatwork.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_clef.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_clef.cpython-36.pyc
new file mode 100644
index 0000000..3e26bb0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_clef.cpython-36.pyc differ
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 0000000..458b2f9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coinbase.cpython-36.pyc differ
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 0000000..3d2a642
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_coursera.cpython-36.pyc differ
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 0000000..8e8df24
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dailymotion.cpython-36.pyc differ
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 0000000..5e734a5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_deezer.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_digitalocean.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_digitalocean.cpython-36.pyc
new file mode 100644
index 0000000..41277ba
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_digitalocean.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_disqus.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_disqus.cpython-36.pyc
new file mode 100644
index 0000000..88c496b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_disqus.cpython-36.pyc differ
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 0000000..bb767a0
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dribbble.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_drip.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_drip.cpython-36.pyc
new file mode 100644
index 0000000..ac31c64
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_drip.cpython-36.pyc differ
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 0000000..3022530
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dropbox.cpython-36.pyc differ
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 0000000..30b0463
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_dummy.cpython-36.pyc differ
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 0000000..316024f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_edmodo.cpython-36.pyc differ
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 0000000..06ce844
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_email.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_evernote.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_evernote.cpython-36.pyc
new file mode 100644
index 0000000..acf9e82
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_evernote.cpython-36.pyc differ
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 0000000..3916bb9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_facebook.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_fitbit.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_fitbit.cpython-36.pyc
new file mode 100644
index 0000000..482fe5a
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_fitbit.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_five_hundred_px.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_five_hundred_px.cpython-36.pyc
new file mode 100644
index 0000000..7593f92
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_five_hundred_px.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_flickr.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_flickr.cpython-36.pyc
new file mode 100644
index 0000000..3ad2bbb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_flickr.cpython-36.pyc differ
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 0000000..4b09dbf
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_foursquare.cpython-36.pyc differ
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 0000000..ebbdfb1
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github_enterprise.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github_enterprise.cpython-36.pyc
new file mode 100644
index 0000000..030d7f4
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_github_enterprise.cpython-36.pyc differ
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 0000000..6034d4c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_gitlab.cpython-36.pyc differ
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 0000000..559c84d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_google.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_instagram.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_instagram.cpython-36.pyc
new file mode 100644
index 0000000..c1fbb1d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_instagram.cpython-36.pyc differ
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 0000000..be35ba8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_itembase.cpython-36.pyc differ
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 0000000..3f970ed
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_kakao.cpython-36.pyc differ
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 0000000..02d7704
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_khanacademy.cpython-36.pyc differ
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 0000000..22376e6
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_linkedin.cpython-36.pyc differ
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 0000000..2e969e3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_live.cpython-36.pyc differ
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 0000000..89c70c2
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_livejournal.cpython-36.pyc differ
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 0000000..9a57620
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_lyft.cpython-36.pyc differ
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 0000000..9f3b321
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mapmyfitness.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mineid.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mineid.cpython-36.pyc
new file mode 100644
index 0000000..6326527
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mineid.cpython-36.pyc differ
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 0000000..d03a730
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_mixcloud.cpython-36.pyc differ
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 0000000..2db7c91
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_nationbuilder.cpython-36.pyc differ
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 0000000..5cd1342
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_naver.cpython-36.pyc differ
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 0000000..b0879bd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_ngpvan.cpython-36.pyc differ
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 0000000..e092d11
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_orbi.cpython-36.pyc differ
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 0000000..004fbfd
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_patreon.cpython-36.pyc differ
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 0000000..44ce819
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_pinterest.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_podio.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_podio.cpython-36.pyc
new file mode 100644
index 0000000..923fd20
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_podio.cpython-36.pyc differ
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 0000000..41a663f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_qiita.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_quizlet.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_quizlet.cpython-36.pyc
new file mode 100644
index 0000000..c6ad074
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_quizlet.cpython-36.pyc differ
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 0000000..69dec16
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_readability.cpython-36.pyc differ
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 0000000..70c7826
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_reddit.cpython-36.pyc differ
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 0000000..a7ca373
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_saml.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_sketchfab.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_sketchfab.cpython-36.pyc
new file mode 100644
index 0000000..d21ad64
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_sketchfab.cpython-36.pyc differ
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 0000000..2ec679f
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_skyrock.cpython-36.pyc differ
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 0000000..4e98c02
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_slack.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_soundcloud.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_soundcloud.cpython-36.pyc
new file mode 100644
index 0000000..ee43f0c
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_soundcloud.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_spotify.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_spotify.cpython-36.pyc
new file mode 100644
index 0000000..ba9ba2e
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_spotify.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stackoverflow.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stackoverflow.cpython-36.pyc
new file mode 100644
index 0000000..0fea9b8
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stackoverflow.cpython-36.pyc differ
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 0000000..03ed777
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_steam.cpython-36.pyc differ
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 0000000..810c0eb
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stocktwits.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_strava.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_strava.cpython-36.pyc
new file mode 100644
index 0000000..65c3649
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_strava.cpython-36.pyc differ
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 0000000..d4a0653
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_stripe.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_taobao.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_taobao.cpython-36.pyc
new file mode 100644
index 0000000..786c8f9
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_taobao.cpython-36.pyc differ
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 0000000..868866d
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_thisismyjam.cpython-36.pyc differ
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 0000000..c40f955
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tripit.cpython-36.pyc differ
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 0000000..9c30014
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_tumblr.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitch.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitch.cpython-36.pyc
new file mode 100644
index 0000000..c3a117b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitch.cpython-36.pyc differ
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 0000000..16d584b
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_twitter.cpython-36.pyc differ
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 0000000..dcf9256
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_uber.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_udata.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_udata.cpython-36.pyc
new file mode 100644
index 0000000..14fc9db
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_udata.cpython-36.pyc differ
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 0000000..6dfaad3
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_upwork.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_username.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_username.cpython-36.pyc
new file mode 100644
index 0000000..e943487
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_username.cpython-36.pyc differ
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 0000000..6370016
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_utils.cpython-36.pyc differ
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 0000000..afcf878
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_vk.cpython-36.pyc differ
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 0000000..ea3efbc
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_wunderlist.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_xing.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_xing.cpython-36.pyc
new file mode 100644
index 0000000..54bd4da
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_xing.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yahoo.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yahoo.cpython-36.pyc
new file mode 100644
index 0000000..13614b5
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yahoo.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yammer.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yammer.cpython-36.pyc
new file mode 100644
index 0000000..d297306
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yammer.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yandex.cpython-36.pyc b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yandex.cpython-36.pyc
new file mode 100644
index 0000000..c1ba834
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_yandex.cpython-36.pyc differ
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 0000000..4b39823
Binary files /dev/null and b/flex/lib/python3.6/site-packages/social_core/tests/backends/__pycache__/test_zotero.cpython-36.pyc differ
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/base.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/base.py
new file mode 100644
index 0000000..b348f9d
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/base.py
@@ -0,0 +1,152 @@
+import unittest2 as unittest
+import requests
+
+from httpretty import HTTPretty
+
+from ...utils import module_member, parse_qs
+from ...backends.utils import user_backends_data, load_backends
+from ..strategy import TestStrategy
+from ..models import User, TestUserSocialAuth, TestNonce, \
+ TestAssociation, TestCode, TestStorage
+
+
+class BaseBackendTest(unittest.TestCase):
+ backend = None
+ backend_path = None
+ name = None
+ complete_url = ''
+ raw_complete_url = '/complete/{0}'
+
+ def setUp(self):
+ HTTPretty.enable()
+ Backend = module_member(self.backend_path)
+ self.strategy = TestStrategy(TestStorage)
+ self.backend = Backend(self.strategy, redirect_uri=self.complete_url)
+ self.name = self.backend.name.upper().replace('-', '_')
+ self.complete_url = self.strategy.build_absolute_uri(
+ self.raw_complete_url.format(self.backend.name)
+ )
+ backends = (self.backend_path,
+ 'social_core.tests.backends.test_broken.BrokenBackendAuth')
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': backends
+ })
+ self.strategy.set_settings(self.extra_settings())
+ # Force backends loading to trash PSA cache
+ load_backends(backends, force_load=True)
+ User.reset_cache()
+ TestUserSocialAuth.reset_cache()
+ TestNonce.reset_cache()
+ TestAssociation.reset_cache()
+ TestCode.reset_cache()
+
+ def tearDown(self):
+ HTTPretty.disable()
+ self.backend = None
+ self.strategy = None
+ self.name = None
+ self.complete_url = None
+ User.reset_cache()
+ TestUserSocialAuth.reset_cache()
+ TestNonce.reset_cache()
+ TestAssociation.reset_cache()
+ TestCode.reset_cache()
+
+ def extra_settings(self):
+ return {}
+
+ def do_start(self):
+ raise NotImplementedError('Implement in subclass')
+
+ def do_login(self):
+ user = self.do_start()
+ username = self.expected_username
+ self.assertEqual(user.username, username)
+ self.assertEqual(self.strategy.session_get('username'), username)
+ self.assertEqual(self.strategy.get_user(user.id), user)
+ self.assertEqual(self.backend.get_user(user.id), user)
+ user_backends = user_backends_data(
+ user,
+ self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'),
+ self.strategy.storage
+ )
+ self.assertEqual(len(list(user_backends.keys())), 3)
+ self.assertEqual('associated' in user_backends, True)
+ self.assertEqual('not_associated' in user_backends, True)
+ self.assertEqual('backends' in user_backends, True)
+ self.assertEqual(len(user_backends['associated']), 1)
+ self.assertEqual(len(user_backends['not_associated']), 1)
+ self.assertEqual(len(user_backends['backends']), 2)
+ return user
+
+ def pipeline_settings(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_PIPELINE': (
+ 'social_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.tests.pipeline.ask_for_slug',
+ '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.tests.pipeline.set_password',
+ 'social_core.tests.pipeline.set_slug',
+ 'social_core.pipeline.user.user_details'
+ )
+ })
+
+ def pipeline_handlers(self, url):
+ HTTPretty.register_uri(HTTPretty.GET, url, status=200, body='foobar')
+ HTTPretty.register_uri(HTTPretty.POST, url, status=200)
+
+ def pipeline_password_handling(self, url):
+ password = 'foobar'
+ requests.get(url)
+ requests.post(url, data={'password': password})
+
+ data = parse_qs(HTTPretty.last_request.body)
+ self.assertEqual(data['password'], password)
+ self.strategy.session_set('password', data['password'])
+ return password
+
+ def pipeline_slug_handling(self, url):
+ slug = 'foo-bar'
+ requests.get(url)
+ requests.post(url, data={'slug': slug})
+
+ data = parse_qs(HTTPretty.last_request.body)
+ self.assertEqual(data['slug'], slug)
+ self.strategy.session_set('slug', data['slug'])
+ return slug
+
+ def do_partial_pipeline(self):
+ url = self.strategy.build_absolute_uri('/password')
+ self.pipeline_settings()
+ redirect = self.do_start()
+ self.assertEqual(redirect.url, url)
+ self.pipeline_handlers(url)
+
+ password = self.pipeline_password_handling(url)
+ token = self.strategy.session_pop('partial_pipeline_token')
+ partial = self.strategy.partial_load(token)
+ self.assertEqual(partial.backend, self.backend.name)
+ redirect = self.backend.continue_pipeline(partial)
+
+ url = self.strategy.build_absolute_uri('/slug')
+ self.assertEqual(redirect.url, url)
+ self.pipeline_handlers(url)
+ slug = self.pipeline_slug_handling(url)
+
+ token = self.strategy.session_pop('partial_pipeline_token')
+ partial = self.strategy.partial_load(token)
+ self.assertEqual(partial.backend, self.backend.name)
+ user = self.backend.continue_pipeline(partial)
+
+ self.assertEqual(user.username, self.expected_username)
+ self.assertEqual(user.slug, slug)
+ self.assertEqual(user.password, password)
+ return user
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/data/saml_response.txt b/flex/lib/python3.6/site-packages/social_core/tests/backends/data/saml_response.txt
new file mode 100644
index 0000000..557bb59
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/data/saml_response.txt
@@ -0,0 +1 @@
+http://myapp.com/?RelayState=testshib&SAMLResponse=PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cDovL215YXBwLmNvbSIgSUQ9Il8yNTk2NTFlOTY3ZGIwOGZjYTQ4MjdkODI3YWY1M2RkMCIgSW5SZXNwb25zZVRvPSJURVNUX0lEIiBJc3N1ZUluc3RhbnQ9IjIwMTUtMDUtMDlUMDM6NTc6NDMuNzkyWiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OmVudGl0eSI%2BaHR0cHM6Ly9pZHAudGVzdHNoaWIub3JnL2lkcC9zaGliYm9sZXRoPC9zYW1sMjpJc3N1ZXI%2BPHNhbWwycDpTdGF0dXM%2BPHNhbWwycDpTdGF0dXNDb2RlIFZhbHVlPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6c3RhdHVzOlN1Y2Nlc3MiLz48L3NhbWwycDpTdGF0dXM%2BPHNhbWwyOkVuY3J5cHRlZEFzc2VydGlvbiB4bWxuczpzYW1sMj0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOmFzc2VydGlvbiI%2BPHhlbmM6RW5jcnlwdGVkRGF0YSB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiIElkPSJfMGM0NzYzNzIyOWFkNmEzMTY1OGU0MDc2ZDNlYzBmNmQiIFR5cGU9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI0VsZW1lbnQiPjx4ZW5jOkVuY3J5cHRpb25NZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNhZXMxMjgtY2JjIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiLz48ZHM6S2V5SW5mbyB4bWxuczpkcz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnIyI%2BPHhlbmM6RW5jcnlwdGVkS2V5IElkPSJfYjZmNmU2YWZjMzYyNGI3NmM1N2JmOWZhODA5YzAzNmMiIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6RW5jcnlwdGlvbk1ldGhvZCBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jI3JzYS1vYWVwLW1nZjFwIiB4bWxuczp4ZW5jPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyMiPjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjc2hhMSIgeG1sbnM6ZHM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvMDkveG1sZHNpZyMiLz48L3hlbmM6RW5jcnlwdGlvbk1ldGhvZD48ZHM6S2V5SW5mbz48ZHM6WDUwOURhdGE%2BPGRzOlg1MDlDZXJ0aWZpY2F0ZT5NSUlDc0RDQ0FobWdBd0lCQWdJSkFPN0J3ZGpEWmNVV01BMEdDU3FHU0liM0RRRUJCUVVBTUVVeEN6QUpCZ05WQkFZVEFrTkJNUmt3CkZ3WURWUVFJRXhCQ2NtbDBhWE5vSUVOdmJIVnRZbWxoTVJzd0dRWURWUVFLRXhKd2VYUm9iMjR0YzI5amFXRnNMV0YxZEdnd0hoY04KTVRVd05UQTRNRGMxT0RRMldoY05NalV3TlRBM01EYzFPRFEyV2pCRk1Rc3dDUVlEVlFRR0V3SkRRVEVaTUJjR0ExVUVDQk1RUW5KcApkR2x6YUNCRGIyeDFiV0pwWVRFYk1Ca0dBMVVFQ2hNU2NIbDBhRzl1TFhOdlkybGhiQzFoZFhSb01JR2ZNQTBHQ1NxR1NJYjNEUUVCCkFRVUFBNEdOQURDQmlRS0JnUUNxM2cxQ2wrM3VSNXZDbk40SGJnalRnK20zbkhodGVFTXliKyt5Y1pZcmUyYnhVZnNzaEVSNngzM2wKMjN0SGNrUll3bTdNZEJicnAzTHJWb2lPQ2RQYmxUbWwxSWhFUFRDd0tNaEJLdnZXcVR2Z2ZjU1NuUnpBV2tMbFFZU3VzYXl5Wks0bgo5cWNZa1Y1TUZuaTFyYmp4K01yNWFPRW1iNXUzM2FtTUtMd1NUd0lEQVFBQm80R25NSUdrTUIwR0ExVWREZ1FXQkJSUmlCUjZ6UzY2CmZLVm9rcDB5SkhiZ3YzUlltakIxQmdOVkhTTUViakJzZ0JSUmlCUjZ6UzY2ZktWb2twMHlKSGJndjNSWW1xRkpwRWN3UlRFTE1Ba0cKQTFVRUJoTUNRMEV4R1RBWEJnTlZCQWdURUVKeWFYUnBjMmdnUTI5c2RXMWlhV0V4R3pBWkJnTlZCQW9URW5CNWRHaHZiaTF6YjJOcApZV3d0WVhWMGFJSUpBTzdCd2RqRFpjVVdNQXdHQTFVZEV3UUZNQU1CQWY4d0RRWUpLb1pJaHZjTkFRRUZCUUFEZ1lFQUp3c01VM1lTCmF5YlZqdUo4VVMwZlVobFBPbE00MFFGQ0dMNHZCM1RFYmIyNE1xOEhyalV3clUwSkZQR2xzOWEyT1l6TjJCM2UzNU5vck11eHMrZ3IKR3RyMnlQNkx2dVgrblY2QTkzd2I0b29HSG9HZkM3VkxseXhTU25zOTM3U1M1UjFwelE0Z1d6Wm1hMktHV0tJQ1dwaDV6UTBBUlZoTAo2Mzk2N21HTG1vST08L2RzOlg1MDlDZXJ0aWZpY2F0ZT48L2RzOlg1MDlEYXRhPjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BTElQdkVNVUVGeXhrVHowQ2N4QVA5TjV4Y3NYT2V4aVV4cXBvR2VIeVFMV0R5RVBBUDVnZ1daL3NLZ1ViL2xWSk92bCtuQXhSdVhXUlc5dGxSWWx3R2orRVhIOWhIbmdEY1BWMDNqSUJMQnFJbElBL1RmMGw4cVliOHFKRy9ZM0RTS2RQNkwvUURtYXBtTXpFM29YOEJxMW5Ea3YrUWh4cmQwMGVGK2ZMYVQ0PTwveGVuYzpDaXBoZXJWYWx1ZT48L3hlbmM6Q2lwaGVyRGF0YT48L3hlbmM6RW5jcnlwdGVkS2V5PjwvZHM6S2V5SW5mbz48eGVuYzpDaXBoZXJEYXRhIHhtbG5zOnhlbmM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMDQveG1sZW5jIyI%2BPHhlbmM6Q2lwaGVyVmFsdWU%2BRVpUWDhHTkM0My9yWStTUVlBMXRudHlUTTVVNkN2dUNCaktsVEVlekZPRjBZZHhCWUdFQVVjYU8xNVNKOXBMemJ1L1h0WGxzTkVMZTdKdEx4RUpwYUxubWFENnIranNWczdLaTBLNHRTMGNBUERDWHV2R1FoMmFOVjVQOGJ3N1JWUGhLOGQwYlJ1RklGR09FOHMwTTZYOUpxWDN4S0MvL1lSbVVoeDlybnU3ZWlwMGh5ZitPaUZiVGR2SDY2NTB2LzQ3aVdKcDNZeFlUV0QyMHBNbVRJMUpwWUEwYjByWVFQRkR0RU93d0JxYktxanRJc3ZYVFJzeXJhQkxvbnFOeHN5dHpEWHEra0JsMXp3WGUvSE5QcUVQblczdnNxaFhZcDVGM3dkWThkKzNCOTRMZlpOdUd4a0p3VDNzdVR0OGY5VHRBSlI4VytBUmtzT2M4eDBVaVNsVG5BNHFHOTBLMTR5dkVoVHcvd2drZjFXV01RT3dpZDNpakFYbUV4MU5MbVZvYUxYb3p4VExkTjN6YnJ6VEJIRXc3R2J3ZEdrdU5pMlhZOW16YUgwaWtGRm51VUxjMHUwc0pycEdGdzlaK0VlUk44RzNVUVZ5MjhtS2g3ZFBwWU5KbzhyajIxZFFaK2JaeUtTUHZablU3REkyakdJRE5US1g2ZkVyVWFINGlOTzN4cUU2Vk90L2d4T3BMNE5VNUhLV0Q0bG93VzcwdUJjVEVQRmhwaThpYUovdTB6YzUvTEhvdVBjMzByc1RLZFc5cmJLL2NWaHNQUHErZzA5WHZpZ0QweTJvN2tOc1pVL25tRXFiSzBKOTBrazhCR3I5cXRSczY4bUJnSURtUHVwUkhwWjM4eXNnU2VZN3V0VlVaSG5tQ0dzTzZ2NDJ6OTVOK05Pb3RCTEVZbFd1ZEdzYnowQWc4VkRDSlY5ak95QW95MDZyL1AyUHBsOFhjdmJza2d2T1BMMWdDNnVYbVJJS1lmOEw4UDJCNXVjN0haK0dtUHNOWXRLS2VKRDFFUHovdCt2NlBIbXNVb3dsSDhSd3FMRHdtMUF4dlNLQTR3UXBlQ0dQd3A5YXRYS0lWMS84NUZzRWMzajVzNjd6VlRybThrVEpydXV2MDZEdFVRZDNMOFdwTkV4cWhQait6RUp6U3RxSG04ckhNMVhNQUVxdVozc0xycTVqLzFSNlpqS0dOdFJCbjhwOE5ERGtrWm0vWTV5TXlJNXJJS3U5bnA3bXdaaEVpeWVHeHdxblV3VVMvUzVDRjNnMHVidnd4eVVnalVvd1ZvTkNqYktBbkdtT2VCSW5abkh0eGdIVUhVOUVlTFdyd2pRc3JtUmpJV0R2RkZQa3l6SzJDL20yaitubmNxc2E1OGRLVXZxcGR1VTRJYnNPQng3UGpXdXRBNmY5bXd6YWxyRU1NK0lGR3VPdk9HMC93eUdzQjZLREV6bldjUC83NkQ4angzaHZFSlAzN3REbFgreGM4Qno5TXdKdkd6VG4xbTdCb2xoR0lzSXlCTys1ZXpXa3RDWVVIUURGVE9wbXA0MDlOWHp6ZUNTUGY1U2NDWG5YYjRPd01ULy9VM1JFUnRRbGMrNmU2WG1JRjhoRkJVc0taUUJsS2ppSDkwZHlzYWlsNmN2V3UyQW55Q3QxbWxXcHFLc0MzU2RTRVZDTG1qRjlUQUFUMEtFSGdZQjg3RjZtZUpTTysvOXkyZkRuYVVvUUlUVzdubnVuSCtkT3dWSGZMU0wyL2N5YTltNlQzR29TSVNMbGJPMVRzalhKclVkZW55OTcvM2tkNmhFQlphdGY1U3NETFQ3SjNsQUVJNDROeXJ0NkIxQWdod2JNdkpqd1JNTXRNdUJLc3ltUytKVzc4UFNEWXQ4MG9waDJQTTc1N0tBNCtUMTAvYnZaQkE5Vk1OdVpqNVV3NXRWMnFIS3dwS0t6ZVVETUFiQlBRaGpYcXlQZzFKa09rd2RQMUpnOHRITjJTelBZQTlmT1htV0pBZGJDS2tMb0F4ZTV6cDZBUzYzS3FXMmFmSUt6SHJ3RTJmS1VtamppeURvMnNuMkJHbWtBaTRzbnpiVzc2SUQvSVgwd044aDBaQ2VRc29vKzdtb1RCMEJxSnBkS1MycXlsUktoc3BSTC9henVQdmxaK1pwckJxdXpJdEZkNFVLMkpzQkp6VXcwZkpxcTV1bk9PZENzVWM3SUU3QTNmZ1NmZ3NBd1R3WFZJMEVoME5ySWZpMkFKV1Z2VFpEMys2eFZ3dS96WWhuVjc0VXkvMFE4Mi8yQWtpSGpFRjNJVGNLWHdTNTB6bWtLakxjZDJqa2h5TUFYMWRoQ0wwZElFMUJoN0RNamVvNC9YbjBqSlpPL3Rrbi9xZmYzc3RNb1BYVG9KTnBIU1RjR2ZheGtaMzJYNCt3Q0xPc0VBRWxlMVZSY0kwUkZyOFhHTSsxWU9BTjBodFdGcFMxaG9kSi9OczJqL1FnUVNEemNpQ1FZeUFDd3lFRWZDZjZybnR0VmJyTlJQZWlmSHhBM3B2UnZ5ZGRhNDE5cXl0ZXI0akJ3cmw3ZUpuVnJ2VEprR2VhU2FRbDdXWk5SQXBscXRnNnZPYmpiMHZDRWlFaFhKbmNzQUhxcXp5QTRGeWFUVGQ2R0FySU9adUNxRWVoWk51T01lOVlrMVpya0VkR3pIalJESWk3Q1BKQk12NEZ4ZHI3bnJvN0I1WEhKb0ZMNE1DSUtOWWU2aWZiTUtYOU5uN1FWdnphUmY2UXlaSW1BWENQZndvU1BkN2x6NXl3UDJLSUIyaGhFMWt5eVZ5YVc5T0praWpUY3dvUnZrSXhIU0RqMXFqeGxueXh0QzhVZ1pNWmlwcGgzQXJpcjRiekIzUDhIbGIzejZ0OW51KzZMemNiN2ZObVo0UHluaU50Vk9OQ0lHbEh4dTBSY3hQK3cwUXNsM1BtTzJLaHBpc2RIanhvSUJ1YVY1NXdoTlFFNmdNNFBrT0xINDc4Rzg4bUxkd2s2RFpkWVl4L2d6RWE3b3ZIL0pReFp2TzRLdFVTNmZjZHJxV2thTFg1cEhkNkdneFBGZ2NFc2Nad1ZqM2hCS0xFQmE5L0dodERINEhzRnNRbmpPZnNDQkNzN0tjRitmTi9oSUdUeHFqTVlKVHJRYmNtdWF5dk9xR3RQMDFPcXltR24rVm5FSVkzKytQcm95SFN3K0Q0b0JIVG1maFNXRmJLZCtuTlVFS3BhRVIxNkdCU256WktQRVRVSmdRWEw5QWJRQ3RXVjFHb0UzRWNnMDZYaVd2aHFHakpGNldtdEU4dHY4Q25rZmxMNm91TDRvNldpbmx2WnNEdkZrS0R6TDkwUTNsWC9NanBtRTFpWU9uYzdISXdEVGwraFRRcHdsYXJiTDVUNGNkZTg1akNwYU0xU3p1TStiQU5zMHlXVDA0ZXJUVFc2cnhlbXFDTHAra202TVVMTlZOcE1CazBiQjJpRU82UlRtc3VpRlhDUU1xdU5xZjdkWXUwTFFCZzQ0MkJzU1pBV1ZrWEVZblduOURLdTRSby8veEFsb2h5VHozWlZmSkhuWVBSdDloSUErRHVUL3c4T2ZzTURIWnlCelUvL0JEa1NiNkxjMHdraVA3QlhIdjBoNVdud2dNWUxlZDBPalR5UWI2aGxpVnQ5b0FjaDRFVy9EZUlBdkpaQ1BYVm1pUFFYTGVsOVJIRko2bXFiYVo0TCtaZG1ONmQwcFZNZ1FveXhmQTR3dEwwYVpiNnFZYkhibjJMd2VBQVZwL3M2TzVlMVExdnZpZDRTWHo0a2l3RW1LSStIeXZEQ1pnekpQQVN5Z1gvWDJFWEZ0NGV3SjVmUFQyVXZmWnhQWlpqMFZGSFpyUFQwWVd2VE16bjUva3hoT09oM2drVGdDSmNwNWVsZnp4cEFPNFl1a0NoNHJXdVNndDRqVUJyaWNYbFdWdWo5U3JSZVhUalNHTktLK202NWovUDllNHRHT0RkMk9BbjNKTVQ3Q3FuaDhreTZpZjVjbmpVMmU3UDhTZnBONGwxWEFiZEZEcGk5bVJYamEyTzR1RWFHNGNvNW4xcWNDT3ZNMWYyblFBY1ZGNUFoSXhueS96TWhmU2l2RXdOQ0Zyd2tBWDRyQVE0WldUNldFakFyUG5jb1Y4Z1VRclhxQVA4NDJmK1lNWWI5RHFncmFicEg1a3ZuMnQzcWRldGJHODJ0QWlTamhPcUxNYW9iU2F4cXdWa1lUOHRTMW9rUUt2MWZoZ2t6elpEOE5IQnVQQzdNVHdXS0VCS2tDRUUzRWRFMXhNQURLd1B1M3NSaGpSaExXZyszZ2srejJtdlU4cTBhTlc0Y3hObUdoekx4eEY0Q3NFNStMQ1cwOWFpUVJOM1VvWmg1aktBZzBiMlh3WHBLS3pycUVTY1BYdnI0L1dWUTMyMm5qRWRvQVdXR0t2WnBKMlRlREo0eDdiT21LVElFc2RHWU1UZzFVaEU2eFFQcnhqS3dWeGFJNVJyaVE4a0xpaGgwa0t0WHQvYTVsSDhzUjVwR0ZISGZ3dlNVb3liQTB1eUVDNnNRVitPbTVReUZmRmpqZHFCOGNpOGxQS1hLTHFCTHJ6bjNmUkh3TmQwbzFiRTg0aGllTkx5UlhZVmhrRCtFNEpGaVd3ZWt3U3VWM3BjQk9ybnRVU3RoWmx6M3hIUURUVGNJNWliOFJyQ2swZEZ6YTgvQmw3VUdtWlUwSXZ2UmdvVXF2TXNHT2dMY3pGWmRpZnJ5aGNiUTY4a2ZzZ3lCMHppdC9MN1BSV3V4RkdYdDFoTVZSVUZ3WXBJS04zVkI3cXVKZlgwamZsU1JaRndMaXdlK3VhYndmTVZ6c2doajUvOXZNNzcwK0JaMGtJcE45NzBTMG5BbHl6R0h0aW1nTUl1RXFhbUt5QTNTQlI1aHZIYmRyNENnTHFUbXIzbFFnWmpnSkNvN1FXYUJWTXdCR0RpdzVOVVhUUnBycWc4U3h2eDlnNWZwbXMrL0o2QjFEelNTM3ZRZzgxdHFRU1ZDWVJpc0Y3M2VqZlFuZk4zcUszd3RJRDkxQnRISmFvMEFaUUdKVFpKOXVsZ0kzV3hzdWR4ejB0VHVpNlJlSWpmSWsxekZRdFpwRExGMnB3NGpTQVdQTlJqNDBYdVIrRzFUVlI3OVFiME9FYkw4RDFoTU5zWmo3MTZNbUhSOTlKaUxNdm1FWHV5a1V4VGhGYjRMTzZVbW1kU3UwTlBpMXQ2NmNkYURpQWhMaVBFTGdUNkZsenA2T2FGSGNSNjRncEtyemtTNDJONEhJeFpNa2R6M0FsYkRhK2pOWHZPR1l3UWl5K0xNNENZWGtrTWtHR3ZTWis5R2xWQ0l5RXBJaXIzbEQ3bmdzZGk4emxGWDYvekNaczlQSUtwZFZlSGJGZi9GS20wV3AreHI0Ykd0R0RrVHR2Nk1Manh2YU8zanFHaUFWeERKVWFkTVBlS2VHSm5uempTdnpKbGdOVHV3c3grRnF5L2dPMkwxMGowWmhDWi92dE9NelVjNjl3cGhKZm9FNzU3V3lOeFJOcThJc0Y1Tkg5Y0x0b3UvbUNxOTc3YnZPSkRrSURCN3lKWEJ6YUhVQkJuSXJra1Qyemg3bGJmUm5SREJUSFZraVZMazVESUxqeC9XL1BSZEZpUUM2SzRmZGx4Y29JbzlMcnM4ZFVWZkt2TTNNYnJ6c1hGT3ZtVVh0K3NsZldvd3UyTC9ndG9mRFhvTUJZZnlEcWIvWlRaRWZ0MC83blliRm1relBEUlZacU5SR0F3YWZVNTU1UjB2SWtNbGR2VjdKUzhNT1BNYWlXQVBpelNLRG4yRzNvcys1MzRFQytaOGZnWmFPVWpZL0xLME9vME9RMmhvNUV6MGNMYWpwUjFINk9FNEhvUm1ydjQzZkFjdGpYc0hYdi81RXg3emdrWk1NZXZhTFNEdjZtcjFGcDk4QXR4L296VTFGVDBoMDUxcVcwR0g2VWpRRXk5aExSZDBBMnFkUTRMZXpReDNvbDFTblhsamt2MG4zTXFlaFozOC94bzZhdHFDdkJtQkc3amlUdXd6YnlVUngzRm1TM0NCNllOYnFON3hPYVRZRnlkOEZDL01nY0xGQmMwS3F4MXllQ2VUd1hucldQb0dvdllVQlYxYjA1cWtIa1d5V0RUaCsveXJFNzF0RjNxbUQvd3F6cUJyNE04NERtWWVuQkdFOWxtb3FIZEMyWnRpK09KVFZKcmlHZWxQQ3RjZnZRaUlQcHdDZ3BFNmg1ekZhRndLajRuZGtBUkRpTC95L1EwWTZxNU5rM1g5RURlTmdjY1pIcFdmOUpKQ3M2a29wdXRtYjdDczIrbVJYdER1S09DaGY5UVUyN3Bmb1NJaklYK3NGdHY1c0hhSms2aHBZMlpzUUhzaTBYbFowc3FMTnQ5ayszdTVnYnBSU1JCczlHaC9BaVY0dkNyYTRkOTh5U0dCdzRSR1FhSStpQ29RaG9YK3lxc3VrYkx6bXJUU3FXMVRXaXJReUlHZ1Q5VnFERE1mUzAxeGdQSlNFSTlIWlp6TGlFVXVGMm1CMi81Y2dqaEFUaWQrdGV1UVB4aldhN2NSc2t5YUhuTENjQURVUU9ESUFPVjJDWXROcnAwY29ZL091S3ZzaXlJT0lacVJ5dE1PMGVNZ1ZJWTBzWmdxeVEycXlubUx0NDBmWmd3SFVyV245Zm9TYTNtMkVRTy9uOS8yU2NuelJWdVZpVnNjM0tCSElQL3AzNlJlSWowTGlNcCtPQ0p3SHlLVW1UeDRBU1V0dXVhWktlRHl1QjlxcXJuUEFNWUVCeElsTGFvdXMzV1pHakIrcW9ub3QvNmk1UE40bUZjbHFDcUxhMGJHbks4ZnJxYy9yd2tuVGV0YUE0c2tXTEw1L21qNEd5MitFQkh3a0x3UXd2K0FKdmZTOXYvNDl1LzY0N1ZFYW15UzdZQ2ZEUHNBQUREQ1FFcWJNQ1h2Ui8xVmEwWi9YUWhoNlkrZUt0MEVpRDdpNmRZODJtQkFoNEJMRmRVV3VGZHVrdUVwaGZ2WXB3N2loVjNxTjB1NFM1NTRXU0dUa0ZsdlpYNG1hbkF4a1g2ekQxS0NWaEFMdEJnSDgzdkhxam9uc0lwOFMydHgwZ0tiYzEreHVaRVppVWlNVVlVdTByQVFsRFcrZHJoN3lVRHZqekFHSnBmTk01eThaMW45em93VzZ5YW5VZWFBNjhSZDd5TUxobFd0NVh6bGhBTVZDZmZYZ0pFelR1YzJEbENVOXNMLzVTVkRaV2N4R1E5aFM1cnJtK2VyQ1Jxd2FJQk1DNUtza0RCZHdOWmh2Q0FCdEpqS2Vla1FUSjd5MFp4SGNhbGVCaU1rbkYwZVRDZzFvUEhPUVZLQ3V3NE94cHRZUS9xS1V0TEFIWFZ2OTlLMGRWcWZDMmpVQWlHQmVYa0t3aGRYTGtJYlZxU0EyZmxraXBBeEhYNnByUEExQjF3eTVab3hPUFg4RVExOW92eXpBbFg1dHU0OXEwWC9PSExFN1o5T1cxenltRXR6ZFpyNXJZbWtFcVdtcHVSNU5jeHFwTWlZam93dUNXZWhubzIyeG5JM09IQ0xDZkFKaHRrcklhL1hPc0tZRFpCRzFJMGJsN2taR2R5cEtUQlhYdXl6WE5WUlU5L005ejhaVytwdG1oZ2NOUzBJS2VaaSs5bFl4cWRlS3lnbldTTTV3czdSYUpmNlRRZTNSaWJZUjFvNkhwRzB2VHpiTEtQZTZnRjJGODdiWlBJei9mcTNLWnZiM3UrSnhZcCtJVjBtQi9VN29YelhRRk1RK3VmWllpNzUxbkx6WlVxRE1ybU53TFJPVUFNUk8rVnJtblkwSVB1cFBVMXc0b0hBb1dnVGRnTk5pNk1uTFQ4V0pmUlhjT0pKMk1lbUc2K2ZNeHNZUU52UVJwa1RGY05vaFV6Y3ZjcHJ3NUV3WEVZQTJzbzczL2MvY3RIRGcreU05YlF4REppUlltRnFydkhYb29hS1JyekxnUjZLVWdoM3ltaWxaQ0lSSm9KbTE3aEtHM1pxTTE0Lzl5OUc5OE9BZjNkVTlqMDk3aUNlaEc3a2VxYXRJQ2hFWmJqbmQ4Y00rS3djN2FtVWp2ekQzQmNvMHl3MDJxT054OWF3OGhSblZiWDZhdkRJbGhySHZ6SU44MzFvUjljRHBwMG1DUEJXZFVDQlNqVGJ1RkZqRC90WElSbGxlT2JraFFKSUdSNlE2U1MxcXkzT29WT1VheFl6THY0U2s3dndrQUMwUitGREVIeVFZbFVhbVVkTWcyUmdwRUdhSVd1V3IxaGNnRm10QmREV2g3ZFBuWTF0U3VKOC95MXp4NkRvN2ZJYmNFenBBK2E0ODNtRG5vemdld3VmaFdqVCsvUS85WlEreFQ5UWJBT1pQSXhHV3VhSXVrVk8zSWxvZDhJM1NGZFJCTHY5ZXBDNzFLeXpSdVlpMktkOHJ5NVNINit1WnMxUHlZUlpRakdDK3Q4VzRtSE82Z1lFRWVXSkJ1UWhnSHdmV2xhZXlWb3hac0NBQVZKRUllT3hPZDZtNW45OHRCUDdHTmgxT1M0eDRCS2FVN1A0UVQzNVVIZW5meE84WWFQUThmbXlobUJhSVJVZklBTVN2ZTJZRFp5SWNNTTkrN0tNSVVabzJ0eXRvYzdCOGVvZzBNaUkrVkpFdFg0c29FRjFSWkhQZVV3NWlCTjI4OTh2MmVTcGNnVUJhWHFzOUN5VlZtTVJQMEtLUDJ1REt4MUdJcUhjS0ZCOXVQVWRkQS9vT3dNa0tVUWsraFZVVDVPbEVMdjd1a0FBUEE0eE4rZkczVmYxeUVKV0FiVGx5dWtGcThjNXBTRkY1cXVHbUgwVmVpQzVvVEFka1VES3Z6WGhWWUs5c3BRYjNVZ1Z0Qld6N1ZScnlOUVVST3BIZU5xeDlhZHA4YWREWCtRSHJUKytYblN4VVI3SVdGanlNTkZJRWlMWmkxdks1UVVrZlRDUU9qdjh2SHdiUi9MRHF3Z3M5bXdsT3pPY0RLdVBVK0dTb2lnVFdRejRWN0N2SHRaVDI3WUdKVG44RFFFM3IzdjB4aWxvODJ2U3VXSDg0WEU3VEJsTUpFb2R5eDNDRngwVUVkc3VhRHBPSEV3UjZYNlUyU0xseERYSXVZeEhlNXh2NjI4bXU0bDRMSnBYUjhkYmljTEZKQW55Q0FVeDJLb2dDamt1cmU4bXNUZktDbG8wamFlN1hNR05PSk15b0ZYbVlHZUh2eGhNUGMzTEtYLy9VY1p0c3p3dFJrQmNFdURXQysvQWNWZVBOSHVOWWI5MEpIcnRucGg1ZDlhL1lpTkpzY1N3QTFwUVZrdW1TQWtPQWdLdWRzcnl3c0N3Zkg1anNydVpHUTJDd1hKRXQzUU4wU2NLUlVnT1NCQ3FYa1BqZDVSVzJuOFZpamt4anovbWptakhCNmk0eHM5NEU2Nzk5STAyaldYNVd3UDZhTFRaTGt5TjhxNDUxT0RmeUZVZEY5WWsyZXQ5VUpsV1NzRFJMSWVCd0ZyQkEyZTdyRWsybWFLVUNCRW5PUWM2bUhVMXQvZ3gzK1VXVVFXbkpMZVUxbWUvbkFEdy96UGUwd3d0Vm9BaERZdDBoR1hQblJydjFoUHRGS01CeWtqckg3a0J5U0R3WDlQMi9XZkNkQlE5K1J4cHRsR2hvRmdpMUs0NVlOeEpEd05wTmd5MDV2WXUzVUtrMkpRYVNGUzcwK0Y1NzluRE5RenZpK0pPRlRsdDFmWDJGNXk5NEV2NHZobWRQSmRVOFVVRjU2Ymx0emxKREVFdmsySlFrOTM0aHpwTXJGZ1d3ZHUxUkxxSEhCN2h2T2hnaHNqV0ZGY01zNjZaRUtWcVhKUytxWWNVMHk0akwySVQrNlF2N2pvQ3BWbUdzUWtGY1FyblhxOUJiOTdaUS96UCtwaldmWTU0UmNRVlMydUU1YURObVVyVkdLK3E0d0xRcUhuRVViT2puSHFFeGlacUtxOVdRaUtUK2c3QS96bVlIQ2k0YzFTejRNVWhHb0t6U2l4aXoxYUNJUEJXdy9vczR2cUVqbXgzOGx6YnV0OWNWbElzeGNkTUpUTERRK3ZOZ0YyY1ZRaVcxRTQ0d3lWcnI3TUFaOE9KRVpFSzlEZWt5MzJQUkFuSkRUVXVqdGFscmJ0T2VOczhyS09uTjcvNFRqUEwvZmRlbEI4bjA4WXdSNXdmbU42VGpGWUhRSDFjbUZmK1AvNUxVMTI4Q1pEYjNQUStxMlFJazV3aE40eGwvcy9lb29pallmeWtDcm5aSEhHWkluTGhoU2pWbk5ISWdTL203VWV0NlhBTDdvZUl5UFRLeHVnbDJzRWtUQzNnZ0tjTnFZR0E5U3ZlYVlaQ00vWHNQRUtQbWs3QmlRNmprWFBKaE1yREd4Vkc0SW9aSDgrYjBrUWJYR2l0Mkw0L3hZdHh1bTVzcFNPSjdsTDltVFpRNnBxM2JOaTEwZU1mZ0ZWaDc3NU5JRlc0SEp3U1FtaTU0bk11blZTQjhxdjZKc0w3SGlsZ2N0ZHFSNThTTjVad1lCa2dOR1hzYjA1QXJWemVXbHh1Y21BSHNPT3dyczFnMzh6bTRZN2ZPZmducmFhV1kxanZZOFlEODZQZThkZzR4cE5paTg3UnNDZk5WK2NKVmMraktFdnpuZVY1Zzd0RmlxZCtsZHp4STlKemdSS2t0WUV6RUpRSVU5M2UvclJaN1lrVkZtNVV1cjVhMWYzcG83T0VtYkJUc2MrQ1FaOGNnYmIvbUphRXJoa3NyL3JURjBNcjNxeDl5SlJWSEJ6YWNWd0dScEFRaURPdnJkWU4xQXBVOTRyR1lrVFVzdWs1YjE1Wll2QVZxRlRzVlVMaS9HY29mbEljMm01Z2RFTFZOblRmdXY1Zlk5S1NlWHFoUU80S0pOYVZmbHAwQ0VKYWFFZFNLUXJJNXRaT2w1RkE4VXZlNmxTWVd5TVk0REl4a1RiT1JoWHVBdzR6b1RTMjgrN3d2TXhydVBkZnlKbUJCTkhQdCtEYmdKNHovcHJZWUhpTmFMTXNZamtQZE44ajNKZDczQXJFZk92Um52MzYxSVVVMFg1RDc1dlRSdlpkbzMzWERzanRlOU4weUo3K2lIQnF1a1FJY2pIVW9ic2RQN0hOajBVYWNSMHIvTmRlVTlGNFBNc1VLY2t6Tk4rZGhyMVI2d1J2R1VZb1pDRWJaWlJMWEt4QnA3SElUNEVQUktHakIvdW1xTFhhMXl6RWx2QW1WQUJhMDFZN3dGdk4wM2Ywb25FbUhTM2w1d1paRmV6cjVibnN5T01XVGxhMU5kaW1ZNXNVeE15VFliZmc4dzB2cXNEc28zWFAxYndLdzZ3M3VIRGQ1UHBSWnVDSnR0eWk0ZzJGeWI0Ymg1UU42ZkdORTI2ekRGN1Y4QmJwZXJLNkFKQ0xTWm5kaDZMMTlPUTBram4xUGpEMGk4c1BZcGFXOWxVeVJkZElPKzRWQS9LemxPUzJ4M2s5VUtUdElsTTBUSVdtZXFIS0dYUVpocGpvVGI2VlNKN203cjZaaVlQMnVsQVVvZmVWL0o2eCtzckxEQXkyQ2ZFNnFrREZ1OU9NWDBBSXVnN3loQUtOMDRyT3hVNk5tcGtjOUZ4bXUvVS9vR3hHdmIzeFVFTDYwdE1sSE9EaWtqY1I5RDJrKzRwbEc1WnV0d0FIY2kwRU02WHRrVEhQOU5QMlRTR1VFN1E5SGYvU0VEc2V0a25hZXhvWmhDczJLWDFMeU5JS0U0N2pkMkR3MTUreDRRVXV0VUFTbzU5Q1lHMVFBeW9BVVhrV3dtbXkzTGdTUWp5T3ZLV25qaE8veWpPd0FyWGd0NFBrSVVnZDQ1N05ReFpMbU41K0J4NVJoQ0FHdkUxYmxOZjlMek9keGJiaG5VZ2Z1RDM5MXVSRkhjS2RYREY3ZmVqb3gveThtaWZJcTRWVzQyajBHQnFOQUtkK0prMnJCMW9hOTRiT2hxcVVzanhqWnlRaGRXTzhNblR6T2tOaGVpZXU2blYxcW5yZ3JHU2huWTNJMlczb29GNFNnczRjZ3drZ2h2dHpFa0xUbU5OUm83RTdudVRuMkxJcmlGSnlvTmZQdUp0aWN0S0JtNzRGZytkWVBTMlIzTzNmOWxBZWxiVWZjbzZGNU9EL3hkS1VuRTh0V3FOMExVcDlWQUptWVZYZFVDaGJ4MjM4MWtDaStLNDJoRzUydFNQYU1hb1dTb0xQY2Zrb24rc1pYdjdEdEtwZi9HTzdhcUMza1pzRGpva29haHJGZGJWSlNTZWhrNGp5K3RzRHplQnJKSjBrMVZrUnJHN1NoVHZjTmd1cjVucVRUTEE5dlJMQmJNTTlhNlI1NEZ0Z1pQOWFKMU1aMEdCcUVpMnF6Ui8yd2tYQlhwcFhZdi9TcU1RV1dhbTVsSHBMVktxaDN4ZHRjNFdmck9mYldsbU1PNXA5Z0JUSFp1YUcxVGFkZXFRVVpKQmZBS01ENFdSR0NsMDFaeDRTVzE0YzZrdnFKdXExL080N215L3RsVHlLWndpYlBkQTNRMVVGd0I3R2Z4anEwaDN2ckxFbUNrS3Vsc0VBUkN6UnZNVjJSVnBVbFpUV240Y1Boc0hjcTNROElHSUYyKy9nOENFSU4vMU8xcVMvMkpXcXlDNmtIb0w4Y2R2R0VHbmkxSTNDTk1JcXhxaHhJL1V0R3REc2VwYmwrSHI0elh4MzZna3BCbXBoT2xkTFVYTHAzVEtibVVZRWJSWHcvZmRmeFQ3WDdZUFhHQ0hHVG1uTzk4WkxDOTA2Zmkvekd2b04rNlpzbCs3MkpWMGxJWEo0V3dZdWxFUmZHbkFDWGNoa0Yzei9ITWR3elcwTUFFaXptQmwvREo2ZUoyU01PSG1Uc25YbElGRDRlcFRrYnFBQ0dpZ2I1UExFdHdQRVRjYkNRckM5YUtTU1FnSTdEZXd1aWlxM2J0Y0RUWkIzeEI5WWxlbmhpU0FXNjIwcmwzc2ZjY3d3eGFSOHBDV2Rzd0x3dmFxcDhjM01PV3RCc2xPcmVTSkNEcWgvdzBYbm1WMFJVWFpNM2JvUmkwVXhsaHVUeDFlM1NTd09pbTlOczNYV3NoTmI4Lzc3VkhnUWhRVFlSUU1NRllYaWRmMElCKzBtSUpocWNoQTlUeUY3dGRjSDhrUUJUSHNEWS96bFpqK3EwNlFMd0JkbTkxc3IyK3VzZmxlaXB3WUMrcmdiNHROVnA3VU5rYkVqTnR6ZWZsTi9VRTlkbHZtT2x6V1dtZkh2NGVkUGkzMmJmeUNRS1d6SGJVVEV3NU0yVFpsZnpNaTFWUjVsaDBxQ1lqaDNITUlmL2MwcHBKd2I1b1lFTnBBenlxbnlmdmlTV3lBYzc2L1l1VWwvb2FVaysrYzBZc2d1TGo5ZGFQdVVvemhoZ3VjSytQRGlNckI0ODU1Mk83VWg0aHRwNmZ3S2dJa1JCTVFIUTd6MmV5WXovV1AwQm9ZZVhjOGc3aUprclhFNzA1bFo1bXhGU0poT3E1WlNleVJSb21pUm41K3VRemM5ZFdWQjBYb2JURXdOc0VRM2FIZ25JY29BczY2UGplUT09PC94ZW5jOkNpcGhlclZhbHVlPjwveGVuYzpDaXBoZXJEYXRhPjwveGVuYzpFbmNyeXB0ZWREYXRhPjwvc2FtbDI6RW5jcnlwdGVkQXNzZXJ0aW9uPjwvc2FtbDJwOlJlc3BvbnNlPg==
\ No newline at end of file
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/legacy.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/legacy.py
new file mode 100644
index 0000000..d78b5d4
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/legacy.py
@@ -0,0 +1,47 @@
+import requests
+
+from httpretty import HTTPretty
+
+from ...utils import parse_qs
+from .base import BaseBackendTest
+
+
+class BaseLegacyTest(BaseBackendTest):
+ form = ''
+ response_body = ''
+
+ def setUp(self):
+ super(BaseLegacyTest, self).setUp()
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name):
+ self.strategy.build_absolute_uri('/login/{0}'.format(
+ self.backend.name))
+ })
+
+ def extra_settings(self):
+ return {'SOCIAL_AUTH_{0}_FORM_URL'.format(self.name):
+ '/login/{0}'.format(self.backend.name)}
+
+ def do_start(self):
+ start_url = self.strategy.build_absolute_uri(self.backend.start().url)
+ HTTPretty.register_uri(
+ HTTPretty.GET,
+ start_url,
+ status=200,
+ body=self.form.format(self.complete_url)
+ )
+ HTTPretty.register_uri(
+ HTTPretty.POST,
+ self.complete_url,
+ status=200,
+ body=self.response_body,
+ content_type='application/x-www-form-urlencoded'
+ )
+ response = requests.get(start_url)
+ self.assertEqual(response.text, self.form.format(self.complete_url))
+ response = requests.post(
+ self.complete_url,
+ data=parse_qs(self.response_body)
+ )
+ self.strategy.set_request_data(parse_qs(response.text), self.backend)
+ return self.backend.complete()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/oauth.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/oauth.py
new file mode 100644
index 0000000..82419c6
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/oauth.py
@@ -0,0 +1,120 @@
+import requests
+
+from httpretty import HTTPretty
+
+from six.moves.urllib_parse import urlencode, urlparse
+
+from ...utils import parse_qs, url_add_parameters
+
+from ..models import User
+from .base import BaseBackendTest
+
+
+class BaseOAuthTest(BaseBackendTest):
+ backend = None
+ backend_path = None
+ user_data_body = None
+ user_data_url = ''
+ user_data_content_type = 'application/json'
+ access_token_body = None
+ access_token_status = 200
+ expected_username = ''
+
+ def extra_settings(self):
+ return {'SOCIAL_AUTH_' + self.name + '_KEY': 'a-key',
+ 'SOCIAL_AUTH_' + self.name + '_SECRET': 'a-secret-key'}
+
+ def _method(self, method):
+ return {'GET': HTTPretty.GET,
+ 'POST': HTTPretty.POST}[method]
+
+ def handle_state(self, start_url, target_url):
+ start_query = parse_qs(urlparse(start_url).query)
+ redirect_uri = start_query.get('redirect_uri')
+
+ if getattr(self.backend, 'STATE_PARAMETER', False):
+ if start_query.get('state'):
+ target_url = url_add_parameters(target_url, {
+ 'state': start_query['state']
+ })
+
+ if redirect_uri and getattr(self.backend, 'REDIRECT_STATE', False):
+ redirect_query = parse_qs(urlparse(redirect_uri).query)
+ if redirect_query.get('redirect_state'):
+ target_url = url_add_parameters(target_url, {
+ 'redirect_state': redirect_query['redirect_state']
+ })
+ return target_url
+
+ def auth_handlers(self, start_url):
+ target_url = self.handle_state(start_url,
+ self.strategy.build_absolute_uri(
+ self.complete_url
+ ))
+ HTTPretty.register_uri(HTTPretty.GET,
+ start_url,
+ status=301,
+ location=target_url)
+ HTTPretty.register_uri(HTTPretty.GET,
+ target_url,
+ status=200,
+ body='foobar')
+ HTTPretty.register_uri(self._method(self.backend.ACCESS_TOKEN_METHOD),
+ uri=self.backend.access_token_url(),
+ status=self.access_token_status,
+ body=self.access_token_body or '',
+ content_type='text/json')
+ if self.user_data_url:
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.user_data_url,
+ body=self.user_data_body or '',
+ content_type=self.user_data_content_type)
+ return target_url
+
+ def do_start(self):
+ start_url = self.backend.start().url
+ target_url = self.auth_handlers(start_url)
+ response = requests.get(start_url)
+ self.assertEqual(response.url, target_url)
+ self.assertEqual(response.text, 'foobar')
+ self.strategy.set_request_data(parse_qs(urlparse(start_url).query),
+ self.backend)
+ self.strategy.set_request_data(parse_qs(urlparse(target_url).query),
+ self.backend)
+ return self.backend.complete()
+
+
+class OAuth1Test(BaseOAuthTest):
+ request_token_body = None
+ raw_complete_url = '/complete/{0}/?oauth_verifier=bazqux&' \
+ 'oauth_token=foobar'
+
+ def request_token_handler(self):
+ HTTPretty.register_uri(self._method(self.backend.REQUEST_TOKEN_METHOD),
+ self.backend.REQUEST_TOKEN_URL,
+ body=self.request_token_body,
+ status=200)
+
+ def do_start(self):
+ self.request_token_handler()
+ return super(OAuth1Test, self).do_start()
+
+
+class OAuth2Test(BaseOAuthTest):
+ raw_complete_url = '/complete/{0}/?code=foobar'
+ refresh_token_body = ''
+
+ def refresh_token_arguments(self):
+ return {}
+
+ def do_refresh_token(self):
+ self.do_login()
+ HTTPretty.register_uri(self._method(self.backend.REFRESH_TOKEN_METHOD),
+ self.backend.refresh_token_url(),
+ status=200,
+ body=self.refresh_token_body)
+ user = list(User.cache.values())[0]
+ social = user.social[0]
+ social.refresh_token(strategy=self.strategy,
+ **self.refresh_token_arguments())
+ return user, social
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id.py
new file mode 100644
index 0000000..ac8ca50
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+import sys
+
+import requests
+
+from six.moves.html_parser import HTMLParser
+from openid import oidutil
+from httpretty import HTTPretty
+
+sys.path.insert(0, '..')
+
+from .base import BaseBackendTest
+from ..strategy import TestStrategy
+from ..models import TestStorage, User, TestUserSocialAuth, \
+ TestNonce, TestAssociation
+from ...utils import parse_qs, module_member
+from ...backends.utils import load_backends
+
+# Patch to remove the too-verbose output until a new version is released
+oidutil.log = lambda *args, **kwargs: None
+
+
+class FormHTMLParser(HTMLParser):
+ form = {}
+ inputs = {}
+
+ def handle_starttag(self, tag, attrs):
+ attrs = dict(attrs)
+ if tag == 'form':
+ self.form.update(attrs)
+ elif tag == 'input' and 'name' in attrs:
+ self.inputs[attrs['name']] = attrs['value']
+
+
+class OpenIdTest(BaseBackendTest):
+ backend_path = None
+ backend = None
+ access_token_body = None
+ user_data_body = None
+ user_data_url = ''
+ expected_username = ''
+ settings = None
+ partial_login_settings = None
+ raw_complete_url = '/complete/{0}/'
+
+ def setUp(self):
+ HTTPretty.enable()
+ Backend = module_member(self.backend_path)
+ self.strategy = TestStrategy(TestStorage)
+ self.complete_url = self.raw_complete_url.format(Backend.name)
+ self.backend = Backend(self.strategy, redirect_uri=self.complete_url)
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_AUTHENTICATION_BACKENDS': (
+ self.backend_path,
+ 'social_core.tests.backends.test_broken.BrokenBackendAuth'
+ )
+ })
+ # Force backends loading to trash PSA cache
+ load_backends(
+ self.strategy.get_setting('SOCIAL_AUTH_AUTHENTICATION_BACKENDS'),
+ force_load=True
+ )
+
+ def tearDown(self):
+ self.strategy = None
+ User.reset_cache()
+ TestUserSocialAuth.reset_cache()
+ TestNonce.reset_cache()
+ TestAssociation.reset_cache()
+ HTTPretty.disable()
+
+ def get_form_data(self, html):
+ parser = FormHTMLParser()
+ parser.feed(html)
+ return parser.form, parser.inputs
+
+ def openid_url(self):
+ return self.backend.openid_url()
+
+ def post_start(self):
+ pass
+
+ def do_start(self):
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.openid_url(),
+ status=200,
+ body=self.discovery_body,
+ content_type='application/xrds+xml')
+ start = self.backend.start()
+ self.post_start()
+ form, inputs = self.get_form_data(start)
+ HTTPretty.register_uri(HTTPretty.POST,
+ form.get('action'),
+ status=200,
+ body=self.server_response)
+ response = requests.post(form.get('action'), data=inputs)
+ self.strategy.set_request_data(parse_qs(response.content),
+ self.backend)
+ HTTPretty.register_uri(HTTPretty.POST,
+ form.get('action'),
+ status=200,
+ body='is_valid:true\n')
+ return self.backend.complete()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id_connect.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id_connect.py
new file mode 100644
index 0000000..3b73631
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/open_id_connect.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+from calendar import timegm
+
+import os
+import sys
+import json
+import datetime
+import unittest2
+
+try:
+ from jwkest.jwk import RSAKey, KEYS
+ from jwkest.jws import JWS
+ from jwkest.jwt import b64encode_item
+ NO_JWKEST = False
+except ImportError:
+ NO_JWKEST = True
+
+from httpretty import HTTPretty
+
+sys.path.insert(0, '..')
+
+from ...exceptions import AuthTokenError
+
+
+class OpenIdConnectTestMixin(object):
+ """
+ Mixin to test OpenID Connect consumers. Inheriting classes should also
+ inherit OAuth2Test.
+ """
+ client_key = 'a-key'
+ client_secret = 'a-secret-key'
+ issuer = None # id_token issuer
+ openid_config_body = None
+ key = None
+
+ def setUp(self):
+ super(OpenIdConnectTestMixin, self).setUp()
+ test_root = os.path.dirname(os.path.dirname(__file__))
+ self.key = RSAKey(kid='testkey').load(os.path.join(test_root, 'testkey.pem'))
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.backend.OIDC_ENDPOINT + '/.well-known/openid-configuration',
+ status=200,
+ body=self.openid_config_body
+ )
+ oidc_config = json.loads(self.openid_config_body)
+
+ def jwks(_request, _uri, headers):
+ ks = KEYS()
+ ks.add(self.key.serialize())
+ return 200, headers, ks.dump_jwks()
+
+ HTTPretty.register_uri(HTTPretty.GET,
+ oidc_config.get('jwks_uri'),
+ status=200,
+ body=jwks)
+
+ def extra_settings(self):
+ settings = super(OpenIdConnectTestMixin, self).extra_settings()
+ settings.update({
+ 'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key,
+ 'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret,
+ 'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name):
+ self.client_secret
+ })
+ return settings
+
+ def access_token_body(self, request, _url, headers):
+ """
+ Get the nonce from the request parameters, add it to the id_token, and
+ return the complete response.
+ """
+ nonce = self.backend.data['nonce'].encode('utf-8')
+ body = self.prepare_access_token_body(nonce=nonce)
+ return 200, headers, body
+
+ def get_id_token(self, client_key=None, expiration_datetime=None,
+ issue_datetime=None, nonce=None, issuer=None):
+ """
+ Return the id_token to be added to the access token body.
+ """
+ return {
+ 'iss': issuer,
+ 'nonce': nonce,
+ 'aud': client_key,
+ 'azp': client_key,
+ 'exp': expiration_datetime,
+ 'iat': issue_datetime,
+ 'sub': '1234'
+ }
+
+ def prepare_access_token_body(self, client_key=None, tamper_message=False,
+ expiration_datetime=None,
+ issue_datetime=None, nonce=None,
+ issuer=None):
+ """
+ Prepares a provider access token response. Arguments:
+
+ client_id -- (str) OAuth ID for the client that requested
+ authentication.
+ expiration_time -- (datetime) Date and time after which the response
+ should be considered invalid.
+ """
+
+ body = {'access_token': 'foobar', 'token_type': 'bearer'}
+ client_key = client_key or self.client_key
+ now = datetime.datetime.utcnow()
+ expiration_datetime = expiration_datetime or \
+ (now + datetime.timedelta(seconds=30))
+ issue_datetime = issue_datetime or now
+ nonce = nonce or 'a-nonce'
+ issuer = issuer or self.issuer
+ id_token = self.get_id_token(
+ client_key, timegm(expiration_datetime.utctimetuple()),
+ timegm(issue_datetime.utctimetuple()), nonce, issuer)
+
+ body['id_token'] = JWS(id_token, jwk=self.key, alg='RS256').sign_compact()
+ if tamper_message:
+ header, msg, sig = body['id_token'].split('.')
+ id_token['sub'] = '1235'
+ msg = b64encode_item(id_token).decode('utf-8')
+ body['id_token'] = '.'.join([header, msg, sig])
+
+ return json.dumps(body)
+
+ def authtoken_raised(self, expected_message, **access_token_kwargs):
+ self.access_token_body = self.prepare_access_token_body(
+ **access_token_kwargs
+ )
+ with self.assertRaisesRegexp(AuthTokenError, expected_message):
+ self.do_login()
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_invalid_signature(self):
+ self.authtoken_raised(
+ 'Token error: Signature verification failed',
+ tamper_message=True
+ )
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_expired_signature(self):
+ expiration_datetime = datetime.datetime.utcnow() - \
+ datetime.timedelta(seconds=30)
+ self.authtoken_raised('Token error: Signature has expired',
+ expiration_datetime=expiration_datetime)
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_invalid_issuer(self):
+ self.authtoken_raised('Token error: Invalid issuer',
+ issuer='someone-else')
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_invalid_audience(self):
+ self.authtoken_raised('Token error: Invalid audience',
+ client_key='someone-else')
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_invalid_issue_time(self):
+ expiration_datetime = datetime.datetime.utcnow() - \
+ datetime.timedelta(hours=1)
+ self.authtoken_raised('Token error: Incorrect id_token: iat',
+ issue_datetime=expiration_datetime)
+
+ @unittest2.skipIf(NO_JWKEST, 'No Jwkest installed')
+ def test_invalid_nonce(self):
+ self.authtoken_raised(
+ 'Token error: Incorrect id_token: nonce',
+ nonce='something-wrong'
+ )
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_amazon.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_amazon.py
new file mode 100644
index 0000000..f6db337
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_amazon.py
@@ -0,0 +1,48 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class AmazonOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.amazon.AmazonOAuth2'
+ user_data_url = 'https://www.amazon.com/ap/user/profile'
+ expected_username = 'FooBar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'user_id': 'amzn1.account.ABCDE1234',
+ 'email': 'foo@bar.com',
+ 'name': 'Foo Bar'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
+
+
+class AmazonOAuth2BrokenServerResponseTest(OAuth2Test):
+ backend_path = 'social_core.backends.amazon.AmazonOAuth2'
+ user_data_url = 'https://www.amazon.com/ap/user/profile'
+ expected_username = 'FooBar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'Request-Id': '02GGTU7CWMNFTV3KH3J6',
+ 'Profile': {
+ 'Name': 'Foo Bar',
+ 'CustomerId': 'amzn1.account.ABCDE1234',
+ 'PrimaryEmail': 'foo@bar.com'
+ }
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_angel.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_angel.py
new file mode 100644
index 0000000..cd2e499
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_angel.py
@@ -0,0 +1,39 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class AngelOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.angel.AngelOAuth2'
+ user_data_url = 'https://api.angel.co/1/me/'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'facebook_url': 'http://www.facebook.com/foobar',
+ 'bio': None,
+ 'name': 'Foo Bar',
+ 'roles': [],
+ 'github_url': None,
+ 'angellist_url': 'https://angel.co/foobar',
+ 'image': 'https://graph.facebook.com/foobar/picture?type=square',
+ 'linkedin_url': None,
+ 'locations': [],
+ 'twitter_url': None,
+ 'what_ive_built': None,
+ 'dribbble_url': None,
+ 'behance_url': None,
+ 'blog_url': None,
+ 'aboutme_url': None,
+ 'follower_count': 0,
+ 'online_bio_url': None,
+ 'id': 101010
+ })
+ expected_username = 'foobar'
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_arcgis.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_arcgis.py
new file mode 100644
index 0000000..4ebf7ed
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_arcgis.py
@@ -0,0 +1,29 @@
+import json
+from .oauth import OAuth2Test
+
+
+class ArcGISOAuth2Test(OAuth2Test):
+ user_data_url = 'https://www.arcgis.com/sharing/rest/community/self'
+ backend_path = 'social_core.backends.arcgis.ArcGISOAuth2'
+ expected_username = 'gis@rocks.com'
+
+ user_data_body = json.dumps({
+ 'first_name': 'Gis',
+ 'last_name': 'Rocks',
+ 'email': 'gis@rocks.com',
+ 'fullName': 'Gis Rocks',
+ 'username': 'gis@rocks.com'
+ })
+
+ access_token_body = json.dumps({
+ 'access_token': 'CM-gcB85taGhRmoI7l3PSGaXUNsaLkTg-dHH7XtA9Dnlin' \
+ 'PYKBBrIvFzhd1JtDhh7hEwSv_6eLLcLtUqe3gD6i1yaYYF' \
+ 'pUQJwy8KEujke5AE87tP9XIoMtp4_l320pUL',
+ 'expires_in': 86400
+ })
+
+ 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_asana.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_asana.py
new file mode 100644
index 0000000..2df37b6
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_asana.py
@@ -0,0 +1,34 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class AsanaOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.asana.AsanaOAuth2'
+ user_data_url = 'https://app.asana.com/api/1.0/users/me'
+ expected_username = 'erlich@bachmanity.com'
+ access_token_body = json.dumps({
+ 'access_token': 'aviato',
+ 'token_type': 'bearer'
+ })
+ # https://asana.com/developers/api-reference/users
+ user_data_body = json.dumps({
+ 'data': {
+ 'id': 12345,
+ 'name': 'Erlich Bachman',
+ 'email': 'erlich@bachmanity.com',
+ 'photo': None,
+ 'workspaces': [
+ {
+ 'id': 123456,
+ 'name': 'Pied Piper'
+ }
+ ]
+ }
+ })
+
+ 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_azuread.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_azuread.py
new file mode 100644
index 0000000..bfcc71d
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_azuread.py
@@ -0,0 +1,73 @@
+"""
+Copyright (c) 2015 Microsoft Open Technologies, Inc.
+
+All rights reserved.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+"""
+
+import json
+
+from .oauth import OAuth2Test
+
+
+class AzureADOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.azuread.AzureADOAuth2'
+ user_data_url = 'https://graph.windows.net/me'
+ expected_username = 'foobar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer',
+ 'id_token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL'
+ '3N0cy53aW5kb3dzLm5ldC83Mjc0MDZhYy03MDY4LTQ4ZmEtOTJiOS1jMmQ'
+ '2NzIxMWJjNTAvIiwiaWF0IjpudWxsLCJleHAiOm51bGwsImF1ZCI6IjAyO'
+ 'WNjMDEwLWJiNzQtNGQyYi1hMDQwLWY5Y2VkM2ZkMmM3NiIsInN1YiI6In'
+ 'FVOHhrczltSHFuVjZRMzR6aDdTQVpvY2loOUV6cnJJOW1wVlhPSWJWQTg'
+ 'iLCJ2ZXIiOiIxLjAiLCJ0aWQiOiI3Mjc0MDZhYy03MDY4LTQ4ZmEtOTJi'
+ 'OS1jMmQ2NzIxMWJjNTAiLCJvaWQiOiI3ZjhlMTk2OS04YjgxLTQzOGMtO'
+ 'GQ0ZS1hZDZmNTYyYjI4YmIiLCJ1cG4iOiJmb29iYXJAdGVzdC5vbm1pY3'
+ 'Jvc29mdC5jb20iLCJnaXZlbl9uYW1lIjoiZm9vIiwiZmFtaWx5X25hbWU'
+ 'iOiJiYXIiLCJuYW1lIjoiZm9vIGJhciIsInVuaXF1ZV9uYW1lIjoiZm9v'
+ 'YmFyQHRlc3Qub25taWNyb3NvZnQuY29tIiwicHdkX2V4cCI6IjQ3MzMwO'
+ 'TY4IiwicHdkX3VybCI6Imh0dHBzOi8vcG9ydGFsLm1pY3Jvc29mdG9ubG'
+ 'luZS5jb20vQ2hhbmdlUGFzc3dvcmQuYXNweCJ9.3V50dHXTZOHj9UWtkn'
+ '2g7BjX5JxNe8skYlK4PdhiLz4',
+ 'expires_in': 3600,
+ 'expires_on': 1423650396,
+ 'not_before': 1423646496
+ })
+ refresh_token_body = json.dumps({
+ 'access_token': 'foobar-new-token',
+ 'token_type': 'bearer',
+ 'expires_in': 3600,
+ 'refresh_token': 'foobar-new-refresh-token',
+ 'scope': 'identity'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
+
+ def test_refresh_token(self):
+ user, social = self.do_refresh_token()
+ self.assertEqual(social.extra_data['access_token'], 'foobar-new-token')
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_behance.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_behance.py
new file mode 100644
index 0000000..ed57a0b
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_behance.py
@@ -0,0 +1,51 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class BehanceOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.behance.BehanceOAuth2'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'valid': 1,
+ 'user': {
+ 'username': 'foobar',
+ 'city': 'Foo City',
+ 'first_name': 'Foo',
+ 'last_name': 'Bar',
+ 'display_name': 'Foo Bar',
+ 'url': 'http://www.behance.net/foobar',
+ 'country': 'Fooland',
+ 'company': '',
+ 'created_on': 1355152329,
+ 'state': '',
+ 'fields': [
+ 'Programming',
+ 'Web Design',
+ 'Web Development'
+ ],
+ 'images': {
+ '32': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-32.jpg',
+ '50': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-50.jpg',
+ '115': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-138.jpg',
+ '129': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-138.jpg',
+ '138': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-138.jpg',
+ '78': 'https://www.behance.net/assets/img/profile/'
+ 'no-image-78.jpg'
+ },
+ 'id': 1010101,
+ 'occupation': 'Software Developer'
+ }
+ })
+ expected_username = 'foobar'
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_bitbucket.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_bitbucket.py
new file mode 100644
index 0000000..eef4d91
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_bitbucket.py
@@ -0,0 +1,167 @@
+import json
+
+from httpretty import HTTPretty
+
+from six.moves.urllib_parse import urlencode
+
+from ...exceptions import AuthForbidden
+from .oauth import OAuth1Test, OAuth2Test
+
+
+class BitbucketOAuthMixin(object):
+ user_data_url = 'https://api.bitbucket.org/2.0/user'
+ expected_username = 'foobar'
+ bb_api_user_emails = 'https://api.bitbucket.org/2.0/user/emails'
+
+ user_data_body = json.dumps({
+ u'created_on': u'2012-03-29T18:07:38+00:00',
+ u'display_name': u'Foo Bar',
+ u'links': {
+ u'avatar': {u'href': u'https://bitbucket.org/account/foobar/avatar/32/'},
+ u'followers': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/followers'},
+ u'following': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/following'},
+ u'hooks': {u'href': u'https://api.bitbucket.org/2.0/users/foobar/hooks'},
+ u'html': {u'href': u'https://bitbucket.org/foobar'},
+ u'repositories': {u'href': u'https://api.bitbucket.org/2.0/repositories/foobar'},
+ u'self': {u'href': u'https://api.bitbucket.org/2.0/users/foobar'}},
+ u'location': u'Fooville, Bar',
+ u'type': u'user',
+ u'username': u'foobar',
+ u'uuid': u'{397621dc-0f78-329f-8d6d-727396248e3f}',
+ u'website': u'http://foobar.com'
+ })
+
+ emails_body = json.dumps({
+ u'page': 1,
+ u'pagelen': 10,
+ u'size': 2,
+ u'values': [
+ {
+ u'email': u'foo@bar.com',
+ u'is_confirmed': True,
+ u'is_primary': True,
+ u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}},
+ u'type': u'email'
+ },
+ {
+ u'email': u'not@confirme.com',
+ u'is_confirmed': False,
+ u'is_primary': False,
+ u'links': {u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/not@confirmed.com'}},
+ u'type': u'email'
+ }
+ ]
+ })
+
+
+class BitbucketOAuth1Test(BitbucketOAuthMixin, OAuth1Test):
+ backend_path = 'social_core.backends.bitbucket.BitbucketOAuth'
+
+ request_token_body = urlencode({
+ 'oauth_token_secret': 'foobar-secret',
+ 'oauth_token': 'foobar',
+ 'oauth_callback_confirmed': 'true'
+ })
+
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+
+ def test_login(self):
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.bb_api_user_emails,
+ status=200, body=self.emails_body)
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.bb_api_user_emails,
+ status=200, body=self.emails_body)
+ self.do_partial_pipeline()
+
+
+class BitbucketOAuth1FailTest(BitbucketOAuth1Test):
+ emails_body = json.dumps({
+ u'page': 1,
+ u'pagelen': 10,
+ u'size': 1,
+ u'values': [
+ {
+ u'email': u'foo@bar.com',
+ u'is_confirmed': False,
+ u'is_primary': True,
+ u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}},
+ u'type': u'email'
+ }
+ ]
+ })
+
+ def test_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True
+ })
+ with self.assertRaises(AuthForbidden):
+ super(BitbucketOAuth1FailTest, self).test_login()
+
+ def test_partial_pipeline(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_BITBUCKET_VERIFIED_EMAILS_ONLY': True
+ })
+ with self.assertRaises(AuthForbidden):
+ super(BitbucketOAuth1FailTest, self).test_partial_pipeline()
+
+
+class BitbucketOAuth2Test(BitbucketOAuthMixin, OAuth2Test):
+ backend_path = 'social_core.backends.bitbucket.BitbucketOAuth2'
+
+ access_token_body = json.dumps({
+ 'access_token': 'foobar_access',
+ 'scopes': 'foo_scope',
+ 'expires_in': 3600,
+ 'refresh_token': 'foobar_refresh',
+ 'token_type': 'bearer'
+ })
+
+ def test_login(self):
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.bb_api_user_emails,
+ status=200, body=self.emails_body)
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ HTTPretty.register_uri(HTTPretty.GET,
+ self.bb_api_user_emails,
+ status=200, body=self.emails_body)
+ self.do_partial_pipeline()
+
+
+class BitbucketOAuth2FailTest(BitbucketOAuth2Test):
+ emails_body = json.dumps({
+ u'page': 1,
+ u'pagelen': 10,
+ u'size': 1,
+ u'values': [
+ {
+ u'email': u'foo@bar.com',
+ u'is_confirmed': False,
+ u'is_primary': True,
+ u'links': { u'self': {u'href': u'https://api.bitbucket.org/2.0/user/emails/foo@bar.com'}},
+ u'type': u'email'
+ }
+ ]
+ })
+
+ def test_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True
+ })
+ with self.assertRaises(AuthForbidden):
+ super(BitbucketOAuth2FailTest, self).test_login()
+
+ def test_partial_pipeline(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_BITBUCKET_OAUTH2_VERIFIED_EMAILS_ONLY': True
+ })
+ with self.assertRaises(AuthForbidden):
+ super(BitbucketOAuth2FailTest, self).test_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_box.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_box.py
new file mode 100644
index 0000000..ca66562
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_box.py
@@ -0,0 +1,68 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class BoxOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.box.BoxOAuth2'
+ user_data_url = 'https://api.box.com/2.0/users/me'
+ expected_username = 'sean+awesome@box.com'
+ access_token_body = json.dumps({
+ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl',
+ 'expires_in': 3600,
+ 'restricted_to': [],
+ 'token_type': 'bearer',
+ 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9bU'
+ 'nsUfGMinKBDLZWP9BgR'
+ })
+ user_data_body = json.dumps({
+ 'type': 'user',
+ 'id': '181216415',
+ 'name': 'sean rose',
+ 'login': 'sean+awesome@box.com',
+ 'created_at': '2012-05-03T21:39:11-07:00',
+ 'modified_at': '2012-11-14T11:21:32-08:00',
+ 'role': 'admin',
+ 'language': 'en',
+ 'space_amount': 11345156112,
+ 'space_used': 1237009912,
+ 'max_upload_size': 2147483648,
+ 'tracking_codes': [],
+ 'can_see_managed_users': True,
+ 'is_sync_enabled': True,
+ 'status': 'active',
+ 'job_title': '',
+ 'phone': '6509241374',
+ 'address': '',
+ 'avatar_url': 'https://www.box.com/api/avatar/large/181216415',
+ 'is_exempt_from_device_limits': False,
+ 'is_exempt_from_login_verification': False,
+ 'enterprise': {
+ 'type': 'enterprise',
+ 'id': '17077211',
+ 'name': 'seanrose enterprise'
+ }
+ })
+ refresh_token_body = json.dumps({
+ 'access_token': 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl',
+ 'expires_in': 3600,
+ 'restricted_to': [],
+ 'token_type': 'bearer',
+ 'refresh_token': 'J7rxTiWOHMoSC1isKZKBZWizoRXjkQzig5C6jFgCVJ9b'
+ 'UnsUfGMinKBDLZWP9BgR'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
+
+ def refresh_token_arguments(self):
+ uri = self.strategy.build_absolute_uri('/complete/box/')
+ return {'redirect_uri': uri}
+
+ def test_refresh_token(self):
+ user, social = self.do_refresh_token()
+ self.assertEqual(social.extra_data['access_token'],
+ 'T9cE5asGnuyYCCqIZFoWjFHvNbvVqHjl')
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_broken.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_broken.py
new file mode 100644
index 0000000..7af0638
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_broken.py
@@ -0,0 +1,37 @@
+import unittest2 as unittest
+
+from ...backends.base import BaseAuth
+from ..strategy import TestStrategy
+from ..models import TestStorage
+
+
+class BrokenBackendAuth(BaseAuth):
+ name = 'broken'
+
+
+class BrokenBackendTest(unittest.TestCase):
+ def setUp(self):
+ self.backend = BrokenBackendAuth(TestStrategy(TestStorage))
+
+ def tearDown(self):
+ self.backend = None
+
+ def test_auth_url(self):
+ with self.assertRaisesRegexp(NotImplementedError,
+ 'Implement in subclass'):
+ self.backend.auth_url()
+
+ def test_auth_html(self):
+ with self.assertRaisesRegexp(NotImplementedError,
+ 'Implement in subclass'):
+ self.backend.auth_html()
+
+ def test_auth_complete(self):
+ with self.assertRaisesRegexp(NotImplementedError,
+ 'Implement in subclass'):
+ self.backend.auth_complete()
+
+ def test_get_user_details(self):
+ with self.assertRaisesRegexp(NotImplementedError,
+ 'Implement in subclass'):
+ self.backend.get_user_details(None)
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_chatwork.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_chatwork.py
new file mode 100644
index 0000000..3a0b42e
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_chatwork.py
@@ -0,0 +1,44 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class ChatworkOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.chatwork.ChatworkOAuth2'
+ user_data_url = 'https://api.chatwork.com/v2/me'
+ expected_username = 'hogehoge'
+ access_token_body = json.dumps({
+ 'access_token': 'pyopyopyopyopyopyopyopyopyopyo',
+ 'token_type': 'Bearer',
+ 'expires_in': '1501138041000',
+ 'refresh_token': 'pyopyopyopyopyopyo',
+ 'scope': 'rooms.all:read_write'
+ })
+
+ user_data_body = json.dumps({
+ 'account_id': 123,
+ 'room_id': 322,
+ 'name': 'Foo Bar',
+ 'chatwork_id': 'hogehoge',
+ 'organization_id': 101,
+ 'organization_name': 'Foo foobar',
+ 'department': 'Support',
+ 'title': 'CMO',
+ 'url': 'http://www.example.com',
+ 'introduction': '',
+ 'mail': 'hogehoge@example.com',
+ 'tel_organization': '',
+ 'tel_extension': '',
+ 'tel_mobile': '',
+ 'skype': '',
+ 'facebook': '',
+ 'twitter': '',
+ 'avatar_image_url': 'https://www.example.com/hogehoge.jpg',
+ 'login_mail': 'hogehoge@example.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_clef.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_clef.py
new file mode 100644
index 0000000..1e6d93d
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_clef.py
@@ -0,0 +1,26 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class ClefOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.clef.ClefOAuth2'
+ user_data_url = 'https://clef.io/api/v1/info'
+ expected_username = 'test'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar'
+ })
+ user_data_body = json.dumps({
+ 'info': {
+ 'id': '123456789',
+ 'first_name': 'Test',
+ 'last_name': 'User',
+ 'email': 'test@example.com'
+ }
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_coinbase.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_coinbase.py
new file mode 100644
index 0000000..7996987
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_coinbase.py
@@ -0,0 +1,32 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class CoinbaseOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.coinbase.CoinbaseOAuth2'
+ user_data_url = 'https://api.coinbase.com/v2/user'
+ expected_username = 'satoshi_nakomoto'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ "data": {
+ "id": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
+ 'name': "Satoshi Nakamoto",
+ "username": "satoshi_nakomoto",
+ "profile_location": None,
+ "profile_bio": None,
+ "profile_url": "https://coinbase.com/satoshi_nakomoto",
+ "avatar_url": None,
+ "resource": "user",
+ "resource_path": "/v2/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_coursera.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_coursera.py
new file mode 100644
index 0000000..aa5cce0
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_coursera.py
@@ -0,0 +1,38 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class CourseraOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.coursera.CourseraOAuth2'
+ user_data_url = \
+ 'https://api.coursera.org/api/externalBasicProfiles.v1?q=me'
+ expected_username = '560e7ed2076e0d589e88bd74b6aad4b7'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'Bearer',
+ 'expires_in': 1795
+ })
+ request_token_body = json.dumps({
+ 'code': 'foobar-code',
+ 'client_id': 'foobar-client-id',
+ 'client_secret': 'foobar-client-secret',
+ 'redirect_uri': 'http://localhost:8000/accounts/coursera/',
+ 'grant_type': 'authorization_code'
+ })
+ user_data_body = json.dumps({
+ 'token_type': 'Bearer',
+ 'paging': None,
+ 'elements': [{
+ 'id': '560e7ed2076e0d589e88bd74b6aad4b7'
+ }],
+ 'access_token': 'foobar',
+ 'expires_in': 1800,
+ 'linked': None
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dailymotion.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dailymotion.py
new file mode 100644
index 0000000..96da8e0
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dailymotion.py
@@ -0,0 +1,23 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DailymotionOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.dailymotion.DailymotionOAuth2'
+ user_data_url = 'https://api.dailymotion.com/auth/'
+ expected_username = 'foobar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'id': 'foobar',
+ 'screenname': 'foobar'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_deezer.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_deezer.py
new file mode 100644
index 0000000..464c8e7
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_deezer.py
@@ -0,0 +1,36 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DeezerOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.deezer.DeezerOAuth2'
+ user_data_url = 'http://api.deezer.com/user/me'
+ expected_username = 'foobar'
+ access_token_body = 'access_token=foobar&expires=0'
+ user_data_body = json.dumps({
+ 'id': '1',
+ 'name': 'foobar',
+ 'lastname': '',
+ 'firstname': '',
+ 'status': 0,
+ 'birthday': '1970-01-01',
+ 'inscription_date': '2015-01-01',
+ 'gender': 'M',
+ 'link': 'https://www.deezer.com/profile/1',
+ 'picture': 'https://api.deezer.com/user/1/image',
+ 'picture_small': 'https://cdns-images.dzcdn.net/images/user//56x56-000000-80-0-0.jpg',
+ 'picture_medium': 'https://cdns-images.dzcdn.net/images/user//250x250-000000-80-0-0.jpg',
+ 'picture_big': 'https://cdns-images.dzcdn.net/images/user//500x500-000000-80-0-0.jpg',
+ 'country': 'FR',
+ 'lang': 'FR',
+ 'is_kid': False,
+ 'tracklist': 'https://api.deezer.com/user/1/flow',
+ 'type': '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_digitalocean.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_digitalocean.py
new file mode 100644
index 0000000..5724783
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_digitalocean.py
@@ -0,0 +1,34 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DigitalOceanOAuthTest(OAuth2Test):
+ backend_path = 'social_core.backends.digitalocean.DigitalOceanOAuth'
+ user_data_url = 'https://api.digitalocean.com/v2/account'
+ expected_username = 'sammy@digitalocean.com'
+ access_token_body = json.dumps({
+ 'access_token': '547cac21118ae7',
+ 'token_type': 'bearer',
+ 'expires_in': 2592000,
+ 'refresh_token': '00a3aae641658d',
+ 'scope': 'read write',
+ 'info': {
+ 'name': 'Sammy Shark',
+ 'email': 'sammy@digitalocean.com'
+ }
+ })
+ user_data_body = json.dumps({
+ "account": {
+ 'droplet_limit': 25,
+ 'email': 'sammy@digitalocean.com',
+ 'uuid': 'b6fr89dbf6d9156cace5f3c78dc9851d957381ef',
+ 'email_verified': True
+ }
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_disqus.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_disqus.py
new file mode 100644
index 0000000..c0de85d
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_disqus.py
@@ -0,0 +1,62 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DisqusOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.disqus.DisqusOAuth2'
+ user_data_url = 'https://disqus.com/api/3.0/users/details.json'
+ expected_username = 'foobar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'code': 0,
+ 'response': {
+ 'username': 'foobar',
+ 'numFollowers': 0,
+ 'isFollowing': False,
+ 'numFollowing': 0,
+ 'name': 'Foo Bar',
+ 'numPosts': 0,
+ 'url': '',
+ 'isAnonymous': False,
+ 'rep': 1.231755,
+ 'about': '',
+ 'isFollowedBy': False,
+ 'connections': {},
+ 'emailHash': '5280f14cedf530b544aecc31fcfe0240',
+ 'reputation': 1.231755,
+ 'avatar': {
+ 'small': {
+ 'permalink': 'https://disqus.com/api/users/avatars/'
+ 'foobar.jpg',
+ 'cache': 'https://securecdn.disqus.com/uploads/'
+ 'users/453/4556/avatar32.jpg?1285535379'
+ },
+ 'isCustom': False,
+ 'permalink': 'https://disqus.com/api/users/avatars/foobar.jpg',
+ 'cache': 'https://securecdn.disqus.com/uploads/users/453/'
+ '4556/avatar92.jpg?1285535379',
+ 'large': {
+ 'permalink': 'https://disqus.com/api/users/avatars/'
+ 'foobar.jpg',
+ 'cache': 'https://securecdn.disqus.com/uploads/users/'
+ '453/4556/avatar92.jpg?1285535379'
+ }
+ },
+ 'profileUrl': 'http://disqus.com/foobar/',
+ 'numLikesReceived': 0,
+ 'isPrimary': True,
+ 'joinedAt': '2010-09-26T21:09:39',
+ 'id': '1010101',
+ 'location': ''
+ }
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dribbble.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dribbble.py
new file mode 100644
index 0000000..b1b1690
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dribbble.py
@@ -0,0 +1,26 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DribbbleOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.dribbble.DribbbleOAuth2'
+ user_data_url = 'https://api.dribbble.com/v1/user'
+ expected_username = 'foobar'
+
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+
+ user_data_body = json.dumps({
+ 'id': 'foobar',
+ 'username': 'foobar',
+ 'name': 'Foo Bar'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_drip.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_drip.py
new file mode 100644
index 0000000..e8300d2
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_drip.py
@@ -0,0 +1,29 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class DripOAuthTest(OAuth2Test):
+ backend_path = 'social_core.backends.drip.DripOAuth'
+ user_data_url = 'https://api.getdrip.com/v2/user'
+ expected_username = 'other@example.com'
+ access_token_body = json.dumps({
+ 'access_token': '822bbf7cd12243df',
+ 'token_type': 'bearer',
+ 'scope': 'public'
+ })
+
+ user_data_body = json.dumps({
+ 'users': [
+ {
+ 'email': 'other@example.com',
+ 'name': 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_dropbox.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dropbox.py
new file mode 100644
index 0000000..6c9c91b
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dropbox.py
@@ -0,0 +1,38 @@
+import json
+
+from six.moves.urllib_parse import urlencode
+
+from .oauth import OAuth1Test
+
+
+class DropboxOAuth1Test(OAuth1Test):
+ backend_path = 'social_core.backends.dropbox.DropboxOAuth'
+ user_data_url = 'https://api.dropbox.com/1/account/info'
+ expected_username = '10101010'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ request_token_body = urlencode({
+ 'oauth_token_secret': 'foobar-secret',
+ 'oauth_token': 'foobar',
+ 'oauth_callback_confirmed': 'true'
+ })
+ user_data_body = json.dumps({
+ 'referral_link': 'https://www.dropbox.com/referrals/foobar',
+ 'display_name': 'Foo Bar',
+ 'uid': 10101010,
+ 'country': 'US',
+ 'quota_info': {
+ 'shared': 138573,
+ 'quota': 2952790016,
+ 'normal': 157327
+ },
+ 'email': 'foo@bar.com'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dummy.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dummy.py
new file mode 100644
index 0000000..6510b2d
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_dummy.py
@@ -0,0 +1,159 @@
+import json
+import datetime
+import time
+
+from httpretty import HTTPretty
+
+from ...actions import do_disconnect
+from ...backends.oauth import BaseOAuth2
+from ...exceptions import AuthForbidden
+
+from ..models import User
+from .oauth import OAuth2Test
+
+
+class DummyOAuth2(BaseOAuth2):
+ name = 'dummy'
+ AUTHORIZATION_URL = 'http://dummy.com/oauth/authorize'
+ ACCESS_TOKEN_URL = 'http://dummy.com/oauth/access_token'
+ REVOKE_TOKEN_URL = 'https://dummy.com/oauth/revoke'
+ REVOKE_TOKEN_METHOD = 'GET'
+ GET_ALL_EXTRA_DATA = False
+ EXTRA_DATA = [
+ ('id', 'id'),
+ ('expires', 'expires'),
+ ('empty', 'empty', True),
+ 'url'
+ ]
+
+ def get_user_details(self, response):
+ """Return user details from Github account"""
+ return {'username': response.get('username'),
+ 'email': response.get('email', ''),
+ 'first_name': response.get('first_name', ''),
+ 'last_name': response.get('last_name', '')}
+
+ def user_data(self, access_token, *args, **kwargs):
+ """Loads user data from service"""
+ return self.get_json('http://dummy.com/user', params={
+ 'access_token': access_token
+ })
+
+
+class Dummy2OAuth2(DummyOAuth2):
+ GET_ALL_EXTRA_DATA = True
+
+
+class DummyOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.tests.backends.test_dummy.DummyOAuth2'
+ user_data_url = 'http://dummy.com/user'
+ expected_username = 'foobar'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'id': 1,
+ 'username': 'foobar',
+ 'url': 'http://dummy.com/user/foobar',
+ 'first_name': 'Foo',
+ 'last_name': 'Bar',
+ 'email': 'foo@bar.com'
+ })
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
+
+ def test_tokens(self):
+ user = self.do_login()
+ self.assertEqual(user.social[0].access_token, 'foobar')
+
+ def test_revoke_token(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_REVOKE_TOKENS_ON_DISCONNECT': True
+ })
+ self.do_login()
+ user = User.get(self.expected_username)
+ user.password = 'password'
+ HTTPretty.register_uri(self._method(self.backend.REVOKE_TOKEN_METHOD),
+ self.backend.REVOKE_TOKEN_URL,
+ status=200)
+ do_disconnect(self.backend, user)
+
+
+class WhitelistEmailsTest(DummyOAuth2Test):
+ def test_valid_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo@bar.com']
+ })
+ self.do_login()
+
+ def test_invalid_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['foo2@bar.com']
+ })
+ with self.assertRaises(AuthForbidden):
+ self.do_login()
+
+
+class WhitelistDomainsTest(DummyOAuth2Test):
+ def test_valid_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_WHITELISTED_DOMAINS': ['bar.com']
+ })
+ self.do_login()
+
+ def test_invalid_login(self):
+ self.strategy.set_settings({
+ 'SOCIAL_AUTH_WHITELISTED_EMAILS': ['bar2.com']
+ })
+ with self.assertRaises(AuthForbidden):
+ self.do_login()
+
+
+DELTA = datetime.timedelta(days=1)
+
+
+class ExpirationTimeTest(DummyOAuth2Test):
+ user_data_body = json.dumps({
+ 'id': 1,
+ 'username': 'foobar',
+ 'url': 'http://dummy.com/user/foobar',
+ 'first_name': 'Foo',
+ 'last_name': 'Bar',
+ 'email': 'foo@bar.com',
+ 'expires': time.mktime((datetime.datetime.utcnow() +
+ DELTA).timetuple())
+ })
+
+ def test_expires_time(self):
+ user = self.do_login()
+ social = user.social[0]
+ expiration = social.expiration_timedelta()
+ self.assertEqual(expiration <= DELTA, True)
+
+
+class AllExtraDataTest(DummyOAuth2Test):
+ backend_path = 'social_core.tests.backends.test_dummy.Dummy2OAuth2'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'id': 1,
+ 'username': 'foobar',
+ 'url': 'http://dummy.com/user/foobar',
+ 'first_name': 'Foo',
+ 'last_name': 'Bar',
+ 'email': 'foo@bar.com',
+ 'not_normally_in_extra_data': 'value'
+ })
+
+ def test_get_all_extra_data(self):
+ user = self.do_login()
+ social = user.social[0]
+ self.assertIn('not_normally_in_extra_data', social.extra_data)
+ self.assertEqual(len(social.extra_data), 10) # Includes auth_time.
diff --git a/flex/lib/python3.6/site-packages/social_core/tests/backends/test_edmodo.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_edmodo.py
new file mode 100644
index 0000000..d125082
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_edmodo.py
@@ -0,0 +1,44 @@
+import json
+
+from .oauth import OAuth2Test
+
+
+class EdmodoOAuth2Test(OAuth2Test):
+ backend_path = 'social_core.backends.edmodo.EdmodoOAuth2'
+ user_data_url = 'https://api.edmodo.com/users/me'
+ expected_username = 'foobar12345'
+ access_token_body = json.dumps({
+ 'access_token': 'foobar',
+ 'token_type': 'bearer'
+ })
+ user_data_body = json.dumps({
+ 'username': 'foobar12345',
+ 'coppa_verified': False,
+ 'first_name': 'Foo',
+ 'last_name': 'Bar',
+ 'premium': False,
+ 'verified_institution_member': False,
+ 'url': 'https://api.edmodo.com/users/12345',
+ 'type': 'teacher',
+ 'time_zone': None,
+ 'end_level': None,
+ 'start_level': None,
+ 'locale': 'en',
+ 'subjects': None,
+ 'utc_offset': None,
+ 'email': 'foo.bar@example.com',
+ 'gender': None,
+ 'about': None,
+ 'user_title': None,
+ 'id': 12345,
+ 'avatars': {
+ 'small': 'https://api.edmodo.com/users/12345/avatar?type=small&u=5a15xug93m53mi4ey3ck4fvkq',
+ 'large': 'https://api.edmodo.com/users/12345/avatar?type=large&u=5a15xug93m53mi4ey3ck4fvkq'
+ }
+ })
+
+ 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_email.py b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_email.py
new file mode 100644
index 0000000..d59a32b
--- /dev/null
+++ b/flex/lib/python3.6/site-packages/social_core/tests/backends/test_email.py
@@ -0,0 +1,19 @@
+from .legacy import BaseLegacyTest
+
+
+class EmailTest(BaseLegacyTest):
+ backend_path = 'social_core.backends.email.EmailAuth'
+ expected_username = 'foo'
+ response_body = 'email=foo@bar.com'
+ form = """
+
+ """
+
+ def test_login(self):
+ self.do_login()
+
+ def test_partial_pipeline(self):
+ self.do_partial_pipeline()
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('