diff --git a/keystoneclient/tests/v3/test_oauth1.py b/keystoneclient/tests/v3/test_oauth1.py index 80082b61f..28464e1bf 100644 --- a/keystoneclient/tests/v3/test_oauth1.py +++ b/keystoneclient/tests/v3/test_oauth1.py @@ -14,13 +14,17 @@ import uuid import httpretty +import mock import six from testtools import matchers from keystoneclient.openstack.common import jsonutils from keystoneclient.openstack.common import timeutils +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 auth from keystoneclient.v3.contrib.oauth1 import consumers from keystoneclient.v3.contrib.oauth1 import request_tokens @@ -227,3 +231,70 @@ class AccessTokenTests(TokenTests): timestamp=expires_at) self._validate_oauth_headers(req_headers['Authorization'], oauth_client) + + +class AuthenticateWithOAuthTests(TokenTests): + def setUp(self): + super(AuthenticateWithOAuthTests, self).setUp() + if oauth1 is None: + self.skipTest('optional package oauthlib is not installed') + + @httpretty.activate + def test_oauth_authenticate_success(self): + 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) + + a = auth.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, self.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 + oauth_client = 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'], + oauth_client) + + +class TestOAuthLibModule(utils.TestCase): + + def setUp(self): + super(TestOAuthLibModule, self).setUp() + + def test_no_oauthlib_installed(self): + with mock.patch.object(auth, 'oauth1', None): + self.assertRaises(NotImplementedError, + auth.OAuth, + self.TEST_URL, + consumer_key=uuid.uuid4().hex, + consumer_secret=uuid.uuid4().hex, + access_key=uuid.uuid4().hex, + access_secret=uuid.uuid4().hex) diff --git a/keystoneclient/v3/contrib/oauth1/auth.py b/keystoneclient/v3/contrib/oauth1/auth.py new file mode 100644 index 000000000..e13c050f1 --- /dev/null +++ b/keystoneclient/v3/contrib/oauth1/auth.py @@ -0,0 +1,58 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from keystoneclient.auth.identity import v3 + +try: + from oauthlib import oauth1 +except ImportError: + oauth1 = None + + +class OAuthMethod(v3.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) + if oauth1 is None: + raise NotImplementedError('optional package oauthlib' + ' is not installed') + + def get_auth_data(self, session, auth, headers, **kwargs): + # Add the oauth specific content into the headers + oauth_client = 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 = oauth_client.sign(auth.token_url, + http_method='POST') + + headers.update(o_headers) + return 'oauth1', {} + + +class OAuth(v3._AuthConstructor): + _auth_method_class = OAuthMethod + + def __init__(self, auth_url, **kwargs): + super(OAuth, self).__init__(auth_url, **kwargs)