Skip to content

Commit

Permalink
Merge pull request #239 from aspiredu/master
Browse files Browse the repository at this point in the history
Add support for custom signature methods to `oauth1.Client`
  • Loading branch information
ib-lundgren committed Apr 12, 2014
2 parents a66fe98 + 1fa7cf1 commit 6450527
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 29 deletions.
19 changes: 13 additions & 6 deletions oauthlib/oauth1/rfc5849/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@


class Client(object):
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

"""A client used to sign OAuth 1.0 RFC 5849 requests"""
def __init__(self, client_key,
client_secret=None,
Expand Down Expand Up @@ -128,14 +138,11 @@ def get_oauth_signature(self, request):

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

if self.signature_method == SIGNATURE_HMAC:
sig = signature.sign_hmac_sha1(base_string, self.client_secret,
self.resource_owner_secret)
elif self.signature_method == SIGNATURE_RSA:
sig = signature.sign_rsa_sha1(base_string, self.rsa_key)
else:
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

Expand Down
13 changes: 13 additions & 0 deletions oauthlib/oauth1/rfc5849/signature.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,12 @@ def normalize_parameters(params):
return '&'.join(parameter_parts)


def sign_hmac_sha1_with_client(base_string, client):
return sign_hmac_sha1(base_string,
client.client_secret,
client.resource_owner_secret
)

def sign_hmac_sha1(base_string, client_secret, resource_owner_secret):
"""**HMAC-SHA1**
Expand Down Expand Up @@ -487,6 +493,10 @@ def sign_rsa_sha1(base_string, rsa_private_key):
return binascii.b2a_base64(p.sign(h))[:-1].decode('utf-8')


def sign_rsa_sha1_with_client(base_string, client):
return sign_rsa_sha1(base_string, client.rsa_key)


def sign_plaintext(client_secret, resource_owner_secret):
"""Sign a request using plaintext.
Expand Down Expand Up @@ -522,6 +532,9 @@ def sign_plaintext(client_secret, resource_owner_secret):
return signature


def sign_plaintext_with_client(base_string, client):
return sign_plaintext(client.client_secret, client.resource_owner_secret)

def verify_hmac_sha1(request, client_secret=None,
resource_owner_secret=None):
"""Verify a HMAC-SHA1 signature.
Expand Down
19 changes: 19 additions & 0 deletions tests/oauth1/rfc5849/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,25 @@ def test_invalid_method(self):
self.assertRaises(ValueError, client.sign, 'http://example.com')


def test_register_method(self):
Client.register_signature_method('PIZZA',
lambda base_string, client: 'PIZZA')

self.assertTrue('PIZZA' in Client.SIGNATURE_METHODS)

client = Client('client_key', signature_method='PIZZA',
timestamp='1234567890', nonce='abc')

u, h, b = client.sign('http://example.com')

self.assertEquals(h['Authorization'], (
'OAuth oauth_nonce="abc", oauth_timestamp="1234567890", '
'oauth_version="1.0", oauth_signature_method="PIZZA", '
'oauth_consumer_key="client_key", '
'oauth_signature="PIZZA"'
))


class SignatureTypeTest(TestCase):

def test_params_in_body(self):
Expand Down
121 changes: 98 additions & 23 deletions tests/oauth1/rfc5849/test_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,24 @@
from oauthlib.oauth1.rfc5849.signature import construct_base_string
from oauthlib.oauth1.rfc5849.signature import normalize_base_string_uri
from oauthlib.oauth1.rfc5849.signature import normalize_parameters
from oauthlib.oauth1.rfc5849.signature import sign_hmac_sha1
from oauthlib.oauth1.rfc5849.signature import sign_rsa_sha1
from oauthlib.oauth1.rfc5849.signature import sign_plaintext
from oauthlib.oauth1.rfc5849.signature import sign_hmac_sha1, sign_hmac_sha1_with_client
from oauthlib.oauth1.rfc5849.signature import sign_rsa_sha1, sign_rsa_sha1_with_client
from oauthlib.oauth1.rfc5849.signature import sign_plaintext, sign_plaintext_with_client
from oauthlib.common import unicode_type
from ...unittest import TestCase


class SignatureTests(TestCase):
class MockClient(dict):
def __getattr__(self, name):
return self[name]

def __setattr__(self, name, value):
self[name] = value

def decode(self):
for k, v in self.items():
self[k] = v.decode('utf-8')

uri_query = "b5=%3D%253D&a3=a&c%40=&a2=r%20b&c2=&a3=2+q"
authorization_header = """OAuth realm="Example",
Expand Down Expand Up @@ -58,6 +68,12 @@ class SignatureTests(TestCase):
"oauth_nonce%253D%25227d8f3e4a%2522%252C"
"oauth_signature%253D%2522bYT5CMsGcbgUdFHObYMEfcx6bsw%25253D%2522")

def setUp(self):
self.client = self.MockClient(
client_secret = self.client_secret,
resource_owner_secret = self.resource_owner_secret
)

def test_construct_base_string(self):
"""
Example text to be turned into a base string::
Expand Down Expand Up @@ -212,11 +228,12 @@ def test_normalize_parameters(self):
self.assertGreater(normalized.index(key), index)
index = normalized.index(key)

# Control signature created using openssl:
# echo -n $(cat <message>) | openssl dgst -binary -hmac <key> | base64
control_signature = "Uau4O9Kpd2k6rvh7UZN/RN+RG7Y="

def test_sign_hmac_sha1(self):
"""Verifying HMAC-SHA1 signature against one created by OpenSSL."""
# Control signature created using openssl:
# echo -n $(cat <message>) | openssl dgst -binary -hmac <key> | base64
control_signature = "Uau4O9Kpd2k6rvh7UZN/RN+RG7Y="

self.assertRaises(ValueError, sign_hmac_sha1, self.control_base_string,
self.client_secret, self.resource_owner_secret)
Expand All @@ -225,25 +242,39 @@ def test_sign_hmac_sha1(self):
self.client_secret.decode('utf-8'),
self.resource_owner_secret.decode('utf-8'))
self.assertEquals(len(sign), 28)
self.assertEquals(sign, control_signature)
self.assertEquals(sign, self.control_signature)

def test_sign_rsa_sha1(self):
"""Verify RSA-SHA1 signature against one created by OpenSSL."""
def test_sign_hmac_sha1_with_client(self):
self.assertRaises(ValueError,
sign_hmac_sha1_with_client,
self.control_base_string,
self.client)

base_string = (b"POST&http%253A%2F%2Fexample.com%2Frequest%253Fb5%253D"
b"%25253D%2525253D%2526a3%253Da%2526c%252540%253D%2526"
b"a2%253Dr%252520b&OAuth%2520realm%253D%2522Example%25"
b"22%252Coauth_consumer_key%253D%25229djdj82h48djs9d2"
b"%2522%252Coauth_token%253D%2522kkk9d7dh3k39sjv7%2522"
b"%252Coauth_signature_method%253D%2522HMAC-SHA1%2522"
b"%252Coauth_timestamp%253D%2522137131201%2522%252Coau"
b"th_nonce%253D%25227d8f3e4a%2522%252Coauth_signature"
b"%253D%2522bYT5CMsGcbgUdFHObYMEfcx6bsw%25253D%2522")
self.client.decode()
sign = sign_hmac_sha1_with_client(
self.control_base_string, self.client)

self.assertEquals(len(sign), 28)
self.assertEquals(sign, self.control_signature)


control_base_string_rsa_sha1 = (
b"POST&http%253A%2F%2Fexample.com%2Frequest%253Fb5%253D"
b"%25253D%2525253D%2526a3%253Da%2526c%252540%253D%2526"
b"a2%253Dr%252520b&OAuth%2520realm%253D%2522Example%25"
b"22%252Coauth_consumer_key%253D%25229djdj82h48djs9d2"
b"%2522%252Coauth_token%253D%2522kkk9d7dh3k39sjv7%2522"
b"%252Coauth_signature_method%253D%2522HMAC-SHA1%2522"
b"%252Coauth_timestamp%253D%2522137131201%2522%252Coau"
b"th_nonce%253D%25227d8f3e4a%2522%252Coauth_signature"
b"%253D%2522bYT5CMsGcbgUdFHObYMEfcx6bsw%25253D%2522")

@property
def rsa_private_key(self):
# Generated using: $ openssl genrsa -out <key>.pem 1024
# PyCrypto / python-rsa requires the key to be concatenated with
# linebreaks.
private_key = (
return (
b"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQDk1/bxy"
b"S8Q8jiheHeYYp/4rEKJopeQRRKKpZI4s5i+UPwVpupG\nAlwXWfzXw"
b"SMaKPAoKJNdu7tqKRniqst5uoHXw98gj0x7zamu0Ck1LtQ4c7pFMVa"
Expand All @@ -263,27 +294,71 @@ def test_sign_rsa_sha1(self):
b"dMlxqXA==\n-----END RSA PRIVATE KEY-----"
)

@property
def control_signature_rsa_sha1(self):
# Base string saved in "<message>". Signature obtained using:
# $ echo -n $(cat <message>) | openssl dgst -sign <key>.pem | base64
# where echo -n suppresses the last linebreak.
control_signature = (
return (
"zV5g8ArdMuJuOXlH8XOqfLHS11XdthfIn4HReDm7jz8JmgLabHGmVBqCkCfZoFJPH"
"dka7tLvCplK/jsV4FUOnftrJOQhbXguuBdi87/hmxOFKLmQYqqlEW7BdXmwKLZcki"
"qq3qE5XziBgKSAFRkxJ4gmJAymvJBtrJYN9728rK8="
)


def test_sign_rsa_sha1(self):
"""Verify RSA-SHA1 signature against one created by OpenSSL."""
base_string = self.control_base_string_rsa_sha1

private_key = self.rsa_private_key

control_signature = self.control_signature_rsa_sha1

sign = sign_rsa_sha1(base_string, private_key)
self.assertEquals(sign, control_signature)
sign = sign_rsa_sha1(base_string.decode('utf-8'), private_key)
self.assertEquals(sign, control_signature)


def test_sign_rsa_sha1_with_client(self):
base_string = self.control_base_string_rsa_sha1

self.client.rsa_key = self.rsa_private_key

control_signature = self.control_signature_rsa_sha1

sign = sign_rsa_sha1_with_client(base_string, self.client)

self.assertEquals(sign, control_signature)

self.client.decode() ## Decode `rsa_private_key` from UTF-8

sign = sign_rsa_sha1_with_client(base_string, self.client)

self.assertEquals(sign, control_signature)


control_signature_plaintext = (
"ECrDNoq1VYzzzzzzzzzyAK7TwZNtPnkqatqZZZZ&"
"just-a-string%20%20%20%20asdasd")

def test_sign_plaintext(self):
""" """

self.assertRaises(ValueError, sign_plaintext, self.client_secret,
self.resource_owner_secret)
sign = sign_plaintext(self.client_secret.decode('utf-8'),
self.resource_owner_secret.decode('utf-8'))
correct = ("ECrDNoq1VYzzzzzzzzzyAK7TwZNtPnkqatqZZZZ&"
"just-a-string%20%20%20%20asdasd")
self.assertEquals(sign, correct)
self.assertEquals(sign, self.control_signature_plaintext)


def test_sign_plaintext_with_client(self):
self.assertRaises(ValueError, sign_plaintext_with_client,
None, self.client)

self.client.decode()

sign = sign_plaintext_with_client(None, self.client)

self.assertEquals(sign, self.control_signature_plaintext)

0 comments on commit 6450527

Please sign in to comment.