Skip to content

Commit

Permalink
Merge pull request oauthlib#548 from skion/2.1.0
Browse files Browse the repository at this point in the history
Release 2.1.0
  • Loading branch information
skion authored May 22, 2018
2 parents 979b172 + 5c76855 commit f853295
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 22 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ Joel Stevenson
Brendan McCollam
Jonathan Huot
Pieter Ennes
Olaf Conradi
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Changelog
=========

2.1.0 (2018-05-21)
------------------

* Fixed some copy and paste typos (#535)
* Use secrets module in Python 3.6 and later (#533)
* Add request argument to confirm_redirect_uri (#504)
* Avoid populating spurious token credentials (#542)
* Make populate attributes API public (#546)

2.0.7 (2018-03-19)
------------------

Expand Down
12 changes: 7 additions & 5 deletions docs/oauth1/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ A few important facts regarding OAuth security

* **Tokens must be random**, OAuthLib provides a method for generating
secure tokens and it's packed into ``oauthlib.common.generate_token``,
use it. If you decide to roll your own, use ``random.SystemRandom``
which is based on ``os.urandom`` rather than the default ``random``
based on the effecient but not truly random Mersenne Twister.
Predictable tokens allow attackers to bypass virtually all defences
OAuth provides.
use it. If you decide to roll your own, use ``secrets.SystemRandom``
for Python 3.6 and later. The ``secrets`` module is designed for
generating cryptographically strong random numbers. For earlier versions
of Python, use ``random.SystemRandom`` which is based on ``os.urandom``
rather than the default ``random`` based on the effecient but not truly
random Mersenne Twister. Predictable tokens allow attackers to bypass
virtually all defences OAuth provides.

* **Timing attacks are real** and more than possible if you host your
application inside a shared datacenter. Ensure all ``validate_`` methods
Expand Down
2 changes: 1 addition & 1 deletion examples/skeleton_oauth2_web_application_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def validate_code(self, client_id, code, client, request, *args, **kwargs):
# state and user to request.scopes and request.user.
pass

def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **kwargs):
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request, *args, **kwargs):
# You did save the redirect uri with the authorization code right?
pass

Expand Down
2 changes: 1 addition & 1 deletion oauthlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"""

__author__ = 'The OAuthlib Community'
__version__ = '2.0.7'
__version__ = '2.1.0'


import logging
Expand Down
11 changes: 8 additions & 3 deletions oauthlib/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@
import collections
import datetime
import logging
import random
import re
import sys
import time

try:
from secrets import randbits
from secrets import SystemRandom
except ImportError:
from random import getrandbits as randbits
from random import SystemRandom
try:
from urllib import quote as _quote
from urllib import unquote as _unquote
Expand Down Expand Up @@ -202,7 +207,7 @@ def generate_nonce():
.. _`section 3.2.1`: https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-01#section-3.2.1
.. _`section 3.3`: https://tools.ietf.org/html/rfc5849#section-3.3
"""
return unicode_type(unicode_type(random.getrandbits(64)) + generate_timestamp())
return unicode_type(unicode_type(randbits(64)) + generate_timestamp())


def generate_timestamp():
Expand All @@ -225,7 +230,7 @@ def generate_token(length=30, chars=UNICODE_ASCII_CHARACTER_SET):
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()
rand = SystemRandom()
return ''.join(rand.choice(chars) for x in range(length))


Expand Down
23 changes: 17 additions & 6 deletions oauthlib/oauth2/rfc6749/clients/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from __future__ import absolute_import, unicode_literals

import time
import warnings

from oauthlib.common import generate_token
from oauthlib.oauth2.rfc6749 import tokens
Expand Down Expand Up @@ -111,8 +112,10 @@ def __init__(self, client_id,
self.state_generator = state_generator
self.state = state
self.redirect_url = redirect_url
self.code = None
self.expires_in = None
self._expires_at = None
self._populate_attributes(self.token)
self.populate_token_attributes(self.token)

@property
def token_types(self):
Expand Down Expand Up @@ -406,7 +409,7 @@ def parse_request_body_response(self, body, scope=None, **kwargs):
.. _`Section 7.1`: https://tools.ietf.org/html/rfc6749#section-7.1
"""
self.token = parse_token_response(body, scope=scope)
self._populate_attributes(self.token)
self.populate_token_attributes(self.token)
return self.token

def prepare_refresh_body(self, body='', refresh_token=None, scope=None, **kwargs):
Expand Down Expand Up @@ -460,7 +463,18 @@ def _add_mac_token(self, uri, http_method='GET', body=None,
return uri, headers, body

def _populate_attributes(self, response):
"""Add commonly used values such as access_token to self."""
warnings.warn("Please switch to the public method "
"populate_token_attributes.", DeprecationWarning)
return self.populate_token_attributes(response)

def populate_code_attributes(self, response):
"""Add attributes from an auth code response to self."""

if 'code' in response:
self.code = response.get('code')

def populate_token_attributes(self, response):
"""Add attributes from a token exchange response to self."""

if 'access_token' in response:
self.access_token = response.get('access_token')
Expand All @@ -478,9 +492,6 @@ def _populate_attributes(self, response):
if 'expires_at' in response:
self._expires_at = int(response.get('expires_at'))

if 'code' in response:
self.code = response.get('code')

if 'mac_key' in response:
self.mac_key = response.get('mac_key')

Expand Down
2 changes: 1 addition & 1 deletion oauthlib/oauth2/rfc6749/clients/mobile_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,5 @@ def parse_request_uri_response(self, uri, state=None, scope=None):
.. _`Section 3.3`: https://tools.ietf.org/html/rfc6749#section-3.3
"""
self.token = parse_implicit_response(uri, state=state, scope=scope)
self._populate_attributes(self.token)
self.populate_token_attributes(self.token)
return self.token
4 changes: 2 additions & 2 deletions oauthlib/oauth2/rfc6749/clients/service_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ def prepare_request_body(self,
' token requests.')
claim = {
'iss': issuer or self.issuer,
'aud': audience or self.issuer,
'sub': subject or self.issuer,
'aud': audience or self.audience,
'sub': subject or self.subject,
'exp': int(expires_at or time.time() + 3600),
'iat': int(issued_at or time.time()),
}
Expand Down
2 changes: 1 addition & 1 deletion oauthlib/oauth2/rfc6749/clients/web_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,5 @@ def parse_request_uri_response(self, uri, state=None):
oauthlib.oauth2.rfc6749.errors.MismatchingStateError
"""
response = parse_authorization_code_response(uri, state=state)
self._populate_attributes(response)
self.populate_code_attributes(response)
return response
3 changes: 2 additions & 1 deletion oauthlib/oauth2/rfc6749/grant_types/authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ def validate_token_request(self, request):
# authorization request as described in Section 4.1.1, and their
# values MUST be identical.
if not self.request_validator.confirm_redirect_uri(request.client_id, request.code,
request.redirect_uri, request.client):
request.redirect_uri, request.client,
request):
log.debug('Redirect_uri (%r) invalid for client %r (%r).',
request.redirect_uri, request.client_id, request.client)
raise errors.MismatchingRedirectURIError(request=request)
Expand Down
2 changes: 1 addition & 1 deletion oauthlib/oauth2/rfc6749/request_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def authenticate_client_id(self, client_id, request, *args, **kwargs):
"""
raise NotImplementedError('Subclasses must implement this method.')

def confirm_redirect_uri(self, client_id, code, redirect_uri, client,
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, request,
*args, **kwargs):
"""Ensure that the authorization process represented by this authorization
code began with this 'redirect_uri'.
Expand Down
12 changes: 12 additions & 0 deletions tests/oauth2/rfc6749/clients/test_mobile_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,18 @@ def test_implicit_token_uri(self):
uri = client.prepare_request_uri(self.uri, **self.kwargs)
self.assertURLEqual(uri, self.uri_kwargs)

def test_populate_attributes(self):

client = MobileApplicationClient(self.client_id)

response_uri = (self.response_uri + "&code=EVIL-CODE")

client.parse_request_uri_response(response_uri, scope=self.scope)

# We must not accidentally pick up any further security
# credentials at this point.
self.assertIsNone(client.code)

def test_parse_token_response(self):
client = MobileApplicationClient(self.client_id)

Expand Down
19 changes: 19 additions & 0 deletions tests/oauth2/rfc6749/clients/test_web_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,25 @@ def test_parse_grant_uri_response(self):
self.response_uri,
state="invalid")

def test_populate_attributes(self):

client = WebApplicationClient(self.client_id)

response_uri = (self.response_uri +
"&access_token=EVIL-TOKEN"
"&refresh_token=EVIL-TOKEN"
"&mac_key=EVIL-KEY")

client.parse_request_uri_response(response_uri, self.state)

self.assertEqual(client.code, self.code)

# We must not accidentally pick up any further security
# credentials at this point.
self.assertIsNone(client.access_token)
self.assertIsNone(client.refresh_token)
self.assertIsNone(client.mac_key)

def test_parse_token_response(self):
client = WebApplicationClient(self.client_id)

Expand Down

0 comments on commit f853295

Please sign in to comment.