Integrated my history into data

This commit is contained in:
Karthik Pullela
2018-01-21 10:46:18 -08:00
parent bce29a723f
commit 97d35e633c
917 changed files with 50890 additions and 11 deletions
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/Users/karthikpullela/Desktop/hackdavis/Flex/flex/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from jwt.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
sys.exit(main())
Binary file not shown.
Binary file not shown.
Binary file not shown.
+14
View File
@@ -38,6 +38,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'social_django',
]
MIDDLEWARE = [
@@ -63,11 +64,20 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
]
AUTHENTICATION_BACKENDS = (
'social_core.backends.open_id.OpenIdAuth',
'social_core.backends.google.GoogleOpenId',
'social_core.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend',
)
WSGI_APPLICATION = 'flex.wsgi.application'
@@ -119,4 +129,8 @@ USE_TZ = True
# https://docs.djangoproject.com/en/1.11/howto/static-files/
STATIC_URL = '/static/'
LOGIN_URL = 'log'
LOGIN_REDIRECT_URL ='/home/dash'
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY ='603222874022-4msscrpqkgc4o8qn9ng3r0tetadcl69t.apps.googleusercontent.com' #Paste CLient Key
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'NgVaidrQ8rG5uMhMKfPOv9QI' #Paste Secret Key
+2 -1
View File
@@ -21,5 +21,6 @@ urlpatterns = [
path('admin/', admin.site.urls),
path('home/', include('home.urls')),
path('logout/', auth_views.logout, name='logout'),
path('accounts/', include('django.contrib.auth.urls'))
path('accounts/', include('django.contrib.auth.urls')),
path('auth/', include('social_django.urls', namespace='social')),
]
Binary file not shown.
Binary file not shown.
+11
View File
@@ -3,6 +3,11 @@
/* "#00897B" */
/* subtitle */
/* button */
/* 603222874022-4msscrpqkgc4o8qn9ng3r0tetadcl69t.apps.googleusercontent.com
*/
/* NgVaidrQ8rG5uMhMKfPOv9QI */
html {
margin: 0;
padding: 0;
@@ -164,6 +169,12 @@ button:hover {
box-shadow: inset 0 0 10px #000000;
cursor: pointer;
}
#googol{
position: relative;
left: 45%;
top: 90%;
color: white;
}
#begin{
color: white;
font-size: 25px;
+21 -9
View File
@@ -17,18 +17,29 @@
<script src="https://use.fontawesome.com/7b29adddad.js"></script>
<script type="text/javascript">
var i = 5;
a = [];
b = [];
{% for num in walk_data %}
a.push(parseInt({{num}}));
{% endfor %}
{% for fit in calories_data %}
b.push(parseInt({{fit}}));
{% endfor %}
google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(drawChart);
function drawChart() {
var data = google.visualization.arrayToDataTable([
['Task', 'Hours per Day'],
['Completed', 11],
['To Go' , 13],
['Completed', 10],
['To Go' , 2],
]);
var options = {
title: 'Personal Goal Graph',
pieHole: 0.8,
colors:['red','#004411'],
pieHole: 0.7,
colors:['orange','white'],
};
var chart = new google.visualization.PieChart(document.getElementById('piechart'));
chart.draw(data, options);
@@ -41,12 +52,13 @@
// Some raw data (not necessarily accurate)
var data = google.visualization.arrayToDataTable([
['Day', 'Steps', 'Calories', 'Sleep', 'Activity', 'Average'],
['01/05', 10, 94, 90, 84, 70],
['01/06', 70, 90, 91, 84, 84],
['01/07', 96, 98, 70, 93, 90],
['01/08', 83, 90, 90, 75, 85],
['01/09', 80, 90, 93, 95, 90]
['01/05', a[0], b[0], 90, 84, 70],
['01/06', a[1], b[1], 91, 84, 84],
['01/07', a[2], b[2], 70, 93, 90],
['01/08', a[3], b[3], 90, 75, 85],
['01/09', a[4], b[4], 93, 95, 90]
]);
var options = {
title : 'Daily Fitness Goals',
vAxis: {title: 'Percent'},
+6 -1
View File
@@ -9,6 +9,10 @@
<link rel="stylesheet" type="text/css" href="{% static '/home/main.css' %}">
<script type="text/javascript" src="jquery-3.1.1.min.js"></script>
<meta name="google-signin-scope" content="profile email">
<meta name="google-signin-client_id" content="603222874022-4msscrpqkgc4o8qn9ng3r0tetadcl69t.apps.googleusercontent.com">
<script src="https://apis.google.com/js/platform.js" async defer></script>
<link href="https://fonts.googleapis.com/css?family=Rubik" rel="stylesheet">
@@ -63,7 +67,8 @@
<p class="inputs">
</p>
<br>
<a href="{% url 'log' %}"><button id="begin">BEGIN</button></a>
<a href="{% url ''social:begin' 'google-oauth2'' %}"><button id="begin">BEGIN</button></a><br>
<br>
{% endif %}
</div>
+1
View File
@@ -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'),
+9
View File
@@ -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):
@@ -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 <https://tools.ietf.org/html/rfc7519>`_. Original implementation
was written by `@progrium <https://github.com/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
@@ -0,0 +1 @@
pip
@@ -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: [email protected]
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 <https://tools.ietf.org/html/rfc7519>`_. Original implementation
was written by `@progrium <https://github.com/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
@@ -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,,
@@ -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
@@ -0,0 +1,3 @@
[console_scripts]
pyjwt = jwt.__main__:main
@@ -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": "[email protected]", "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"}
@@ -0,0 +1,851 @@
===================================================
defusedxml -- defusing XML bombs and other exploits
===================================================
"It's just XML, what could probably go wrong?"
Christian Heimes <[email protected]>
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 ``<!DOCTYPE>`` 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::
<!DOCTYPE xmlbomb [
<!ENTITY a "1234567890" >
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;">
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;">
]>
<bomb>&d;</bomb>
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.
::
<!DOCTYPE bomb [
<!ENTITY a "xxxxxxx... a couple of ten thousand chars">
]>
<bomb>&a;&a;&a;... repeat</bomb>
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::
<!DOCTYPE external [
<!ENTITY ee SYSTEM "http://www.python.org/some.xml">
]>
<root>&ee;</root>
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.
::
<!DOCTYPE external [
<!ENTITY ee SYSTEM "file:///PATH/TO/simple.xml">
]>
<root>&ee;</root>
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.
::
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head/>
<body>text</body>
</html>
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 ``<!DOCTYPE>`` processing instruction and raise a
*DTDForbidden* exception when a DTD processing instruction is found.
forbid_entities (default: True)
disallow XML with ``<!ENTITY>`` 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 <!DOCTYPE> 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 <http://bugs.python.org/issue16043>
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::
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
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 ``<!NOTATION>``. 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::
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="filename.txt" parse="text" />
</root>
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.
::
<ead xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd">
</ead>
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::
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
exclude-result-prefixes= "rt ob">
<xsl:template match="/">
<xsl:variable name="runtimeObject" select="rt:getRuntime()"/>
<xsl:variable name="command"
select="rt:exec($runtimeObject, &apos;c:\Windows\system32\cmd.exe&apos;)"/>
<xsl:variable name="commandAsString" select="ob:toString($command)"/>
<xsl:value-of select="$commandAsString"/>
</xsl:template>
</xsl:stylesheet>
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 <[email protected]>
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
@@ -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: [email protected]
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 <[email protected]>
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 ``<!DOCTYPE>`` 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::
<!DOCTYPE xmlbomb [
<!ENTITY a "1234567890" >
<!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;">
<!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;">
<!ENTITY d "&c;&c;&c;&c;&c;&c;&c;&c;">
]>
<bomb>&d;</bomb>
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.
::
<!DOCTYPE bomb [
<!ENTITY a "xxxxxxx... a couple of ten thousand chars">
]>
<bomb>&a;&a;&a;... repeat</bomb>
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::
<!DOCTYPE external [
<!ENTITY ee SYSTEM "http://www.python.org/some.xml">
]>
<root>&ee;</root>
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.
::
<!DOCTYPE external [
<!ENTITY ee SYSTEM "file:///PATH/TO/simple.xml">
]>
<root>&ee;</root>
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.
::
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head/>
<body>text</body>
</html>
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 ``<!DOCTYPE>`` processing instruction and raise a
*DTDForbidden* exception when a DTD processing instruction is found.
forbid_entities (default: True)
disallow XML with ``<!ENTITY>`` 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 <!DOCTYPE> 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 <http://bugs.python.org/issue16043>
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::
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
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 ``<!NOTATION>``. 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::
<root xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="filename.txt" parse="text" />
</root>
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.
::
<ead xmlns="urn:isbn:1-931666-22-9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:isbn:1-931666-22-9 http://www.loc.gov/ead/ead.xsd">
</ead>
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::
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
exclude-result-prefixes= "rt ob">
<xsl:template match="/">
<xsl:variable name="runtimeObject" select="rt:getRuntime()"/>
<xsl:variable name="command"
select="rt:exec($runtimeObject, &apos;c:\Windows\system32\cmd.exe&apos;)"/>
<xsl:variable name="commandAsString" select="ob:toString($command)"/>
<xsl:value-of select="$commandAsString"/>
</xsl:template>
</xsl:stylesheet>
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 <[email protected]>
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
@@ -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,,
@@ -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
@@ -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": "[email protected]", "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"}
@@ -0,0 +1 @@
defusedxml
@@ -0,0 +1,112 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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']
@@ -0,0 +1,45 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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']
@@ -0,0 +1,30 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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']
@@ -0,0 +1,120 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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
@@ -0,0 +1,110 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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)
@@ -0,0 +1,59 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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)
@@ -0,0 +1,153 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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")
@@ -0,0 +1,42 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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})
@@ -0,0 +1,34 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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)
@@ -0,0 +1,49 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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()
@@ -0,0 +1,157 @@
# defusedxml
#
# Copyright (c) 2013 by Christian Heimes <[email protected]>
# 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
@@ -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
)
@@ -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] <command> [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()
@@ -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
@@ -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
@@ -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
@@ -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)
@@ -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
@@ -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)
@@ -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
@@ -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)
@@ -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``.
@@ -0,0 +1 @@
pip
@@ -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: [email protected]
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``.
@@ -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,,
@@ -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
@@ -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": "[email protected]", "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"}
@@ -0,0 +1 @@
oauthlib
@@ -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 <[email protected]>'
__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())
@@ -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<SANITIZED>', str(body))
if 'Authorization' in headers:
headers['Authorization'] = '<SANITIZED>'
return '<oauthlib.Request url="%s", http_method="%s", headers="%s", body="%s">' % (
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]
@@ -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
@@ -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

Some files were not shown because too many files have changed in this diff Show More