Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documented PyJWT dependency and improved logging and exception messages #458

Merged
merged 7 commits into from
Aug 2, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions docs/oauth1/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,16 @@ Using the Client

**RSA Signatures**

OAuthLib supports the 'RSA-SHA1' signature but does not install the jwt or
cryptography dependency by default. The cryptography package is much better
supported on Windows and Mac OS X than PyCrypto, and simpler to install.
OAuthLib uses the jwt package to smooth out its internal code.
Users can install cryptography using pip::
OAuthLib supports 'RSA-SHA1' signatures, but does not install the
PyJWT or cryptography dependencies by default. OAuthLib uses the
PyJWT package to smooth out its internal code. The cryptography
package is much better supported on Windows and Mac OS X than
PyCrypto, and simpler to install. Users can install PyJWT and
cryptography using pip::

pip install jwt cryptography
pip install pyjwt cryptography

When you have cryptography and jwt installed using RSA signatures is
When you have cryptography and PyJWT installed, using RSA signatures is
similar to HMAC but differ in a few aspects. RSA signatures does not make
use of client secrets nor resource owner secrets (token secrets) and
requires you to specify the signature type when constructing a client::
Expand Down
2 changes: 1 addition & 1 deletion docs/oauth1/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Creating a Provider
===================

OAuthLib is a dependency free library that may be used with any web
OAuthLib is a framework independent library that may be used with any web
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you change this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because OAuthLib has dependencies, so it is not "dependency free". The description of "dependency free" would mean it does not need any other package to run. OAuthLib (as indicated in the requirements.txt file) needs PyJWT, blinker and cryptography packages to function.

I'm trying to guess what the original author really meant, and assume they meant to say it can be used with any framework (e.g. doesn't have to be Django) rather than "dependency free" (which it is clearly not, since it has dependencies).

framework. That said, there are framework specific helper libraries
to make your life easier.

Expand Down
2 changes: 1 addition & 1 deletion oauthlib/oauth1/rfc5849/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def get_oauth_signature(self, request):
base_string = signature.construct_base_string(request.http_method,
normalized_uri, normalized_params)

log.debug("Base signing string: {0}".format(base_string))
log.debug("Signing: signature base string: {0}".format(base_string))

if self.signature_method not in self.SIGNATURE_METHODS:
raise ValueError('Invalid signature method.')
Expand Down
1 change: 1 addition & 0 deletions oauthlib/oauth1/rfc5849/endpoints/signature_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def validate_request(self, uri, http_method='GET',

if not self.request_validator.validate_timestamp_and_nonce(
request.client_key, request.timestamp, request.nonce, request):
log.debug('[Failure] verification failed: timestamp/nonce')
return False, request

# The server SHOULD return a 401 (Unauthorized) status code when
Expand Down
57 changes: 33 additions & 24 deletions oauthlib/oauth1/rfc5849/request_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from __future__ import absolute_import, unicode_literals

from . import SIGNATURE_METHODS, utils
import sys


class RequestValidator(object):
Expand Down Expand Up @@ -197,6 +198,14 @@ def check_realms(self, realms):
"""Check that the realm is one of a set allowed realms."""
return all((r in self.realms for r in realms))

def _subclass_must_implement(self, fn):
"""
Returns a NotImplementedError for a function that should be implemented.
:param fn: name of the function
"""
m = "Missing function implementation in {}: {}".format(type(self), fn)
return NotImplementedError(m)

@property
def dummy_client(self):
"""Dummy client used when an invalid client key is supplied.
Expand All @@ -219,7 +228,7 @@ def dummy_client(self):
* ResourceEndpoint
* SignatureOnlyEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("dummy_client")

@property
def dummy_request_token(self):
Expand All @@ -235,7 +244,7 @@ def dummy_request_token(self):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("dummy_request_token")

@property
def dummy_access_token(self):
Expand All @@ -251,7 +260,7 @@ def dummy_access_token(self):

* ResourceEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("dummy_access_token")

def get_client_secret(self, client_key, request):
"""Retrieves the client secret associated with the client key.
Expand Down Expand Up @@ -286,7 +295,7 @@ def get_client_secret(self, client_key, request):
* ResourceEndpoint
* SignatureOnlyEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement('get_client_secret')

def get_request_token_secret(self, client_key, token, request):
"""Retrieves the shared secret associated with the request token.
Expand Down Expand Up @@ -318,7 +327,7 @@ def get_request_token_secret(self, client_key, token, request):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement('get_request_token_secret')

def get_access_token_secret(self, client_key, token, request):
"""Retrieves the shared secret associated with the access token.
Expand Down Expand Up @@ -350,7 +359,7 @@ def get_access_token_secret(self, client_key, token, request):

* ResourceEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("get_access_token_secret")

def get_default_realms(self, client_key, request):
"""Get the default realms for a client.
Expand All @@ -366,7 +375,7 @@ def get_default_realms(self, client_key, request):

* RequestTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("get_default_realms")

def get_realms(self, token, request):
"""Get realms associated with a request token.
Expand All @@ -380,7 +389,7 @@ def get_realms(self, token, request):
* AuthorizationEndpoint
* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("get_realms")

def get_redirect_uri(self, token, request):
"""Get the redirect URI associated with a request token.
Expand All @@ -397,7 +406,7 @@ def get_redirect_uri(self, token, request):

* AuthorizationEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("get_redirect_uri")

def get_rsa_key(self, client_key, request):
"""Retrieves a previously stored client provided RSA key.
Expand All @@ -420,7 +429,7 @@ def get_rsa_key(self, client_key, request):
* ResourceEndpoint
* SignatureOnlyEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("get_rsa_key")

def invalidate_request_token(self, client_key, request_token, request):
"""Invalidates a used request token.
Expand All @@ -446,7 +455,7 @@ def invalidate_request_token(self, client_key, request_token, request):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("invalidate_request_token")

def validate_client_key(self, client_key, request):
"""Validates that supplied client key is a registered and valid client.
Expand Down Expand Up @@ -482,7 +491,7 @@ def validate_client_key(self, client_key, request):
* ResourceEndpoint
* SignatureOnlyEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_client_key")

def validate_request_token(self, client_key, token, request):
"""Validates that supplied request token is registered and valid.
Expand Down Expand Up @@ -516,7 +525,7 @@ def validate_request_token(self, client_key, token, request):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_request_token")

def validate_access_token(self, client_key, token, request):
"""Validates that supplied access token is registered and valid.
Expand Down Expand Up @@ -550,7 +559,7 @@ def validate_access_token(self, client_key, token, request):

* ResourceEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_access_token")

def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
request, request_token=None, access_token=None):
Expand Down Expand Up @@ -600,7 +609,7 @@ def validate_timestamp_and_nonce(self, client_key, timestamp, nonce,
* ResourceEndpoint
* SignatureOnlyEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_timestamp_and_nonce")

def validate_redirect_uri(self, client_key, redirect_uri, request):
"""Validates the client supplied redirection URI.
Expand Down Expand Up @@ -633,7 +642,7 @@ def validate_redirect_uri(self, client_key, redirect_uri, request):

* RequestTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_redirect_uri")

def validate_requested_realms(self, client_key, realms, request):
"""Validates that the client may request access to the realm.
Expand All @@ -651,7 +660,7 @@ def validate_requested_realms(self, client_key, realms, request):

* RequestTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_requested_realms")

def validate_realms(self, client_key, token, request, uri=None,
realms=None):
Expand Down Expand Up @@ -685,7 +694,7 @@ def validate_realms(self, client_key, token, request, uri=None,

* ResourceEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_realms")

def validate_verifier(self, client_key, token, verifier, request):
"""Validates a verification code.
Expand Down Expand Up @@ -716,7 +725,7 @@ def validate_verifier(self, client_key, token, verifier, request):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("validate_verifier")

def verify_request_token(self, token, request):
"""Verify that the given OAuth1 request token is valid.
Expand All @@ -734,7 +743,7 @@ def verify_request_token(self, token, request):

* AuthorizationEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("verify_request_token")

def verify_realms(self, token, realms, request):
"""Verify authorized realms to see if they match those given to token.
Expand All @@ -757,7 +766,7 @@ def verify_realms(self, token, realms, request):

* AuthorizationEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("verify_realms")

def save_access_token(self, token, request):
"""Save an OAuth1 access token.
Expand All @@ -780,7 +789,7 @@ def save_access_token(self, token, request):

* AccessTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("save_access_token")

def save_request_token(self, token, request):
"""Save an OAuth1 request token.
Expand All @@ -800,7 +809,7 @@ def save_request_token(self, token, request):

* RequestTokenEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("save_request_token")

def save_verifier(self, token, verifier, request):
"""Associate an authorization verifier with a request token.
Expand All @@ -820,4 +829,4 @@ def save_verifier(self, token, verifier, request):

* AuthorizationEndpoint
"""
raise NotImplementedError("Subclasses must implement this function.")
raise self._subclass_must_implement("save_verifier")
20 changes: 17 additions & 3 deletions oauthlib/oauth1/rfc5849/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import binascii
import hashlib
import hmac
import logging

try:
import urlparse
except ImportError:
Expand All @@ -34,6 +36,7 @@
from oauthlib.common import urldecode, extract_params, safe_string_equals
from oauthlib.common import bytes_type, unicode_type

log = logging.getLogger(__name__)

def construct_base_string(http_method, base_string_uri,
normalized_encoded_request_parameters):
Expand Down Expand Up @@ -566,7 +569,11 @@ def verify_hmac_sha1(request, client_secret=None,
base_string = construct_base_string(request.http_method, uri, norm_params)
signature = sign_hmac_sha1(base_string, client_secret,
resource_owner_secret)
return safe_string_equals(signature, request.signature)
match = safe_string_equals(signature, request.signature)
if not match:
log.debug('Verify HMAC-SHA1 failed: sig base string: %s', base_string)
return match


def _prepare_key_plus(alg, keystr):
if isinstance(keystr, bytes_type):
Expand Down Expand Up @@ -597,7 +604,11 @@ def verify_rsa_sha1(request, rsa_public_key):

alg = _jwt_rs1_signing_algorithm()
key = _prepare_key_plus(alg, rsa_public_key)
return alg.verify(message, key, sig)

verify_ok = alg.verify(message, key, sig)
if not verify_ok:
log.debug('Verify RSA-SHA1 failed: sig base string: %s', message)
return verify_ok


def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
Expand All @@ -608,4 +619,7 @@ def verify_plaintext(request, client_secret=None, resource_owner_secret=None):
.. _`section 3.4`: http://tools.ietf.org/html/rfc5849#section-3.4
"""
signature = sign_plaintext(client_secret, resource_owner_secret)
return safe_string_equals(signature, request.signature)
match = safe_string_equals(signature, request.signature)
if not match:
log.debug('Verify PLAINTEXT failed')
return match