diff --git a/keystoneclient/auth/identity/v3.py b/keystoneclient/auth/identity/v3.py index 19085b655..6a7d89581 100644 --- a/keystoneclient/auth/identity/v3.py +++ b/keystoneclient/auth/identity/v3.py @@ -15,6 +15,7 @@ import abc import logging +import oauthlib.oauth1 as oauth1 import six from keystoneclient import access @@ -128,6 +129,10 @@ class Auth(base.BaseIdentityPlugin): if method_kwargs.get('token'): methods.append(TokenMethod(**method_kwargs)) + method_kwargs = OAuthMethod._extract_kwargs(kwargs) + if method_kwargs.get('consumer_key'): + methods.append(OAuthMethod(**method_kwargs)) + if not methods: msg = 'A username and password or token is required.' raise exceptions.AuthorizationFailure(msg) @@ -255,3 +260,37 @@ class Token(_AuthConstructor): def __init__(self, auth_url, token, **kwargs): super(Token, self).__init__(auth_url, token=token, **kwargs) + + +class OAuthMethod(AuthMethod): + + _method_parameters = ['consumer_key', + 'consumer_secret', + 'access_key', + 'access_secret'] + + def __init__(self, **kwargs): + """Construct an OAuth based authentication method. + + :param string consumer_key: Consumer key. + :param string consumer_secret: Consumer secret. + :param string access_key: Access token key. + :param string access_secret: Access token secret. + """ + super(OAuthMethod, self).__init__(**kwargs) + + def get_auth_data(self, session, auth, headers, **kwargs): + # Add the oauth specific content into the headers + oc = oauth1.Client(self.consumer_key, + client_secret=self.consumer_secret, + resource_owner_key=self.access_key, + resource_owner_secret=self.access_secret, + signature_method=oauth1.SIGNATURE_HMAC) + o_url, o_headers, o_body = oc.sign(auth.token_url, http_method='POST') + + headers.update(o_headers) + return 'oauth1', {} + + +class OAuth(_AuthConstructor): + _auth_method_class = OAuthMethod diff --git a/keystoneclient/tests/v3/test_oauth1.py b/keystoneclient/tests/v3/test_oauth1.py index 662ab7aae..59bfd5a7f 100644 --- a/keystoneclient/tests/v3/test_oauth1.py +++ b/keystoneclient/tests/v3/test_oauth1.py @@ -16,6 +16,9 @@ import uuid import httpretty import oauthlib.oauth1 as oauth1 +from keystoneclient.auth.identity import v3 +from keystoneclient import session +from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.oauth1 import access_tokens from keystoneclient.v3.contrib.oauth1 import consumers @@ -174,3 +177,52 @@ class AccessTokenTests(TokenTests): signature_method=oauth1.SIGNATURE_HMAC, verifier=verifier) self._validate_oauth_headers(req_headers['Authorization'], oc) + + +class AuthenticateWithOAuthTests(TokenTests): + def setUp(self): + super(AuthenticateWithOAuthTests, self).setUp() + + @httpretty.activate + def test_oauth_authenticate_success(self): + TEST_TOKEN = "abcdef" + consumer_key = uuid.uuid4().hex + consumer_secret = uuid.uuid4().hex + access_key = uuid.uuid4().hex + access_secret = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to oauth1, and add an OS-OAUTH1 section. + oauth_token = client_fixtures.PROJECT_SCOPED_TOKEN + oauth_token['methods'] = ["oauth1"] + oauth_token['OS-OAUTH1'] = {"consumer_id": consumer_key, + "access_token_id": access_key} + self.stub_auth(json=oauth_token, subject_token=TEST_TOKEN) + + a = v3.OAuth(self.TEST_URL, consumer_key=consumer_key, + consumer_secret=consumer_secret, + access_key=access_key, + access_secret=access_secret) + s = session.Session(auth=a) + t = s.get_token() + + self.assertEqual(t, TEST_TOKEN) + + OAUTH_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["oauth1"], + "oauth1": {} + } + } + } + + self.assertRequestBodyIs(json=OAUTH_REQUEST_BODY) + + # Assert that the headers have the same oauthlib data + req_headers = httpretty.last_request().headers + oc = oauth1.Client(consumer_key, client_secret=consumer_secret, + resource_owner_key=access_key, + resource_owner_secret=access_secret, + signature_method=oauth1.SIGNATURE_HMAC) + self._validate_oauth_headers(req_headers['Authorization'], oc)