From f2be260207e249d7b59aecfac20eaf09c0b2e661 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 15 Oct 2014 17:21:29 +0200 Subject: [PATCH 001/102] consumers and auth --- README.rst | 204 +----------------- keystoneclient/tests/v3/test_oauth2.py | 132 ++++++++++++ keystoneclient/v3/client.py | 3 + keystoneclient/v3/contrib/oauth2/__init__.py | 14 ++ keystoneclient/v3/contrib/oauth2/auth.py | 46 ++++ keystoneclient/v3/contrib/oauth2/consumers.py | 74 +++++++ keystoneclient/v3/contrib/oauth2/core.py | 58 +++++ keystoneclient/v3/contrib/oauth2/utils.py | 16 ++ 8 files changed, 348 insertions(+), 199 deletions(-) create mode 100644 keystoneclient/tests/v3/test_oauth2.py create mode 100644 keystoneclient/v3/contrib/oauth2/__init__.py create mode 100644 keystoneclient/v3/contrib/oauth2/auth.py create mode 100644 keystoneclient/v3/contrib/oauth2/consumers.py create mode 100644 keystoneclient/v3/contrib/oauth2/core.py create mode 100644 keystoneclient/v3/contrib/oauth2/utils.py diff --git a/README.rst b/README.rst index 153b500cd..8698d5363 100644 --- a/README.rst +++ b/README.rst @@ -1,203 +1,9 @@ -Python bindings to the OpenStack Identity API (Keystone) -======================================================== +Python bindings to the FIWARE-IdM modified Keystone +=================================================== -This is a client for the OpenStack Identity API, implemented by Keystone. -There's a Python API (the ``keystoneclient`` module), and a command-line script -(``keystone``). +This is an extended client for the OpenStack Identity API, implemented by Keystone. It adds python bindings to the extra functionality added to Keystone through extensions implemented at `GING Keystone `_. The CLI part of the client is not binded, no work is done in that regard as is not a project-requirement at the time of this writting and because the OpenStack keystoneclient CLI is deprecated. If requested could be developed in the future, although probably as a contribution to `OpenStack Client `_. -Development takes place via the usual OpenStack processes as outlined in the -`OpenStack wiki `_. The master -repository is on `GitHub `_. +The master repository is on `GitHub `_. -This code is a fork of `Rackspace's python-novaclient -`_ which is in turn a fork of -`Jacobian's python-cloudservers -`_. ``python-keystoneclient`` -is licensed under the Apache License like the rest of OpenStack. +This code is a fork of `OpenStack KeystoneClient `_. The future goal is to contribute back all the bindings for the extensions that get accepted in Keystone from the complementary project `GING Keystone `_. -.. contents:: Contents: - :local: - -Python API ----------- - -By way of a quick-start:: - - # use v2.0 auth with http://example.com:5000/v2.0 - >>> from keystoneclient.v2_0 import client - >>> keystone = client.Client(username=USERNAME, password=PASSWORD, tenant_name=TENANT, auth_url=AUTH_URL) - >>> keystone.tenants.list() - >>> tenant = keystone.tenants.create(tenant_name="test", description="My new tenant!", enabled=True) - >>> tenant.delete() - - -Command-line API ----------------- - -Installing this package gets you a shell command, ``keystone``, that you can -use to interact with OpenStack's Identity API. - -You'll need to provide your OpenStack tenant, username and password. You can do -this with the ``--os-tenant-name``, ``--os-username`` and ``--os-password`` -params, but it's easier to just set them as environment variables:: - - export OS_TENANT_NAME=project - export OS_USERNAME=user - export OS_PASSWORD=pass - -You will also need to define the authentication url with ``--os-auth-url`` and -the version of the API with ``--os-identity-api-version``. Or set them as an -environment variables as well:: - - export OS_AUTH_URL=http://example.com:5000/v2.0 - export OS_IDENTITY_API_VERSION=2.0 - -Alternatively, to bypass username/password authentication, you can provide a -pre-established token. In Keystone, this approach is necessary to bootstrap the -service with an administrative user, tenant & role (to do so, provide the -client with the value of your ``admin_token`` defined in ``keystone.conf`` in -addition to the URL of your admin API deployment, typically on port 35357):: - - export OS_SERVICE_TOKEN=thequickbrownfox-jumpsover-thelazydog - export OS_SERVICE_ENDPOINT=http://example.com:35357/v2.0 - -Since the Identity service can return multiple regions in the service catalog, -you can specify the one you want with ``--os-region-name`` (or ``export -OS_REGION_NAME``):: - - export OS_REGION_NAME=north - -.. WARNING:: - - If a region is not specified and multiple regions are returned by the - Identity service, the client may not access the same region consistently. - -If you need to connect to a server that is TLS-enabled (the auth URL begins -with 'https') and it uses a certificate from a private CA or a self-signed -certificate you will need to specify the path to an appropriate CA certificate -to use to validate the server certificate with ``--os-cacert`` or an -environment variable:: - - export OS_CACERT=/etc/ssl/my-root-cert.pem - -Certificate verification can be turned off using ``--insecure``. This should -be used with caution. - -You'll find complete documentation on the shell by running ``keystone help``:: - - usage: keystone [--version] [--timeout ] - [--os-username ] - [--os-password ] - [--os-tenant-name ] - [--os-tenant-id ] [--os-auth-url ] - [--os-region-name ] - [--os-identity-api-version ] - [--os-token ] - [--os-endpoint ] - [--os-cacert ] [--insecure] - [--os-cert ] [--os-key ] [--os-cache] - [--force-new-token] [--stale-duration ] - ... - - Command-line interface to the OpenStack Identity API. - - Positional arguments: - - catalog - ec2-credentials-create - Create EC2-compatible credentials for user per tenant - ec2-credentials-delete - Delete EC2-compatible credentials - ec2-credentials-get - Display EC2-compatible credentials - ec2-credentials-list - List EC2-compatible credentials for a user - endpoint-create Create a new endpoint associated with a service - endpoint-delete Delete a service endpoint - endpoint-get - endpoint-list List configured service endpoints - password-update Update own password - role-create Create new role - role-delete Delete role - role-get Display role details - role-list List all roles - service-create Add service to Service Catalog - service-delete Delete service from Service Catalog - service-get Display service from Service Catalog - service-list List all services in Service Catalog - tenant-create Create new tenant - tenant-delete Delete tenant - tenant-get Display tenant details - tenant-list List all tenants - tenant-update Update tenant name, description, enabled status - token-get - user-create Create new user - user-delete Delete user - user-get Display user details. - user-list List users - user-password-update - Update user password - user-role-add Add role to user - user-role-list List roles granted to a user - user-role-remove Remove role from user - user-update Update user's name, email, and enabled status - discover Discover Keystone servers, supported API versions and - extensions. - bootstrap Grants a new role to a new user on a new tenant, after - creating each. - bash-completion Prints all of the commands and options to stdout. - help Display help about this program or one of its - subcommands. - - Optional arguments: - --version Shows the client version and exits - --timeout Set request timeout (in seconds) - --os-username - Name used for authentication with the OpenStack - Identity service. Defaults to env[OS_USERNAME] - --os-password - Password used for authentication with the OpenStack - Identity service. Defaults to env[OS_PASSWORD] - --os-tenant-name - Tenant to request authorization on. Defaults to - env[OS_TENANT_NAME] - --os-tenant-id - Tenant to request authorization on. Defaults to - env[OS_TENANT_ID] - --os-auth-url - Specify the Identity endpoint to use for - authentication. Defaults to env[OS_AUTH_URL] - --os-region-name - Defaults to env[OS_REGION_NAME] - --os-identity-api-version - Defaults to env[OS_IDENTITY_API_VERSION] or 2.0 - --os-token - Specify an existing token to use instead of retrieving - one via authentication (e.g. with username & - password). Defaults to env[OS_SERVICE_TOKEN] - --os-endpoint - Specify an endpoint to use instead of retrieving one - from the service catalog (via authentication). - Defaults to env[OS_SERVICE_ENDPOINT] - --os-cacert - Specify a CA bundle file to use in verifying a TLS - (https) server certificate. Defaults to env[OS_CACERT] - --insecure Explicitly allow keystoneclient to perform "insecure" - TLS (https) requests. The server's certificate will - not be verified against any certificate authorities. - This option should be used with caution. - --os-cert - Defaults to env[OS_CERT] - --os-key Defaults to env[OS_KEY] - --os-cache Use the auth token cache. Defaults to env[OS_CACHE] - --force-new-token If the keyring is available and in use, token will - always be stored and fetched from the keyring until - the token has expired. Use this option to request a - new token and replace the existing one in the keyring. - --stale-duration - Stale duration (in seconds) used to determine whether - a token has expired when retrieving it from keyring. - This is useful in mitigating process or network - delays. Default is 30 seconds. - - See "keystone help COMMAND" for help on a specific command. diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py new file mode 100644 index 000000000..fb369b415 --- /dev/null +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -0,0 +1,132 @@ +# 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. + +import uuid +import mock +import six +from six.moves.urllib import parse as urlparse +from testtools import matchers + +from keystoneclient import session +from keystoneclient.openstack.common import timeutils +from keystoneclient.tests.v3 import client_fixtures +from keystoneclient.tests.v3 import utils +from keystoneclient.v3.contrib.oauth2 import auth +from keystoneclient.v3.contrib.oauth2 import consumers + +try: + import oauthlib + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class BaseTest(utils.TestCase): + + + + def setUp(self): + super(BaseTest, self).setUp() + if oauth2 is None: + self.skipTest('oauthlib package not available') + + +class ConsumerTests(BaseTest, utils.CrudTests): + + DEFAULT_REDIRECT_URIS = ['https://uri.com'] + DEFAULT_SCOPES = ['all_info'] + DEFAULT_CLIENT_TYPE='confidential' + DEFAULT_GRANT_TYPE='authorization_code' + + def setUp(self): + super(ConsumerTests, self).setUp() + self.key = 'consumer' + self.collection_key = 'consumers' + self.model = consumers.Consumer + self.manager = self.client.oauth2.consumers + self.path_prefix = 'OS-OAUTH2' + + def new_ref(self, **kwargs): + kwargs = super(ConsumerTests, self).new_ref(**kwargs) + kwargs.setdefault('description', uuid.uuid4().hex) + kwargs.setdefault('client_type', self.DEFAULT_CLIENT_TYPE) + kwargs.setdefault('redirect_uris', self.DEFAULT_REDIRECT_URIS) + kwargs.setdefault('scopes', self.DEFAULT_SCOPES) + kwargs.setdefault('grant_type', self.DEFAULT_GRANT_TYPE) + return kwargs + + def _consumer_data(self, description=None, + client_type=DEFAULT_CLIENT_TYPE, + redirect_uris=DEFAULT_REDIRECT_URIS, + grant_type=DEFAULT_GRANT_TYPE, + scopes=DEFAULT_SCOPES): + data = { + 'consumer': { + 'description': description, + 'client_type': client_type, + 'redirect_uris': redirect_uris, + 'grant_type': grant_type, + 'scopes': scopes + } + } + return data + + def _create_consumer(self, consumer_data): + self.stub_url('POST', + [self.path_prefix, self.collection_key], + status_code=201, json=consumer_data) + + consumer = self.manager.create() + return consumer + + def test_create_consumer_defaults(self): + consumer_data = self._consumer_data() + consumer = self._create_consumer(consumer_data) + self.assertEqual(self.DEFAULT_CLIENT_TYPE, consumer.client_type) + #self.assertIsNotNone(consumer.id) + self.assertIsNone(consumer.description) + + +class AuthenticateWithOAuthTests(BaseTest): + def setUp(self): + super(AuthenticateWithOAuthTests, self).setUp() + if oauth2 is None: + self.skipTest('optional package oauthlib is not installed') + + def test_oauth_authenticate_success(self): + access_token = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to oauth2, and add its section. + oauth_token = client_fixtures.project_scoped_token() + oauth_token['methods'] = ["oauth2"] + oauth_token['oauth2'] = { + "access_token_id": access_token + } + self.stub_auth(json=oauth_token) + + a = auth.OAuth(self.TEST_URL, access_token=access_token) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + +class TestOAuthLibModule(utils.TestCase): + + def test_no_oauthlib_installed(self): + with mock.patch.object(auth, 'oauth2', None): + self.assertRaises(NotImplementedError, + auth.OAuth, + self.TEST_URL, + project_id=uuid.uuid4().hex, + access_token=uuid.uuid4().hex) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index a271db37d..73131a5d5 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -23,6 +23,7 @@ from keystoneclient.v3.contrib import endpoint_policy from keystoneclient.v3.contrib import federation from keystoneclient.v3.contrib import oauth1 +from keystoneclient.v3.contrib import oauth2 from keystoneclient.v3.contrib import trusts from keystoneclient.v3 import credentials from keystoneclient.v3 import domains @@ -175,6 +176,8 @@ def __init__(self, **kwargs): self.federation = federation.FederationManager(self) self.groups = groups.GroupManager(self) self.oauth1 = oauth1.create_oauth_manager(self) + # TODO(garcianavalon) document this + self.oauth2 = oauth2.create_oauth_manager(self) self.policies = policies.PolicyManager(self) self.projects = projects.ProjectManager(self) self.regions = regions.RegionManager(self) diff --git a/keystoneclient/v3/contrib/oauth2/__init__.py b/keystoneclient/v3/contrib/oauth2/__init__.py new file mode 100644 index 000000000..467a82bac --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/__init__.py @@ -0,0 +1,14 @@ +# 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.v3.contrib.oauth2.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py new file mode 100644 index 000000000..7e230631a --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -0,0 +1,46 @@ +# 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 oauth2 +except ImportError: + oauth2 = None + +class OAuthMethod(v3.AuthMethod): + _method_parameters = ['access_token'] + + 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 oauth2 is None: + raise NotImplementedError('optional package oauthlib' + ' is not installed') + + def get_auth_data(self, session, auth, headers, **kwargs): + # Build the data for our custom auth method. Check the OAuth2.0 keystone + # auth plugin for more info: https://www.github.com/ging/keystone + auth_data = { + 'access_token_id':self.access_token + } + name = 'oauth2' + return name, auth_data + + +class OAuth(v3.AuthConstructor): + _auth_method_class = OAuthMethod \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py new file mode 100644 index 000000000..9f41d42ec --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -0,0 +1,74 @@ +# 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. +import logging + +from keystoneclient import base as ks_base +from keystoneclient.v3.contrib.oauth2 import utils + +LOG = logging.getLogger(__name__) + +class Consumer(ks_base.Resource): + """Represents an OAuth2 consumer. + Attributes: + * id: a uuid that identifies the consumer + * description: a short description of the consumer + * secret: string used by consumers to sign certain requests + * client_type: string OAuth2 client type. The extension defines which client types + are accepted + * redirect_uris: List of strings with valid urls. This are the only urls the + consumer is allowed to redirect to + * grant_type: string with OAuth2 grant type. The extension defines which + ones are accepted + * response_type: string with the response types associated with the grant. The + extension handles assigning this value to the correspoding one for grant_type. See + https://oauthlib.readthedocs.org/en/latest/oauth2/oauth2.html for more info + * scopes: list of strings with the scopes the consumer is going to use. Defined + at the extension + """ + pass + +class ConsumerManager(ks_base.CrudManager): + """Manager class for manipulating identity consumers.""" + resource_class = Consumer + collection_key = 'consumers' + key = 'consumer' + base_url = utils.OAUTH2_PATH + + def create(self, description=None, client_type=None, redirect_uris=[], + grant_type=None, scopes=[], **kwargs): + return super(ConsumerManager, self).create( + description=description, + client_type=client_type, + redirect_uris=redirect_uris, + grant_type=grant_type, + scopes=scopes, + **kwargs) + + def get(self, consumer): + return super(ConsumerManager, self).get( + consumer_id=ks_base.getid(consumer)) + + def update(self, consumer, description=None, client_type=None, + redirect_uris=[], grant_type=None, scopes=[], **kwargs): + return super(ConsumerManager, self).update( + consumer_id=ks_base.getid(consumer), + description=description, + client_type=client_type, + redirect_uris=redirect_uris, + grant_type=grant_type, + scopes=scopes, + **kwargs) + + def delete(self, consumer): + return super(ConsumerManager, self).delete( + consumer_id=ks_base.getid(consumer)) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py new file mode 100644 index 000000000..f1d82d183 --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/core.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.v3.contrib.oauth2 import consumers + +# NOTE(garcianavalon): If followed the oauth1 solution here, I'm +# leaving the original notes as reference and to understand why + +def create_oauth_manager(self): + + # NOTE(stevemar): Attempt to import the oauthlib package at this point. + try: + import oauthlib # noqa + # NOTE(stevemar): Return an object instead of raising an exception here, + # this will allow users to see an exception only when trying to access the + # oauth portions of client. Otherwise an exception would be raised + # when the client is created. + except ImportError: + return OAuthManagerOptionalImportProxy() + else: + return OAuthManager(self) +class OAuthManager(object): + def __init__(self, api): + #self.access_tokens = access_tokens.AccessTokenManager(api) + self.consumers = consumers.ConsumerManager(api) + #self.request_tokens = request_tokens.RequestTokenManager(api) + +class OAuthManagerOptionalImportProxy(object): + """Act as a proxy manager in case oauthlib is no installed. + This class will only be created if oauthlib is not in the system, + trying to access any of the attributes in name (access_tokens, + consumers, request_tokens), will result in a NotImplementedError, + and a message. + >>> manager.access_tokens.blah + NotImplementedError: To use 'access_tokens' oauthlib must be installed + Otherwise, if trying to access an attribute other than the ones in name, + the manager will state that the attribute does not exist. + >>> manager.dne.blah + AttributeError: 'OAuthManagerOptionalImportProxy' object has no + attribute 'dne' + """ + def __getattribute__(self, name): + if name in ('access_tokens', 'consumers', 'request_tokens'): + raise NotImplementedError( + 'To use %r oauthlib must be installed' % name) + return super(OAuthManagerOptionalImportProxy, + self).__getattribute__(name) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py new file mode 100644 index 000000000..0793fa21f --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -0,0 +1,16 @@ + +# 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. + + +OAUTH2_PATH = '/OS-OAUTH2' \ No newline at end of file From b6814bb2686d5fa8f2a9d3ac75d276491963f75f Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 15 Oct 2014 17:53:04 +0200 Subject: [PATCH 002/102] access token request --- .../v3/contrib/oauth2/access_tokens.py | 61 +++++++++++++++++++ keystoneclient/v3/contrib/oauth2/utils.py | 13 +++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 keystoneclient/v3/contrib/oauth2/access_tokens.py diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py new file mode 100644 index 000000000..e9e467663 --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -0,0 +1,61 @@ +# 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 __future__ import unicode_literals + +from keystoneclient import base +from keystoneclient.v3.contrib.oauth2 import utils + +try: + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class AccessToken(base.Resource): + pass + + +class AccessTokenManager(base.CrudManager): + """Manager class for manipulating identity OAuth access tokens.""" + resource_class = AccessToken + + def create(self, consumer_id, consumer_secret, authorization_code, + redirect_uri): + endpoint = utils.OAUTH2_PATH + '/access_token' + + url = self.client.auth_url.rstrip("/") + endpoint + headers, body = self._generate_json_request(consumer_id, consumer_secret, + authorization_code, redirect_uri) + + resp, body = self.client.post(endpoint, headers=headers, body=body) + token = utils.get_oauth_token_from_body(resp.content) + return self.resource_class(self, token) + + def _generate_json_request(self, consumer_id, consumer_secret, + authorization_code, redirect_uri): + body = { + 'token_request' : { + 'grant_type':'authorization_code', + 'code': authorization_code, + 'redirect_uri':redirect_uri + } + } + headers = { + 'Authorization': self._http_basic(consumer_id,consumer_secret) + } + return headers, body + + def _http_basic(self, consumer_id, consumer_secret): + auth_string = consumer_id + ':' + consumer_secret + return 'Basic ' + auth_string.encode('base64') \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py index 0793fa21f..386615588 100644 --- a/keystoneclient/v3/contrib/oauth2/utils.py +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -12,5 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json -OAUTH2_PATH = '/OS-OAUTH2' \ No newline at end of file + +OAUTH2_PATH = '/OS-OAUTH2' + +def get_oauth_token_from_body(body): + """Parse the URL response body to retrieve the access token + + The response body is JSON + """ + # TODO(garcianavalon) this seems to be unnecesary, refactor it + token = json.loads(body) + return token From cb78c43db7de421f8799a919a35fe36a7bffdeb1 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 13:15:52 +0200 Subject: [PATCH 003/102] request and grant authorization --- keystoneclient/tests/v3/test_oauth2.py | 95 ++++++++++++++- .../v3/contrib/oauth2/access_tokens.py | 5 +- .../v3/contrib/oauth2/authorization_codes.py | 114 ++++++++++++++++++ keystoneclient/v3/contrib/oauth2/core.py | 7 +- keystoneclient/v3/contrib/oauth2/utils.py | 13 +- 5 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 keystoneclient/v3/contrib/oauth2/authorization_codes.py diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index fb369b415..8ca1eae2f 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import urllib import uuid import mock import six @@ -22,6 +23,8 @@ from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.oauth2 import auth +from keystoneclient.v3.contrib.oauth2 import access_tokens +from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers try: @@ -33,8 +36,6 @@ class BaseTest(utils.TestCase): - - def setUp(self): super(BaseTest, self).setUp() if oauth2 is None: @@ -97,6 +98,96 @@ def test_create_consumer_defaults(self): self.assertIsNone(consumer.description) + +class AuthorizationCodeTests(BaseTest): + + + def setUp(self): + super(AuthorizationCodeTests, self).setUp() + self.model = authorization_codes.AuthorizationCode + self.manager = self.client.oauth2.authorization_codes + self.path_prefix = 'OS-OAUTH2' + + def test_authorize(self): + stub_headers = { + 'Location':'https://foo.com/welcome_back?code=somerandomstring&state=xyz' + } + self.stub_url('POST', + [self.path_prefix, 'authorize',], + status_code=200,headers=stub_headers) + + + user_id = uuid.uuid4().hex + consumer_id = uuid.uuid4().hex + scopes = [uuid.uuid4().hex] + + # Assert the manager is returning the expected data + authorization_code = self.manager.authorize( + user=user_id, + consumer=consumer_id, + scopes=scopes) + + self.assertIsNotNone(authorization_code.code) + self.assertIsNotNone(authorization_code.state) + self.assertIsNotNone(authorization_code.redirect_uri) + + # Assert that the request was sent in the expected structure + expected_body = { + 'user_auth': { + 'client_id':consumer_id, + 'user_id':user_id, + 'scopes':scopes + } + } + self.assertRequestBodyIs(json=expected_body) + + def test_request_authorization(self): + scope = [uuid.uuid4().hex] + consumer_id = uuid.uuid4().hex + redirect_uri = uuid.uuid4().hex + state = uuid.uuid4().hex + + # NOTE(garcianavalon) we use a list of tuples to ensure param order + # in the query string + stub_credentials = [ + ('response_type','code'), + ('client_id',consumer_id), + ('redirect_uri',redirect_uri), + ('scope',scope), + ('state',state) + ] + query_string = '?%s' %urllib.urlencode(stub_credentials) + + # NOTE(garcianavalon) this JSON emulates the provider response body + # but it might not be up-to-date because it's changing continuosly + # during development to adjust to different needs that keep appearing. + # Only take it as mean to test that the request_authorization call + # returns a dict, to know more about the response body check the Keystone + # OAuth2 Extension documentation + stub_body = { + 'data': { + 'consumer': { + 'id':consumer_id + }, + 'redirect_uri':redirect_uri, + 'requested_scopes':scope + } + } + + self.stub_url('GET', [self.path_prefix, 'authorize', query_string], + status_code=201,json=stub_body) + + # Assert the manager is returning a dict with the info from the server + response_body = self.manager.request_authorization( + consumer=consumer_id, + redirect_uri=redirect_uri, + scope=scope, + state=state) + + assert(isinstance(response_body,dict)) + + + class AuthenticateWithOAuthTests(BaseTest): def setUp(self): super(AuthenticateWithOAuthTests, self).setUp() diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index e9e467663..6f0e8baa4 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals +import json from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils @@ -34,12 +34,11 @@ def create(self, consumer_id, consumer_secret, authorization_code, redirect_uri): endpoint = utils.OAUTH2_PATH + '/access_token' - url = self.client.auth_url.rstrip("/") + endpoint headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) resp, body = self.client.post(endpoint, headers=headers, body=body) - token = utils.get_oauth_token_from_body(resp.content) + token = json.loads(resp.content) return self.resource_class(self, token) def _generate_json_request(self, consumer_id, consumer_secret, diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py new file mode 100644 index 000000000..f6068120d --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -0,0 +1,114 @@ +# 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. + +import json +import urllib +import six +from six.moves.urllib import parse as urlparse + +from keystoneclient import base +from keystoneclient.v3.contrib.oauth2 import utils + +try: + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class AuthorizationCode(base.Resource): + """ TODO(garcianavalon) + """ + pass + + +class AuthorizationCodeManager(base.CrudManager): + """Manager class for manipulating identity OAuth authorization codes.""" + resource_class = AuthorizationCode + collection_key = 'authorization_codes' + key = 'authorization_code' + base_url = utils.OAUTH2_PATH + + def authorize(self, user, consumer, scopes): + """Authorize a Consumer for certain scopes, getting an authorization code. + + The way the provider (Keystone) will return the code is in the header, as an + HTTP redirection: + 'Location': 'https://foo.com/welcome_back?code=somerandomstring&state=xyz' + + Utilize Identity API operation: + POST /OS-OAUTH2/authorize/ + + :param user: the user granting authorization + :param consumer: the client that will be authorized, and + will exchange the authorization code for an access token. + :param scopes: a list of scopes. They are provided by the consumer + in the authorization request + """ + + endpoint = self.base_url + '/authorize' + body = { + 'user_auth': { + 'client_id':base.getid(consumer), + 'user_id':base.getid(user), + 'scopes':scopes + } + } + response, body = self.client.post(endpoint, body=body) + + redirect_uri = response.headers.get('Location') + + parsed = urlparse.urlparse(redirect_uri) + query = dict(urlparse.parse_qsl(parsed.query)) + authorization_code = { + 'redirect_uri':redirect_uri, + 'code': query['code'], + 'state': query['state'] + } + + return self.resource_class(self, authorization_code) + + def request_authorization(self, consumer, redirect_uri, scope, state=None): + """ Send the consumer credentials to the OAuth2 provider. + + The user then will be asked to authorize the client for the requested scopes. In + the OAuth2 flow this happens when the client(consumer) redirects the resource + owner(user) through his user agent to the authorization server(provider). + Therefore, this call is done by the user but with the data provided by the + consumer. + + Utilize Identity API operation: + GET /OS-OAUTH2/authorize/?client_id=&redirect_uri=&response_type=code&state= + + :param consumer: the consumer asking for authorization + :param redirect_uri: The url the user will be redirected to. It must be + registered in the server asociated with the requesting consumer. + :param scope: list of strings with the requested scopes from the ones + defined by the provider. + :param state: Optional, a string for consumer use. + """ + + # NOTE(garcianavalon) we use a list of tuples to ensure param order + # in the query string to be able to mock it during testing. + credentials = [ + ('response_type','code'), + ('client_id',base.getid(consumer)), + ('redirect_uri',redirect_uri), + ('scope',scope), + ('state',state) + ] + query= urllib.urlencode(credentials) + endpoint = self.base_url + '/authorize?%s' %query + + response, body = self.client.get(endpoint) + # TODO(garcianavalon) figure out the return. Do we need a separated manager? + return json.loads(response.content) diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py index f1d82d183..8b24c31a6 100644 --- a/keystoneclient/v3/contrib/oauth2/core.py +++ b/keystoneclient/v3/contrib/oauth2/core.py @@ -11,7 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. - +from keystoneclient.v3.contrib.oauth2 import access_tokens +from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers # NOTE(garcianavalon): If followed the oauth1 solution here, I'm @@ -32,9 +33,9 @@ def create_oauth_manager(self): return OAuthManager(self) class OAuthManager(object): def __init__(self, api): - #self.access_tokens = access_tokens.AccessTokenManager(api) + self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) - #self.request_tokens = request_tokens.RequestTokenManager(api) + self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) class OAuthManagerOptionalImportProxy(object): """Act as a proxy manager in case oauthlib is no installed. diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py index 386615588..0eed3fd53 100644 --- a/keystoneclient/v3/contrib/oauth2/utils.py +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -12,16 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json - - OAUTH2_PATH = '/OS-OAUTH2' -def get_oauth_token_from_body(body): - """Parse the URL response body to retrieve the access token - The response body is JSON - """ - # TODO(garcianavalon) this seems to be unnecesary, refactor it - token = json.loads(body) - return token +# TODO(garcianavalon) this seems to be unnecesary, refactor it + + From 9f6ab6ffdc8531a99f61c5e59350cdff13d3029c Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 14:50:00 +0200 Subject: [PATCH 004/102] removed unused oauthlib --- keystoneclient/tests/v3/test_oauth2.py | 36 ++---------------- .../v3/contrib/oauth2/access_tokens.py | 6 --- keystoneclient/v3/contrib/oauth2/auth.py | 7 ---- .../v3/contrib/oauth2/authorization_codes.py | 7 +--- keystoneclient/v3/contrib/oauth2/core.py | 38 ++----------------- 5 files changed, 8 insertions(+), 86 deletions(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 8ca1eae2f..be80e4b51 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -27,22 +27,8 @@ from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers -try: - import oauthlib - from oauthlib import oauth2 -except ImportError: - oauth2 = None - -class BaseTest(utils.TestCase): - - def setUp(self): - super(BaseTest, self).setUp() - if oauth2 is None: - self.skipTest('oauthlib package not available') - - -class ConsumerTests(BaseTest, utils.CrudTests): +class ConsumerTests(utils.TestCase, utils.CrudTests): DEFAULT_REDIRECT_URIS = ['https://uri.com'] DEFAULT_SCOPES = ['all_info'] @@ -99,7 +85,7 @@ def test_create_consumer_defaults(self): -class AuthorizationCodeTests(BaseTest): +class AuthorizationCodeTests(utils.TestCase): def setUp(self): @@ -188,11 +174,8 @@ def test_request_authorization(self): -class AuthenticateWithOAuthTests(BaseTest): - def setUp(self): - super(AuthenticateWithOAuthTests, self).setUp() - if oauth2 is None: - self.skipTest('optional package oauthlib is not installed') +class AuthenticateWithOAuthTests(utils.TestCase): + def test_oauth_authenticate_success(self): access_token = uuid.uuid4().hex @@ -210,14 +193,3 @@ def test_oauth_authenticate_success(self): s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) - - -class TestOAuthLibModule(utils.TestCase): - - def test_no_oauthlib_installed(self): - with mock.patch.object(auth, 'oauth2', None): - self.assertRaises(NotImplementedError, - auth.OAuth, - self.TEST_URL, - project_id=uuid.uuid4().hex, - access_token=uuid.uuid4().hex) diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 6f0e8baa4..7011f298a 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -16,12 +16,6 @@ from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None - - class AccessToken(base.Resource): pass diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py index 7e230631a..ebea2e502 100644 --- a/keystoneclient/v3/contrib/oauth2/auth.py +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -12,10 +12,6 @@ # limitations under the License. from keystoneclient.auth.identity import v3 -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None class OAuthMethod(v3.AuthMethod): _method_parameters = ['access_token'] @@ -28,9 +24,6 @@ def __init__(self, **kwargs): :param string access_secret: Access token secret. """ super(OAuthMethod, self).__init__(**kwargs) - if oauth2 is None: - raise NotImplementedError('optional package oauthlib' - ' is not installed') def get_auth_data(self, session, auth, headers, **kwargs): # Build the data for our custom auth method. Check the OAuth2.0 keystone diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index f6068120d..432202970 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -19,11 +19,6 @@ from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None - class AuthorizationCode(base.Resource): """ TODO(garcianavalon) @@ -110,5 +105,5 @@ def request_authorization(self, consumer, redirect_uri, scope, state=None): endpoint = self.base_url + '/authorize?%s' %query response, body = self.client.get(endpoint) - # TODO(garcianavalon) figure out the return. Do we need a separated manager? + # FIXME(garcianavalon) figure out the return. Do we need a separated manager? return json.loads(response.content) diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py index 8b24c31a6..775c8cb42 100644 --- a/keystoneclient/v3/contrib/oauth2/core.py +++ b/keystoneclient/v3/contrib/oauth2/core.py @@ -15,45 +15,13 @@ from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers -# NOTE(garcianavalon): If followed the oauth1 solution here, I'm -# leaving the original notes as reference and to understand why def create_oauth_manager(self): - - # NOTE(stevemar): Attempt to import the oauthlib package at this point. - try: - import oauthlib # noqa - # NOTE(stevemar): Return an object instead of raising an exception here, - # this will allow users to see an exception only when trying to access the - # oauth portions of client. Otherwise an exception would be raised - # when the client is created. - except ImportError: - return OAuthManagerOptionalImportProxy() - else: + # TODO(garcianavalon) this is no longer necesary, remove return OAuthManager(self) + class OAuthManager(object): def __init__(self, api): self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) - self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) - -class OAuthManagerOptionalImportProxy(object): - """Act as a proxy manager in case oauthlib is no installed. - This class will only be created if oauthlib is not in the system, - trying to access any of the attributes in name (access_tokens, - consumers, request_tokens), will result in a NotImplementedError, - and a message. - >>> manager.access_tokens.blah - NotImplementedError: To use 'access_tokens' oauthlib must be installed - Otherwise, if trying to access an attribute other than the ones in name, - the manager will state that the attribute does not exist. - >>> manager.dne.blah - AttributeError: 'OAuthManagerOptionalImportProxy' object has no - attribute 'dne' - """ - def __getattribute__(self, name): - if name in ('access_tokens', 'consumers', 'request_tokens'): - raise NotImplementedError( - 'To use %r oauthlib must be installed' % name) - return super(OAuthManagerOptionalImportProxy, - self).__getattribute__(name) \ No newline at end of file + self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) \ No newline at end of file From 12430da48f5645b0f30ee77664b9efdc234c9154 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 17:16:34 +0200 Subject: [PATCH 005/102] integration testing and fixes --- integration_test_oauth2.py | 31 ++++++++++ keystoneclient/tests/v3/test_oauth2.py | 56 +++++++++++++++++++ .../v3/contrib/oauth2/access_tokens.py | 8 ++- .../v3/contrib/oauth2/authorization_codes.py | 9 ++- 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 integration_test_oauth2.py diff --git a/integration_test_oauth2.py b/integration_test_oauth2.py new file mode 100644 index 000000000..671f51caa --- /dev/null +++ b/integration_test_oauth2.py @@ -0,0 +1,31 @@ +# TODO(garcianavalon) make proper integration tests +from keystoneclient.v3 import client + +keystone = client.Client(token='ADMIN',endpoint='http://127.0.0.1:5000/v3') + +redirect_uri='https://testuri.com' +scope='all' +scopes=[scope] +user_id=1 + +#create a consumer +consumer = keystone.oauth2.consumers.create(client_type='confidential', + redirect_uris=[redirect_uri], + grant_type='authorization_code', + scopes=scopes) +consumer_id = consumer.id +consumer_secret = consumer.secret + +#store credentials +keystone.oauth2.authorization_codes.request_authorization(consumer=consumer_id, + redirect_uri=redirect_uri, + scope=scope) +#grant authorization +authorization_code = keystone.oauth2.authorization_codes.authorize(user=user_id, + consumer=consumer_id, + scopes=scopes) +#get an access token +keystone.oauth2.access_tokens.create(consumer_id=consumer_id, + authorization_code=authorization_code.code, + redirect_uri=redirect_uri, + consumer_secret=consumer_secret) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index be80e4b51..3fd3452c8 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import urllib import uuid import mock @@ -173,6 +174,61 @@ def test_request_authorization(self): assert(isinstance(response_body,dict)) +class AccessTokenTests(utils.TestCase): + def setUp(self): + super(AccessTokenTests, self).setUp() + self.manager = self.client.oauth2.access_tokens + self.model = access_tokens.AccessToken + self.path_prefix = 'OS-OAUTH2' + + def test_create_access_token(self): + consumer_id = uuid.uuid4().hex + consumer_secret = uuid.uuid4().hex + redirect_uri = uuid.uuid4().hex + authorization_code = uuid.uuid4().hex + + stub_body = { + 'access_token': uuid.uuid4().hex, + 'refresh_token': uuid.uuid4().hex, + 'expires_in': 3600, + 'scopes': [ + uuid.uuid4().hex, + uuid.uuid4().hex + ], + 'token_type': 'Bearer' + } + self.stub_url('POST', [self.path_prefix, 'access_token'], + status_code=201, json=stub_body) + + # Assert that the manager creates an access token object + access_token = self.manager.create(consumer_id=consumer_id, + consumer_secret=consumer_secret, + authorization_code=authorization_code, + redirect_uri=redirect_uri) + + self.assertIsInstance(access_token, self.model) + self.assertIsNotNone(access_token.access_token) + self.assertIsNotNone(access_token.scopes) + self.assertIsNotNone(access_token.expires_in) + + # Assert that the request was sent in the expected structure + expected_body = { + 'token_request' : { + 'grant_type':'authorization_code', + 'code': authorization_code, + 'redirect_uri':redirect_uri + } + } + self.assertRequestBodyIs(json=expected_body) + + auth_string = consumer_id + ':' + consumer_secret + expected_auth = 'Basic ' + base64.b64encode(auth_string) + self.assertRequestHeaderEqual('Authorization', expected_auth) + + + + + class AuthenticateWithOAuthTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 7011f298a..c55bb3f26 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import json from keystoneclient import base @@ -30,8 +31,9 @@ def create(self, consumer_id, consumer_secret, authorization_code, headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) - - resp, body = self.client.post(endpoint, headers=headers, body=body) + #import pdb; pdb.set_trace() + resp, body = self.client.post(endpoint,headers=headers, body=body) + import pdb; pdb.set_trace() token = json.loads(resp.content) return self.resource_class(self, token) @@ -51,4 +53,4 @@ def _generate_json_request(self, consumer_id, consumer_secret, def _http_basic(self, consumer_id, consumer_secret): auth_string = consumer_id + ':' + consumer_secret - return 'Basic ' + auth_string.encode('base64') \ No newline at end of file + return 'Basic ' + base64.b64encode(auth_string) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 432202970..1e4397a0d 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -33,7 +33,7 @@ class AuthorizationCodeManager(base.CrudManager): key = 'authorization_code' base_url = utils.OAUTH2_PATH - def authorize(self, user, consumer, scopes): + def authorize(self, user, consumer, scopes, redirect=False): """Authorize a Consumer for certain scopes, getting an authorization code. The way the provider (Keystone) will return the code is in the header, as an @@ -48,8 +48,11 @@ def authorize(self, user, consumer, scopes): will exchange the authorization code for an access token. :param scopes: a list of scopes. They are provided by the consumer in the authorization request + :param redirect: The Keystone OAuth2 extension returns an HTTP 302 to + comply with RFC 6749 but in general we dont want the redirect to happen + if we are using the keystoneclient. """ - + #import pdb; pdb.set_trace() endpoint = self.base_url + '/authorize' body = { 'user_auth': { @@ -58,7 +61,7 @@ def authorize(self, user, consumer, scopes): 'scopes':scopes } } - response, body = self.client.post(endpoint, body=body) + response, body = self.client.post(endpoint, body=body, redirect=redirect) redirect_uri = response.headers.get('Location') From bef50071229b370c6edf06f5506a440fbe17d604 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 15 Oct 2014 17:21:29 +0200 Subject: [PATCH 006/102] consumers and auth --- README.rst | 204 +----------------- keystoneclient/tests/v3/test_oauth2.py | 132 ++++++++++++ keystoneclient/v3/client.py | 3 + keystoneclient/v3/contrib/oauth2/__init__.py | 14 ++ keystoneclient/v3/contrib/oauth2/auth.py | 46 ++++ keystoneclient/v3/contrib/oauth2/consumers.py | 74 +++++++ keystoneclient/v3/contrib/oauth2/core.py | 58 +++++ keystoneclient/v3/contrib/oauth2/utils.py | 16 ++ 8 files changed, 348 insertions(+), 199 deletions(-) create mode 100644 keystoneclient/tests/v3/test_oauth2.py create mode 100644 keystoneclient/v3/contrib/oauth2/__init__.py create mode 100644 keystoneclient/v3/contrib/oauth2/auth.py create mode 100644 keystoneclient/v3/contrib/oauth2/consumers.py create mode 100644 keystoneclient/v3/contrib/oauth2/core.py create mode 100644 keystoneclient/v3/contrib/oauth2/utils.py diff --git a/README.rst b/README.rst index 153b500cd..8698d5363 100644 --- a/README.rst +++ b/README.rst @@ -1,203 +1,9 @@ -Python bindings to the OpenStack Identity API (Keystone) -======================================================== +Python bindings to the FIWARE-IdM modified Keystone +=================================================== -This is a client for the OpenStack Identity API, implemented by Keystone. -There's a Python API (the ``keystoneclient`` module), and a command-line script -(``keystone``). +This is an extended client for the OpenStack Identity API, implemented by Keystone. It adds python bindings to the extra functionality added to Keystone through extensions implemented at `GING Keystone `_. The CLI part of the client is not binded, no work is done in that regard as is not a project-requirement at the time of this writting and because the OpenStack keystoneclient CLI is deprecated. If requested could be developed in the future, although probably as a contribution to `OpenStack Client `_. -Development takes place via the usual OpenStack processes as outlined in the -`OpenStack wiki `_. The master -repository is on `GitHub `_. +The master repository is on `GitHub `_. -This code is a fork of `Rackspace's python-novaclient -`_ which is in turn a fork of -`Jacobian's python-cloudservers -`_. ``python-keystoneclient`` -is licensed under the Apache License like the rest of OpenStack. +This code is a fork of `OpenStack KeystoneClient `_. The future goal is to contribute back all the bindings for the extensions that get accepted in Keystone from the complementary project `GING Keystone `_. -.. contents:: Contents: - :local: - -Python API ----------- - -By way of a quick-start:: - - # use v2.0 auth with http://example.com:5000/v2.0 - >>> from keystoneclient.v2_0 import client - >>> keystone = client.Client(username=USERNAME, password=PASSWORD, tenant_name=TENANT, auth_url=AUTH_URL) - >>> keystone.tenants.list() - >>> tenant = keystone.tenants.create(tenant_name="test", description="My new tenant!", enabled=True) - >>> tenant.delete() - - -Command-line API ----------------- - -Installing this package gets you a shell command, ``keystone``, that you can -use to interact with OpenStack's Identity API. - -You'll need to provide your OpenStack tenant, username and password. You can do -this with the ``--os-tenant-name``, ``--os-username`` and ``--os-password`` -params, but it's easier to just set them as environment variables:: - - export OS_TENANT_NAME=project - export OS_USERNAME=user - export OS_PASSWORD=pass - -You will also need to define the authentication url with ``--os-auth-url`` and -the version of the API with ``--os-identity-api-version``. Or set them as an -environment variables as well:: - - export OS_AUTH_URL=http://example.com:5000/v2.0 - export OS_IDENTITY_API_VERSION=2.0 - -Alternatively, to bypass username/password authentication, you can provide a -pre-established token. In Keystone, this approach is necessary to bootstrap the -service with an administrative user, tenant & role (to do so, provide the -client with the value of your ``admin_token`` defined in ``keystone.conf`` in -addition to the URL of your admin API deployment, typically on port 35357):: - - export OS_SERVICE_TOKEN=thequickbrownfox-jumpsover-thelazydog - export OS_SERVICE_ENDPOINT=http://example.com:35357/v2.0 - -Since the Identity service can return multiple regions in the service catalog, -you can specify the one you want with ``--os-region-name`` (or ``export -OS_REGION_NAME``):: - - export OS_REGION_NAME=north - -.. WARNING:: - - If a region is not specified and multiple regions are returned by the - Identity service, the client may not access the same region consistently. - -If you need to connect to a server that is TLS-enabled (the auth URL begins -with 'https') and it uses a certificate from a private CA or a self-signed -certificate you will need to specify the path to an appropriate CA certificate -to use to validate the server certificate with ``--os-cacert`` or an -environment variable:: - - export OS_CACERT=/etc/ssl/my-root-cert.pem - -Certificate verification can be turned off using ``--insecure``. This should -be used with caution. - -You'll find complete documentation on the shell by running ``keystone help``:: - - usage: keystone [--version] [--timeout ] - [--os-username ] - [--os-password ] - [--os-tenant-name ] - [--os-tenant-id ] [--os-auth-url ] - [--os-region-name ] - [--os-identity-api-version ] - [--os-token ] - [--os-endpoint ] - [--os-cacert ] [--insecure] - [--os-cert ] [--os-key ] [--os-cache] - [--force-new-token] [--stale-duration ] - ... - - Command-line interface to the OpenStack Identity API. - - Positional arguments: - - catalog - ec2-credentials-create - Create EC2-compatible credentials for user per tenant - ec2-credentials-delete - Delete EC2-compatible credentials - ec2-credentials-get - Display EC2-compatible credentials - ec2-credentials-list - List EC2-compatible credentials for a user - endpoint-create Create a new endpoint associated with a service - endpoint-delete Delete a service endpoint - endpoint-get - endpoint-list List configured service endpoints - password-update Update own password - role-create Create new role - role-delete Delete role - role-get Display role details - role-list List all roles - service-create Add service to Service Catalog - service-delete Delete service from Service Catalog - service-get Display service from Service Catalog - service-list List all services in Service Catalog - tenant-create Create new tenant - tenant-delete Delete tenant - tenant-get Display tenant details - tenant-list List all tenants - tenant-update Update tenant name, description, enabled status - token-get - user-create Create new user - user-delete Delete user - user-get Display user details. - user-list List users - user-password-update - Update user password - user-role-add Add role to user - user-role-list List roles granted to a user - user-role-remove Remove role from user - user-update Update user's name, email, and enabled status - discover Discover Keystone servers, supported API versions and - extensions. - bootstrap Grants a new role to a new user on a new tenant, after - creating each. - bash-completion Prints all of the commands and options to stdout. - help Display help about this program or one of its - subcommands. - - Optional arguments: - --version Shows the client version and exits - --timeout Set request timeout (in seconds) - --os-username - Name used for authentication with the OpenStack - Identity service. Defaults to env[OS_USERNAME] - --os-password - Password used for authentication with the OpenStack - Identity service. Defaults to env[OS_PASSWORD] - --os-tenant-name - Tenant to request authorization on. Defaults to - env[OS_TENANT_NAME] - --os-tenant-id - Tenant to request authorization on. Defaults to - env[OS_TENANT_ID] - --os-auth-url - Specify the Identity endpoint to use for - authentication. Defaults to env[OS_AUTH_URL] - --os-region-name - Defaults to env[OS_REGION_NAME] - --os-identity-api-version - Defaults to env[OS_IDENTITY_API_VERSION] or 2.0 - --os-token - Specify an existing token to use instead of retrieving - one via authentication (e.g. with username & - password). Defaults to env[OS_SERVICE_TOKEN] - --os-endpoint - Specify an endpoint to use instead of retrieving one - from the service catalog (via authentication). - Defaults to env[OS_SERVICE_ENDPOINT] - --os-cacert - Specify a CA bundle file to use in verifying a TLS - (https) server certificate. Defaults to env[OS_CACERT] - --insecure Explicitly allow keystoneclient to perform "insecure" - TLS (https) requests. The server's certificate will - not be verified against any certificate authorities. - This option should be used with caution. - --os-cert - Defaults to env[OS_CERT] - --os-key Defaults to env[OS_KEY] - --os-cache Use the auth token cache. Defaults to env[OS_CACHE] - --force-new-token If the keyring is available and in use, token will - always be stored and fetched from the keyring until - the token has expired. Use this option to request a - new token and replace the existing one in the keyring. - --stale-duration - Stale duration (in seconds) used to determine whether - a token has expired when retrieving it from keyring. - This is useful in mitigating process or network - delays. Default is 30 seconds. - - See "keystone help COMMAND" for help on a specific command. diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py new file mode 100644 index 000000000..fb369b415 --- /dev/null +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -0,0 +1,132 @@ +# 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. + +import uuid +import mock +import six +from six.moves.urllib import parse as urlparse +from testtools import matchers + +from keystoneclient import session +from keystoneclient.openstack.common import timeutils +from keystoneclient.tests.v3 import client_fixtures +from keystoneclient.tests.v3 import utils +from keystoneclient.v3.contrib.oauth2 import auth +from keystoneclient.v3.contrib.oauth2 import consumers + +try: + import oauthlib + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class BaseTest(utils.TestCase): + + + + def setUp(self): + super(BaseTest, self).setUp() + if oauth2 is None: + self.skipTest('oauthlib package not available') + + +class ConsumerTests(BaseTest, utils.CrudTests): + + DEFAULT_REDIRECT_URIS = ['https://uri.com'] + DEFAULT_SCOPES = ['all_info'] + DEFAULT_CLIENT_TYPE='confidential' + DEFAULT_GRANT_TYPE='authorization_code' + + def setUp(self): + super(ConsumerTests, self).setUp() + self.key = 'consumer' + self.collection_key = 'consumers' + self.model = consumers.Consumer + self.manager = self.client.oauth2.consumers + self.path_prefix = 'OS-OAUTH2' + + def new_ref(self, **kwargs): + kwargs = super(ConsumerTests, self).new_ref(**kwargs) + kwargs.setdefault('description', uuid.uuid4().hex) + kwargs.setdefault('client_type', self.DEFAULT_CLIENT_TYPE) + kwargs.setdefault('redirect_uris', self.DEFAULT_REDIRECT_URIS) + kwargs.setdefault('scopes', self.DEFAULT_SCOPES) + kwargs.setdefault('grant_type', self.DEFAULT_GRANT_TYPE) + return kwargs + + def _consumer_data(self, description=None, + client_type=DEFAULT_CLIENT_TYPE, + redirect_uris=DEFAULT_REDIRECT_URIS, + grant_type=DEFAULT_GRANT_TYPE, + scopes=DEFAULT_SCOPES): + data = { + 'consumer': { + 'description': description, + 'client_type': client_type, + 'redirect_uris': redirect_uris, + 'grant_type': grant_type, + 'scopes': scopes + } + } + return data + + def _create_consumer(self, consumer_data): + self.stub_url('POST', + [self.path_prefix, self.collection_key], + status_code=201, json=consumer_data) + + consumer = self.manager.create() + return consumer + + def test_create_consumer_defaults(self): + consumer_data = self._consumer_data() + consumer = self._create_consumer(consumer_data) + self.assertEqual(self.DEFAULT_CLIENT_TYPE, consumer.client_type) + #self.assertIsNotNone(consumer.id) + self.assertIsNone(consumer.description) + + +class AuthenticateWithOAuthTests(BaseTest): + def setUp(self): + super(AuthenticateWithOAuthTests, self).setUp() + if oauth2 is None: + self.skipTest('optional package oauthlib is not installed') + + def test_oauth_authenticate_success(self): + access_token = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to oauth2, and add its section. + oauth_token = client_fixtures.project_scoped_token() + oauth_token['methods'] = ["oauth2"] + oauth_token['oauth2'] = { + "access_token_id": access_token + } + self.stub_auth(json=oauth_token) + + a = auth.OAuth(self.TEST_URL, access_token=access_token) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + +class TestOAuthLibModule(utils.TestCase): + + def test_no_oauthlib_installed(self): + with mock.patch.object(auth, 'oauth2', None): + self.assertRaises(NotImplementedError, + auth.OAuth, + self.TEST_URL, + project_id=uuid.uuid4().hex, + access_token=uuid.uuid4().hex) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 1967e8e6a..9e15eaaf1 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -24,6 +24,7 @@ from keystoneclient.v3.contrib import endpoint_policy from keystoneclient.v3.contrib import federation from keystoneclient.v3.contrib import oauth1 +from keystoneclient.v3.contrib import oauth2 from keystoneclient.v3.contrib import trusts from keystoneclient.v3 import credentials from keystoneclient.v3 import domains @@ -176,6 +177,8 @@ def __init__(self, **kwargs): self.federation = federation.FederationManager(self) self.groups = groups.GroupManager(self) self.oauth1 = oauth1.create_oauth_manager(self) + # TODO(garcianavalon) document this + self.oauth2 = oauth2.create_oauth_manager(self) self.policies = policies.PolicyManager(self) self.projects = projects.ProjectManager(self) self.regions = regions.RegionManager(self) diff --git a/keystoneclient/v3/contrib/oauth2/__init__.py b/keystoneclient/v3/contrib/oauth2/__init__.py new file mode 100644 index 000000000..467a82bac --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/__init__.py @@ -0,0 +1,14 @@ +# 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.v3.contrib.oauth2.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py new file mode 100644 index 000000000..7e230631a --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -0,0 +1,46 @@ +# 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 oauth2 +except ImportError: + oauth2 = None + +class OAuthMethod(v3.AuthMethod): + _method_parameters = ['access_token'] + + 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 oauth2 is None: + raise NotImplementedError('optional package oauthlib' + ' is not installed') + + def get_auth_data(self, session, auth, headers, **kwargs): + # Build the data for our custom auth method. Check the OAuth2.0 keystone + # auth plugin for more info: https://www.github.com/ging/keystone + auth_data = { + 'access_token_id':self.access_token + } + name = 'oauth2' + return name, auth_data + + +class OAuth(v3.AuthConstructor): + _auth_method_class = OAuthMethod \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py new file mode 100644 index 000000000..9f41d42ec --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -0,0 +1,74 @@ +# 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. +import logging + +from keystoneclient import base as ks_base +from keystoneclient.v3.contrib.oauth2 import utils + +LOG = logging.getLogger(__name__) + +class Consumer(ks_base.Resource): + """Represents an OAuth2 consumer. + Attributes: + * id: a uuid that identifies the consumer + * description: a short description of the consumer + * secret: string used by consumers to sign certain requests + * client_type: string OAuth2 client type. The extension defines which client types + are accepted + * redirect_uris: List of strings with valid urls. This are the only urls the + consumer is allowed to redirect to + * grant_type: string with OAuth2 grant type. The extension defines which + ones are accepted + * response_type: string with the response types associated with the grant. The + extension handles assigning this value to the correspoding one for grant_type. See + https://oauthlib.readthedocs.org/en/latest/oauth2/oauth2.html for more info + * scopes: list of strings with the scopes the consumer is going to use. Defined + at the extension + """ + pass + +class ConsumerManager(ks_base.CrudManager): + """Manager class for manipulating identity consumers.""" + resource_class = Consumer + collection_key = 'consumers' + key = 'consumer' + base_url = utils.OAUTH2_PATH + + def create(self, description=None, client_type=None, redirect_uris=[], + grant_type=None, scopes=[], **kwargs): + return super(ConsumerManager, self).create( + description=description, + client_type=client_type, + redirect_uris=redirect_uris, + grant_type=grant_type, + scopes=scopes, + **kwargs) + + def get(self, consumer): + return super(ConsumerManager, self).get( + consumer_id=ks_base.getid(consumer)) + + def update(self, consumer, description=None, client_type=None, + redirect_uris=[], grant_type=None, scopes=[], **kwargs): + return super(ConsumerManager, self).update( + consumer_id=ks_base.getid(consumer), + description=description, + client_type=client_type, + redirect_uris=redirect_uris, + grant_type=grant_type, + scopes=scopes, + **kwargs) + + def delete(self, consumer): + return super(ConsumerManager, self).delete( + consumer_id=ks_base.getid(consumer)) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py new file mode 100644 index 000000000..f1d82d183 --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/core.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.v3.contrib.oauth2 import consumers + +# NOTE(garcianavalon): If followed the oauth1 solution here, I'm +# leaving the original notes as reference and to understand why + +def create_oauth_manager(self): + + # NOTE(stevemar): Attempt to import the oauthlib package at this point. + try: + import oauthlib # noqa + # NOTE(stevemar): Return an object instead of raising an exception here, + # this will allow users to see an exception only when trying to access the + # oauth portions of client. Otherwise an exception would be raised + # when the client is created. + except ImportError: + return OAuthManagerOptionalImportProxy() + else: + return OAuthManager(self) +class OAuthManager(object): + def __init__(self, api): + #self.access_tokens = access_tokens.AccessTokenManager(api) + self.consumers = consumers.ConsumerManager(api) + #self.request_tokens = request_tokens.RequestTokenManager(api) + +class OAuthManagerOptionalImportProxy(object): + """Act as a proxy manager in case oauthlib is no installed. + This class will only be created if oauthlib is not in the system, + trying to access any of the attributes in name (access_tokens, + consumers, request_tokens), will result in a NotImplementedError, + and a message. + >>> manager.access_tokens.blah + NotImplementedError: To use 'access_tokens' oauthlib must be installed + Otherwise, if trying to access an attribute other than the ones in name, + the manager will state that the attribute does not exist. + >>> manager.dne.blah + AttributeError: 'OAuthManagerOptionalImportProxy' object has no + attribute 'dne' + """ + def __getattribute__(self, name): + if name in ('access_tokens', 'consumers', 'request_tokens'): + raise NotImplementedError( + 'To use %r oauthlib must be installed' % name) + return super(OAuthManagerOptionalImportProxy, + self).__getattribute__(name) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py new file mode 100644 index 000000000..0793fa21f --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -0,0 +1,16 @@ + +# 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. + + +OAUTH2_PATH = '/OS-OAUTH2' \ No newline at end of file From c7023f18b124736708b770bedf33391154a15303 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 15 Oct 2014 17:53:04 +0200 Subject: [PATCH 007/102] access token request --- .../v3/contrib/oauth2/access_tokens.py | 61 +++++++++++++++++++ keystoneclient/v3/contrib/oauth2/utils.py | 13 +++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 keystoneclient/v3/contrib/oauth2/access_tokens.py diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py new file mode 100644 index 000000000..e9e467663 --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -0,0 +1,61 @@ +# 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 __future__ import unicode_literals + +from keystoneclient import base +from keystoneclient.v3.contrib.oauth2 import utils + +try: + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class AccessToken(base.Resource): + pass + + +class AccessTokenManager(base.CrudManager): + """Manager class for manipulating identity OAuth access tokens.""" + resource_class = AccessToken + + def create(self, consumer_id, consumer_secret, authorization_code, + redirect_uri): + endpoint = utils.OAUTH2_PATH + '/access_token' + + url = self.client.auth_url.rstrip("/") + endpoint + headers, body = self._generate_json_request(consumer_id, consumer_secret, + authorization_code, redirect_uri) + + resp, body = self.client.post(endpoint, headers=headers, body=body) + token = utils.get_oauth_token_from_body(resp.content) + return self.resource_class(self, token) + + def _generate_json_request(self, consumer_id, consumer_secret, + authorization_code, redirect_uri): + body = { + 'token_request' : { + 'grant_type':'authorization_code', + 'code': authorization_code, + 'redirect_uri':redirect_uri + } + } + headers = { + 'Authorization': self._http_basic(consumer_id,consumer_secret) + } + return headers, body + + def _http_basic(self, consumer_id, consumer_secret): + auth_string = consumer_id + ':' + consumer_secret + return 'Basic ' + auth_string.encode('base64') \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py index 0793fa21f..386615588 100644 --- a/keystoneclient/v3/contrib/oauth2/utils.py +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -12,5 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json -OAUTH2_PATH = '/OS-OAUTH2' \ No newline at end of file + +OAUTH2_PATH = '/OS-OAUTH2' + +def get_oauth_token_from_body(body): + """Parse the URL response body to retrieve the access token + + The response body is JSON + """ + # TODO(garcianavalon) this seems to be unnecesary, refactor it + token = json.loads(body) + return token From e6399e6a9d566195230a0624dfc6cd21b27a187e Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 13:15:52 +0200 Subject: [PATCH 008/102] request and grant authorization --- keystoneclient/tests/v3/test_oauth2.py | 95 ++++++++++++++- .../v3/contrib/oauth2/access_tokens.py | 5 +- .../v3/contrib/oauth2/authorization_codes.py | 114 ++++++++++++++++++ keystoneclient/v3/contrib/oauth2/core.py | 7 +- keystoneclient/v3/contrib/oauth2/utils.py | 13 +- 5 files changed, 216 insertions(+), 18 deletions(-) create mode 100644 keystoneclient/v3/contrib/oauth2/authorization_codes.py diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index fb369b415..8ca1eae2f 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import urllib import uuid import mock import six @@ -22,6 +23,8 @@ from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.oauth2 import auth +from keystoneclient.v3.contrib.oauth2 import access_tokens +from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers try: @@ -33,8 +36,6 @@ class BaseTest(utils.TestCase): - - def setUp(self): super(BaseTest, self).setUp() if oauth2 is None: @@ -97,6 +98,96 @@ def test_create_consumer_defaults(self): self.assertIsNone(consumer.description) + +class AuthorizationCodeTests(BaseTest): + + + def setUp(self): + super(AuthorizationCodeTests, self).setUp() + self.model = authorization_codes.AuthorizationCode + self.manager = self.client.oauth2.authorization_codes + self.path_prefix = 'OS-OAUTH2' + + def test_authorize(self): + stub_headers = { + 'Location':'https://foo.com/welcome_back?code=somerandomstring&state=xyz' + } + self.stub_url('POST', + [self.path_prefix, 'authorize',], + status_code=200,headers=stub_headers) + + + user_id = uuid.uuid4().hex + consumer_id = uuid.uuid4().hex + scopes = [uuid.uuid4().hex] + + # Assert the manager is returning the expected data + authorization_code = self.manager.authorize( + user=user_id, + consumer=consumer_id, + scopes=scopes) + + self.assertIsNotNone(authorization_code.code) + self.assertIsNotNone(authorization_code.state) + self.assertIsNotNone(authorization_code.redirect_uri) + + # Assert that the request was sent in the expected structure + expected_body = { + 'user_auth': { + 'client_id':consumer_id, + 'user_id':user_id, + 'scopes':scopes + } + } + self.assertRequestBodyIs(json=expected_body) + + def test_request_authorization(self): + scope = [uuid.uuid4().hex] + consumer_id = uuid.uuid4().hex + redirect_uri = uuid.uuid4().hex + state = uuid.uuid4().hex + + # NOTE(garcianavalon) we use a list of tuples to ensure param order + # in the query string + stub_credentials = [ + ('response_type','code'), + ('client_id',consumer_id), + ('redirect_uri',redirect_uri), + ('scope',scope), + ('state',state) + ] + query_string = '?%s' %urllib.urlencode(stub_credentials) + + # NOTE(garcianavalon) this JSON emulates the provider response body + # but it might not be up-to-date because it's changing continuosly + # during development to adjust to different needs that keep appearing. + # Only take it as mean to test that the request_authorization call + # returns a dict, to know more about the response body check the Keystone + # OAuth2 Extension documentation + stub_body = { + 'data': { + 'consumer': { + 'id':consumer_id + }, + 'redirect_uri':redirect_uri, + 'requested_scopes':scope + } + } + + self.stub_url('GET', [self.path_prefix, 'authorize', query_string], + status_code=201,json=stub_body) + + # Assert the manager is returning a dict with the info from the server + response_body = self.manager.request_authorization( + consumer=consumer_id, + redirect_uri=redirect_uri, + scope=scope, + state=state) + + assert(isinstance(response_body,dict)) + + + class AuthenticateWithOAuthTests(BaseTest): def setUp(self): super(AuthenticateWithOAuthTests, self).setUp() diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index e9e467663..6f0e8baa4 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -11,7 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from __future__ import unicode_literals +import json from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils @@ -34,12 +34,11 @@ def create(self, consumer_id, consumer_secret, authorization_code, redirect_uri): endpoint = utils.OAUTH2_PATH + '/access_token' - url = self.client.auth_url.rstrip("/") + endpoint headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) resp, body = self.client.post(endpoint, headers=headers, body=body) - token = utils.get_oauth_token_from_body(resp.content) + token = json.loads(resp.content) return self.resource_class(self, token) def _generate_json_request(self, consumer_id, consumer_secret, diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py new file mode 100644 index 000000000..f6068120d --- /dev/null +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -0,0 +1,114 @@ +# 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. + +import json +import urllib +import six +from six.moves.urllib import parse as urlparse + +from keystoneclient import base +from keystoneclient.v3.contrib.oauth2 import utils + +try: + from oauthlib import oauth2 +except ImportError: + oauth2 = None + + +class AuthorizationCode(base.Resource): + """ TODO(garcianavalon) + """ + pass + + +class AuthorizationCodeManager(base.CrudManager): + """Manager class for manipulating identity OAuth authorization codes.""" + resource_class = AuthorizationCode + collection_key = 'authorization_codes' + key = 'authorization_code' + base_url = utils.OAUTH2_PATH + + def authorize(self, user, consumer, scopes): + """Authorize a Consumer for certain scopes, getting an authorization code. + + The way the provider (Keystone) will return the code is in the header, as an + HTTP redirection: + 'Location': 'https://foo.com/welcome_back?code=somerandomstring&state=xyz' + + Utilize Identity API operation: + POST /OS-OAUTH2/authorize/ + + :param user: the user granting authorization + :param consumer: the client that will be authorized, and + will exchange the authorization code for an access token. + :param scopes: a list of scopes. They are provided by the consumer + in the authorization request + """ + + endpoint = self.base_url + '/authorize' + body = { + 'user_auth': { + 'client_id':base.getid(consumer), + 'user_id':base.getid(user), + 'scopes':scopes + } + } + response, body = self.client.post(endpoint, body=body) + + redirect_uri = response.headers.get('Location') + + parsed = urlparse.urlparse(redirect_uri) + query = dict(urlparse.parse_qsl(parsed.query)) + authorization_code = { + 'redirect_uri':redirect_uri, + 'code': query['code'], + 'state': query['state'] + } + + return self.resource_class(self, authorization_code) + + def request_authorization(self, consumer, redirect_uri, scope, state=None): + """ Send the consumer credentials to the OAuth2 provider. + + The user then will be asked to authorize the client for the requested scopes. In + the OAuth2 flow this happens when the client(consumer) redirects the resource + owner(user) through his user agent to the authorization server(provider). + Therefore, this call is done by the user but with the data provided by the + consumer. + + Utilize Identity API operation: + GET /OS-OAUTH2/authorize/?client_id=&redirect_uri=&response_type=code&state= + + :param consumer: the consumer asking for authorization + :param redirect_uri: The url the user will be redirected to. It must be + registered in the server asociated with the requesting consumer. + :param scope: list of strings with the requested scopes from the ones + defined by the provider. + :param state: Optional, a string for consumer use. + """ + + # NOTE(garcianavalon) we use a list of tuples to ensure param order + # in the query string to be able to mock it during testing. + credentials = [ + ('response_type','code'), + ('client_id',base.getid(consumer)), + ('redirect_uri',redirect_uri), + ('scope',scope), + ('state',state) + ] + query= urllib.urlencode(credentials) + endpoint = self.base_url + '/authorize?%s' %query + + response, body = self.client.get(endpoint) + # TODO(garcianavalon) figure out the return. Do we need a separated manager? + return json.loads(response.content) diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py index f1d82d183..8b24c31a6 100644 --- a/keystoneclient/v3/contrib/oauth2/core.py +++ b/keystoneclient/v3/contrib/oauth2/core.py @@ -11,7 +11,8 @@ # See the License for the specific language governing permissions and # limitations under the License. - +from keystoneclient.v3.contrib.oauth2 import access_tokens +from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers # NOTE(garcianavalon): If followed the oauth1 solution here, I'm @@ -32,9 +33,9 @@ def create_oauth_manager(self): return OAuthManager(self) class OAuthManager(object): def __init__(self, api): - #self.access_tokens = access_tokens.AccessTokenManager(api) + self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) - #self.request_tokens = request_tokens.RequestTokenManager(api) + self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) class OAuthManagerOptionalImportProxy(object): """Act as a proxy manager in case oauthlib is no installed. diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py index 386615588..0eed3fd53 100644 --- a/keystoneclient/v3/contrib/oauth2/utils.py +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -12,16 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json - - OAUTH2_PATH = '/OS-OAUTH2' -def get_oauth_token_from_body(body): - """Parse the URL response body to retrieve the access token - The response body is JSON - """ - # TODO(garcianavalon) this seems to be unnecesary, refactor it - token = json.loads(body) - return token +# TODO(garcianavalon) this seems to be unnecesary, refactor it + + From eb1764e646c5d40814703a80a1770595aa30731d Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 14:50:00 +0200 Subject: [PATCH 009/102] removed unused oauthlib --- keystoneclient/tests/v3/test_oauth2.py | 36 ++---------------- .../v3/contrib/oauth2/access_tokens.py | 6 --- keystoneclient/v3/contrib/oauth2/auth.py | 7 ---- .../v3/contrib/oauth2/authorization_codes.py | 7 +--- keystoneclient/v3/contrib/oauth2/core.py | 38 ++----------------- 5 files changed, 8 insertions(+), 86 deletions(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 8ca1eae2f..be80e4b51 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -27,22 +27,8 @@ from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers -try: - import oauthlib - from oauthlib import oauth2 -except ImportError: - oauth2 = None - -class BaseTest(utils.TestCase): - - def setUp(self): - super(BaseTest, self).setUp() - if oauth2 is None: - self.skipTest('oauthlib package not available') - - -class ConsumerTests(BaseTest, utils.CrudTests): +class ConsumerTests(utils.TestCase, utils.CrudTests): DEFAULT_REDIRECT_URIS = ['https://uri.com'] DEFAULT_SCOPES = ['all_info'] @@ -99,7 +85,7 @@ def test_create_consumer_defaults(self): -class AuthorizationCodeTests(BaseTest): +class AuthorizationCodeTests(utils.TestCase): def setUp(self): @@ -188,11 +174,8 @@ def test_request_authorization(self): -class AuthenticateWithOAuthTests(BaseTest): - def setUp(self): - super(AuthenticateWithOAuthTests, self).setUp() - if oauth2 is None: - self.skipTest('optional package oauthlib is not installed') +class AuthenticateWithOAuthTests(utils.TestCase): + def test_oauth_authenticate_success(self): access_token = uuid.uuid4().hex @@ -210,14 +193,3 @@ def test_oauth_authenticate_success(self): s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) - - -class TestOAuthLibModule(utils.TestCase): - - def test_no_oauthlib_installed(self): - with mock.patch.object(auth, 'oauth2', None): - self.assertRaises(NotImplementedError, - auth.OAuth, - self.TEST_URL, - project_id=uuid.uuid4().hex, - access_token=uuid.uuid4().hex) diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 6f0e8baa4..7011f298a 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -16,12 +16,6 @@ from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None - - class AccessToken(base.Resource): pass diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py index 7e230631a..ebea2e502 100644 --- a/keystoneclient/v3/contrib/oauth2/auth.py +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -12,10 +12,6 @@ # limitations under the License. from keystoneclient.auth.identity import v3 -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None class OAuthMethod(v3.AuthMethod): _method_parameters = ['access_token'] @@ -28,9 +24,6 @@ def __init__(self, **kwargs): :param string access_secret: Access token secret. """ super(OAuthMethod, self).__init__(**kwargs) - if oauth2 is None: - raise NotImplementedError('optional package oauthlib' - ' is not installed') def get_auth_data(self, session, auth, headers, **kwargs): # Build the data for our custom auth method. Check the OAuth2.0 keystone diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index f6068120d..432202970 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -19,11 +19,6 @@ from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils -try: - from oauthlib import oauth2 -except ImportError: - oauth2 = None - class AuthorizationCode(base.Resource): """ TODO(garcianavalon) @@ -110,5 +105,5 @@ def request_authorization(self, consumer, redirect_uri, scope, state=None): endpoint = self.base_url + '/authorize?%s' %query response, body = self.client.get(endpoint) - # TODO(garcianavalon) figure out the return. Do we need a separated manager? + # FIXME(garcianavalon) figure out the return. Do we need a separated manager? return json.loads(response.content) diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py index 8b24c31a6..775c8cb42 100644 --- a/keystoneclient/v3/contrib/oauth2/core.py +++ b/keystoneclient/v3/contrib/oauth2/core.py @@ -15,45 +15,13 @@ from keystoneclient.v3.contrib.oauth2 import authorization_codes from keystoneclient.v3.contrib.oauth2 import consumers -# NOTE(garcianavalon): If followed the oauth1 solution here, I'm -# leaving the original notes as reference and to understand why def create_oauth_manager(self): - - # NOTE(stevemar): Attempt to import the oauthlib package at this point. - try: - import oauthlib # noqa - # NOTE(stevemar): Return an object instead of raising an exception here, - # this will allow users to see an exception only when trying to access the - # oauth portions of client. Otherwise an exception would be raised - # when the client is created. - except ImportError: - return OAuthManagerOptionalImportProxy() - else: + # TODO(garcianavalon) this is no longer necesary, remove return OAuthManager(self) + class OAuthManager(object): def __init__(self, api): self.access_tokens = access_tokens.AccessTokenManager(api) self.consumers = consumers.ConsumerManager(api) - self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) - -class OAuthManagerOptionalImportProxy(object): - """Act as a proxy manager in case oauthlib is no installed. - This class will only be created if oauthlib is not in the system, - trying to access any of the attributes in name (access_tokens, - consumers, request_tokens), will result in a NotImplementedError, - and a message. - >>> manager.access_tokens.blah - NotImplementedError: To use 'access_tokens' oauthlib must be installed - Otherwise, if trying to access an attribute other than the ones in name, - the manager will state that the attribute does not exist. - >>> manager.dne.blah - AttributeError: 'OAuthManagerOptionalImportProxy' object has no - attribute 'dne' - """ - def __getattribute__(self, name): - if name in ('access_tokens', 'consumers', 'request_tokens'): - raise NotImplementedError( - 'To use %r oauthlib must be installed' % name) - return super(OAuthManagerOptionalImportProxy, - self).__getattribute__(name) \ No newline at end of file + self.authorization_codes = authorization_codes.AuthorizationCodeManager(api) \ No newline at end of file From bc562b98253d903d4a8237fbb8d1eb3ca9e89634 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 16 Oct 2014 17:16:34 +0200 Subject: [PATCH 010/102] integration testing and fixes --- integration_test_oauth2.py | 31 ++++++++++ keystoneclient/tests/v3/test_oauth2.py | 56 +++++++++++++++++++ .../v3/contrib/oauth2/access_tokens.py | 8 ++- .../v3/contrib/oauth2/authorization_codes.py | 9 ++- 4 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 integration_test_oauth2.py diff --git a/integration_test_oauth2.py b/integration_test_oauth2.py new file mode 100644 index 000000000..671f51caa --- /dev/null +++ b/integration_test_oauth2.py @@ -0,0 +1,31 @@ +# TODO(garcianavalon) make proper integration tests +from keystoneclient.v3 import client + +keystone = client.Client(token='ADMIN',endpoint='http://127.0.0.1:5000/v3') + +redirect_uri='https://testuri.com' +scope='all' +scopes=[scope] +user_id=1 + +#create a consumer +consumer = keystone.oauth2.consumers.create(client_type='confidential', + redirect_uris=[redirect_uri], + grant_type='authorization_code', + scopes=scopes) +consumer_id = consumer.id +consumer_secret = consumer.secret + +#store credentials +keystone.oauth2.authorization_codes.request_authorization(consumer=consumer_id, + redirect_uri=redirect_uri, + scope=scope) +#grant authorization +authorization_code = keystone.oauth2.authorization_codes.authorize(user=user_id, + consumer=consumer_id, + scopes=scopes) +#get an access token +keystone.oauth2.access_tokens.create(consumer_id=consumer_id, + authorization_code=authorization_code.code, + redirect_uri=redirect_uri, + consumer_secret=consumer_secret) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index be80e4b51..3fd3452c8 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import urllib import uuid import mock @@ -173,6 +174,61 @@ def test_request_authorization(self): assert(isinstance(response_body,dict)) +class AccessTokenTests(utils.TestCase): + def setUp(self): + super(AccessTokenTests, self).setUp() + self.manager = self.client.oauth2.access_tokens + self.model = access_tokens.AccessToken + self.path_prefix = 'OS-OAUTH2' + + def test_create_access_token(self): + consumer_id = uuid.uuid4().hex + consumer_secret = uuid.uuid4().hex + redirect_uri = uuid.uuid4().hex + authorization_code = uuid.uuid4().hex + + stub_body = { + 'access_token': uuid.uuid4().hex, + 'refresh_token': uuid.uuid4().hex, + 'expires_in': 3600, + 'scopes': [ + uuid.uuid4().hex, + uuid.uuid4().hex + ], + 'token_type': 'Bearer' + } + self.stub_url('POST', [self.path_prefix, 'access_token'], + status_code=201, json=stub_body) + + # Assert that the manager creates an access token object + access_token = self.manager.create(consumer_id=consumer_id, + consumer_secret=consumer_secret, + authorization_code=authorization_code, + redirect_uri=redirect_uri) + + self.assertIsInstance(access_token, self.model) + self.assertIsNotNone(access_token.access_token) + self.assertIsNotNone(access_token.scopes) + self.assertIsNotNone(access_token.expires_in) + + # Assert that the request was sent in the expected structure + expected_body = { + 'token_request' : { + 'grant_type':'authorization_code', + 'code': authorization_code, + 'redirect_uri':redirect_uri + } + } + self.assertRequestBodyIs(json=expected_body) + + auth_string = consumer_id + ':' + consumer_secret + expected_auth = 'Basic ' + base64.b64encode(auth_string) + self.assertRequestHeaderEqual('Authorization', expected_auth) + + + + + class AuthenticateWithOAuthTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 7011f298a..c55bb3f26 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -11,6 +11,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import json from keystoneclient import base @@ -30,8 +31,9 @@ def create(self, consumer_id, consumer_secret, authorization_code, headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) - - resp, body = self.client.post(endpoint, headers=headers, body=body) + #import pdb; pdb.set_trace() + resp, body = self.client.post(endpoint,headers=headers, body=body) + import pdb; pdb.set_trace() token = json.loads(resp.content) return self.resource_class(self, token) @@ -51,4 +53,4 @@ def _generate_json_request(self, consumer_id, consumer_secret, def _http_basic(self, consumer_id, consumer_secret): auth_string = consumer_id + ':' + consumer_secret - return 'Basic ' + auth_string.encode('base64') \ No newline at end of file + return 'Basic ' + base64.b64encode(auth_string) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 432202970..1e4397a0d 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -33,7 +33,7 @@ class AuthorizationCodeManager(base.CrudManager): key = 'authorization_code' base_url = utils.OAUTH2_PATH - def authorize(self, user, consumer, scopes): + def authorize(self, user, consumer, scopes, redirect=False): """Authorize a Consumer for certain scopes, getting an authorization code. The way the provider (Keystone) will return the code is in the header, as an @@ -48,8 +48,11 @@ def authorize(self, user, consumer, scopes): will exchange the authorization code for an access token. :param scopes: a list of scopes. They are provided by the consumer in the authorization request + :param redirect: The Keystone OAuth2 extension returns an HTTP 302 to + comply with RFC 6749 but in general we dont want the redirect to happen + if we are using the keystoneclient. """ - + #import pdb; pdb.set_trace() endpoint = self.base_url + '/authorize' body = { 'user_auth': { @@ -58,7 +61,7 @@ def authorize(self, user, consumer, scopes): 'scopes':scopes } } - response, body = self.client.post(endpoint, body=body) + response, body = self.client.post(endpoint, body=body, redirect=redirect) redirect_uri = response.headers.get('Location') From f87f2b4beb5e67870af874844254ac9287afcb0c Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 20 Oct 2014 13:05:21 +0200 Subject: [PATCH 011/102] integration fixes --- integration_test_oauth2.py | 18 +++++-- keystoneclient/tests/v3/test_oauth2.py | 51 +++++++++++++++++-- .../v3/contrib/oauth2/access_tokens.py | 3 +- keystoneclient/v3/contrib/oauth2/auth.py | 13 ++--- 4 files changed, 65 insertions(+), 20 deletions(-) diff --git a/integration_test_oauth2.py b/integration_test_oauth2.py index 671f51caa..b288f4165 100644 --- a/integration_test_oauth2.py +++ b/integration_test_oauth2.py @@ -1,12 +1,15 @@ # TODO(garcianavalon) make proper integration tests from keystoneclient.v3 import client - -keystone = client.Client(token='ADMIN',endpoint='http://127.0.0.1:5000/v3') +from keystoneclient import session +from keystoneclient.v3.contrib.oauth2 import auth +url = 'http://127.0.0.1:5000/v3' +keystone = client.Client(token='ADMIN',endpoint=url) redirect_uri='https://testuri.com' -scope='all' +scope='all_info' scopes=[scope] -user_id=1 +admin_user = keystone.users.find(name='admin') +user_id=admin_user.id #create a consumer consumer = keystone.oauth2.consumers.create(client_type='confidential', @@ -25,7 +28,12 @@ consumer=consumer_id, scopes=scopes) #get an access token -keystone.oauth2.access_tokens.create(consumer_id=consumer_id, +access_token = keystone.oauth2.access_tokens.create(consumer_id=consumer_id, authorization_code=authorization_code.code, redirect_uri=redirect_uri, consumer_secret=consumer_secret) +# log in and get a keystone token +a = auth.OAuth2(url,access_token=access_token.access_token) +s = session.Session(auth=a) +keystone = client.Client(session=s) +keystone.users.list() diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 3fd3452c8..4727101f7 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -175,6 +175,7 @@ def test_request_authorization(self): class AccessTokenTests(utils.TestCase): + def setUp(self): super(AccessTokenTests, self).setUp() self.manager = self.client.oauth2.access_tokens @@ -225,10 +226,6 @@ def test_create_access_token(self): expected_auth = 'Basic ' + base64.b64encode(auth_string) self.assertRequestHeaderEqual('Authorization', expected_auth) - - - - class AuthenticateWithOAuthTests(utils.TestCase): @@ -236,6 +233,37 @@ class AuthenticateWithOAuthTests(utils.TestCase): def test_oauth_authenticate_success(self): access_token = uuid.uuid4().hex + # Just use an existing project scoped token and change + # the methods to oauth2, and add its section. + oauth_token = client_fixtures.unscoped_token() + + oauth_token['methods'] = ["oauth2"] + oauth_token['oauth2'] = { + "access_token_id": access_token + } + self.stub_auth(json=oauth_token) + + a = auth.OAuth2(self.TEST_URL, access_token=access_token) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + OAUTH2_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["oauth2"], + "oauth2": { + "access_token_id": access_token + } + } + } + } + + self.assertRequestBodyIs(json=OAUTH2_REQUEST_BODY) + + def test_oauth_authenticate_scoped_success(self): + access_token = uuid.uuid4().hex + # Just use an existing project scoped token and change # the methods to oauth2, and add its section. oauth_token = client_fixtures.project_scoped_token() @@ -245,7 +273,20 @@ def test_oauth_authenticate_success(self): } self.stub_auth(json=oauth_token) - a = auth.OAuth(self.TEST_URL, access_token=access_token) + a = auth.OAuth2(self.TEST_URL, access_token=access_token) s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) + + OAUTH2_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["oauth2"], + "oauth2": { + "access_token_id": access_token + } + } + } + } + + self.assertRequestBodyIs(json=OAUTH2_REQUEST_BODY) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index c55bb3f26..5a6bc15b6 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -31,9 +31,8 @@ def create(self, consumer_id, consumer_secret, authorization_code, headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) - #import pdb; pdb.set_trace() + resp, body = self.client.post(endpoint,headers=headers, body=body) - import pdb; pdb.set_trace() token = json.loads(resp.content) return self.resource_class(self, token) diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py index ebea2e502..1e19d2c28 100644 --- a/keystoneclient/v3/contrib/oauth2/auth.py +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -13,17 +13,14 @@ from keystoneclient.auth.identity import v3 -class OAuthMethod(v3.AuthMethod): +class OAuth2Method(v3.AuthMethod): _method_parameters = ['access_token'] 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. + :param string access_token: Access token id. """ - super(OAuthMethod, self).__init__(**kwargs) + super(OAuth2Method, self).__init__(**kwargs) def get_auth_data(self, session, auth, headers, **kwargs): # Build the data for our custom auth method. Check the OAuth2.0 keystone @@ -35,5 +32,5 @@ def get_auth_data(self, session, auth, headers, **kwargs): return name, auth_data -class OAuth(v3.AuthConstructor): - _auth_method_class = OAuthMethod \ No newline at end of file +class OAuth2(v3.AuthConstructor): + _auth_method_class = OAuth2Method \ No newline at end of file From 201919da1030cd4bbd6784cc60465dec7e3189f8 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 22 Oct 2014 18:11:55 +0200 Subject: [PATCH 012/102] update to follow the extension changes --- integration_test_oauth2.py | 10 ++++++---- keystoneclient/tests/v3/test_oauth2.py | 6 +----- .../v3/contrib/oauth2/authorization_codes.py | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/integration_test_oauth2.py b/integration_test_oauth2.py index b288f4165..0975dfca7 100644 --- a/integration_test_oauth2.py +++ b/integration_test_oauth2.py @@ -3,13 +3,15 @@ from keystoneclient import session from keystoneclient.v3.contrib.oauth2 import auth url = 'http://127.0.0.1:5000/v3' -keystone = client.Client(token='ADMIN',endpoint=url) +#keystone = client.Client(token='ADMIN',endpoint=url) + +keystone=client.Client(username='admin', password='secrete', + project_name='demo', auth_url=url) redirect_uri='https://testuri.com' scope='all_info' scopes=[scope] -admin_user = keystone.users.find(name='admin') -user_id=admin_user.id + #create a consumer consumer = keystone.oauth2.consumers.create(client_type='confidential', @@ -24,7 +26,7 @@ redirect_uri=redirect_uri, scope=scope) #grant authorization -authorization_code = keystone.oauth2.authorization_codes.authorize(user=user_id, +authorization_code = keystone.oauth2.authorization_codes.authorize( consumer=consumer_id, scopes=scopes) #get an access token diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 4727101f7..3f8634b40 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -103,14 +103,11 @@ def test_authorize(self): [self.path_prefix, 'authorize',], status_code=200,headers=stub_headers) - - user_id = uuid.uuid4().hex consumer_id = uuid.uuid4().hex scopes = [uuid.uuid4().hex] # Assert the manager is returning the expected data - authorization_code = self.manager.authorize( - user=user_id, + authorization_code = self.manager.authorize( consumer=consumer_id, scopes=scopes) @@ -122,7 +119,6 @@ def test_authorize(self): expected_body = { 'user_auth': { 'client_id':consumer_id, - 'user_id':user_id, 'scopes':scopes } } diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 1e4397a0d..03032b9c5 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -33,7 +33,7 @@ class AuthorizationCodeManager(base.CrudManager): key = 'authorization_code' base_url = utils.OAUTH2_PATH - def authorize(self, user, consumer, scopes, redirect=False): + def authorize(self, consumer, scopes, redirect=False): """Authorize a Consumer for certain scopes, getting an authorization code. The way the provider (Keystone) will return the code is in the header, as an @@ -57,7 +57,6 @@ def authorize(self, user, consumer, scopes, redirect=False): body = { 'user_auth': { 'client_id':base.getid(consumer), - 'user_id':base.getid(user), 'scopes':scopes } } From e0d1f1f27bddfaf569437d5e0a1920377ba46b8f Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 23 Oct 2014 17:05:02 +0200 Subject: [PATCH 013/102] fixed scope format in authorization request --- keystoneclient/tests/v3/test_oauth2.py | 23 ++++++++++--------- .../v3/contrib/oauth2/authorization_codes.py | 16 ++++++++----- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index fee9175d2..2c405502d 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -33,8 +33,8 @@ class ConsumerTests(utils.TestCase, utils.CrudTests): DEFAULT_REDIRECT_URIS = ['https://uri.com'] DEFAULT_SCOPES = ['all_info'] - DEFAULT_CLIENT_TYPE='confidential' - DEFAULT_GRANT_TYPE='authorization_code' + DEFAULT_CLIENT_TYPE = 'confidential' + DEFAULT_GRANT_TYPE = 'authorization_code' def setUp(self): super(ConsumerTests, self).setUp() @@ -101,7 +101,7 @@ def test_authorize(self): } self.stub_url('POST', [self.path_prefix, 'authorize',], - status_code=200,headers=stub_headers) + status_code=200, headers=stub_headers) consumer_id = uuid.uuid4().hex scopes = [uuid.uuid4().hex] @@ -131,14 +131,15 @@ def test_request_authorization(self): redirect_uri = uuid.uuid4().hex state = uuid.uuid4().hex + scope_string = ' '.join(scope) # NOTE(garcianavalon) we use a list of tuples to ensure param order # in the query string stub_credentials = [ - ('response_type','code'), - ('client_id',consumer_id), - ('redirect_uri',redirect_uri), - ('scope',scope), - ('state',state) + ('response_type', 'code'), + ('client_id', consumer_id), + ('redirect_uri', redirect_uri), + ('scope', scope_string), + ('state', state) ] query_string = '?%s' %urllib.urlencode(stub_credentials) @@ -159,16 +160,16 @@ def test_request_authorization(self): } self.stub_url('GET', [self.path_prefix, 'authorize', query_string], - status_code=201,json=stub_body) + status_code=201, json=stub_body) # Assert the manager is returning a dict with the info from the server - response_body = self.manager.request_authorization( + response_body = self.manager.request_authorization( consumer=consumer_id, redirect_uri=redirect_uri, scope=scope, state=state) - assert(isinstance(response_body,dict)) + assert(isinstance(response_body, dict)) class AccessTokenTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 260f49af7..ab8fa7de0 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -92,17 +92,21 @@ def request_authorization(self, consumer, redirect_uri, scope, state=None): defined by the provider. :param state: Optional, a string for consumer use. """ + # Transform the array with the requested scopes into a list of + # space-delimited, case-sensitive strings as specified in RFC 6749 + # http://tools.ietf.org/html/rfc6749#section-3.3 + scope_string = ' '.join(scope) # NOTE(garcianavalon) we use a list of tuples to ensure param order # in the query string to be able to mock it during testing. credentials = [ - ('response_type','code'), - ('client_id',base.getid(consumer)), - ('redirect_uri',redirect_uri), - ('scope',scope), - ('state',state) + ('response_type', 'code'), + ('client_id', base.getid(consumer)), + ('redirect_uri', redirect_uri), + ('scope', scope_string), + ('state', state) ] - query= urllib.urlencode(credentials) + query = urllib.urlencode(credentials) endpoint = self.base_url + '/authorize?%s' %query response, body = self.client.get(endpoint) From af0dc1a86dadf3ea8250b24842790d9b62e0e9a1 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 29 Oct 2014 15:25:25 +0100 Subject: [PATCH 014/102] updated scope in access_token --- keystoneclient/tests/v3/test_oauth2.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 2c405502d..09cecda4b 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -191,10 +191,7 @@ def test_create_access_token(self): 'access_token': uuid.uuid4().hex, 'refresh_token': uuid.uuid4().hex, 'expires_in': 3600, - 'scopes': [ - uuid.uuid4().hex, - uuid.uuid4().hex - ], + 'scope': ' '.join([uuid.uuid4().hex, uuid.uuid4().hex]), 'token_type': 'Bearer' } self.stub_url('POST', [self.path_prefix, 'access_token'], @@ -208,7 +205,7 @@ def test_create_access_token(self): self.assertIsInstance(access_token, self.model) self.assertIsNotNone(access_token.access_token) - self.assertIsNotNone(access_token.scopes) + self.assertIsNotNone(access_token.scope) self.assertIsNotNone(access_token.expires_in) # Assert that the request was sent in the expected structure From 1174c0b375027212d346149ace0d60d81e93d254 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Date: Tue, 4 Nov 2014 12:40:32 +0100 Subject: [PATCH 015/102] added copyright --- keystoneclient/tests/v3/test_oauth2.py | 1 + keystoneclient/v3/contrib/oauth2/__init__.py | 1 + keystoneclient/v3/contrib/oauth2/access_tokens.py | 1 + keystoneclient/v3/contrib/oauth2/auth.py | 1 + keystoneclient/v3/contrib/oauth2/authorization_codes.py | 1 + keystoneclient/v3/contrib/oauth2/consumers.py | 1 + keystoneclient/v3/contrib/oauth2/core.py | 1 + keystoneclient/v3/contrib/oauth2/utils.py | 2 +- 8 files changed, 8 insertions(+), 1 deletion(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 09cecda4b..1aff01f41 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/__init__.py b/keystoneclient/v3/contrib/oauth2/__init__.py index 467a82bac..0e621d89e 100644 --- a/keystoneclient/v3/contrib/oauth2/__init__.py +++ b/keystoneclient/v3/contrib/oauth2/__init__.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 3d41e5fa8..778724044 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/auth.py b/keystoneclient/v3/contrib/oauth2/auth.py index dd2b22f8d..bccedfa0f 100644 --- a/keystoneclient/v3/contrib/oauth2/auth.py +++ b/keystoneclient/v3/contrib/oauth2/auth.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index ab8fa7de0..483e13eba 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index 9f41d42ec..86b190328 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/core.py b/keystoneclient/v3/contrib/oauth2/core.py index 775c8cb42..8e5d022c5 100644 --- a/keystoneclient/v3/contrib/oauth2/core.py +++ b/keystoneclient/v3/contrib/oauth2/core.py @@ -1,3 +1,4 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 diff --git a/keystoneclient/v3/contrib/oauth2/utils.py b/keystoneclient/v3/contrib/oauth2/utils.py index 0eed3fd53..3e1bc79d9 100644 --- a/keystoneclient/v3/contrib/oauth2/utils.py +++ b/keystoneclient/v3/contrib/oauth2/utils.py @@ -1,4 +1,4 @@ - +# Copyright (C) 2014 Universidad Politecnica de Madrid # 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 From c22d248708097783ff32d501472c691f0d08e75c Mon Sep 17 00:00:00 2001 From: Enrique Garcia Date: Tue, 4 Nov 2014 17:08:00 +0100 Subject: [PATCH 016/102] roles and permissions basic managers --- keystoneclient/tests/v3/test_fiware_roles.py | 63 +++++++++++++++++++ .../v3/contrib/fiware_roles/__init__.py | 15 +++++ .../v3/contrib/fiware_roles/core.py | 23 +++++++ .../v3/contrib/fiware_roles/permission.py | 29 +++++++++ .../v3/contrib/fiware_roles/roles.py | 51 +++++++++++++++ 5 files changed, 181 insertions(+) create mode 100644 keystoneclient/tests/v3/test_fiware_roles.py create mode 100644 keystoneclient/v3/contrib/fiware_roles/__init__.py create mode 100644 keystoneclient/v3/contrib/fiware_roles/core.py create mode 100644 keystoneclient/v3/contrib/fiware_roles/permission.py create mode 100644 keystoneclient/v3/contrib/fiware_roles/roles.py diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py new file mode 100644 index 000000000..f93c37e11 --- /dev/null +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -0,0 +1,63 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import uuid + +from keystoneclient import exceptions +from keystoneclient.tests.v3 import utils +from keystoneclient.v3.contrib.fiware_roles import roles + + +EXTENSION_PATH = 'OS-ROLES' + +class RoleTests(utils.TestCase, utils.CrudTests): + + + def setUp(self): + super(RoleTests, self).setUp() + self.key = 'role' + self.collection_key = 'roles' + self.model = roles.Role + self.manager = self.client.fiware_roles.roles + self.path_prefix = EXTENSION_PATH + + def new_ref(self, **kwargs): + kwargs = super(RoleTests, self).new_ref(**kwargs) + kwargs.setdefault('name', uuid.uuid4().hex) + kwargs.setdefault('is_editable', True) + return kwargs + + def test_add_permission_to_role(self): + permission_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('PUT', + [self.path_prefix, self.collection_key, role_ref['id'], + 'permissions'], + status_code=204) + + self.manager.add_permission(role=role_ref['id'], permission=permission_id) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.add_permission, + role=role_ref['id'], + permission=None) + self.assertRaises(exceptions.ValidationError, + self.manager.add_permission, + role=None, + permission=permission_id) + + + + diff --git a/keystoneclient/v3/contrib/fiware_roles/__init__.py b/keystoneclient/v3/contrib/fiware_roles/__init__.py new file mode 100644 index 000000000..aed2e647b --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.roles.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py new file mode 100644 index 000000000..683409286 --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -0,0 +1,23 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.fiware_roles import roles +from keystoneclient.v3.contrib.fiware_roles import permissions + +ROLES_PATH = '/OS-ROLES' + +class OAuthManager(object): + def __init__(self, api): + self.roles = roles.RoleManager(api) + self.permissions = permissions.PermissionManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/permission.py b/keystoneclient/v3/contrib/fiware_roles/permission.py new file mode 100644 index 000000000..04428fc7d --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/permission.py @@ -0,0 +1,29 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient.v3.contrib.fiware_roles import core + +class Permission(base.Resource): + pass + +class PermissionManager(base.CrudManager): + """Manager class for manipulating permissions in the FIWARE ROLES extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Permission + collection_key = 'permissions' + key = 'permission' + base_url = core.ROLES_PATH diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py new file mode 100644 index 000000000..61540c475 --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -0,0 +1,51 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import logging + +from keystoneclient import base +from keystoneclient import exceptions + +from keystoneclient.v3.contrib.fiware_roles import core + + +LOG = logging.getLogger(__name__) + +class Role(base.Resource): + pass + +class RoleManager(base.CrudManager): + """Manager class for manipulating roles in the FIWARE ROLES extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Role + collection_key = 'roles' + key = 'role' + base_url = core.ROLES_PATH + + def _require_role_and_permission(self, role, permission): + if not (role and permission): + msg = 'Specify both a role and a permission' + raise exceptions.ValidationError(msg) + + def add_permission(self, role, permission): + self._require_role_and_permission(role, permission) + + # PUT to roles/{role_id}/permissions + base_url = self.base_url + '%s/permissions/' %base.getid(role) + return super(RoleManager, self).put( + base_url=base_url, + permission_id=base.getid(permission)) + From 3f4ec945143bc8de8a369e8b9c5928bcfee1ce40 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Date: Tue, 4 Nov 2014 17:15:32 +0100 Subject: [PATCH 017/102] fixed (stub)url to add permissions --- keystoneclient/tests/v3/test_fiware_roles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index f93c37e11..e31144124 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -43,7 +43,7 @@ def test_add_permission_to_role(self): role_ref = self.new_ref() self.stub_url('PUT', [self.path_prefix, self.collection_key, role_ref['id'], - 'permissions'], + 'permissions', permission_id], status_code=204) self.manager.add_permission(role=role_ref['id'], permission=permission_id) From 3d55332208b27cf28808862ee00021bbda9568e2 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 5 Nov 2014 12:45:10 +0100 Subject: [PATCH 018/102] permisions and roles CRUD, add permision to role support --- keystoneclient/tests/v3/test_fiware_roles.py | 8 +-- keystoneclient/v3/client.py | 2 + .../v3/contrib/fiware_roles/__init__.py | 2 +- .../v3/contrib/fiware_roles/core.py | 10 ++-- .../{permission.py => permissions.py} | 4 +- .../v3/contrib/fiware_roles/roles.py | 49 ++++++++++++++----- .../v3/contrib/fiware_roles/utils.py | 15 ++++++ 7 files changed, 64 insertions(+), 26 deletions(-) rename keystoneclient/v3/contrib/fiware_roles/{permission.py => permissions.py} (91%) create mode 100644 keystoneclient/v3/contrib/fiware_roles/utils.py diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index e31144124..d867b303a 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -39,13 +39,13 @@ def new_ref(self, **kwargs): return kwargs def test_add_permission_to_role(self): + permission_id = uuid.uuid4().hex role_ref = self.new_ref() self.stub_url('PUT', [self.path_prefix, self.collection_key, role_ref['id'], 'permissions', permission_id], status_code=204) - self.manager.add_permission(role=role_ref['id'], permission=permission_id) # Test invalid args @@ -56,8 +56,4 @@ def test_add_permission_to_role(self): self.assertRaises(exceptions.ValidationError, self.manager.add_permission, role=None, - permission=permission_id) - - - - + permission=permission_id) \ No newline at end of file diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 9e15eaaf1..0183dcd14 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -22,6 +22,7 @@ from keystoneclient import httpclient from keystoneclient.v3.contrib import endpoint_filter from keystoneclient.v3.contrib import endpoint_policy +from keystoneclient.v3.contrib import fiware_roles from keystoneclient.v3.contrib import federation from keystoneclient.v3.contrib import oauth1 from keystoneclient.v3.contrib import oauth2 @@ -178,6 +179,7 @@ def __init__(self, **kwargs): self.groups = groups.GroupManager(self) self.oauth1 = oauth1.create_oauth_manager(self) # TODO(garcianavalon) document this + self.fiware_roles = fiware_roles.FiwareRolesManager(self) self.oauth2 = oauth2.create_oauth_manager(self) self.policies = policies.PolicyManager(self) self.projects = projects.ProjectManager(self) diff --git a/keystoneclient/v3/contrib/fiware_roles/__init__.py b/keystoneclient/v3/contrib/fiware_roles/__init__.py index aed2e647b..7028e2eb5 100644 --- a/keystoneclient/v3/contrib/fiware_roles/__init__.py +++ b/keystoneclient/v3/contrib/fiware_roles/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient.v3.contrib.roles.core import * \ No newline at end of file +from keystoneclient.v3.contrib.fiware_roles.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py index 683409286..9a6f9ea26 100644 --- a/keystoneclient/v3/contrib/fiware_roles/core.py +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -15,9 +15,7 @@ from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import permissions -ROLES_PATH = '/OS-ROLES' - -class OAuthManager(object): - def __init__(self, api): - self.roles = roles.RoleManager(api) - self.permissions = permissions.PermissionManager(api) \ No newline at end of file +class FiwareRolesManager(object): + def __init__(self, api): + self.roles = roles.RoleManager(api) + self.permissions = permissions.PermissionManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/permission.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py similarity index 91% rename from keystoneclient/v3/contrib/fiware_roles/permission.py rename to keystoneclient/v3/contrib/fiware_roles/permissions.py index 04428fc7d..0eec30515 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permission.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -13,7 +13,7 @@ # limitations under the License. from keystoneclient import base -from keystoneclient.v3.contrib.fiware_roles import core +from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH class Permission(base.Resource): pass @@ -26,4 +26,4 @@ class PermissionManager(base.CrudManager): resource_class = Permission collection_key = 'permissions' key = 'permission' - base_url = core.ROLES_PATH + base_url = ROLES_PATH diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 61540c475..e20706fdd 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -12,16 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging - from keystoneclient import base from keystoneclient import exceptions - -from keystoneclient.v3.contrib.fiware_roles import core +from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH -LOG = logging.getLogger(__name__) - class Role(base.Resource): pass @@ -33,19 +28,51 @@ class RoleManager(base.CrudManager): resource_class = Role collection_key = 'roles' key = 'role' - base_url = core.ROLES_PATH + base_url = ROLES_PATH def _require_role_and_permission(self, role, permission): if not (role and permission): msg = 'Specify both a role and a permission' raise exceptions.ValidationError(msg) + @base.filter_kwargs + def put(self, append_to_url='', **kwargs): + """Override to append elements to the url""" + url = self.build_url(dict_args_in_out=kwargs) + if append_to_url: + url += append_to_url + + return self._update( + url, + method='PUT') + def add_permission(self, role, permission): self._require_role_and_permission(role, permission) # PUT to roles/{role_id}/permissions - base_url = self.base_url + '%s/permissions/' %base.getid(role) - return super(RoleManager, self).put( - base_url=base_url, - permission_id=base.getid(permission)) + endpoint = '/permissions/%s' %base.getid(permission) + return self.put(append_to_url=endpoint, + role_id=base.getid(role)) + + def create(self, name=None, is_editable=True, application=None, **kwargs): + return super(RoleManager, self).create( + name=name, + is_editable=is_editable, + application=application, + **kwargs) + def get(self, role): + return super(RoleManager, self).get( + role_id=base.getid(role)) + def update(self, role, name=None, is_editable=True, + application=None, **kwargs): + return super(RoleManager, self).update( + role_id=base.getid(role), + name=name, + is_editable=is_editable, + application=application, + **kwargs) + + def delete(self, role): + return super(RoleManager, self).delete( + role_id=base.getid(role)) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/utils.py b/keystoneclient/v3/contrib/fiware_roles/utils.py new file mode 100644 index 000000000..2cd330589 --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/utils.py @@ -0,0 +1,15 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +ROLES_PATH = '/OS-ROLES' \ No newline at end of file From 36ab981d474a46d431a7303a8703e8cdea89a60a Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 5 Nov 2014 14:41:24 +0100 Subject: [PATCH 019/102] CRUD methods for permissions --- keystoneclient/tests/v3/test_fiware_roles.py | 21 +++++++++++++++-- .../v3/contrib/fiware_roles/permissions.py | 23 +++++++++++++++++++ .../v3/contrib/fiware_roles/roles.py | 2 +- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index d867b303a..aa20f94b9 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -17,7 +17,7 @@ from keystoneclient import exceptions from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.fiware_roles import roles - +from keystoneclient.v3.contrib.fiware_roles import permissions EXTENSION_PATH = 'OS-ROLES' @@ -56,4 +56,21 @@ def test_add_permission_to_role(self): self.assertRaises(exceptions.ValidationError, self.manager.add_permission, role=None, - permission=permission_id) \ No newline at end of file + permission=permission_id) + +class PermissionTests(utils.TestCase, utils.CrudTests): + + + def setUp(self): + super(PermissionTests, self).setUp() + self.key = 'permission' + self.collection_key = 'permissions' + self.model = permissions.Permission + self.manager = self.client.fiware_roles.permissions + self.path_prefix = EXTENSION_PATH + + def new_ref(self, **kwargs): + kwargs = super(PermissionTests, self).new_ref(**kwargs) + kwargs.setdefault('name', uuid.uuid4().hex) + kwargs.setdefault('is_editable', True) + return kwargs \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index 0eec30515..a383438c2 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -27,3 +27,26 @@ class PermissionManager(base.CrudManager): collection_key = 'permissions' key = 'permission' base_url = ROLES_PATH + + def create(self, name, is_editable=True, application=None, **kwargs): + return super(PermissionManager, self).create( + name=name, + is_editable=is_editable, + application=application, + **kwargs) + def get(self, permission): + return super(PermissionManager, self).get( + permission_id=base.getid(permission)) + + def update(self, permission, name=None, is_editable=True, + application=None, **kwargs): + return super(PermissionManager, self).update( + permission_id=base.getid(permission), + name=name, + is_editable=is_editable, + application=application, + **kwargs) + + def delete(self, permission): + return super(PermissionManager, self).delete( + permission_id=base.getid(permission)) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index e20706fdd..e8381d7c9 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -54,7 +54,7 @@ def add_permission(self, role, permission): return self.put(append_to_url=endpoint, role_id=base.getid(role)) - def create(self, name=None, is_editable=True, application=None, **kwargs): + def create(self, name, is_editable=True, application=None, **kwargs): return super(RoleManager, self).create( name=name, is_editable=is_editable, From 6c3cc7969d5561a4fd4c02a0b75cc05c4e7a5ca6 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Wed, 19 Nov 2014 13:08:48 +0100 Subject: [PATCH 020/102] list roles --- keystoneclient/tests/v3/test_fiware_roles.py | 60 ++++++++++---- .../v3/contrib/fiware_roles/core.py | 4 +- .../v3/contrib/fiware_roles/permissions.py | 33 +++++++- .../v3/contrib/fiware_roles/roles.py | 79 ++++++++++++++----- 4 files changed, 136 insertions(+), 40 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index aa20f94b9..17e06ea19 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -38,25 +38,51 @@ def new_ref(self, **kwargs): kwargs.setdefault('is_editable', True) return kwargs - def test_add_permission_to_role(self): + # def test_add_permission_to_role(self): + # permission_id = uuid.uuid4().hex + # role_ref = self.new_ref() + # self.stub_url('PUT', + # [self.path_prefix, self.collection_key, role_ref['id'], + # 'permissions', permission_id], + # status_code=204) + # self.manager.add_permission(role=role_ref['id'], permission=permission_id) + + # # Test invalid args + # self.assertRaises(exceptions.ValidationError, + # self.manager.add_permission, + # role=role_ref['id'], + # permission=None) + # self.assertRaises(exceptions.ValidationError, + # self.manager.add_permission, + # role=None, + # permission=permission_id) + + def test_list_roles_by_permission(self): permission_id = uuid.uuid4().hex - role_ref = self.new_ref() - self.stub_url('PUT', - [self.path_prefix, self.collection_key, role_ref['id'], - 'permissions', permission_id], - status_code=204) - self.manager.add_permission(role=role_ref['id'], permission=permission_id) - - # Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.add_permission, - role=role_ref['id'], - permission=None) - self.assertRaises(exceptions.ValidationError, - self.manager.add_permission, - role=None, - permission=permission_id) + ref_list = [self.new_ref(), self.new_ref()] + + self.stub_entity('GET', + parts=[self.path_prefix, 'permissions', permission_id, self.collection_key], + entity=ref_list) + + returned_list = self.manager.list(permission=permission_id) + + self.assertEqual(len(ref_list), len(returned_list)) + [self.assertIsInstance(r, self.model) for r in returned_list] + + def test_list_roles_by_user(self): + user_id = uuid.uuid4().hex + ref_list = [self.new_ref(), self.new_ref()] + + self.stub_entity('GET', + parts=[self.path_prefix, 'users', user_id, self.collection_key], + entity=ref_list) + + returned_list = self.manager.list(user=user_id) + + self.assertEqual(len(ref_list), len(returned_list)) + [self.assertIsInstance(r, self.model) for r in returned_list] class PermissionTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py index 9a6f9ea26..6b316d39d 100644 --- a/keystoneclient/v3/contrib/fiware_roles/core.py +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -14,8 +14,10 @@ from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import permissions +from keystoneclient.v3.contrib.fiware_roles import users class FiwareRolesManager(object): def __init__(self, api): self.roles = roles.RoleManager(api) - self.permissions = permissions.PermissionManager(api) \ No newline at end of file + self.permissions = permissions.PermissionManager(api) + # self.users = users.UserManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index a383438c2..cba9802e4 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -28,6 +28,11 @@ class PermissionManager(base.CrudManager): key = 'permission' base_url = ROLES_PATH + def _require_role_and_permission(self, role, permission): + if not (role and permission): + msg = 'Specify both a role and a permission' + raise exceptions.ValidationError(msg) + def create(self, name, is_editable=True, application=None, **kwargs): return super(PermissionManager, self).create( name=name, @@ -46,7 +51,31 @@ def update(self, permission, name=None, is_editable=True, is_editable=is_editable, application=application, **kwargs) - + + def delete(self, permission): return super(PermissionManager, self).delete( - permission_id=base.getid(permission)) \ No newline at end of file + permission_id=base.getid(permission)) + + #No Funciona + # def add_role(self, role, permission): + # self._require_role_and_permission(role, permission) + # base_url = '/OS-ROLES' + + # base_url = base_url + '/roles/%s/permissions/' % base.getid(role) + # return super(PermissionManager, self).put( + # base_url=base_url, + # permission_id=base.getid(permission)) + + # def remove_role(self, role, permission): + # self._require_role_and_permission(role, permission) + # base_url = '/OS-ROLES' + # base_url += '/roles/%s' % base.getid(role) + # return super(PermissionManager, self).delete( + # base_url=base_url, + # permission_id=base.getid(permission)) + + def list(self, **kwargs): + return super(PermissionManager, self).list(**kwargs) + + \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index e8381d7c9..09d051934 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -30,29 +30,50 @@ class RoleManager(base.CrudManager): key = 'role' base_url = ROLES_PATH - def _require_role_and_permission(self, role, permission): - if not (role and permission): - msg = 'Specify both a role and a permission' + # def _require_role_and_permission(self, role, permission): + # if not (role and permission): + # msg = 'Specify both a role and a permission' + # raise exceptions.ValidationError(msg) + + def _require_user_xor_permission(self, user, permission): + if user and permission: + msg = 'Specify either a user or permission, not both' + raise exceptions.ValidationError(msg) + elif not (user or permission): + msg = 'Must specify either a user or permission' raise exceptions.ValidationError(msg) - @base.filter_kwargs - def put(self, append_to_url='', **kwargs): - """Override to append elements to the url""" - url = self.build_url(dict_args_in_out=kwargs) - if append_to_url: - url += append_to_url + # def _role_grants_base_url(self, user, permission): + # # When called, we have already checked that only one of user & group + # # and one of domain & project have been specified + # params = {} + # if user: + # params['user_id'] = base.getid(user) + # base_url += '/users/%(user_id)s/roles' + # elif permission: + # params['permission_id'] = base.getid(permission) + # base_url += '/permissions/%(permission_id)s/roles' + + # return base_url % params - return self._update( - url, - method='PUT') + # @base.filter_kwargs + # def put(self, append_to_url='', **kwargs): + # """Override to append elements to the url""" + # url = self.build_url(dict_args_in_out=kwargs) + # if append_to_url: + # url += append_to_url - def add_permission(self, role, permission): - self._require_role_and_permission(role, permission) + # return self._update( + # url, + # method='PUT') + + # def add_permission(self, role, permission): + # self._require_role_and_permission(role, permission) - # PUT to roles/{role_id}/permissions - endpoint = '/permissions/%s' %base.getid(permission) - return self.put(append_to_url=endpoint, - role_id=base.getid(role)) + # # PUT to roles/{role_id}/permissions + # endpoint = '/permissions/%s' %base.getid(permission) + # return self.put(append_to_url=endpoint, + # role_id=base.getid(role)) def create(self, name, is_editable=True, application=None, **kwargs): return super(RoleManager, self).create( @@ -74,5 +95,23 @@ def update(self, role, name=None, is_editable=True, **kwargs) def delete(self, role): - return super(RoleManager, self).delete( - role_id=base.getid(role)) \ No newline at end of file + return super(RoleManager, self).delete(role_id=base.getid(role)) + + def list(self, user=None, permission=None, **kwargs): + + if user or permission: + self._require_user_xor_permission(user, permission) + + if user: + base_url = self.base_url + '/users/%s' % base.getid(user) + + elif permission: + base_url = self.base_url + '/permissions/%s' % base.getid(permission) + + else: + base_url = self.base_url + + return super(RoleManager, self).list(base_url=base_url, **kwargs) + + + \ No newline at end of file From 697348b6d467b3172710f7fe1d3c2f2912be6e81 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Wed, 19 Nov 2014 13:57:50 +0100 Subject: [PATCH 021/102] add permission to role --- keystoneclient/tests/v3/test_fiware_roles.py | 67 +++++++++++++------ .../v3/contrib/fiware_roles/permissions.py | 21 +++++- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 17e06ea19..255cf4f65 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -38,25 +38,6 @@ def new_ref(self, **kwargs): kwargs.setdefault('is_editable', True) return kwargs - # def test_add_permission_to_role(self): - - # permission_id = uuid.uuid4().hex - # role_ref = self.new_ref() - # self.stub_url('PUT', - # [self.path_prefix, self.collection_key, role_ref['id'], - # 'permissions', permission_id], - # status_code=204) - # self.manager.add_permission(role=role_ref['id'], permission=permission_id) - - # # Test invalid args - # self.assertRaises(exceptions.ValidationError, - # self.manager.add_permission, - # role=role_ref['id'], - # permission=None) - # self.assertRaises(exceptions.ValidationError, - # self.manager.add_permission, - # role=None, - # permission=permission_id) def test_list_roles_by_permission(self): permission_id = uuid.uuid4().hex @@ -84,6 +65,17 @@ def test_list_roles_by_user(self): self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] + def test_list_roles_by_user_and_permission(self): + user_id = uuid.uuid4().hex + permission_id = uuid.uuid4().hex + ref_list = [self.new_ref(), self.new_ref()] + + self.assertRaises(exceptions.ValidationError, + self.manager.list, + user=user_id, + permission=permission_id) + + class PermissionTests(utils.TestCase, utils.CrudTests): @@ -99,4 +91,39 @@ def new_ref(self, **kwargs): kwargs = super(PermissionTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('is_editable', True) - return kwargs \ No newline at end of file + return kwargs + + def test_list_permissions_by_role(self): + role_id = uuid.uuid4().hex + ref_list = [self.new_ref(), self.new_ref()] + + self.stub_entity('GET', + parts=[self.path_prefix, 'roles', role_id, self.collection_key], + entity=ref_list) + + returned_list = self.manager.list(role=role_id) + + self.assertEqual(len(ref_list), len(returned_list)) + [self.assertIsInstance(r, self.model) for r in returned_list] + + def test_add_permission_to_role(self): + + permission_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('PUT', + [self.path_prefix, 'roles',role_ref['id'], + self.collection_key, permission_id], + status_code=204) + self.manager.add_role(role=role_ref['id'], permission=permission_id) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.add_role, + role=role_ref['id'], + permission=None) + self.assertRaises(exceptions.ValidationError, + self.manager.add_role, + role=None, + permission=permission_id) + + diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index cba9802e4..b294143d3 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -13,6 +13,7 @@ # limitations under the License. from keystoneclient import base +from keystoneclient import exceptions from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH class Permission(base.Resource): @@ -75,7 +76,21 @@ def delete(self, permission): # base_url=base_url, # permission_id=base.getid(permission)) - def list(self, **kwargs): - return super(PermissionManager, self).list(**kwargs) + def list(self, role=None, **kwargs): - \ No newline at end of file + if role: + base_url = self.base_url + '/roles/%s' % base.getid(role) + + else: + base_url = self.base_url + return super(PermissionManager, self).list(base_url=base_url,**kwargs) + + def add_role(self, role, permission): + self._require_role_and_permission(role, permission) + base_url = self.base_url + '/roles/%s' % base.getid(role) + + return super(PermissionManager, self).put( + base_url=base_url, + permission_id=base.getid(permission)) + + # def remove_role(self, role, permission): From 3810cb075adbf261f23f0351eebc88e0660bf773 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 20 Nov 2014 11:07:12 +0100 Subject: [PATCH 022/102] test permissions add remove --- keystoneclient/tests/v3/test_fiware_roles.py | 20 +++++++++++++++++++ .../v3/contrib/fiware_roles/core.py | 4 ++-- .../v3/contrib/fiware_roles/permissions.py | 8 +++++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 255cf4f65..ed17d5654 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -19,6 +19,7 @@ from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import permissions + EXTENSION_PATH = 'OS-ROLES' class RoleTests(utils.TestCase, utils.CrudTests): @@ -126,4 +127,23 @@ def test_add_permission_to_role(self): role=None, permission=permission_id) + def test_remove_permission_from_role(self): + + permission_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('DELETE', + [self.path_prefix, 'roles',role_ref['id'], + self.collection_key, permission_id], + status_code=204) + self.manager.remove_role(role=role_ref['id'], permission=permission_id) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.remove_role, + role=role_ref['id'], + permission=None) + self.assertRaises(exceptions.ValidationError, + self.manager.remove_role, + role=None, + permission=permission_id) diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py index 6b316d39d..865e8b846 100644 --- a/keystoneclient/v3/contrib/fiware_roles/core.py +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -14,10 +14,10 @@ from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import permissions -from keystoneclient.v3.contrib.fiware_roles import users + class FiwareRolesManager(object): def __init__(self, api): self.roles = roles.RoleManager(api) self.permissions = permissions.PermissionManager(api) - # self.users = users.UserManager(api) \ No newline at end of file + \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index b294143d3..d14af4e72 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -93,4 +93,10 @@ def add_role(self, role, permission): base_url=base_url, permission_id=base.getid(permission)) - # def remove_role(self, role, permission): + def remove_role(self, role, permission): + self._require_role_and_permission(role, permission) + base_url = self.base_url + '/roles/%s' % base.getid(role) + + return super(PermissionManager, self).delete( + base_url=base_url, + permission_id=base.getid(permission)) From c74b8e76746b81ca2608f9817cec17e5f62a47f7 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 20 Nov 2014 13:19:43 +0100 Subject: [PATCH 023/102] add/remove role to users/permissions --- keystoneclient/tests/v3/test_fiware_roles.py | 79 +++++++++++++++++++ .../v3/contrib/fiware_roles/roles.py | 45 ++++++++++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index ed17d5654..8a02cad17 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -76,6 +76,85 @@ def test_list_roles_by_user_and_permission(self): user=user_id, permission=permission_id) + def test_add_role_to_permission(self): + permission_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('PUT', + [self.path_prefix, 'permissions',permission_id, + self.collection_key, role_ref['id']], + status_code=204) + self.manager.add_permission(role=role_ref['id'], permission=permission_id) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.add_permission, + role=role_ref['id'], + permission=None) + self.assertRaises(exceptions.ValidationError, + self.manager.add_permission, + role=None, + permission=permission_id) + + def test_remove_role_from_permission(self): + + permission_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('DELETE', + [self.path_prefix, 'permissions',permission_id, + self.collection_key, role_ref['id']], + status_code=204) + self.manager.remove_permission(role=role_ref['id'], permission=permission_id) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.remove_permission, + role=role_ref['id'], + permission=None) + self.assertRaises(exceptions.ValidationError, + self.manager.remove_permission, + role=None, + permission=permission_id) + + def test_add_role_to_user(self): + + user_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('PUT', + [self.path_prefix,'users',user_id, + self.collection_key, role_ref['id']], + status_code=204) + self.manager.add_user(role=role_ref['id'], user=user_id) + + #Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.add_user, + role=role_ref['id'], + user=None) + self.assertRaises(exceptions.ValidationError, + self.manager.add_user, + role=None, + user=user_id) + + def test_remove_role_from_user(self): + user_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('DELETE', + [self.path_prefix,'users', user_id, + self.collection_key, role_ref['id']], + status_code=204) + self.manager.remove_user(role=role_ref['id'], user=user_id) + + #Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.remove_user, + role=role_ref['id'], + user=None) + self.assertRaises(exceptions.ValidationError, + self.manager.remove_user, + role=None, + user=user_id) + + class PermissionTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 09d051934..97f785291 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -30,10 +30,15 @@ class RoleManager(base.CrudManager): key = 'role' base_url = ROLES_PATH - # def _require_role_and_permission(self, role, permission): - # if not (role and permission): - # msg = 'Specify both a role and a permission' - # raise exceptions.ValidationError(msg) + def _require_role_and_permission(self, role, permission): + if not (role and permission): + msg = 'Specify both a role and a permission' + raise exceptions.ValidationError(msg) + + def _require_role_and_user(self, role, user): + if not(role and user): + msg = 'Specify both a role and a user' + raise exceptions.ValidationError(msg) def _require_user_xor_permission(self, user, permission): if user and permission: @@ -113,5 +118,37 @@ def list(self, user=None, permission=None, **kwargs): return super(RoleManager, self).list(base_url=base_url, **kwargs) + def add_permission(self, role, permission): + self._require_role_and_permission(role, permission) + base_url = self.base_url + '/permissions/%s' % base.getid(permission) + + return super(RoleManager, self).put( + base_url=base_url, + role_id=base.getid(role)) + + def remove_permission(self, role, permission): + self._require_role_and_permission(role, permission) + base_url = self.base_url + '/permissions/%s' % base.getid(permission) + + return super(RoleManager, self).delete( + base_url=base_url, + role_id=base.getid(role)) + + def add_user(self, role, user): + self._require_role_and_user(role, user) + base_url = self.base_url + '/users/%s' % base.getid(user) + + return super(RoleManager, self).put( + base_url=base_url, + role_id=base.getid(role)) + + def remove_user(self, role, user): + self._require_role_and_user(role, user) + base_url = self.base_url + '/users/%s' % base.getid(user) + + return super(RoleManager, self).delete( + base_url=base_url, + role_id=base.getid(role)) + \ No newline at end of file From a252948ddcd6a9531d7f4fc8f2d4bf683a982e87 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 20 Nov 2014 13:55:59 +0100 Subject: [PATCH 024/102] integration test roles --- integration_test_roles.py | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 integration_test_roles.py diff --git a/integration_test_roles.py b/integration_test_roles.py new file mode 100644 index 000000000..abcc18617 --- /dev/null +++ b/integration_test_roles.py @@ -0,0 +1,67 @@ + +from keystoneclient.v3 import client +from keystoneclient import session +from keystoneclient.v3.contrib.oauth2 import auth +url = 'http://127.0.0.1:5000/v3' +#keystone = client.Client(token='ADMIN',endpoint=url) + +keystone=client.Client(username='admin', password='secrete', + project_name='demo', auth_url=url) + +#Basic Role Actions: +if 0: + role1 = keystone.fiware_roles.roles.create(name='Role Test 1') + role1_id = role1.id + role2 = keystone.fiware_roles.roles.create(name='Role Test 2') + role2_id = role2.id + + print('\n Get role 1: \n') + print keystone.fiware_roles.roles.get(role1_id) + print('\n Roles list (after updating the name of role 1): \n') + keystone.fiware_roles.roles.update(role1, name='Role Cool Test') + print keystone.fiware_roles.roles.list() + print('\n Roles list (after deleting role 2): \n') + keystone.fiware_roles.roles.delete(role2_id) + print keystone.fiware_roles.roles.list() + + keystone.fiware_roles.roles.delete(role1_id) + +#Basic Permission Actions: +if 0: + permission1 = keystone.fiware_roles.permissions.create(name='Permission Test 1') + permission1_id = permission1.id + permission2 = keystone.fiware_roles.permissions.create(name='Permission Test 2') + permission2_id = permission2.id + + print('\n Get permission 1: \n') + print keystone.fiware_roles.permissions.get(permission1_id) + print('\n Permission list (after updating the name of permission 1): \n') + keystone.fiware_roles.permissions.update(permission1, name='Permission Cool Test') + print keystone.fiware_roles.permissions.list() + print('\n Permission list (after deleting permission 2): \n') + keystone.fiware_roles.permissions.delete(permission2_id) + print keystone.fiware_roles.permissions.list() + + keystone.fiware_roles.permissions.delete(permission1_id) + +#Roles-Permissions relation +if 1: + role1 = keystone.fiware_roles.roles.create(name='Role Test 1') + role1_id = role1.id + role2 = keystone.fiware_roles.roles.create(name='Role Test 2') + role2_id = role2.id + permission1 = keystone.fiware_roles.permissions.create(name='Permission Test 1') + permission1_id = permission1.id + permission2 = keystone.fiware_roles.permissions.create(name='Permission Test 2') + permission2_id = permission2.id + + keystone.fiware_roles.roles.add_permission(role1, permission1) + print('\n Roles that belong to permission 1: \n') + #Mirar esto porque no funciona! + keystone.fiware_roles.roles.list(permission=permission1) + + + keystone.fiware_roles.roles.delete(role1_id) + keystone.fiware_roles.roles.delete(role2_id) + keystone.fiware_roles.permissions.delete(permission1_id) + keystone.fiware_roles.permissions.delete(permission2_id) \ No newline at end of file From 19b6ba46da7d3926b68087b202ae73beb8d4a0a4 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Fri, 21 Nov 2014 12:57:18 +0100 Subject: [PATCH 025/102] add organization to user roles relationship --- keystoneclient/tests/v3/test_fiware_roles.py | 138 ++++++++++-------- .../v3/contrib/fiware_roles/permissions.py | 19 --- .../v3/contrib/fiware_roles/roles.py | 105 +++++-------- 3 files changed, 112 insertions(+), 150 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 8a02cad17..baebbfa07 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -40,18 +40,18 @@ def new_ref(self, **kwargs): return kwargs - def test_list_roles_by_permission(self): - permission_id = uuid.uuid4().hex - ref_list = [self.new_ref(), self.new_ref()] + # def test_list_roles_by_permission(self): + # permission_id = uuid.uuid4().hex + # ref_list = [self.new_ref(), self.new_ref()] - self.stub_entity('GET', - parts=[self.path_prefix, 'permissions', permission_id, self.collection_key], - entity=ref_list) + # self.stub_entity('GET', + # parts=[self.path_prefix, 'permissions', permission_id, self.collection_key], + # entity=ref_list) - returned_list = self.manager.list(permission=permission_id) + # returned_list = self.manager.list(permission=permission_id) - self.assertEqual(len(ref_list), len(returned_list)) - [self.assertIsInstance(r, self.model) for r in returned_list] + # self.assertEqual(len(ref_list), len(returned_list)) + # [self.assertIsInstance(r, self.model) for r in returned_list] def test_list_roles_by_user(self): user_id = uuid.uuid4().hex @@ -66,93 +66,105 @@ def test_list_roles_by_user(self): self.assertEqual(len(ref_list), len(returned_list)) [self.assertIsInstance(r, self.model) for r in returned_list] - def test_list_roles_by_user_and_permission(self): - user_id = uuid.uuid4().hex - permission_id = uuid.uuid4().hex - ref_list = [self.new_ref(), self.new_ref()] - - self.assertRaises(exceptions.ValidationError, - self.manager.list, - user=user_id, - permission=permission_id) - - def test_add_role_to_permission(self): - permission_id = uuid.uuid4().hex - role_ref = self.new_ref() - self.stub_url('PUT', - [self.path_prefix, 'permissions',permission_id, - self.collection_key, role_ref['id']], - status_code=204) - self.manager.add_permission(role=role_ref['id'], permission=permission_id) - - # Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.add_permission, - role=role_ref['id'], - permission=None) - self.assertRaises(exceptions.ValidationError, - self.manager.add_permission, - role=None, - permission=permission_id) - - def test_remove_role_from_permission(self): - - permission_id = uuid.uuid4().hex - role_ref = self.new_ref() - self.stub_url('DELETE', - [self.path_prefix, 'permissions',permission_id, - self.collection_key, role_ref['id']], - status_code=204) - self.manager.remove_permission(role=role_ref['id'], permission=permission_id) - - # Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.remove_permission, - role=role_ref['id'], - permission=None) - self.assertRaises(exceptions.ValidationError, - self.manager.remove_permission, - role=None, - permission=permission_id) + # def test_list_roles_by_user_and_permission(self): + # user_id = uuid.uuid4().hex + # permission_id = uuid.uuid4().hex + # ref_list = [self.new_ref(), self.new_ref()] + + # self.assertRaises(exceptions.ValidationError, + # self.manager.list, + # user=user_id, + # permission=permission_id) + + # def test_add_role_to_permission(self): + # permission_id = uuid.uuid4().hex + # role_ref = self.new_ref() + # self.stub_url('PUT', + # [self.path_prefix, 'permissions',permission_id, + # self.collection_key, role_ref['id']], + # status_code=204) + # self.manager.add_permission(role=role_ref['id'], permission=permission_id) + + # # Test invalid args + # self.assertRaises(exceptions.ValidationError, + # self.manager.add_permission, + # role=role_ref['id'], + # permission=None) + # self.assertRaises(exceptions.ValidationError, + # self.manager.add_permission, + # role=None, + # permission=permission_id) + + # def test_remove_role_from_permission(self): + + # permission_id = uuid.uuid4().hex + # role_ref = self.new_ref() + # self.stub_url('DELETE', + # [self.path_prefix, 'permissions',permission_id, + # self.collection_key, role_ref['id']], + # status_code=204) + # self.manager.remove_permission(role=role_ref['id'], permission=permission_id) + + # # Test invalid args + # self.assertRaises(exceptions.ValidationError, + # self.manager.remove_permission, + # role=role_ref['id'], + # permission=None) + # self.assertRaises(exceptions.ValidationError, + # self.manager.remove_permission, + # role=None, + # permission=permission_id) def test_add_role_to_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() + organization_id = uuid.uuid4().hex self.stub_url('PUT', [self.path_prefix,'users',user_id, + 'organizations',organization_id, self.collection_key, role_ref['id']], status_code=204) - self.manager.add_user(role=role_ref['id'], user=user_id) + self.manager.add_user(role=role_ref['id'], + user=user_id, + organization=organization_id) #Test invalid args self.assertRaises(exceptions.ValidationError, self.manager.add_user, role=role_ref['id'], - user=None) + user=None, + organization=None) self.assertRaises(exceptions.ValidationError, self.manager.add_user, role=None, - user=user_id) + user=user_id, + organization=organization_id) def test_remove_role_from_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() + organization_id = uuid.uuid4().hex self.stub_url('DELETE', [self.path_prefix,'users', user_id, + 'organizations',organization_id, self.collection_key, role_ref['id']], status_code=204) - self.manager.remove_user(role=role_ref['id'], user=user_id) + self.manager.remove_user(role=role_ref['id'], + user=user_id, + organization=organization_id) #Test invalid args self.assertRaises(exceptions.ValidationError, self.manager.remove_user, role=role_ref['id'], - user=None) + user=None, + organization=None) self.assertRaises(exceptions.ValidationError, self.manager.remove_user, role=None, - user=user_id) + user=user_id, + organization=organization_id) diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index d14af4e72..caa306ccd 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -58,26 +58,7 @@ def delete(self, permission): return super(PermissionManager, self).delete( permission_id=base.getid(permission)) - #No Funciona - # def add_role(self, role, permission): - # self._require_role_and_permission(role, permission) - # base_url = '/OS-ROLES' - - # base_url = base_url + '/roles/%s/permissions/' % base.getid(role) - # return super(PermissionManager, self).put( - # base_url=base_url, - # permission_id=base.getid(permission)) - - # def remove_role(self, role, permission): - # self._require_role_and_permission(role, permission) - # base_url = '/OS-ROLES' - # base_url += '/roles/%s' % base.getid(role) - # return super(PermissionManager, self).delete( - # base_url=base_url, - # permission_id=base.getid(permission)) - def list(self, role=None, **kwargs): - if role: base_url = self.base_url + '/roles/%s' % base.getid(role) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 97f785291..f174d9fe2 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -35,50 +35,19 @@ def _require_role_and_permission(self, role, permission): msg = 'Specify both a role and a permission' raise exceptions.ValidationError(msg) - def _require_role_and_user(self, role, user): - if not(role and user): - msg = 'Specify both a role and a user' + def _require_role_and_user_and_organization(self, role, user, organization): + if not(role and user and organization): + msg = 'Specify a role, a user and an organization' raise exceptions.ValidationError(msg) - def _require_user_xor_permission(self, user, permission): - if user and permission: - msg = 'Specify either a user or permission, not both' - raise exceptions.ValidationError(msg) - elif not (user or permission): - msg = 'Must specify either a user or permission' - raise exceptions.ValidationError(msg) - - # def _role_grants_base_url(self, user, permission): - # # When called, we have already checked that only one of user & group - # # and one of domain & project have been specified - # params = {} - # if user: - # params['user_id'] = base.getid(user) - # base_url += '/users/%(user_id)s/roles' - # elif permission: - # params['permission_id'] = base.getid(permission) - # base_url += '/permissions/%(permission_id)s/roles' - - # return base_url % params - - # @base.filter_kwargs - # def put(self, append_to_url='', **kwargs): - # """Override to append elements to the url""" - # url = self.build_url(dict_args_in_out=kwargs) - # if append_to_url: - # url += append_to_url - - # return self._update( - # url, - # method='PUT') + # def _require_user_xor_permission(self, user, permission): + # if user and permission: + # msg = 'Specify either a user or permission, not both' + # raise exceptions.ValidationError(msg) + # elif not (user or permission): + # msg = 'Must specify either a user or permission' + # raise exceptions.ValidationError(msg) - # def add_permission(self, role, permission): - # self._require_role_and_permission(role, permission) - - # # PUT to roles/{role_id}/permissions - # endpoint = '/permissions/%s' %base.getid(permission) - # return self.put(append_to_url=endpoint, - # role_id=base.getid(role)) def create(self, name, is_editable=True, application=None, **kwargs): return super(RoleManager, self).create( @@ -102,50 +71,50 @@ def update(self, role, name=None, is_editable=True, def delete(self, role): return super(RoleManager, self).delete(role_id=base.getid(role)) - def list(self, user=None, permission=None, **kwargs): - - if user or permission: - self._require_user_xor_permission(user, permission) + def list(self, user=None, **kwargs): + # def list(self, user=None, permission=None, **kwargs): + # if user or permission: + # self._require_user_xor_permission(user, permission) if user: base_url = self.base_url + '/users/%s' % base.getid(user) - elif permission: - base_url = self.base_url + '/permissions/%s' % base.getid(permission) + # elif permission: + # base_url = self.base_url + '/permissions/%s' % base.getid(permission) else: base_url = self.base_url return super(RoleManager, self).list(base_url=base_url, **kwargs) - def add_permission(self, role, permission): - self._require_role_and_permission(role, permission) - base_url = self.base_url + '/permissions/%s' % base.getid(permission) - - return super(RoleManager, self).put( - base_url=base_url, - role_id=base.getid(role)) - - def remove_permission(self, role, permission): - self._require_role_and_permission(role, permission) - base_url = self.base_url + '/permissions/%s' % base.getid(permission) + # def add_permission(self, role, permission): + # self._require_role_and_permission(role, permission) + # base_url = self.base_url + '/permissions/%s' % base.getid(permission) + + # return super(RoleManager, self).put( + # base_url=base_url, + # role_id=base.getid(role)) - return super(RoleManager, self).delete( - base_url=base_url, - role_id=base.getid(role)) + # def remove_permission(self, role, permission): + # self._require_role_and_permission(role, permission) + # base_url = self.base_url + '/permissions/%s' % base.getid(permission) - def add_user(self, role, user): - self._require_role_and_user(role, user) - base_url = self.base_url + '/users/%s' % base.getid(user) + # return super(RoleManager, self).delete( + # base_url=base_url, + # role_id=base.getid(role)) + def add_user(self, role, user, organization): + self._require_role_and_user_and_organization(role, user, organization) + base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) + return super(RoleManager, self).put( base_url=base_url, role_id=base.getid(role)) - def remove_user(self, role, user): - self._require_role_and_user(role, user) - base_url = self.base_url + '/users/%s' % base.getid(user) - + def remove_user(self, role, user, organization): + self._require_role_and_user_and_organization(role, user, organization) + base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) + return super(RoleManager, self).delete( base_url=base_url, role_id=base.getid(role)) From 61f4ddcca5b144cc0d8565f881e21e43a41205cb Mon Sep 17 00:00:00 2001 From: sorube13 Date: Fri, 21 Nov 2014 13:57:04 +0100 Subject: [PATCH 026/102] added permission and user integration tests --- integration_test_roles.py | 63 +++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 12 deletions(-) diff --git a/integration_test_roles.py b/integration_test_roles.py index abcc18617..5d1bf0851 100644 --- a/integration_test_roles.py +++ b/integration_test_roles.py @@ -17,8 +17,8 @@ print('\n Get role 1: \n') print keystone.fiware_roles.roles.get(role1_id) - print('\n Roles list (after updating the name of role 1): \n') keystone.fiware_roles.roles.update(role1, name='Role Cool Test') + print('\n Roles list (after updating the name of role 1): \n') print keystone.fiware_roles.roles.list() print('\n Roles list (after deleting role 2): \n') keystone.fiware_roles.roles.delete(role2_id) @@ -35,8 +35,8 @@ print('\n Get permission 1: \n') print keystone.fiware_roles.permissions.get(permission1_id) - print('\n Permission list (after updating the name of permission 1): \n') keystone.fiware_roles.permissions.update(permission1, name='Permission Cool Test') + print('\n Permission list (after updating the name of permission 1): \n') print keystone.fiware_roles.permissions.list() print('\n Permission list (after deleting permission 2): \n') keystone.fiware_roles.permissions.delete(permission2_id) @@ -45,23 +45,62 @@ keystone.fiware_roles.permissions.delete(permission1_id) #Roles-Permissions relation -if 1: +if 0: role1 = keystone.fiware_roles.roles.create(name='Role Test 1') role1_id = role1.id - role2 = keystone.fiware_roles.roles.create(name='Role Test 2') - role2_id = role2.id permission1 = keystone.fiware_roles.permissions.create(name='Permission Test 1') permission1_id = permission1.id permission2 = keystone.fiware_roles.permissions.create(name='Permission Test 2') permission2_id = permission2.id - keystone.fiware_roles.roles.add_permission(role1, permission1) - print('\n Roles that belong to permission 1: \n') - #Mirar esto porque no funciona! - keystone.fiware_roles.roles.list(permission=permission1) + keystone.fiware_roles.permissions.add_role(role1, permission1) + print('\n Permissions that belong to role 1: \n') + print keystone.fiware_roles.permissions.list(role=role1) + keystone.fiware_roles.permissions.add_role(role1, permission2) + print('\n Permissions that belong to role 1 (after adding permission 1 and 2): \n') + print keystone.fiware_roles.permissions.list(role=role1) + + keystone.fiware_roles.permissions.remove_role(role1, permission1) + print('\n Permissions that belong to role 1 (after removing permission 1): \n') + print keystone.fiware_roles.permissions.list(role=role1) + + keystone.fiware_roles.permissions.add_role(role1, permission1) + keystone.fiware_roles.permissions.delete(permission1) + print('\n Permissions that belong to role 1 (after deleting permission 1): \n') + print keystone.fiware_roles.permissions.list(role=role1) keystone.fiware_roles.roles.delete(role1_id) - keystone.fiware_roles.roles.delete(role2_id) - keystone.fiware_roles.permissions.delete(permission1_id) - keystone.fiware_roles.permissions.delete(permission2_id) \ No newline at end of file + keystone.fiware_roles.permissions.delete(permission2_id) + +#Roles-Users relation +if 0: + role1 = keystone.fiware_roles.roles.create(name='Role Test 1') + role1_id = role1.id + role2 = keystone.fiware_roles.roles.create(name='Role Test 2') + role2_id = role2.id + + organization= keystone.projects.create(name='organization', domain='default') + user = keystone.users.create(name='user',password='user',project=organization) + + keystone.fiware_roles.roles.add_user(role1, user, organization) + print('\n Roles that belong to user: \n') + print keystone.fiware_roles.roles.list(user=user) + + keystone.fiware_roles.roles.add_user(role2, user, organization) + print('\n Roles that belong to user (after adding role 1 and 2 to user): \n') + print keystone.fiware_roles.roles.list(user=user) + + keystone.fiware_roles.roles.remove_role(role1, user, organization) + print('\n Roles that belong to user (after removing role 1): \n') + print keystone.fiware_roles.roles.list(user=user) + + keystone.fiware_roles.roles.add_user(role1, user) + keystone.fiware_roles.roles.delete(role1) + print('\n Roles that belong to user (after deleting role 1): \n') + print keystone.fiware_roles.roles.list(user=user) + + + keystone.fiware_roles.roles.delete(role2) + keystone.projects.delete(organization) + kesytone.users.delete(user) From 28c13ba7c94684657428af020282391cc90a6b3d Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 10:46:54 +0100 Subject: [PATCH 027/102] test cleanup --- keystoneclient/tests/v3/test_fiware_roles.py | 54 +++----------------- 1 file changed, 8 insertions(+), 46 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index baebbfa07..dc7a1f1b0 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -64,7 +64,8 @@ def test_list_roles_by_user(self): returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) - [self.assertIsInstance(r, self.model) for r in returned_list] + for role in returned_list: + self.assertIsInstance(role, self.model) # def test_list_roles_by_user_and_permission(self): # user_id = uuid.uuid4().hex @@ -76,53 +77,14 @@ def test_list_roles_by_user(self): # user=user_id, # permission=permission_id) - # def test_add_role_to_permission(self): - # permission_id = uuid.uuid4().hex - # role_ref = self.new_ref() - # self.stub_url('PUT', - # [self.path_prefix, 'permissions',permission_id, - # self.collection_key, role_ref['id']], - # status_code=204) - # self.manager.add_permission(role=role_ref['id'], permission=permission_id) - - # # Test invalid args - # self.assertRaises(exceptions.ValidationError, - # self.manager.add_permission, - # role=role_ref['id'], - # permission=None) - # self.assertRaises(exceptions.ValidationError, - # self.manager.add_permission, - # role=None, - # permission=permission_id) - - # def test_remove_role_from_permission(self): - - # permission_id = uuid.uuid4().hex - # role_ref = self.new_ref() - # self.stub_url('DELETE', - # [self.path_prefix, 'permissions',permission_id, - # self.collection_key, role_ref['id']], - # status_code=204) - # self.manager.remove_permission(role=role_ref['id'], permission=permission_id) - - # # Test invalid args - # self.assertRaises(exceptions.ValidationError, - # self.manager.remove_permission, - # role=role_ref['id'], - # permission=None) - # self.assertRaises(exceptions.ValidationError, - # self.manager.remove_permission, - # role=None, - # permission=permission_id) - def test_add_role_to_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() organization_id = uuid.uuid4().hex self.stub_url('PUT', - [self.path_prefix,'users',user_id, - 'organizations',organization_id, + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, self.collection_key, role_ref['id']], status_code=204) self.manager.add_user(role=role_ref['id'], @@ -146,8 +108,8 @@ def test_remove_role_from_user(self): role_ref = self.new_ref() organization_id = uuid.uuid4().hex self.stub_url('DELETE', - [self.path_prefix,'users', user_id, - 'organizations',organization_id, + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, self.collection_key, role_ref['id']], status_code=204) self.manager.remove_user(role=role_ref['id'], @@ -203,7 +165,7 @@ def test_add_permission_to_role(self): permission_id = uuid.uuid4().hex role_ref = self.new_ref() self.stub_url('PUT', - [self.path_prefix, 'roles',role_ref['id'], + [self.path_prefix, 'roles', role_ref['id'], self.collection_key, permission_id], status_code=204) self.manager.add_role(role=role_ref['id'], permission=permission_id) @@ -223,7 +185,7 @@ def test_remove_permission_from_role(self): permission_id = uuid.uuid4().hex role_ref = self.new_ref() self.stub_url('DELETE', - [self.path_prefix, 'roles',role_ref['id'], + [self.path_prefix, 'roles', role_ref['id'], self.collection_key, permission_id], status_code=204) self.manager.remove_role(role=role_ref['id'], permission=permission_id) From eecb3c7771c0c4a2132d3d96102ff7cbea590fe9 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 10:48:43 +0100 Subject: [PATCH 028/102] test cleanup --- keystoneclient/tests/v3/test_fiware_roles.py | 4 ++-- keystoneclient/v3/contrib/fiware_roles/permissions.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index dc7a1f1b0..befc45932 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -168,7 +168,7 @@ def test_add_permission_to_role(self): [self.path_prefix, 'roles', role_ref['id'], self.collection_key, permission_id], status_code=204) - self.manager.add_role(role=role_ref['id'], permission=permission_id) + self.manager.add_to_role(role=role_ref['id'], permission=permission_id) # Test invalid args self.assertRaises(exceptions.ValidationError, @@ -188,7 +188,7 @@ def test_remove_permission_from_role(self): [self.path_prefix, 'roles', role_ref['id'], self.collection_key, permission_id], status_code=204) - self.manager.remove_role(role=role_ref['id'], permission=permission_id) + self.manager.remove_from_role(role=role_ref['id'], permission=permission_id) # Test invalid args self.assertRaises(exceptions.ValidationError, diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index caa306ccd..b1d5ddd77 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -64,9 +64,9 @@ def list(self, role=None, **kwargs): else: base_url = self.base_url - return super(PermissionManager, self).list(base_url=base_url,**kwargs) + return super(PermissionManager, self).list(base_url=base_url, **kwargs) - def add_role(self, role, permission): + def add_to_role(self, role, permission): self._require_role_and_permission(role, permission) base_url = self.base_url + '/roles/%s' % base.getid(role) @@ -74,7 +74,7 @@ def add_role(self, role, permission): base_url=base_url, permission_id=base.getid(permission)) - def remove_role(self, role, permission): + def remove_from_role(self, role, permission): self._require_role_and_permission(role, permission) base_url = self.base_url + '/roles/%s' % base.getid(role) From 06c51224430d23c4a28434f70d2c7b8ce1061b11 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 10:59:35 +0100 Subject: [PATCH 029/102] more cleanup --- keystoneclient/tests/v3/test_fiware_roles.py | 27 ++++++++++--------- .../v3/contrib/fiware_roles/roles.py | 25 ++--------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index befc45932..51b0663f5 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -64,8 +64,8 @@ def test_list_roles_by_user(self): returned_list = self.manager.list(user=user_id) self.assertEqual(len(ref_list), len(returned_list)) - for role in returned_list: - self.assertIsInstance(role, self.model) + for item in returned_list: + self.assertIsInstance(item, self.model) # def test_list_roles_by_user_and_permission(self): # user_id = uuid.uuid4().hex @@ -87,18 +87,18 @@ def test_add_role_to_user(self): 'organizations', organization_id, self.collection_key, role_ref['id']], status_code=204) - self.manager.add_user(role=role_ref['id'], + self.manager.add_to_user(role=role_ref['id'], user=user_id, organization=organization_id) #Test invalid args self.assertRaises(exceptions.ValidationError, - self.manager.add_user, + self.manager.add_to_user, role=role_ref['id'], user=None, organization=None) self.assertRaises(exceptions.ValidationError, - self.manager.add_user, + self.manager.add_to_user, role=None, user=user_id, organization=organization_id) @@ -112,18 +112,18 @@ def test_remove_role_from_user(self): 'organizations', organization_id, self.collection_key, role_ref['id']], status_code=204) - self.manager.remove_user(role=role_ref['id'], + self.manager.remove_from_user(role=role_ref['id'], user=user_id, organization=organization_id) #Test invalid args self.assertRaises(exceptions.ValidationError, - self.manager.remove_user, + self.manager.remove_from_user, role=role_ref['id'], user=None, organization=None) self.assertRaises(exceptions.ValidationError, - self.manager.remove_user, + self.manager.remove_from_user, role=None, user=user_id, organization=organization_id) @@ -158,7 +158,8 @@ def test_list_permissions_by_role(self): returned_list = self.manager.list(role=role_id) self.assertEqual(len(ref_list), len(returned_list)) - [self.assertIsInstance(r, self.model) for r in returned_list] + for item in returned_list: + self.assertIsInstance(item, self.model) def test_add_permission_to_role(self): @@ -172,11 +173,11 @@ def test_add_permission_to_role(self): # Test invalid args self.assertRaises(exceptions.ValidationError, - self.manager.add_role, + self.manager.add_to_role, role=role_ref['id'], permission=None) self.assertRaises(exceptions.ValidationError, - self.manager.add_role, + self.manager.add_to_role, role=None, permission=permission_id) @@ -192,11 +193,11 @@ def test_remove_permission_from_role(self): # Test invalid args self.assertRaises(exceptions.ValidationError, - self.manager.remove_role, + self.manager.remove_from_role, role=role_ref['id'], permission=None) self.assertRaises(exceptions.ValidationError, - self.manager.remove_role, + self.manager.remove_from_role, role=None, permission=permission_id) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index f174d9fe2..a3e5d6f74 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -30,11 +30,6 @@ class RoleManager(base.CrudManager): key = 'role' base_url = ROLES_PATH - def _require_role_and_permission(self, role, permission): - if not (role and permission): - msg = 'Specify both a role and a permission' - raise exceptions.ValidationError(msg) - def _require_role_and_user_and_organization(self, role, user, organization): if not(role and user and organization): msg = 'Specify a role, a user and an organization' @@ -48,7 +43,6 @@ def _require_role_and_user_and_organization(self, role, user, organization): # msg = 'Must specify either a user or permission' # raise exceptions.ValidationError(msg) - def create(self, name, is_editable=True, application=None, **kwargs): return super(RoleManager, self).create( name=name, @@ -87,23 +81,8 @@ def list(self, user=None, **kwargs): return super(RoleManager, self).list(base_url=base_url, **kwargs) - # def add_permission(self, role, permission): - # self._require_role_and_permission(role, permission) - # base_url = self.base_url + '/permissions/%s' % base.getid(permission) - - # return super(RoleManager, self).put( - # base_url=base_url, - # role_id=base.getid(role)) - - # def remove_permission(self, role, permission): - # self._require_role_and_permission(role, permission) - # base_url = self.base_url + '/permissions/%s' % base.getid(permission) - - # return super(RoleManager, self).delete( - # base_url=base_url, - # role_id=base.getid(role)) - def add_user(self, role, user, organization): + def add_to_user(self, role, user, organization): self._require_role_and_user_and_organization(role, user, organization) base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) @@ -111,7 +90,7 @@ def add_user(self, role, user, organization): base_url=base_url, role_id=base.getid(role)) - def remove_user(self, role, user, organization): + def remove_from_user(self, role, user, organization): self._require_role_and_user_and_organization(role, user, organization) base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) From d206259ef5d4b494c4ea2b7e34c0d351d7a42ebe Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 11:18:36 +0100 Subject: [PATCH 030/102] updated integration tests --- integration_test_roles.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/integration_test_roles.py b/integration_test_roles.py index 5d1bf0851..d6e5aa6e6 100644 --- a/integration_test_roles.py +++ b/integration_test_roles.py @@ -1,11 +1,10 @@ - from keystoneclient.v3 import client from keystoneclient import session from keystoneclient.v3.contrib.oauth2 import auth url = 'http://127.0.0.1:5000/v3' #keystone = client.Client(token='ADMIN',endpoint=url) -keystone=client.Client(username='admin', password='secrete', +keystone = client.Client(username='admin', password='secrete', project_name='demo', auth_url=url) #Basic Role Actions: @@ -53,11 +52,11 @@ permission2 = keystone.fiware_roles.permissions.create(name='Permission Test 2') permission2_id = permission2.id - keystone.fiware_roles.permissions.add_role(role1, permission1) + keystone.fiware_roles.permissions.add_to_role(role1, permission1) print('\n Permissions that belong to role 1: \n') print keystone.fiware_roles.permissions.list(role=role1) - keystone.fiware_roles.permissions.add_role(role1, permission2) + keystone.fiware_roles.permissions.add_to_role(role1, permission2) print('\n Permissions that belong to role 1 (after adding permission 1 and 2): \n') print keystone.fiware_roles.permissions.list(role=role1) @@ -65,7 +64,7 @@ print('\n Permissions that belong to role 1 (after removing permission 1): \n') print keystone.fiware_roles.permissions.list(role=role1) - keystone.fiware_roles.permissions.add_role(role1, permission1) + keystone.fiware_roles.permissions.add_to_role(role1, permission1) keystone.fiware_roles.permissions.delete(permission1) print('\n Permissions that belong to role 1 (after deleting permission 1): \n') print keystone.fiware_roles.permissions.list(role=role1) @@ -80,14 +79,15 @@ role2 = keystone.fiware_roles.roles.create(name='Role Test 2') role2_id = role2.id - organization= keystone.projects.create(name='organization', domain='default') - user = keystone.users.create(name='user',password='user',project=organization) + name = 'testitester' + organization = keystone.projects.create(name=name, domain='default') + user = keystone.users.create(name=name, password='user', project=organization) - keystone.fiware_roles.roles.add_user(role1, user, organization) + keystone.fiware_roles.roles.add_to_user(role1, user, organization) print('\n Roles that belong to user: \n') print keystone.fiware_roles.roles.list(user=user) - keystone.fiware_roles.roles.add_user(role2, user, organization) + keystone.fiware_roles.roles.add_to_user(role2, user, organization) print('\n Roles that belong to user (after adding role 1 and 2 to user): \n') print keystone.fiware_roles.roles.list(user=user) @@ -95,7 +95,7 @@ print('\n Roles that belong to user (after removing role 1): \n') print keystone.fiware_roles.roles.list(user=user) - keystone.fiware_roles.roles.add_user(role1, user) + keystone.fiware_roles.roles.add_to_user(role1, user) keystone.fiware_roles.roles.delete(role1) print('\n Roles that belong to user (after deleting role 1): \n') print keystone.fiware_roles.roles.list(user=user) @@ -103,4 +103,4 @@ keystone.fiware_roles.roles.delete(role2) keystone.projects.delete(organization) - kesytone.users.delete(user) + keystone.users.delete(user) From ea9581c93f2bb1754f2e87b07a21dd945d24dfb3 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 12:10:20 +0100 Subject: [PATCH 031/102] updated consumers manager to support name --- keystoneclient/tests/v3/test_oauth2.py | 8 ++++++-- keystoneclient/v3/contrib/oauth2/consumers.py | 6 ++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index 1aff01f41..cc079b6e6 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -47,6 +47,7 @@ def setUp(self): def new_ref(self, **kwargs): kwargs = super(ConsumerTests, self).new_ref(**kwargs) + kwargs.setdefault('name', uuid.uuid4().hex) kwargs.setdefault('description', uuid.uuid4().hex) kwargs.setdefault('client_type', self.DEFAULT_CLIENT_TYPE) kwargs.setdefault('redirect_uris', self.DEFAULT_REDIRECT_URIS) @@ -54,13 +55,16 @@ def new_ref(self, **kwargs): kwargs.setdefault('grant_type', self.DEFAULT_GRANT_TYPE) return kwargs - def _consumer_data(self, description=None, + def _consumer_data(self, name=None, description=None, client_type=DEFAULT_CLIENT_TYPE, redirect_uris=DEFAULT_REDIRECT_URIS, grant_type=DEFAULT_GRANT_TYPE, scopes=DEFAULT_SCOPES): + if not name: + name = uuid.uuid4().hex data = { 'consumer': { + 'name': name, 'description': description, 'client_type': client_type, 'redirect_uris': redirect_uris, @@ -75,7 +79,7 @@ def _create_consumer(self, consumer_data): [self.path_prefix, self.collection_key], status_code=201, json=consumer_data) - consumer = self.manager.create() + consumer = self.manager.create(uuid.uuid4().hex) return consumer def test_create_consumer_defaults(self): diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index 86b190328..a804eec67 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -45,9 +45,10 @@ class ConsumerManager(ks_base.CrudManager): key = 'consumer' base_url = utils.OAUTH2_PATH - def create(self, description=None, client_type=None, redirect_uris=[], + def create(self, name, description=None, client_type=None, redirect_uris=[], grant_type=None, scopes=[], **kwargs): return super(ConsumerManager, self).create( + name=name, description=description, client_type=client_type, redirect_uris=redirect_uris, @@ -59,10 +60,11 @@ def get(self, consumer): return super(ConsumerManager, self).get( consumer_id=ks_base.getid(consumer)) - def update(self, consumer, description=None, client_type=None, + def update(self, consumer, name=None, description=None, client_type=None, redirect_uris=[], grant_type=None, scopes=[], **kwargs): return super(ConsumerManager, self).update( consumer_id=ks_base.getid(consumer), + name=name, description=description, client_type=client_type, redirect_uris=redirect_uris, From 9a886048e9d5116d48bf9852c60ed46a13b5fcc9 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 13:51:58 +0100 Subject: [PATCH 032/102] added option to filter by user(owner) --- keystoneclient/tests/v3/test_oauth2.py | 14 ++++++++++++++ keystoneclient/v3/contrib/oauth2/consumers.py | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/v3/test_oauth2.py index cc079b6e6..a0318faf7 100644 --- a/keystoneclient/tests/v3/test_oauth2.py +++ b/keystoneclient/tests/v3/test_oauth2.py @@ -89,6 +89,20 @@ def test_create_consumer_defaults(self): #self.assertIsNotNone(consumer.id) self.assertIsNone(consumer.description) + def test_list_consumers_by_user(self): + user_id = uuid.uuid4().hex + ref_list = [self.new_ref(), self.new_ref()] + + self.stub_entity('GET', + parts=[self.path_prefix, 'users', user_id, self.collection_key], + entity=ref_list) + + returned_list = self.manager.list(user=user_id) + + self.assertEqual(len(ref_list), len(returned_list)) + for item in returned_list: + self.assertIsInstance(item, self.model) + class AuthorizationCodeTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index a804eec67..a05b52f6c 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -74,4 +74,12 @@ def update(self, consumer, name=None, description=None, client_type=None, def delete(self, consumer): return super(ConsumerManager, self).delete( - consumer_id=ks_base.getid(consumer)) \ No newline at end of file + consumer_id=ks_base.getid(consumer)) + + def list(self, user=None, **kwargs): + if user: + base_url = self.base_url + '/users/%s' % base.getid(user) + + else: + base_url = self.base_url + return super(ConsumerManager, self).list(base_url=base_url, **kwargs) \ No newline at end of file From b227f6dd130ee151dfccf91a18058b4c2ec8cfe4 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 24 Nov 2014 16:35:26 +0100 Subject: [PATCH 033/102] fixed import --- keystoneclient/v3/contrib/oauth2/consumers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index a05b52f6c..d1e35d0ab 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -13,12 +13,12 @@ # limitations under the License. import logging -from keystoneclient import base as ks_base +from keystoneclient import base from keystoneclient.v3.contrib.oauth2 import utils LOG = logging.getLogger(__name__) -class Consumer(ks_base.Resource): +class Consumer(base.Resource): """Represents an OAuth2 consumer. Attributes: * id: a uuid that identifies the consumer @@ -38,7 +38,7 @@ class Consumer(ks_base.Resource): """ pass -class ConsumerManager(ks_base.CrudManager): +class ConsumerManager(base.CrudManager): """Manager class for manipulating identity consumers.""" resource_class = Consumer collection_key = 'consumers' @@ -46,7 +46,7 @@ class ConsumerManager(ks_base.CrudManager): base_url = utils.OAUTH2_PATH def create(self, name, description=None, client_type=None, redirect_uris=[], - grant_type=None, scopes=[], **kwargs): + grant_type=None, scopes=[], extras=None, **kwargs): return super(ConsumerManager, self).create( name=name, description=description, @@ -54,16 +54,17 @@ def create(self, name, description=None, client_type=None, redirect_uris=[], redirect_uris=redirect_uris, grant_type=grant_type, scopes=scopes, + extras=None, **kwargs) def get(self, consumer): return super(ConsumerManager, self).get( - consumer_id=ks_base.getid(consumer)) + consumer_id=base.getid(consumer)) def update(self, consumer, name=None, description=None, client_type=None, redirect_uris=[], grant_type=None, scopes=[], **kwargs): return super(ConsumerManager, self).update( - consumer_id=ks_base.getid(consumer), + consumer_id=base.getid(consumer), name=name, description=description, client_type=client_type, @@ -74,7 +75,7 @@ def update(self, consumer, name=None, description=None, client_type=None, def delete(self, consumer): return super(ConsumerManager, self).delete( - consumer_id=ks_base.getid(consumer)) + consumer_id=base.getid(consumer)) def list(self, user=None, **kwargs): if user: From 340235439536cd6f779bbc9af1614b89c02009ca Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Tue, 25 Nov 2014 15:12:07 +0100 Subject: [PATCH 034/102] python egg config --- keystoneclient/__init__.py | 2 +- setup.cfg | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/keystoneclient/__init__.py b/keystoneclient/__init__.py index a3c0408b8..16ab5e969 100644 --- a/keystoneclient/__init__.py +++ b/keystoneclient/__init__.py @@ -34,7 +34,7 @@ from keystoneclient import v3 -__version__ = pbr.version.VersionInfo('python-keystoneclient').version_string() +#__version__ = pbr.version.VersionInfo('python-keystoneclient').version_string() __all__ = [ # Modules diff --git a/setup.cfg b/setup.cfg index e88046e85..962fedffc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] -name = python-keystoneclient -summary = Client Library for OpenStack Identity +name = fiwareclient +summary = Modified Client Library for OpenStack Identity to work with FIWARE modified Keystone description-file = README.rst -author = OpenStack -author-email = openstack-dev@lists.openstack.org -home-page = http://www.openstack.org/ +author = UPM-GING +author-email = garcianavalon@gmail.com +home-page = http://www.github.com/ging classifier = Environment :: OpenStack Intended Audience :: Information Technology From aba0dc4318bb633bacfdb39207bc56962f77fefb Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Tue, 25 Nov 2014 15:13:23 +0100 Subject: [PATCH 035/102] added fiwareclient.egg-info to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9f14a9f52..90e9b8a6e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ ChangeLog build dist python_keystoneclient.egg-info +fiwareclient.egg-info keystoneclient/versioninfo doc/source/api # Development environment files From 93406747a5ba875d52784583d3c0631e81d05814 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 15 Dec 2014 15:32:09 +0100 Subject: [PATCH 036/102] removed extras keyword arg from consumer --- keystoneclient/v3/contrib/oauth2/consumers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index d1e35d0ab..c30a9e007 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -46,7 +46,7 @@ class ConsumerManager(base.CrudManager): base_url = utils.OAUTH2_PATH def create(self, name, description=None, client_type=None, redirect_uris=[], - grant_type=None, scopes=[], extras=None, **kwargs): + grant_type=None, scopes=[], **kwargs): return super(ConsumerManager, self).create( name=name, description=description, @@ -54,7 +54,6 @@ def create(self, name, description=None, client_type=None, redirect_uris=[], redirect_uris=redirect_uris, grant_type=grant_type, scopes=scopes, - extras=None, **kwargs) def get(self, consumer): From ee463189bc391ecb63f6e6568b8aa48dabe85913 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 18 Dec 2014 16:59:40 +0100 Subject: [PATCH 037/102] added organization filtering to list roles --- keystoneclient/tests/v3/test_fiware_roles.py | 47 +++++++------------ .../v3/contrib/fiware_roles/roles.py | 30 +++++------- 2 files changed, 30 insertions(+), 47 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 51b0663f5..ce3660dc6 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -56,16 +56,29 @@ def new_ref(self, **kwargs): def test_list_roles_by_user(self): user_id = uuid.uuid4().hex ref_list = [self.new_ref(), self.new_ref()] - + organization_id = uuid.uuid4().hex self.stub_entity('GET', - parts=[self.path_prefix, 'users', user_id, self.collection_key], + parts=[self.path_prefix, 'users', user_id, + 'organizations', organization_id, + self.collection_key], entity=ref_list) - returned_list = self.manager.list(user=user_id) + returned_list = self.manager.list(user=user_id, + organization=organization_id) self.assertEqual(len(ref_list), len(returned_list)) for item in returned_list: - self.assertIsInstance(item, self.model) + self.assertIsInstance(item, self.model) + + # Test invalid args + self.assertRaises(exceptions.ValidationError, + self.manager.list, + user=user_id, + organization=None) + self.assertRaises(exceptions.ValidationError, + self.manager.list, + user=None, + organization=organization_id) # def test_list_roles_by_user_and_permission(self): # user_id = uuid.uuid4().hex @@ -91,18 +104,6 @@ def test_add_role_to_user(self): user=user_id, organization=organization_id) - #Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.add_to_user, - role=role_ref['id'], - user=None, - organization=None) - self.assertRaises(exceptions.ValidationError, - self.manager.add_to_user, - role=None, - user=user_id, - organization=organization_id) - def test_remove_role_from_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() @@ -112,23 +113,11 @@ def test_remove_role_from_user(self): 'organizations', organization_id, self.collection_key, role_ref['id']], status_code=204) + self.manager.remove_from_user(role=role_ref['id'], user=user_id, organization=organization_id) - #Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.remove_from_user, - role=role_ref['id'], - user=None, - organization=None) - self.assertRaises(exceptions.ValidationError, - self.manager.remove_from_user, - role=None, - user=user_id, - organization=organization_id) - - class PermissionTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index a3e5d6f74..a0d49c954 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -30,9 +30,9 @@ class RoleManager(base.CrudManager): key = 'role' base_url = ROLES_PATH - def _require_role_and_user_and_organization(self, role, user, organization): - if not(role and user and organization): - msg = 'Specify a role, a user and an organization' + def _require_user_and_organization(self, user, organization): + if (not user and organization) or (user and not organization): + msg = 'Specify both a user and an organization' raise exceptions.ValidationError(msg) # def _require_user_xor_permission(self, user, permission): @@ -65,34 +65,28 @@ def update(self, role, name=None, is_editable=True, def delete(self, role): return super(RoleManager, self).delete(role_id=base.getid(role)) - def list(self, user=None, **kwargs): - # def list(self, user=None, permission=None, **kwargs): - # if user or permission: - # self._require_user_xor_permission(user, permission) - - if user: - base_url = self.base_url + '/users/%s' % base.getid(user) - - # elif permission: - # base_url = self.base_url + '/permissions/%s' % base.getid(permission) + def list(self, user=None, organization=None, **kwargs): + self._require_user_and_organization(user, organization) + if user and organization: + base_url = self.base_url + '/users/%s/organizations/%s' \ + % (base.getid(user), base.getid(organization)) else: base_url = self.base_url return super(RoleManager, self).list(base_url=base_url, **kwargs) - def add_to_user(self, role, user, organization): - self._require_role_and_user_and_organization(role, user, organization) - base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) + base_url = self.base_url + '/users/%s/organizations/%s' \ + % (base.getid(user), base.getid(organization)) return super(RoleManager, self).put( base_url=base_url, role_id=base.getid(role)) def remove_from_user(self, role, user, organization): - self._require_role_and_user_and_organization(role, user, organization) - base_url = self.base_url + '/users/%s/organizations/%s' % (base.getid(user), base.getid(organization)) + base_url = self.base_url + '/users/%s/organizations/%s' \ + % (base.getid(user), base.getid(organization)) return super(RoleManager, self).delete( base_url=base_url, From bb95308f67ac593299c32bd0d8938df1c3dcf0ef Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 22 Dec 2014 13:24:55 +0100 Subject: [PATCH 038/102] binding for allowed roles to assign endpoint --- keystoneclient/tests/v3/test_fiware_roles.py | 21 +++++++++++++++++++ .../v3/contrib/fiware_roles/roles.py | 16 ++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index ce3660dc6..7038de8ef 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -118,6 +118,27 @@ def test_remove_role_from_user(self): user=user_id, organization=organization_id) + def test_list_allowed_roles_to_assign(self): + user_id = uuid.uuid4().hex + organization_id = uuid.uuid4().hex + allowed_roles_ref = { + 'some_application': [ + self.new_ref(), + self.new_ref(), + ] + } + self.stub_url('GET', + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, + 'roles/allowed'], + json=allowed_roles_ref) + allowed_roles = self.manager.list_allowed_roles_to_assign(user=user_id, + organization=organization_id) + + self.assertIsNotNone(allowed_roles) + for item in allowed_roles['some_application']: + self.assertIsInstance(item, self.model) + class PermissionTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index a0d49c954..56e89a814 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json + from keystoneclient import base from keystoneclient import exceptions from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH @@ -92,5 +94,19 @@ def remove_from_user(self, role, user, organization): base_url=base_url, role_id=base.getid(role)) + def list_allowed_roles_to_assign(self, user, organization): + """Obtain a list of all the roles the user is allowed to assign + for every application. + """ + endpoint = self.base_url + '/users/%s/organizations/%s/roles/allowed' \ + % (base.getid(user), base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_roles = json.loads(resp.content) + roles_as_resource = {} + for app in allowed_roles: + for role in allowed_roles[app]: + roles_as_resource[app] = roles_as_resource.get(app, []) + roles_as_resource[app].append(self.resource_class(self, role)) + return roles_as_resource \ No newline at end of file From f7547a13ff351acc2cc369ae897316a24d6fe126 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 7 Jan 2015 11:48:25 +0100 Subject: [PATCH 039/102] changed editable to internal --- keystoneclient/tests/v3/test_fiware_roles.py | 4 ++-- keystoneclient/v3/contrib/fiware_roles/permissions.py | 8 ++++---- keystoneclient/v3/contrib/fiware_roles/roles.py | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 7038de8ef..9ec8e9114 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -36,7 +36,7 @@ def setUp(self): def new_ref(self, **kwargs): kwargs = super(RoleTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) - kwargs.setdefault('is_editable', True) + kwargs.setdefault('is_internal', False) return kwargs @@ -154,7 +154,7 @@ def setUp(self): def new_ref(self, **kwargs): kwargs = super(PermissionTests, self).new_ref(**kwargs) kwargs.setdefault('name', uuid.uuid4().hex) - kwargs.setdefault('is_editable', True) + kwargs.setdefault('is_internal', False) return kwargs def test_list_permissions_by_role(self): diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index b1d5ddd77..9d07204b7 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -34,22 +34,22 @@ def _require_role_and_permission(self, role, permission): msg = 'Specify both a role and a permission' raise exceptions.ValidationError(msg) - def create(self, name, is_editable=True, application=None, **kwargs): + def create(self, name, is_internal=False, application=None, **kwargs): return super(PermissionManager, self).create( name=name, - is_editable=is_editable, + is_internal=is_internal, application=application, **kwargs) def get(self, permission): return super(PermissionManager, self).get( permission_id=base.getid(permission)) - def update(self, permission, name=None, is_editable=True, + def update(self, permission, name=None, is_internal=False, application=None, **kwargs): return super(PermissionManager, self).update( permission_id=base.getid(permission), name=name, - is_editable=is_editable, + is_internal=is_internal, application=application, **kwargs) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 56e89a814..635f2de7d 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -45,22 +45,22 @@ def _require_user_and_organization(self, user, organization): # msg = 'Must specify either a user or permission' # raise exceptions.ValidationError(msg) - def create(self, name, is_editable=True, application=None, **kwargs): + def create(self, name, is_internal=False, application=None, **kwargs): return super(RoleManager, self).create( name=name, - is_editable=is_editable, + is_internal=is_internal, application=application, **kwargs) def get(self, role): return super(RoleManager, self).get( role_id=base.getid(role)) - def update(self, role, name=None, is_editable=True, + def update(self, role, name=None, is_internal=False, application=None, **kwargs): return super(RoleManager, self).update( role_id=base.getid(role), name=name, - is_editable=is_editable, + is_internal=is_internal, application=application, **kwargs) From 1c50e13f8da52ef82f1cea8d414459f1dcc01f52 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 29 Jan 2015 10:43:59 +0100 Subject: [PATCH 040/102] Tests user registration --- keystoneclient/v3/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 0183dcd14..5719f5580 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -27,6 +27,7 @@ from keystoneclient.v3.contrib import oauth1 from keystoneclient.v3.contrib import oauth2 from keystoneclient.v3.contrib import trusts +from keystoneclient.v3.contrib import user_registration from keystoneclient.v3 import credentials from keystoneclient.v3 import domains from keystoneclient.v3 import endpoints @@ -181,6 +182,7 @@ def __init__(self, **kwargs): # TODO(garcianavalon) document this self.fiware_roles = fiware_roles.FiwareRolesManager(self) self.oauth2 = oauth2.create_oauth_manager(self) + self.user_registration = user_registration.UserRegistrationManager(self) self.policies = policies.PolicyManager(self) self.projects = projects.ProjectManager(self) self.regions = regions.RegionManager(self) From 0efc754f977dab4ecad5edac6129154a4459b189 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 29 Jan 2015 10:44:24 +0100 Subject: [PATCH 041/102] user registration tests done --- .../tests/v3/test_user_registration.py | 118 ++++++++++++++++++ keystoneclient/v3/contrib/activation.py | 38 ++++++ .../v3/contrib/user_registration/__init__.py | 15 +++ .../contrib/user_registration/activation.py | 39 ++++++ .../v3/contrib/user_registration/core.py | 21 ++++ .../contrib/user_registration/registration.py | 43 +++++++ .../user_registration/resetpassword.py | 40 ++++++ .../v3/contrib/user_registration/utils.py | 15 +++ 8 files changed, 329 insertions(+) create mode 100644 keystoneclient/tests/v3/test_user_registration.py create mode 100644 keystoneclient/v3/contrib/activation.py create mode 100644 keystoneclient/v3/contrib/user_registration/__init__.py create mode 100644 keystoneclient/v3/contrib/user_registration/activation.py create mode 100644 keystoneclient/v3/contrib/user_registration/core.py create mode 100644 keystoneclient/v3/contrib/user_registration/registration.py create mode 100644 keystoneclient/v3/contrib/user_registration/resetpassword.py create mode 100644 keystoneclient/v3/contrib/user_registration/utils.py diff --git a/keystoneclient/tests/v3/test_user_registration.py b/keystoneclient/tests/v3/test_user_registration.py new file mode 100644 index 000000000..727ebfac0 --- /dev/null +++ b/keystoneclient/tests/v3/test_user_registration.py @@ -0,0 +1,118 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import uuid + +from keystoneclient import exceptions +from keystoneclient.tests.v3 import utils +from keystoneclient.v3.contrib.user_registration import registration +from keystoneclient.v3.contrib.user_registration import activation +from keystoneclient.v3.contrib.user_registration import resetpassword + + +EXTENSION_PATH = 'OS-REGISTRATION' + +class RegistrationTests(utils.TestCase): + + def setUp(self): + super(RegistrationTests, self).setUp() + self.key = 'user' + self.collection_key = 'users' + self.model = registration.Registration + self.manager = self.client.user_registration.registration + self.path_prefix = EXTENSION_PATH + + + def test_register_user(self): + name = uuid.uuid4().hex + user_ref = { + 'user': { + 'id': uuid.uuid4().hex, + 'name': name, + 'activation_key': uuid.uuid4().hex, + } + } + self.stub_url('POST', + [self.path_prefix, self.collection_key], + json=user_ref, + status_code=201) + + self.manager.register_user(name=name) + +class ActivationTests(utils.TestCase): + + def setUp(self): + super(ActivationTests, self).setUp() + self.key = 'activation_key' + self.collection_key = 'activate' + self.model = activation.Activation + self.manager = self.client.user_registration.activation + self.path_prefix = EXTENSION_PATH + + def test_new_activation_key(self): + user_id = uuid.uuid4().hex + activation_key_ref = { + 'activation_key': { + 'id': uuid.uuid4().hex + } + } + self.stub_url('GET', + [self.path_prefix, 'users', + user_id, 'activate'], + json=activation_key_ref, + status_code=200) + self.manager.new_activation_key(user=user_id) + + def test_activate_user(self): + user_id = uuid.uuid4().hex + activation_key = uuid.uuid4().hex + self.stub_url('PATCH', + [self.path_prefix, 'users', + user_id, 'activate', activation_key], + status_code=200) + self.manager.activate_user(user=user_id, activation_key=activation_key) + +class ResetPasswordTest(utils.TestCase): + + def setUp(self): + super(ResetPasswordTest, self).setUp() + self.key = 'token' + self.collection_key = 'reset_password' + self.model = resetpassword.ResetPassword + self.manager = self.client.user_registration.resetpassword + self.path_prefix = EXTENSION_PATH + + def test_get_reset_token(self): + user_id = uuid.uuid4().hex + reset_token_ref = { + 'reset_token': { + 'id': uuid.uuid4().hex + } + } + self.stub_url('GET', + [self.path_prefix, 'users', + user_id, self.collection_key], + json=reset_token_ref, + status_code=204) + self.manager.get_reset_token(user=user_id) + + def test_reset_password(self): + user_id = uuid.uuid4().hex + token = uuid.uuid4().hex + self.stub_url('PATCH', + [self.path_prefix, 'users', user_id, + self.collection_key, token], + status_code=204) + self.manager.reset_password(user=user_id, reset_token=token) + diff --git a/keystoneclient/v3/contrib/activation.py b/keystoneclient/v3/contrib/activation.py new file mode 100644 index 000000000..1cc82b461 --- /dev/null +++ b/keystoneclient/v3/contrib/activation.py @@ -0,0 +1,38 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class Activation(base.Resource): + pass + +class ActivationManager(base.CrudManager): + """Manager class for activating user in the USER REGISTRATION extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Activation + collection_key = 'activate' + key = 'activation_key' + base_url = REGISTRATION_PATH + + def new_activation_key(self, user): + base_url = self.base_url + '/users/{0}/'.format(base.getid(user)) + return super(ActivationManager, self).get(base_url) + + def activate_user(self, user, activation_key): + base_url = self.base_url + '/users/{0}/'.format(base.getid(user)) + return super(ActivationManager, self).update(base_url, activation_key) diff --git a/keystoneclient/v3/contrib/user_registration/__init__.py b/keystoneclient/v3/contrib/user_registration/__init__.py new file mode 100644 index 000000000..46a07774a --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.user_registration.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/user_registration/activation.py b/keystoneclient/v3/contrib/user_registration/activation.py new file mode 100644 index 000000000..5ba0fb531 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/activation.py @@ -0,0 +1,39 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class Activation(base.Resource): + pass + +class ActivationManager(base.CrudManager): + """Manager class for activating user in the USER REGISTRATION extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Activation + collection_key = 'activate' + key = 'activation_key' + base_url = REGISTRATION_PATH + + def new_activation_key(self, user): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(ActivationManager, self).get(base_url=base_url) + + def activate_user(self, user, activation_key): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(ActivationManager, self).update(base_url=base_url, + activation_key_id=base.getid(activation_key)) diff --git a/keystoneclient/v3/contrib/user_registration/core.py b/keystoneclient/v3/contrib/user_registration/core.py new file mode 100644 index 000000000..820546211 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/core.py @@ -0,0 +1,21 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.user_registration import registration, activation, resetpassword + +class UserRegistrationManager(object): + def __init__(self, api): + self.registration = registration.RegistrationManager(api) + self.activation = activation.ActivationManager(api) + self.resetpassword = resetpassword.ResetPasswordManager(api) diff --git a/keystoneclient/v3/contrib/user_registration/registration.py b/keystoneclient/v3/contrib/user_registration/registration.py new file mode 100644 index 000000000..56f9c46de --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/registration.py @@ -0,0 +1,43 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class Registration(base.Resource): + pass + +class RegistrationManager(base.CrudManager): + """Manager class for manipulating user registration in the USER REGISTRATION + extension for Keystone. + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Registration + collection_key = 'users' + key = 'user' + base_url = REGISTRATION_PATH + + def register_user(self, name, domain=None, password=None, + email=None, description=None, **kwargs): + + user_data = base.filter_none(name=name, + domain_id=base.getid(domain), + password=password, + email=email, + description=description, + **kwargs) + + return self._create(self.base_url+'/users', {'user': user_data}, 'user', + log=not bool(password)) diff --git a/keystoneclient/v3/contrib/user_registration/resetpassword.py b/keystoneclient/v3/contrib/user_registration/resetpassword.py new file mode 100644 index 000000000..b88ee5444 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/resetpassword.py @@ -0,0 +1,40 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class ResetPassword(base.Resource): + pass + +class ResetPasswordManager(base.CrudManager): + """Manager class for manipulating passwords and tokens in + the USER REGISTRATION extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = ResetPassword + collection_key = 'reset_password' + key = 'reset_token' + base_url = REGISTRATION_PATH + + def get_reset_token(self, user): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(ResetPasswordManager, self).get(base_url=base_url) + + def reset_password(self, user, reset_token): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(ResetPasswordManager, self).update(base_url=base_url, + reset_token_id=base.getid(reset_token)) diff --git a/keystoneclient/v3/contrib/user_registration/utils.py b/keystoneclient/v3/contrib/user_registration/utils.py new file mode 100644 index 000000000..f75472580 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/utils.py @@ -0,0 +1,15 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +REGISTRATION_PATH = '/OS-REGISTRATION' From dd51e9480239873145f2ff4f3eca4b47a844abac Mon Sep 17 00:00:00 2001 From: sorube13 Date: Fri, 30 Jan 2015 13:56:05 +0100 Subject: [PATCH 042/102] fix keyerror in activate user --- keystoneclient/base.py | 1 - .../tests/v3/test_user_registration.py | 73 +++++++++---------- .../user_registration/activation_key.py | 36 +++++++++ .../v3/contrib/user_registration/core.py | 8 +- .../v3/contrib/user_registration/token.py | 37 ++++++++++ .../v3/contrib/user_registration/users.py | 54 ++++++++++++++ 6 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 keystoneclient/v3/contrib/user_registration/activation_key.py create mode 100644 keystoneclient/v3/contrib/user_registration/token.py create mode 100644 keystoneclient/v3/contrib/user_registration/users.py diff --git a/keystoneclient/base.py b/keystoneclient/base.py index 2571a37a2..f4e0a8973 100644 --- a/keystoneclient/base.py +++ b/keystoneclient/base.py @@ -368,7 +368,6 @@ def put(self, **kwargs): @filter_kwargs def update(self, **kwargs): url = self.build_url(dict_args_in_out=kwargs) - return self._update( url, {self.key: kwargs}, diff --git a/keystoneclient/tests/v3/test_user_registration.py b/keystoneclient/tests/v3/test_user_registration.py index 727ebfac0..c900ec38c 100644 --- a/keystoneclient/tests/v3/test_user_registration.py +++ b/keystoneclient/tests/v3/test_user_registration.py @@ -16,21 +16,21 @@ from keystoneclient import exceptions from keystoneclient.tests.v3 import utils -from keystoneclient.v3.contrib.user_registration import registration -from keystoneclient.v3.contrib.user_registration import activation -from keystoneclient.v3.contrib.user_registration import resetpassword +from keystoneclient.v3.contrib.user_registration import users +from keystoneclient.v3.contrib.user_registration import activation_key +from keystoneclient.v3.contrib.user_registration import token EXTENSION_PATH = 'OS-REGISTRATION' -class RegistrationTests(utils.TestCase): +class UsersTests(utils.TestCase): def setUp(self): - super(RegistrationTests, self).setUp() + super(UsersTests, self).setUp() self.key = 'user' self.collection_key = 'users' - self.model = registration.Registration - self.manager = self.client.user_registration.registration + self.model = users.Users + self.manager = self.client.user_registration.users self.path_prefix = EXTENSION_PATH @@ -50,14 +50,32 @@ def test_register_user(self): self.manager.register_user(name=name) -class ActivationTests(utils.TestCase): + def test_activate_user(self): + user_id = uuid.uuid4().hex + activation_key = uuid.uuid4().hex + self.stub_url('PATCH', + [self.path_prefix, 'activate', activation_key, + 'users', user_id], + status_code=200) + self.manager.activate_user(user=user_id, activation_key=activation_key) + + def test_reset_password(self): + user_id = uuid.uuid4().hex + token = uuid.uuid4().hex + self.stub_url('PATCH', + [self.path_prefix, 'reset_password', token, + self.collection_key, user_id], + status_code=204) + self.manager.reset_password(user=user_id, reset_token=token) + +class ActivationKeyTests(utils.TestCase): def setUp(self): - super(ActivationTests, self).setUp() + super(ActivationKeyTests, self).setUp() self.key = 'activation_key' self.collection_key = 'activate' - self.model = activation.Activation - self.manager = self.client.user_registration.activation + self.model = activation_key.ActivationKey + self.manager = self.client.user_registration.activation_key self.path_prefix = EXTENSION_PATH def test_new_activation_key(self): @@ -68,29 +86,20 @@ def test_new_activation_key(self): } } self.stub_url('GET', - [self.path_prefix, 'users', - user_id, 'activate'], - json=activation_key_ref, - status_code=200) - self.manager.new_activation_key(user=user_id) - - def test_activate_user(self): - user_id = uuid.uuid4().hex - activation_key = uuid.uuid4().hex - self.stub_url('PATCH', [self.path_prefix, 'users', - user_id, 'activate', activation_key], + user_id, 'activate'], + json=activation_key_ref, status_code=200) - self.manager.activate_user(user=user_id, activation_key=activation_key) + self.manager.new_activation_key(user=user_id) -class ResetPasswordTest(utils.TestCase): +class TokenTest(utils.TestCase): def setUp(self): - super(ResetPasswordTest, self).setUp() + super(TokenTest, self).setUp() self.key = 'token' self.collection_key = 'reset_password' - self.model = resetpassword.ResetPassword - self.manager = self.client.user_registration.resetpassword + self.model = token.Token + self.manager = self.client.user_registration.token self.path_prefix = EXTENSION_PATH def test_get_reset_token(self): @@ -106,13 +115,3 @@ def test_get_reset_token(self): json=reset_token_ref, status_code=204) self.manager.get_reset_token(user=user_id) - - def test_reset_password(self): - user_id = uuid.uuid4().hex - token = uuid.uuid4().hex - self.stub_url('PATCH', - [self.path_prefix, 'users', user_id, - self.collection_key, token], - status_code=204) - self.manager.reset_password(user=user_id, reset_token=token) - diff --git a/keystoneclient/v3/contrib/user_registration/activation_key.py b/keystoneclient/v3/contrib/user_registration/activation_key.py new file mode 100644 index 000000000..3a4e634ea --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/activation_key.py @@ -0,0 +1,36 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class ActivationKey(base.Resource): + pass + +class ActivationKeyManager(base.CrudManager): + """Manager class for activating user in the USER REGISTRATION extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = ActivationKey + collection_key = 'activate' + key = 'activation_key' + base_url = REGISTRATION_PATH + + def new_activation_key(self, user): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(ActivationKeyManager, self).get(base_url=base_url) + + \ No newline at end of file diff --git a/keystoneclient/v3/contrib/user_registration/core.py b/keystoneclient/v3/contrib/user_registration/core.py index 820546211..c48edc184 100644 --- a/keystoneclient/v3/contrib/user_registration/core.py +++ b/keystoneclient/v3/contrib/user_registration/core.py @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient.v3.contrib.user_registration import registration, activation, resetpassword +from keystoneclient.v3.contrib.user_registration import users, activation_key, token class UserRegistrationManager(object): def __init__(self, api): - self.registration = registration.RegistrationManager(api) - self.activation = activation.ActivationManager(api) - self.resetpassword = resetpassword.ResetPasswordManager(api) + self.users = users.UsersManager(api) + self.activation_key = activation_key.ActivationKeyManager(api) + self.token = token.TokenManager(api) diff --git a/keystoneclient/v3/contrib/user_registration/token.py b/keystoneclient/v3/contrib/user_registration/token.py new file mode 100644 index 000000000..f88da1647 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/token.py @@ -0,0 +1,37 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class Token(base.Resource): + pass + +class TokenManager(base.CrudManager): + """Manager class for manipulating passwords and tokens in + the USER REGISTRATION extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Token + collection_key = 'reset_password' + key = 'reset_token' + base_url = REGISTRATION_PATH + + def get_reset_token(self, user): + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + return super(TokenManager, self).get(base_url=base_url) + + diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py new file mode 100644 index 000000000..64c2c0040 --- /dev/null +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -0,0 +1,54 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH + +class Users(base.Resource): + pass + +class UsersManager(base.CrudManager): + """Manager class for manipulating user registration in the USER REGISTRATION + extension for Keystone. + For more information about the extension: https://www.github.com/ging/keystone + """ + resource_class = Users + collection_key = 'users' + key = 'user' + base_url = REGISTRATION_PATH + + + def register_user(self, name, domain=None, password=None, + email=None, description=None, **kwargs): + + user_data = base.filter_none(name=name, + domain_id=base.getid(domain), + password=password, + email=email, + description=description, + **kwargs) + + return self._create(self.base_url+'/users', {'user': user_data}, 'user', + log=not bool(password)) + + def activate_user(self, user, activation_key): + base_url = self.base_url + '/activate/{0}'.format(base.getid(activation_key)) + return super(UsersManager, self).update(base_url=base_url, + user_id=base.getid(user)) + + def reset_password(self, user, reset_token): + base_url = self.base_url + '/reset_password/{0}'.format(base.getid(reset_token)) + return super(UsersManager, self).update(base_url=base_url, + user_id=base.getid(user)) \ No newline at end of file From 8b6d15c62af350f7a4b092a6434e4ba2845254a1 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Wed, 4 Feb 2015 11:49:11 +0100 Subject: [PATCH 043/102] fixed issues users --- .../v3/contrib/user_registration/users.py | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 64c2c0040..70ef02870 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -44,11 +44,19 @@ def register_user(self, name, domain=None, password=None, log=not bool(password)) def activate_user(self, user, activation_key): - base_url = self.base_url + '/activate/{0}'.format(base.getid(activation_key)) - return super(UsersManager, self).update(base_url=base_url, - user_id=base.getid(user)) + url = self.base_url + '/activate/{0}/users/{1}'.format( + base.getid(activation_key), + base.getid(user)) + return self._update( + url, + None, + self.key, + method='PATCH') def reset_password(self, user, reset_token): - base_url = self.base_url + '/reset_password/{0}'.format(base.getid(reset_token)) - return super(UsersManager, self).update(base_url=base_url, - user_id=base.getid(user)) \ No newline at end of file + url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token), base.getid(user)) + return self._update( + url, + None, + self.key, + method='PATCH') \ No newline at end of file From 1adfbc9d03270362eb93b5fd549bdcf556711b86 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Wed, 4 Feb 2015 12:42:34 +0100 Subject: [PATCH 044/102] removed old code --- .../contrib/user_registration/activation.py | 39 ----------------- .../contrib/user_registration/registration.py | 43 ------------------- .../user_registration/resetpassword.py | 40 ----------------- 3 files changed, 122 deletions(-) delete mode 100644 keystoneclient/v3/contrib/user_registration/activation.py delete mode 100644 keystoneclient/v3/contrib/user_registration/registration.py delete mode 100644 keystoneclient/v3/contrib/user_registration/resetpassword.py diff --git a/keystoneclient/v3/contrib/user_registration/activation.py b/keystoneclient/v3/contrib/user_registration/activation.py deleted file mode 100644 index 5ba0fb531..000000000 --- a/keystoneclient/v3/contrib/user_registration/activation.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (C) 2014 Universidad Politecnica de Madrid -# 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 import base -from keystoneclient import exceptions -from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH - -class Activation(base.Resource): - pass - -class ActivationManager(base.CrudManager): - """Manager class for activating user in the USER REGISTRATION extension for Keystone. - - For more information about the extension: https://www.github.com/ging/keystone - """ - resource_class = Activation - collection_key = 'activate' - key = 'activation_key' - base_url = REGISTRATION_PATH - - def new_activation_key(self, user): - base_url = self.base_url + '/users/{0}'.format(base.getid(user)) - return super(ActivationManager, self).get(base_url=base_url) - - def activate_user(self, user, activation_key): - base_url = self.base_url + '/users/{0}'.format(base.getid(user)) - return super(ActivationManager, self).update(base_url=base_url, - activation_key_id=base.getid(activation_key)) diff --git a/keystoneclient/v3/contrib/user_registration/registration.py b/keystoneclient/v3/contrib/user_registration/registration.py deleted file mode 100644 index 56f9c46de..000000000 --- a/keystoneclient/v3/contrib/user_registration/registration.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (C) 2014 Universidad Politecnica de Madrid -# 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 import base -from keystoneclient import exceptions -from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH - -class Registration(base.Resource): - pass - -class RegistrationManager(base.CrudManager): - """Manager class for manipulating user registration in the USER REGISTRATION - extension for Keystone. - For more information about the extension: https://www.github.com/ging/keystone - """ - resource_class = Registration - collection_key = 'users' - key = 'user' - base_url = REGISTRATION_PATH - - def register_user(self, name, domain=None, password=None, - email=None, description=None, **kwargs): - - user_data = base.filter_none(name=name, - domain_id=base.getid(domain), - password=password, - email=email, - description=description, - **kwargs) - - return self._create(self.base_url+'/users', {'user': user_data}, 'user', - log=not bool(password)) diff --git a/keystoneclient/v3/contrib/user_registration/resetpassword.py b/keystoneclient/v3/contrib/user_registration/resetpassword.py deleted file mode 100644 index b88ee5444..000000000 --- a/keystoneclient/v3/contrib/user_registration/resetpassword.py +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2014 Universidad Politecnica de Madrid -# 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 import base -from keystoneclient import exceptions -from keystoneclient.v3.contrib.user_registration.utils import REGISTRATION_PATH - -class ResetPassword(base.Resource): - pass - -class ResetPasswordManager(base.CrudManager): - """Manager class for manipulating passwords and tokens in - the USER REGISTRATION extension for Keystone. - - For more information about the extension: https://www.github.com/ging/keystone - """ - resource_class = ResetPassword - collection_key = 'reset_password' - key = 'reset_token' - base_url = REGISTRATION_PATH - - def get_reset_token(self, user): - base_url = self.base_url + '/users/{0}'.format(base.getid(user)) - return super(ResetPasswordManager, self).get(base_url=base_url) - - def reset_password(self, user, reset_token): - base_url = self.base_url + '/users/{0}'.format(base.getid(user)) - return super(ResetPasswordManager, self).update(base_url=base_url, - reset_token_id=base.getid(reset_token)) From 50078bcce9fbe56423ec83c9c3343caeb735f854 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 5 Feb 2015 12:10:41 +0100 Subject: [PATCH 045/102] Check user token for forgotten password --- keystoneclient/v3/contrib/user_registration/users.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 70ef02870..c78031589 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -54,9 +54,12 @@ def activate_user(self, user, activation_key): method='PATCH') def reset_password(self, user, reset_token): - url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token), base.getid(user)) + url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user['id']) + body = { + 'user': user + } return self._update( url, - None, - self.key, + body=body, + response_key=self.key, method='PATCH') \ No newline at end of file From 2a0f60e70c9ead3cec926c8a83d5b31f72f17f9d Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 5 Feb 2015 13:44:22 +0100 Subject: [PATCH 046/102] user_id saved as parameter --- keystoneclient/v3/contrib/user_registration/users.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index c78031589..0e0b318e1 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -54,7 +54,8 @@ def activate_user(self, user, activation_key): method='PATCH') def reset_password(self, user, reset_token): - url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user['id']) + user_id = user['id'] + url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),base.getid(user_id)) body = { 'user': user } From 104ece7b8b05282972a8e16356ffbcde2e1a2916 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Thu, 5 Feb 2015 14:03:54 +0100 Subject: [PATCH 047/102] integration test user_registration --- integration_test_user_registration.py | 84 +++++++++++++++++++ .../v3/contrib/user_registration/users.py | 2 +- 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 integration_test_user_registration.py diff --git a/integration_test_user_registration.py b/integration_test_user_registration.py new file mode 100644 index 000000000..32e175ee2 --- /dev/null +++ b/integration_test_user_registration.py @@ -0,0 +1,84 @@ +from keystoneclient.v3 import client +from keystoneclient import session +from keystoneclient.v3.contrib.oauth2 import auth +from keystoneclient.auth.identity import v3 + +def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this + """Encapsulates all the logic for communicating with the modified keystone server. + + The IdM has its own admin account in the keystone server, and uses it to perform + operations like create users, projects, etc. when there is no user with admin rights + (for example, when user registration) to overcome the Keystone limitations. + + Also adds the methods to operate with the OAuth2.0 extension. + """ + # TODO(garcianavalon) find a way to integrate this with the existng keystone api + # TODO(garcianavalon)caching and efficiency with the client object. + if not session: + session = _password_session() + keystone = client.Client(session=session) + return keystone + +def _password_session(): + auth = v3.Password(auth_url="http://localhost:5000/v3", + username='idm', + password='idm', + project_name='idm', + user_domain_id='default', + project_domain_id='default') + return session.Session(auth=auth) + +keystone = fiwareclient() + +#Registrer User: +new_user = keystone.user_registration.users.register_user(name='Test User', + domain='default', + password='test', + email='user@test.com') + +print ('\nUser activated? ') +print new_user.enabled + +#Activate User +activated_user = keystone.user_registration.users.activate_user(new_user.id, new_user.activation_key) + +print ('\nUser activated? ') +print activated_user.enabled + +#Forgot Password +token = keystone.user_registration.token.get_reset_token(new_user.id) +print token + +user_ref = { + 'password' : 'newpassword', + 'id' : new_user.id, +} +user = keystone.user_registration.users.reset_password(user_ref, token.id) +print user + +#Delete created user and associated project +keystone.users.delete(new_user.id) +project = keystone.projects.find(name=new_user.name) +keystone.projects.delete(project.id) + +#New Activation Key +user_reset = keystone.user_registration.users.register_user(name='Test User', + domain='default', + password='test', + email='user@test.com') +print ('\nUser activated? ') +print user_reset.enabled + +new_activation_key = keystone.user_registration.activation_key.new_activation_key(user_reset.id) +print new_activation_key + +activated_user = keystone.user_registration.users.activate_user(user_reset.id, new_activation_key.id) + +print ('\nUser activated? ') +print user_reset.enabled + +keystone.users.delete(user_reset.id) +project = keystone.projects.find(name=user_reset.name) +keystone.projects.delete(project.id) + + diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 0e0b318e1..7b96a37ad 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -55,7 +55,7 @@ def activate_user(self, user, activation_key): def reset_password(self, user, reset_token): user_id = user['id'] - url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),base.getid(user_id)) + url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user_id) body = { 'user': user } From 36e4ca656a5017e7c28d30c7b7e6bf22ec894052 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Fri, 6 Feb 2015 12:50:24 +0100 Subject: [PATCH 048/102] integration test and reset password modifications --- integration_test_user_registration.py | 85 ++++++++++--------- .../v3/contrib/user_registration/users.py | 9 +- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/integration_test_user_registration.py b/integration_test_user_registration.py index 32e175ee2..e6e07bf5d 100644 --- a/integration_test_user_registration.py +++ b/integration_test_user_registration.py @@ -31,54 +31,63 @@ def _password_session(): keystone = fiwareclient() #Registrer User: -new_user = keystone.user_registration.users.register_user(name='Test User', - domain='default', - password='test', - email='user@test.com') +if 0: + new_user = keystone.user_registration.users.register_user(name='Test User', + domain='default', + password='test', + email='user@test.com') -print ('\nUser activated? ') -print new_user.enabled + print ('\nUser activated? ') + print new_user.enabled #Activate User -activated_user = keystone.user_registration.users.activate_user(new_user.id, new_user.activation_key) +if 0: + activated_user = keystone.user_registration.users.activate_user(new_user.id, new_user.activation_key) -print ('\nUser activated? ') -print activated_user.enabled + print ('\nUser activated? ') + print activated_user.enabled #Forgot Password -token = keystone.user_registration.token.get_reset_token(new_user.id) -print token +if 0: + token = keystone.user_registration.token.get_reset_token(new_user.id) + print token -user_ref = { - 'password' : 'newpassword', - 'id' : new_user.id, -} -user = keystone.user_registration.users.reset_password(user_ref, token.id) -print user + user = keystone.user_registration.users.reset_password(new_user, token.id, 'newpassword') + print user #Delete created user and associated project -keystone.users.delete(new_user.id) -project = keystone.projects.find(name=new_user.name) -keystone.projects.delete(project.id) +if 0: + keystone.users.delete(new_user.id) + project = keystone.projects.find(name=new_user.name) + keystone.projects.delete(project.id) #New Activation Key -user_reset = keystone.user_registration.users.register_user(name='Test User', - domain='default', - password='test', - email='user@test.com') -print ('\nUser activated? ') -print user_reset.enabled - -new_activation_key = keystone.user_registration.activation_key.new_activation_key(user_reset.id) -print new_activation_key - -activated_user = keystone.user_registration.users.activate_user(user_reset.id, new_activation_key.id) - -print ('\nUser activated? ') -print user_reset.enabled - -keystone.users.delete(user_reset.id) -project = keystone.projects.find(name=user_reset.name) -keystone.projects.delete(project.id) +# The new activation key does not work in these tests. +if 1: + user_reset = keystone.user_registration.users.register_user(name='Test User', + domain='default', + password='test', + email='user@test.com') + project = keystone.projects.find(name=user_reset.name) + print user_reset.id + print project.id + print ('\nUser activated? ') + print user_reset.enabled + + print user_reset.activation_key + + new_activation_key = keystone.user_registration.activation_key.new_activation_key(user_reset.id) + print new_activation_key.id + print user_reset.activation_key + + activated_user = keystone.user_registration.users.activate_user(user_reset.id, new_activation_key.id) + print activated_user + + print ('\nUser activated? ') + print user_reset.enabled + + keystone.users.delete(user_reset.id) + # project = keystone.projects.find(name=user_reset.name) + keystone.projects.delete(project.id) diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 7b96a37ad..9bee312d7 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -53,11 +53,12 @@ def activate_user(self, user, activation_key): self.key, method='PATCH') - def reset_password(self, user, reset_token): - user_id = user['id'] - url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user_id) + def reset_password(self, user, reset_token, new_password): + url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user.id) body = { - 'user': user + 'user' : { + 'password' : new_password, + } } return self._update( url, From 0dc76f73110585680a8558b1db3b28952f770170 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Thu, 12 Feb 2015 11:58:48 +0100 Subject: [PATCH 049/102] role assignments, roles for organizations --- keystoneclient/tests/v3/test_fiware_roles.py | 291 ++++++++++++++---- .../v3/contrib/fiware_roles/core.py | 2 + .../contrib/fiware_roles/role_assignments.py | 114 +++++++ .../v3/contrib/fiware_roles/roles.py | 101 +++--- 4 files changed, 410 insertions(+), 98 deletions(-) create mode 100644 keystoneclient/v3/contrib/fiware_roles/role_assignments.py diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 9ec8e9114..43badf1bf 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -17,6 +17,7 @@ from keystoneclient import exceptions from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.fiware_roles import roles +from keystoneclient.v3.contrib.fiware_roles import role_assignments from keystoneclient.v3.contrib.fiware_roles import permissions @@ -39,57 +40,7 @@ def new_ref(self, **kwargs): kwargs.setdefault('is_internal', False) return kwargs - - # def test_list_roles_by_permission(self): - # permission_id = uuid.uuid4().hex - # ref_list = [self.new_ref(), self.new_ref()] - - # self.stub_entity('GET', - # parts=[self.path_prefix, 'permissions', permission_id, self.collection_key], - # entity=ref_list) - - # returned_list = self.manager.list(permission=permission_id) - - # self.assertEqual(len(ref_list), len(returned_list)) - # [self.assertIsInstance(r, self.model) for r in returned_list] - - def test_list_roles_by_user(self): - user_id = uuid.uuid4().hex - ref_list = [self.new_ref(), self.new_ref()] - organization_id = uuid.uuid4().hex - self.stub_entity('GET', - parts=[self.path_prefix, 'users', user_id, - 'organizations', organization_id, - self.collection_key], - entity=ref_list) - - returned_list = self.manager.list(user=user_id, - organization=organization_id) - - self.assertEqual(len(ref_list), len(returned_list)) - for item in returned_list: - self.assertIsInstance(item, self.model) - - # Test invalid args - self.assertRaises(exceptions.ValidationError, - self.manager.list, - user=user_id, - organization=None) - self.assertRaises(exceptions.ValidationError, - self.manager.list, - user=None, - organization=organization_id) - - # def test_list_roles_by_user_and_permission(self): - # user_id = uuid.uuid4().hex - # permission_id = uuid.uuid4().hex - # ref_list = [self.new_ref(), self.new_ref()] - - # self.assertRaises(exceptions.ValidationError, - # self.manager.list, - # user=user_id, - # permission=permission_id) - + # ROLES-USER def test_add_role_to_user(self): user_id = uuid.uuid4().hex @@ -104,6 +55,7 @@ def test_add_role_to_user(self): user=user_id, organization=organization_id) + def test_remove_role_from_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() @@ -118,7 +70,8 @@ def test_remove_role_from_user(self): user=user_id, organization=organization_id) - def test_list_allowed_roles_to_assign(self): + + def test_list_user_allowed_roles_to_assign(self): user_id = uuid.uuid4().hex organization_id = uuid.uuid4().hex allowed_roles_ref = { @@ -132,14 +85,244 @@ def test_list_allowed_roles_to_assign(self): 'organizations', organization_id, 'roles/allowed'], json=allowed_roles_ref) - allowed_roles = self.manager.list_allowed_roles_to_assign(user=user_id, - organization=organization_id) + allowed_roles = self.manager.list_user_allowed_roles_to_assign( + user=user_id, organization=organization_id) + + self.assertIsNotNone(allowed_roles) + for item in allowed_roles['some_application']: + self.assertIsInstance(item, self.model) + + + # ROLES-ORGANIZATIONS + def test_add_role_to_organization(self): + organization_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('PUT', + [self.path_prefix, 'organizations', organization_id, + self.collection_key, role_ref['id']], + status_code=204) + self.manager.add_to_organization(role=role_ref['id'], + organization=organization_id) + + + def test_remove_role_from_organization(self): + organization_id = uuid.uuid4().hex + role_ref = self.new_ref() + self.stub_url('DELETE', + [self.path_prefix, 'organizations', organization_id, + self.collection_key, role_ref['id']], + status_code=204) + + self.manager.remove_from_organization(role=role_ref['id'], + organization=organization_id) + + + def test_list_organization_allowed_roles_to_assign(self): + organization_id = uuid.uuid4().hex + allowed_roles_ref = { + 'some_application': [ + self.new_ref(), + self.new_ref(), + ] + } + self.stub_url('GET', + [self.path_prefix, 'organizations', organization_id, + 'roles/allowed'], + json=allowed_roles_ref) + allowed_roles = self.manager.list_organization_allowed_roles_to_assign( + organization=organization_id) self.assertIsNotNone(allowed_roles) for item in allowed_roles['some_application']: self.assertIsInstance(item, self.model) + +class RoleAssignmentsTests(utils.TestCase, utils.CrudTests): + + def setUp(self): + super(RoleAssignmentsTests, self).setUp() + self.key = 'role_assignment' + self.collection_key = 'role_assignments' + self.model = role_assignments.RoleAssignment + self.manager = self.client.fiware_roles.role_assignments + self.USER_ASSIGNMENT_TEST_LIST = [{ + 'role_id': uuid.uuid4().hex, + 'organization_id': uuid.uuid4().hex, + 'user_id': uuid.uuid4().hex, + 'application_id': uuid.uuid4().hex, + }] + self.ORGANIZATION_ASSIGNMENT_TEST_LIST = [{ + 'role_id': uuid.uuid4().hex, + 'organization_id': uuid.uuid4().hex, + 'application_id': uuid.uuid4().hex, + }] + self.path_prefix = EXTENSION_PATH + + + def _assert_returned_list(self, ref_list, returned_list): + self.assertEqual(len(ref_list), len(returned_list)) + [self.assertIsInstance(r, self.model) for r in returned_list] + + + # ROLE-USER + def test_all_user_assignments_list(self): + ref_list = self.USER_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'users', self.collection_key], + entity=ref_list) + + returned_list = self.manager.list_user_role_assignments() + self._assert_returned_list(ref_list, returned_list) + + kwargs = {} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_organization_user_assignments(self): + ref_list = self.USER_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'users', self.collection_key, + '?organization_id=%s' % self.TEST_TENANT_ID], + entity=ref_list) + + returned_list = self.manager.list_user_role_assignments( + organization=self.TEST_TENANT_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'organization_id': self.TEST_TENANT_ID} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_application_user_assignments(self): + ref_list = self.USER_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'users', self.collection_key, + '?application_id=%s' % self.TEST_DOMAIN_ID], + entity=ref_list) + + returned_list = self.manager.list_user_role_assignments( + application=self.TEST_DOMAIN_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'application_id': self.TEST_DOMAIN_ID} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_user_user_assignments(self): + ref_list = self.USER_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'users', self.collection_key, + '?user_id=%s' % self.TEST_USER_ID], + entity=ref_list) + + returned_list = self.manager.list_user_role_assignments( + user=self.TEST_USER_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'user_id': self.TEST_USER_ID} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_user_and_organization_user_assignments(self): + ref_list = self.USER_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'users', self.collection_key, + '?organization_id=%s&user_id=%s' % + (self.TEST_TENANT_ID, self.TEST_USER_ID)], + entity=ref_list) + + returned_list = self.manager.list_user_role_assignments( + user=self.TEST_USER_ID, organization=self.TEST_TENANT_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'organization_id': self.TEST_TENANT_ID, + 'user_id': self.TEST_USER_ID} + self.assertQueryStringContains(**kwargs) + + + #ROLE-ORGANIZATION + def test_all_organization_assignments_list(self): + ref_list = self.ORGANIZATION_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'organizations', self.collection_key], + entity=ref_list) + + returned_list = self.manager.list_organization_role_assignments() + self._assert_returned_list(ref_list, returned_list) + + kwargs = {} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_organization_organization_assignments(self): + ref_list = self.ORGANIZATION_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'organizations', self.collection_key, + '?organization_id=%s' % self.TEST_TENANT_ID], + entity=ref_list) + + returned_list = self.manager.list_organization_role_assignments( + organization=self.TEST_TENANT_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'organization_id': self.TEST_TENANT_ID} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_application_organization_assignments(self): + ref_list = self.ORGANIZATION_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'organizations', self.collection_key, + '?application_id=%s' % self.TEST_DOMAIN_ID], + entity=ref_list) + + returned_list = self.manager.list_organization_role_assignments( + application=self.TEST_DOMAIN_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'application_id': self.TEST_DOMAIN_ID} + self.assertQueryStringContains(**kwargs) + + + def test_filter_by_application_and_organization_organization_assignments(self): + ref_list = self.ORGANIZATION_ASSIGNMENT_TEST_LIST + self.stub_entity('GET', + [self.path_prefix, 'organizations', self.collection_key, + '?organization_id=%s&application_id=%s' % + (self.TEST_TENANT_ID, self.TEST_DOMAIN_ID)], + entity=ref_list) + + returned_list = self.manager.list_organization_role_assignments( + application=self.TEST_DOMAIN_ID, organization=self.TEST_TENANT_ID) + self._assert_returned_list(ref_list, returned_list) + + kwargs = {'organization_id': self.TEST_TENANT_ID, + 'application_id': self.TEST_DOMAIN_ID} + self.assertQueryStringContains(**kwargs) + + + def test_create(self): + # Create not supported for role assignments + self.assertRaises(exceptions.MethodNotImplemented, self.manager.create) + + def test_update(self): + # Update not supported for role assignments + self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) + + def test_delete(self): + # Delete not supported for role assignments + self.assertRaises(exceptions.MethodNotImplemented, self.manager.delete) + + def test_get(self): + # Get not supported for role assignments + self.assertRaises(exceptions.MethodNotImplemented, self.manager.get) + + def test_find(self): + # Find not supported for role assignments + self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) + + class PermissionTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py index 865e8b846..20d058fc7 100644 --- a/keystoneclient/v3/contrib/fiware_roles/core.py +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -13,6 +13,7 @@ # limitations under the License. from keystoneclient.v3.contrib.fiware_roles import roles +from keystoneclient.v3.contrib.fiware_roles import role_assignments from keystoneclient.v3.contrib.fiware_roles import permissions @@ -20,4 +21,5 @@ class FiwareRolesManager(object): def __init__(self, api): self.roles = roles.RoleManager(api) self.permissions = permissions.PermissionManager(api) + self.role_assignments = role_assignments.RoleAssignmentManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/role_assignments.py b/keystoneclient/v3/contrib/fiware_roles/role_assignments.py new file mode 100644 index 000000000..1a8033370 --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/role_assignments.py @@ -0,0 +1,114 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +# 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 import base +from keystoneclient import exceptions +from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH + +class RoleAssignment(base.Resource): + pass + + +class RoleAssignmentManager(base.CrudManager): + + """Manager class for manipulating user and organization roles assignments.""" + resource_class = RoleAssignment + collection_key = 'role_assignments' + key = 'role_assignment' + base_url = ROLES_PATH + + def list_user_role_assignments(self, user=None, organization=None, + application=None): + """Lists role assignments for users. + + If no arguments are provided, all role assignments in the + system will be listed. + + :param user: User to be used as query filter. (optional) + :param organization: Project to be used as query filter. + (optional) + :param application: Domain to be used as query + filter. (optional) + """ + + query_params = {} + if user: + query_params['user_id'] = base.getid(user) + if organization: + query_params['organization_id'] = base.getid(organization) + if application: + query_params['application_id'] = base.getid(application) + + base_url = self.base_url + '/users' + return super(RoleAssignmentManager, self).list(base_url=base_url, + **query_params) + + def list_organization_role_assignments(self, organization=None, + application=None): + """Lists role assignments for organizations. + + If no arguments are provided, all role assignments in the + system will be listed. + + :param organization: Project to be used as query filter. + (optional) + :param application: Domain to be used as query + filter. (optional) + """ + + query_params = {} + if organization: + query_params['organization_id'] = base.getid(organization) + if application: + query_params['application_id'] = base.getid(application) + + base_url = self.base_url + '/organizations' + return super(RoleAssignmentManager, self).list(base_url=base_url, + **query_params) + + def create(self, **kwargs): + raise exceptions.MethodNotImplemented('Create not supported for' + ' role assignments') + + def update(self, **kwargs): + raise exceptions.MethodNotImplemented('Update not supported for' + ' role assignments') + + def get(self, **kwargs): + raise exceptions.MethodNotImplemented('Get not supported for' + ' role assignments') + + def find(self, **kwargs): + raise exceptions.MethodNotImplemented('Find not supported for' + ' role assignments') + + def put(self, **kwargs): + raise exceptions.MethodNotImplemented('Put not supported for' + ' role assignments') + + def delete(self, **kwargs): + raise exceptions.MethodNotImplemented('Delete not supported for' + ' role assignments') diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 635f2de7d..aa1e1d880 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -15,7 +15,6 @@ import json from keystoneclient import base -from keystoneclient import exceptions from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH @@ -32,69 +31,53 @@ class RoleManager(base.CrudManager): key = 'role' base_url = ROLES_PATH - def _require_user_and_organization(self, user, organization): - if (not user and organization) or (user and not organization): - msg = 'Specify both a user and an organization' - raise exceptions.ValidationError(msg) - - # def _require_user_xor_permission(self, user, permission): - # if user and permission: - # msg = 'Specify either a user or permission, not both' - # raise exceptions.ValidationError(msg) - # elif not (user or permission): - # msg = 'Must specify either a user or permission' - # raise exceptions.ValidationError(msg) - def create(self, name, is_internal=False, application=None, **kwargs): - return super(RoleManager, self).create( - name=name, - is_internal=is_internal, - application=application, - **kwargs) + return super(RoleManager, self).create(name=name, + is_internal=is_internal, + application=application, + **kwargs) + + def get(self, role): - return super(RoleManager, self).get( - role_id=base.getid(role)) + return super(RoleManager, self).get(role_id=base.getid(role)) + def update(self, role, name=None, is_internal=False, - application=None, **kwargs): - return super(RoleManager, self).update( - role_id=base.getid(role), - name=name, - is_internal=is_internal, - application=application, - **kwargs) - + application=None, **kwargs): + return super(RoleManager, self).update(role_id=base.getid(role), + name=name, + is_internal=is_internal, + application=application, + **kwargs) + + def delete(self, role): return super(RoleManager, self).delete(role_id=base.getid(role)) - def list(self, user=None, organization=None, **kwargs): - self._require_user_and_organization(user, organization) - - if user and organization: - base_url = self.base_url + '/users/%s/organizations/%s' \ - % (base.getid(user), base.getid(organization)) - else: - base_url = self.base_url + def list(self, **kwargs): + base_url = self.base_url return super(RoleManager, self).list(base_url=base_url, **kwargs) + + # ROLE-USER def add_to_user(self, role, user, organization): base_url = self.base_url + '/users/%s/organizations/%s' \ % (base.getid(user), base.getid(organization)) - return super(RoleManager, self).put( - base_url=base_url, - role_id=base.getid(role)) + return super(RoleManager, self).put(base_url=base_url, + role_id=base.getid(role)) + def remove_from_user(self, role, user, organization): base_url = self.base_url + '/users/%s/organizations/%s' \ % (base.getid(user), base.getid(organization)) - return super(RoleManager, self).delete( - base_url=base_url, - role_id=base.getid(role)) + return super(RoleManager, self).delete(base_url=base_url, + role_id=base.getid(role)) - def list_allowed_roles_to_assign(self, user, organization): + + def list_user_allowed_roles_to_assign(self, user, organization): """Obtain a list of all the roles the user is allowed to assign for every application. """ @@ -109,4 +92,34 @@ def list_allowed_roles_to_assign(self, user, organization): roles_as_resource[app].append(self.resource_class(self, role)) return roles_as_resource + + def add_to_organization(self, role, organization): + base_url = self.base_url + '/organizations/{0}'.format(base.getid(organization)) + + return super(RoleManager, self).put(base_url=base_url, + role_id=base.getid(role)) + + + def remove_from_organization(self, role, organization): + base_url = self.base_url + '/organizations/{0}'.format(base.getid(organization)) + + return super(RoleManager, self).delete(base_url=base_url, + role_id=base.getid(role)) + + + def list_organization_allowed_roles_to_assign(self, organization): + """Obtain a list of all the roles the user is allowed to assign + for every application. + """ + endpoint = self.base_url + '/organizations/{0}/roles/allowed'.format( + base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_roles = json.loads(resp.content) + roles_as_resource = {} + for app in allowed_roles: + for role in allowed_roles[app]: + roles_as_resource[app] = roles_as_resource.get(app, []) + roles_as_resource[app].append(self.resource_class(self, role)) + return roles_as_resource + \ No newline at end of file From 7b13a10382e3d5245caab3d295a934a126201475 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Thu, 12 Feb 2015 13:07:43 +0100 Subject: [PATCH 050/102] added application in which to add the role to a user or an organization --- keystoneclient/tests/v3/test_fiware_roles.py | 29 +++++++++++----- .../v3/contrib/fiware_roles/permissions.py | 4 +-- .../v3/contrib/fiware_roles/roles.py | 34 +++++++++++-------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 43badf1bf..3f135eeb3 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -42,33 +42,38 @@ def new_ref(self, **kwargs): # ROLES-USER def test_add_role_to_user(self): - user_id = uuid.uuid4().hex role_ref = self.new_ref() organization_id = uuid.uuid4().hex + app_id = uuid.uuid4().hex self.stub_url('PUT', [self.path_prefix, 'users', user_id, 'organizations', organization_id, + 'applications', app_id, self.collection_key, role_ref['id']], status_code=204) self.manager.add_to_user(role=role_ref['id'], user=user_id, - organization=organization_id) + organization=organization_id, + application=app_id) def test_remove_role_from_user(self): user_id = uuid.uuid4().hex role_ref = self.new_ref() organization_id = uuid.uuid4().hex + app_id = uuid.uuid4().hex self.stub_url('DELETE', - [self.path_prefix, 'users', user_id, - 'organizations', organization_id, - self.collection_key, role_ref['id']], - status_code=204) + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, + 'applications', app_id, + self.collection_key, role_ref['id']], + status_code=204) self.manager.remove_from_user(role=role_ref['id'], user=user_id, - organization=organization_id) + organization=organization_id, + application=app_id) def test_list_user_allowed_roles_to_assign(self): @@ -97,24 +102,30 @@ def test_list_user_allowed_roles_to_assign(self): def test_add_role_to_organization(self): organization_id = uuid.uuid4().hex role_ref = self.new_ref() + app_id = uuid.uuid4().hex self.stub_url('PUT', [self.path_prefix, 'organizations', organization_id, + 'applications', app_id, self.collection_key, role_ref['id']], status_code=204) self.manager.add_to_organization(role=role_ref['id'], - organization=organization_id) + organization=organization_id, + application=app_id) def test_remove_role_from_organization(self): organization_id = uuid.uuid4().hex role_ref = self.new_ref() + app_id = uuid.uuid4().hex self.stub_url('DELETE', [self.path_prefix, 'organizations', organization_id, + 'applications', app_id, self.collection_key, role_ref['id']], status_code=204) self.manager.remove_from_organization(role=role_ref['id'], - organization=organization_id) + organization=organization_id, + application=app_id) def test_list_organization_allowed_roles_to_assign(self): diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index 9d07204b7..bf14f1471 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -38,7 +38,7 @@ def create(self, name, is_internal=False, application=None, **kwargs): return super(PermissionManager, self).create( name=name, is_internal=is_internal, - application=application, + application=base.getid(application), **kwargs) def get(self, permission): return super(PermissionManager, self).get( @@ -50,7 +50,7 @@ def update(self, permission, name=None, is_internal=False, permission_id=base.getid(permission), name=name, is_internal=is_internal, - application=application, + application=base.getid(application), **kwargs) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index aa1e1d880..d019de103 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -34,7 +34,7 @@ class RoleManager(base.CrudManager): def create(self, name, is_internal=False, application=None, **kwargs): return super(RoleManager, self).create(name=name, is_internal=is_internal, - application=application, + application=base.getid(application), **kwargs) @@ -47,7 +47,7 @@ def update(self, role, name=None, is_internal=False, return super(RoleManager, self).update(role_id=base.getid(role), name=name, is_internal=is_internal, - application=application, + application=base.getid(application), **kwargs) @@ -61,17 +61,19 @@ def list(self, **kwargs): # ROLE-USER - def add_to_user(self, role, user, organization): - base_url = self.base_url + '/users/%s/organizations/%s' \ - % (base.getid(user), base.getid(organization)) + def add_to_user(self, role, user, organization, application): + base_url = (self.base_url + '/users/{0}/organizations/{1}/applications/{2}' + ).format(base.getid(user), base.getid(organization), + base.getid(application)) return super(RoleManager, self).put(base_url=base_url, role_id=base.getid(role)) - def remove_from_user(self, role, user, organization): - base_url = self.base_url + '/users/%s/organizations/%s' \ - % (base.getid(user), base.getid(organization)) + def remove_from_user(self, role, user, organization, application): + base_url = (self.base_url + '/users/{0}/organizations/{1}/applications/{2}' + ).format(base.getid(user), base.getid(organization), + base.getid(application)) return super(RoleManager, self).delete(base_url=base_url, role_id=base.getid(role)) @@ -81,8 +83,8 @@ def list_user_allowed_roles_to_assign(self, user, organization): """Obtain a list of all the roles the user is allowed to assign for every application. """ - endpoint = self.base_url + '/users/%s/organizations/%s/roles/allowed' \ - % (base.getid(user), base.getid(organization)) + endpoint = (self.base_url + '/users/{0}/organizations/{1}/roles/allowed' + ).format(base.getid(user), base.getid(organization)) resp, body = self.client.get(endpoint) allowed_roles = json.loads(resp.content) roles_as_resource = {} @@ -93,16 +95,18 @@ def list_user_allowed_roles_to_assign(self, user, organization): return roles_as_resource - def add_to_organization(self, role, organization): - base_url = self.base_url + '/organizations/{0}'.format(base.getid(organization)) + # ROLES-ORGANIZATIONS + def add_to_organization(self, role, organization, application): + base_url = (self.base_url + '/organizations/{0}/applications/{1}' + ).format(base.getid(organization), base.getid(application)) return super(RoleManager, self).put(base_url=base_url, role_id=base.getid(role)) - def remove_from_organization(self, role, organization): - base_url = self.base_url + '/organizations/{0}'.format(base.getid(organization)) - + def remove_from_organization(self, role, organization, application): + base_url = (self.base_url + '/organizations/{0}/applications/{1}' + ).format(base.getid(organization), base.getid(application)) return super(RoleManager, self).delete(base_url=base_url, role_id=base.getid(role)) From 6f314b61ab83b7cd5e437a4b0c92a24322bac560 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Thu, 12 Feb 2015 18:27:36 +0100 Subject: [PATCH 051/102] style fix --- keystoneclient/v3/contrib/fiware_roles/roles.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index d019de103..c3e19c63e 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -32,10 +32,11 @@ class RoleManager(base.CrudManager): base_url = ROLES_PATH def create(self, name, is_internal=False, application=None, **kwargs): - return super(RoleManager, self).create(name=name, - is_internal=is_internal, - application=base.getid(application), - **kwargs) + return super(RoleManager, self).create( + name=name, + is_internal=is_internal, + application=base.getid(application), + **kwargs) def get(self, role): From 03e2fdc69291f02bea4b34a5289cff7a89378329 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Fri, 13 Feb 2015 12:27:38 +0100 Subject: [PATCH 052/102] server only returns ids now --- keystoneclient/tests/v3/test_fiware_roles.py | 16 +++++------- .../v3/contrib/fiware_roles/roles.py | 26 ++++++++++--------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 3f135eeb3..ade53bd2b 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -81,8 +81,8 @@ def test_list_user_allowed_roles_to_assign(self): organization_id = uuid.uuid4().hex allowed_roles_ref = { 'some_application': [ - self.new_ref(), - self.new_ref(), + uuid.uuid4().hex, + uuid.uuid4().hex, ] } self.stub_url('GET', @@ -93,9 +93,7 @@ def test_list_user_allowed_roles_to_assign(self): allowed_roles = self.manager.list_user_allowed_roles_to_assign( user=user_id, organization=organization_id) - self.assertIsNotNone(allowed_roles) - for item in allowed_roles['some_application']: - self.assertIsInstance(item, self.model) + self.assertEqual(2, len(allowed_roles['some_application'])) # ROLES-ORGANIZATIONS @@ -132,8 +130,8 @@ def test_list_organization_allowed_roles_to_assign(self): organization_id = uuid.uuid4().hex allowed_roles_ref = { 'some_application': [ - self.new_ref(), - self.new_ref(), + uuid.uuid4().hex, + uuid.uuid4().hex, ] } self.stub_url('GET', @@ -143,9 +141,7 @@ def test_list_organization_allowed_roles_to_assign(self): allowed_roles = self.manager.list_organization_allowed_roles_to_assign( organization=organization_id) - self.assertIsNotNone(allowed_roles) - for item in allowed_roles['some_application']: - self.assertIsInstance(item, self.model) + self.assertEqual(2, len(allowed_roles['some_application'])) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index c3e19c63e..4e6c2e8af 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -88,12 +88,13 @@ def list_user_allowed_roles_to_assign(self, user, organization): ).format(base.getid(user), base.getid(organization)) resp, body = self.client.get(endpoint) allowed_roles = json.loads(resp.content) - roles_as_resource = {} - for app in allowed_roles: - for role in allowed_roles[app]: - roles_as_resource[app] = roles_as_resource.get(app, []) - roles_as_resource[app].append(self.resource_class(self, role)) - return roles_as_resource + return allowed_roles + # roles_as_resource = {} + # for app in allowed_roles: + # for role in allowed_roles[app]: + # roles_as_resource[app] = roles_as_resource.get(app, []) + # roles_as_resource[app].append(self.resource_class(self, role)) + # return roles_as_resource # ROLES-ORGANIZATIONS @@ -120,11 +121,12 @@ def list_organization_allowed_roles_to_assign(self, organization): base.getid(organization)) resp, body = self.client.get(endpoint) allowed_roles = json.loads(resp.content) - roles_as_resource = {} - for app in allowed_roles: - for role in allowed_roles[app]: - roles_as_resource[app] = roles_as_resource.get(app, []) - roles_as_resource[app].append(self.resource_class(self, role)) - return roles_as_resource + return allowed_roles + # roles_as_resource = {} + # for app in allowed_roles: + # for role in allowed_roles[app]: + # roles_as_resource[app] = roles_as_resource.get(app, []) + # roles_as_resource[app].append(self.resource_class(self, role)) + # return roles_as_resource \ No newline at end of file From 6e4bfac06d918fa0f7678162b355aafb1f3c4bd0 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Fri, 13 Feb 2015 12:59:10 +0100 Subject: [PATCH 053/102] integration fixes --- keystoneclient/tests/v3/test_fiware_roles.py | 20 +++++++++++-------- .../v3/contrib/fiware_roles/roles.py | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index ade53bd2b..43666e418 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -80,10 +80,12 @@ def test_list_user_allowed_roles_to_assign(self): user_id = uuid.uuid4().hex organization_id = uuid.uuid4().hex allowed_roles_ref = { - 'some_application': [ - uuid.uuid4().hex, - uuid.uuid4().hex, - ] + 'allowed_roles': { + 'some_application': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } } self.stub_url('GET', [self.path_prefix, 'users', user_id, @@ -129,10 +131,12 @@ def test_remove_role_from_organization(self): def test_list_organization_allowed_roles_to_assign(self): organization_id = uuid.uuid4().hex allowed_roles_ref = { - 'some_application': [ - uuid.uuid4().hex, - uuid.uuid4().hex, - ] + 'allowed_roles': { + 'some_application': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } } self.stub_url('GET', [self.path_prefix, 'organizations', organization_id, diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 4e6c2e8af..d7318c000 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -87,7 +87,7 @@ def list_user_allowed_roles_to_assign(self, user, organization): endpoint = (self.base_url + '/users/{0}/organizations/{1}/roles/allowed' ).format(base.getid(user), base.getid(organization)) resp, body = self.client.get(endpoint) - allowed_roles = json.loads(resp.content) + allowed_roles = json.loads(resp.content)['allowed_roles'] return allowed_roles # roles_as_resource = {} # for app in allowed_roles: @@ -120,7 +120,7 @@ def list_organization_allowed_roles_to_assign(self, organization): endpoint = self.base_url + '/organizations/{0}/roles/allowed'.format( base.getid(organization)) resp, body = self.client.get(endpoint) - allowed_roles = json.loads(resp.content) + allowed_roles = json.loads(resp.content)['allowed_roles'] return allowed_roles # roles_as_resource = {} # for app in allowed_roles: From 549fbf751e5703d9fcfa640b567ef52b50e95497 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Thu, 19 Feb 2015 16:30:59 +0100 Subject: [PATCH 054/102] added bindings to allowed applications endpoint, refactored all the allowed bindings to a new manager --- keystoneclient/tests/v3/test_fiware_roles.py | 99 ++++++++++++++----- .../v3/contrib/fiware_roles/allowed.py | 66 +++++++++++++ .../v3/contrib/fiware_roles/core.py | 2 + .../v3/contrib/fiware_roles/roles.py | 40 +------- 4 files changed, 142 insertions(+), 65 deletions(-) create mode 100644 keystoneclient/v3/contrib/fiware_roles/allowed.py diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 43666e418..24be46a22 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -16,6 +16,7 @@ from keystoneclient import exceptions from keystoneclient.tests.v3 import utils +from keystoneclient.v3.contrib.fiware_roles import allowed from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import role_assignments from keystoneclient.v3.contrib.fiware_roles import permissions @@ -76,26 +77,7 @@ def test_remove_role_from_user(self): application=app_id) - def test_list_user_allowed_roles_to_assign(self): - user_id = uuid.uuid4().hex - organization_id = uuid.uuid4().hex - allowed_roles_ref = { - 'allowed_roles': { - 'some_application': [ - uuid.uuid4().hex, - uuid.uuid4().hex, - ] - } - } - self.stub_url('GET', - [self.path_prefix, 'users', user_id, - 'organizations', organization_id, - 'roles/allowed'], - json=allowed_roles_ref) - allowed_roles = self.manager.list_user_allowed_roles_to_assign( - user=user_id, organization=organization_id) - self.assertEqual(2, len(allowed_roles['some_application'])) # ROLES-ORGANIZATIONS @@ -128,25 +110,90 @@ def test_remove_role_from_organization(self): application=app_id) +class AllowedTests(utils.TestCase): + + def setUp(self): + super(AllowedTests, self).setUp() + self.manager = self.client.fiware_roles.allowed + self.path_prefix = EXTENSION_PATH + + def test_list_user_allowed_roles_to_assign(self): + user_id = uuid.uuid4().hex + organization_id = uuid.uuid4().hex + allowed_roles_ref = { + 'allowed_roles': { + 'some_application': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } + } + self.stub_url('GET', + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, + 'roles/allowed'], + json=allowed_roles_ref) + allowed_roles = self.manager.list_user_allowed_roles_to_assign( + user=user_id, organization=organization_id) + + self.assertEqual(2, len(allowed_roles['some_application'])) + def test_list_organization_allowed_roles_to_assign(self): - organization_id = uuid.uuid4().hex - allowed_roles_ref = { + organization_id = uuid.uuid4().hex + allowed_roles_ref = { 'allowed_roles': { 'some_application': [ uuid.uuid4().hex, uuid.uuid4().hex, ] } - } - self.stub_url('GET', + } + self.stub_url('GET', [self.path_prefix, 'organizations', organization_id, 'roles/allowed'], json=allowed_roles_ref) - allowed_roles = self.manager.list_organization_allowed_roles_to_assign( - organization=organization_id) + allowed_roles = self.manager.list_organization_allowed_roles_to_assign( + organization=organization_id) + + self.assertEqual(2, len(allowed_roles['some_application'])) + + def test_list_user_allowed_applications_to_manage(self): + user_id = uuid.uuid4().hex + organization_id = uuid.uuid4().hex + allowed_applications_ref = { + 'allowed_applications': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } + self.stub_url('GET', + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, + 'applications/allowed'], + json=allowed_applications_ref) + allowed_applications = self.manager.list_user_allowed_applications_to_manage( + user=user_id, organization=organization_id) - self.assertEqual(2, len(allowed_roles['some_application'])) + self.assertEqual(2, len(allowed_applications)) + + def test_list_organization_allowed_applications_to_manage(self): + organization_id = uuid.uuid4().hex + allowed_applications_ref = { + 'allowed_applications': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } + self.stub_url('GET', + [self.path_prefix, + 'organizations', organization_id, + 'applications/allowed'], + json=allowed_applications_ref) + allowed_applications = \ + self.manager.list_organization_allowed_applications_to_manage( + organization=organization_id) + self.assertEqual(2, len(allowed_applications)) class RoleAssignmentsTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/allowed.py b/keystoneclient/v3/contrib/fiware_roles/allowed.py new file mode 100644 index 000000000..e0524923f --- /dev/null +++ b/keystoneclient/v3/contrib/fiware_roles/allowed.py @@ -0,0 +1,66 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import json + +from keystoneclient import base +from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH + + + +class AllowedManager(base.Manager): + """Manager class for obtaining allowed items based on internal permissions + in the FIWARE ROLES extension for Keystone. + + For more information about the extension: https://www.github.com/ging/keystone + """ + base_url = ROLES_PATH + + def list_user_allowed_roles_to_assign(self, user, organization): + """Obtain a list of all the roles the user is allowed to assign + for every application. + """ + endpoint = (self.base_url + '/users/{0}/organizations/{1}/roles/allowed' + ).format(base.getid(user), base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_roles = json.loads(resp.content)['allowed_roles'] + return allowed_roles + + def list_organization_allowed_roles_to_assign(self, organization): + """Obtain a list of all the roles the user is allowed to assign + for every application. + """ + endpoint = self.base_url + '/organizations/{0}/roles/allowed'.format( + base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_roles = json.loads(resp.content)['allowed_roles'] + return allowed_roles + + def list_user_allowed_applications_to_manage(self, user, organization): + """Obtain a list of all the applications the user is allowed to manage. + """ + endpoint = (self.base_url + '/users/{0}/organizations/{1}/applications/allowed' + ).format(base.getid(user), base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_applications = json.loads(resp.content)['allowed_applications'] + return allowed_applications + + def list_organization_allowed_applications_to_manage(self, organization): + """Obtain a list of all the applications the user is allowed to manage. + """ + endpoint = self.base_url + '/organizations/{0}/applications/allowed'.format( + base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_applications = json.loads(resp.content)['allowed_applications'] + return allowed_applications \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/core.py b/keystoneclient/v3/contrib/fiware_roles/core.py index 20d058fc7..4963d539e 100644 --- a/keystoneclient/v3/contrib/fiware_roles/core.py +++ b/keystoneclient/v3/contrib/fiware_roles/core.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from keystoneclient.v3.contrib.fiware_roles import allowed from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import role_assignments from keystoneclient.v3.contrib.fiware_roles import permissions @@ -22,4 +23,5 @@ def __init__(self, api): self.roles = roles.RoleManager(api) self.permissions = permissions.PermissionManager(api) self.role_assignments = role_assignments.RoleAssignmentManager(api) + self.allowed = allowed.AllowedManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index d7318c000..a9c8c3563 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json from keystoneclient import base from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH @@ -79,24 +78,6 @@ def remove_from_user(self, role, user, organization, application): return super(RoleManager, self).delete(base_url=base_url, role_id=base.getid(role)) - - def list_user_allowed_roles_to_assign(self, user, organization): - """Obtain a list of all the roles the user is allowed to assign - for every application. - """ - endpoint = (self.base_url + '/users/{0}/organizations/{1}/roles/allowed' - ).format(base.getid(user), base.getid(organization)) - resp, body = self.client.get(endpoint) - allowed_roles = json.loads(resp.content)['allowed_roles'] - return allowed_roles - # roles_as_resource = {} - # for app in allowed_roles: - # for role in allowed_roles[app]: - # roles_as_resource[app] = roles_as_resource.get(app, []) - # roles_as_resource[app].append(self.resource_class(self, role)) - # return roles_as_resource - - # ROLES-ORGANIZATIONS def add_to_organization(self, role, organization, application): base_url = (self.base_url + '/organizations/{0}/applications/{1}' @@ -110,23 +91,4 @@ def remove_from_organization(self, role, organization, application): base_url = (self.base_url + '/organizations/{0}/applications/{1}' ).format(base.getid(organization), base.getid(application)) return super(RoleManager, self).delete(base_url=base_url, - role_id=base.getid(role)) - - - def list_organization_allowed_roles_to_assign(self, organization): - """Obtain a list of all the roles the user is allowed to assign - for every application. - """ - endpoint = self.base_url + '/organizations/{0}/roles/allowed'.format( - base.getid(organization)) - resp, body = self.client.get(endpoint) - allowed_roles = json.loads(resp.content)['allowed_roles'] - return allowed_roles - # roles_as_resource = {} - # for app in allowed_roles: - # for role in allowed_roles[app]: - # roles_as_resource[app] = roles_as_resource.get(app, []) - # roles_as_resource[app].append(self.resource_class(self, role)) - # return roles_as_resource - - \ No newline at end of file + role_id=base.getid(role)) \ No newline at end of file From d61e1d70691deb6f669d4b5bd68387b9dd292566 Mon Sep 17 00:00:00 2001 From: sorube13 Date: Tue, 24 Feb 2015 13:22:28 +0100 Subject: [PATCH 055/102] added username to registration --- keystoneclient/v3/contrib/user_registration/users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 9bee312d7..40413370b 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -31,12 +31,12 @@ class UsersManager(base.CrudManager): def register_user(self, name, domain=None, password=None, - email=None, description=None, **kwargs): + username=None, description=None, **kwargs): user_data = base.filter_none(name=name, domain_id=base.getid(domain), password=password, - email=email, + username=username, description=description, **kwargs) From 592147f5e30b4cdf3dcdb04c0ba9485ab23b6641 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 25 Feb 2015 11:52:26 +0100 Subject: [PATCH 056/102] bindings for manage roles permission --- keystoneclient/tests/v3/test_fiware_roles.py | 38 +++++++++++++++++++ .../v3/contrib/fiware_roles/allowed.py | 18 +++++++++ 2 files changed, 56 insertions(+) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/v3/test_fiware_roles.py index 24be46a22..40b1950eb 100644 --- a/keystoneclient/tests/v3/test_fiware_roles.py +++ b/keystoneclient/tests/v3/test_fiware_roles.py @@ -195,6 +195,44 @@ def test_list_organization_allowed_applications_to_manage(self): self.assertEqual(2, len(allowed_applications)) + def test_list_user_allowed_applications_to_manage_roles(self): + user_id = uuid.uuid4().hex + organization_id = uuid.uuid4().hex + allowed_applications_ref = { + 'allowed_applications': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } + self.stub_url('GET', + [self.path_prefix, 'users', user_id, + 'organizations', organization_id, + 'applications/allowed_roles'], + json=allowed_applications_ref) + allowed_applications = self.manager.list_user_allowed_applications_to_manage_roles( + user=user_id, organization=organization_id) + + self.assertEqual(2, len(allowed_applications)) + + def test_list_organization_allowed_applications_to_manage_roles(self): + organization_id = uuid.uuid4().hex + allowed_applications_ref = { + 'allowed_applications': [ + uuid.uuid4().hex, + uuid.uuid4().hex, + ] + } + self.stub_url('GET', + [self.path_prefix, + 'organizations', organization_id, + 'applications/allowed_roles'], + json=allowed_applications_ref) + allowed_applications = \ + self.manager.list_organization_allowed_applications_to_manage_roles( + organization=organization_id) + + self.assertEqual(2, len(allowed_applications)) + class RoleAssignmentsTests(utils.TestCase, utils.CrudTests): diff --git a/keystoneclient/v3/contrib/fiware_roles/allowed.py b/keystoneclient/v3/contrib/fiware_roles/allowed.py index e0524923f..66995c77b 100644 --- a/keystoneclient/v3/contrib/fiware_roles/allowed.py +++ b/keystoneclient/v3/contrib/fiware_roles/allowed.py @@ -63,4 +63,22 @@ def list_organization_allowed_applications_to_manage(self, organization): base.getid(organization)) resp, body = self.client.get(endpoint) allowed_applications = json.loads(resp.content)['allowed_applications'] + return allowed_applications + + def list_user_allowed_applications_to_manage_roles(self, user, organization): + """Obtain a list of all the applications the user is allowed to manage. + """ + endpoint = (self.base_url + '/users/{0}/organizations/{1}/applications/allowed_roles' + ).format(base.getid(user), base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_applications = json.loads(resp.content)['allowed_applications'] + return allowed_applications + + def list_organization_allowed_applications_to_manage_roles(self, organization): + """Obtain a list of all the applications the user is allowed to manage. + """ + endpoint = self.base_url + '/organizations/{0}/applications/allowed_roles'.format( + base.getid(organization)) + resp, body = self.client.get(endpoint) + allowed_applications = json.loads(resp.content)['allowed_applications'] return allowed_applications \ No newline at end of file From 6a09e5c4dffddebd407242f539302434fd1eb3f6 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Fri, 27 Feb 2015 12:41:44 +0100 Subject: [PATCH 057/102] updated tests location, fixed typo --- keystoneclient/tests/{ => unit}/v3/test_fiware_roles.py | 0 keystoneclient/tests/{ => unit}/v3/test_oauth2.py | 0 keystoneclient/tests/{ => unit}/v3/test_user_registration.py | 5 ++++- keystoneclient/v3/contrib/user_registration/users.py | 3 ++- 4 files changed, 6 insertions(+), 2 deletions(-) rename keystoneclient/tests/{ => unit}/v3/test_fiware_roles.py (100%) rename keystoneclient/tests/{ => unit}/v3/test_oauth2.py (100%) rename keystoneclient/tests/{ => unit}/v3/test_user_registration.py (95%) diff --git a/keystoneclient/tests/v3/test_fiware_roles.py b/keystoneclient/tests/unit/v3/test_fiware_roles.py similarity index 100% rename from keystoneclient/tests/v3/test_fiware_roles.py rename to keystoneclient/tests/unit/v3/test_fiware_roles.py diff --git a/keystoneclient/tests/v3/test_oauth2.py b/keystoneclient/tests/unit/v3/test_oauth2.py similarity index 100% rename from keystoneclient/tests/v3/test_oauth2.py rename to keystoneclient/tests/unit/v3/test_oauth2.py diff --git a/keystoneclient/tests/v3/test_user_registration.py b/keystoneclient/tests/unit/v3/test_user_registration.py similarity index 95% rename from keystoneclient/tests/v3/test_user_registration.py rename to keystoneclient/tests/unit/v3/test_user_registration.py index c900ec38c..cc21647d2 100644 --- a/keystoneclient/tests/v3/test_user_registration.py +++ b/keystoneclient/tests/unit/v3/test_user_registration.py @@ -62,11 +62,14 @@ def test_activate_user(self): def test_reset_password(self): user_id = uuid.uuid4().hex token = uuid.uuid4().hex + password = uuid.uuid4().hex self.stub_url('PATCH', [self.path_prefix, 'reset_password', token, self.collection_key, user_id], status_code=204) - self.manager.reset_password(user=user_id, reset_token=token) + self.manager.reset_password(user=user_id, + reset_token=token, + new_password=password) class ActivationKeyTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/user_registration/users.py b/keystoneclient/v3/contrib/user_registration/users.py index 40413370b..72fc6f256 100644 --- a/keystoneclient/v3/contrib/user_registration/users.py +++ b/keystoneclient/v3/contrib/user_registration/users.py @@ -54,7 +54,8 @@ def activate_user(self, user, activation_key): method='PATCH') def reset_password(self, user, reset_token, new_password): - url = self.base_url + '/reset_password/{0}/users/{1}'.format(base.getid(reset_token),user.id) + url = self.base_url + '/reset_password/{0}/users/{1}'.format( + base.getid(reset_token), base.getid(user)) body = { 'user' : { 'password' : new_password, From 99d1c412be85c819bfa24c65b8f5825873b306e3 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Fri, 27 Feb 2015 12:44:01 +0100 Subject: [PATCH 058/102] updated user-consumer url --- keystoneclient/tests/unit/v3/test_oauth2.py | 3 ++- keystoneclient/v3/contrib/oauth2/consumers.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_oauth2.py b/keystoneclient/tests/unit/v3/test_oauth2.py index a0318faf7..7aa6630b7 100644 --- a/keystoneclient/tests/unit/v3/test_oauth2.py +++ b/keystoneclient/tests/unit/v3/test_oauth2.py @@ -94,7 +94,8 @@ def test_list_consumers_by_user(self): ref_list = [self.new_ref(), self.new_ref()] self.stub_entity('GET', - parts=[self.path_prefix, 'users', user_id, self.collection_key], + parts=['users', user_id, + self.path_prefix, self.collection_key], entity=ref_list) returned_list = self.manager.list(user=user_id) diff --git a/keystoneclient/v3/contrib/oauth2/consumers.py b/keystoneclient/v3/contrib/oauth2/consumers.py index c30a9e007..d1d8d10fe 100644 --- a/keystoneclient/v3/contrib/oauth2/consumers.py +++ b/keystoneclient/v3/contrib/oauth2/consumers.py @@ -78,7 +78,7 @@ def delete(self, consumer): def list(self, user=None, **kwargs): if user: - base_url = self.base_url + '/users/%s' % base.getid(user) + base_url = '/users/{0}'.format(base.getid(user)) + self.base_url else: base_url = self.base_url From dd29ea5ae3f843cbb0830ac324865a710541b987 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Tue, 17 Mar 2015 10:54:25 +0100 Subject: [PATCH 059/102] application to application_id --- keystoneclient/v3/contrib/fiware_roles/permissions.py | 4 ++-- keystoneclient/v3/contrib/fiware_roles/roles.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/keystoneclient/v3/contrib/fiware_roles/permissions.py b/keystoneclient/v3/contrib/fiware_roles/permissions.py index bf14f1471..e068bf96e 100644 --- a/keystoneclient/v3/contrib/fiware_roles/permissions.py +++ b/keystoneclient/v3/contrib/fiware_roles/permissions.py @@ -38,7 +38,7 @@ def create(self, name, is_internal=False, application=None, **kwargs): return super(PermissionManager, self).create( name=name, is_internal=is_internal, - application=base.getid(application), + application_id=base.getid(application), **kwargs) def get(self, permission): return super(PermissionManager, self).get( @@ -50,7 +50,7 @@ def update(self, permission, name=None, is_internal=False, permission_id=base.getid(permission), name=name, is_internal=is_internal, - application=base.getid(application), + application_id=base.getid(application), **kwargs) diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index a9c8c3563..108edb695 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -34,7 +34,7 @@ def create(self, name, is_internal=False, application=None, **kwargs): return super(RoleManager, self).create( name=name, is_internal=is_internal, - application=base.getid(application), + application_id=base.getid(application), **kwargs) @@ -47,7 +47,7 @@ def update(self, role, name=None, is_internal=False, return super(RoleManager, self).update(role_id=base.getid(role), name=name, is_internal=is_internal, - application=base.getid(application), + application_id=base.getid(application), **kwargs) From 09e16d907aa2fb6e2d9b06d85ced12c1b24c1621 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Tue, 14 Apr 2015 18:15:07 +0200 Subject: [PATCH 060/102] list acces tokens for users --- keystoneclient/tests/unit/v3/test_oauth2.py | 39 ++++++++++++++--- .../v3/contrib/fiware_roles/roles.py | 1 - .../v3/contrib/oauth2/access_tokens.py | 43 +++++++++++++++++-- 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_oauth2.py b/keystoneclient/tests/unit/v3/test_oauth2.py index 7aa6630b7..33281e88f 100644 --- a/keystoneclient/tests/unit/v3/test_oauth2.py +++ b/keystoneclient/tests/unit/v3/test_oauth2.py @@ -15,13 +15,9 @@ import base64 import urllib import uuid -import mock -import six -from six.moves.urllib import parse as urlparse -from testtools import matchers +from keystoneclient import exceptions from keystoneclient import session -from keystoneclient.openstack.common import timeutils from keystoneclient.tests.v3 import client_fixtures from keystoneclient.tests.v3 import utils from keystoneclient.v3.contrib.oauth2 import auth @@ -192,7 +188,7 @@ def test_request_authorization(self): assert(isinstance(response_body, dict)) -class AccessTokenTests(utils.TestCase): +class AccessTokenTests(utils.TestCase, utils.CrudTests): def setUp(self): @@ -200,8 +196,10 @@ def setUp(self): self.manager = self.client.oauth2.access_tokens self.model = access_tokens.AccessToken self.path_prefix = 'OS-OAUTH2' + self.key = 'access_token' + self.collection_key = 'access_tokens' - def test_create_access_token(self): + def test_create(self): consumer_id = uuid.uuid4().hex consumer_secret = uuid.uuid4().hex redirect_uri = uuid.uuid4().hex @@ -242,6 +240,33 @@ def test_create_access_token(self): expected_auth = 'Basic ' + base64.b64encode(auth_string) self.assertRequestHeaderEqual('Authorization', expected_auth) + def test_list_for_user(self): + # TODO(garcianavalon) implement + pass + + def test_list_params(self): + # list not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) + + def test_list(self): + # list not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.list) + + def test_update(self): + # Update not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.update) + + def test_delete(self): + # Delete not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.delete) + + def test_get(self): + # Get not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.get) + + def test_find(self): + # Find not supported for access tokens + self.assertRaises(exceptions.MethodNotImplemented, self.manager.find) class AuthenticateWithOAuthTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/fiware_roles/roles.py b/keystoneclient/v3/contrib/fiware_roles/roles.py index 108edb695..2e1665224 100644 --- a/keystoneclient/v3/contrib/fiware_roles/roles.py +++ b/keystoneclient/v3/contrib/fiware_roles/roles.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - from keystoneclient import base from keystoneclient.v3.contrib.fiware_roles.utils import ROLES_PATH diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 778724044..81f637636 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -16,6 +16,7 @@ import json from keystoneclient import base +from keystoneclient import exceptions from keystoneclient.v3.contrib.oauth2 import utils class AccessToken(base.Resource): @@ -25,10 +26,13 @@ class AccessToken(base.Resource): class AccessTokenManager(base.CrudManager): """Manager class for manipulating identity OAuth access tokens.""" resource_class = AccessToken + collection_key = 'access_tokens' + key = 'access_token' + base_url = utils.OAUTH2_PATH def create(self, consumer_id, consumer_secret, authorization_code, redirect_uri): - endpoint = utils.OAUTH2_PATH + '/access_token' + endpoint = self.base_url + '/access_token' headers, body = self._generate_json_request(consumer_id, consumer_secret, authorization_code, redirect_uri) @@ -38,6 +42,38 @@ def create(self, consumer_id, consumer_secret, authorization_code, token = json.loads(resp.content) return self.resource_class(self, token) + def list_for_user(self, user, **kwargs): + """lists all the created access token for a user.""" + base_url = self.base_url + return super(AccessTokenManager, self).list( + base_url=base_url, + user=base.getid(user), + **kwargs) + + def list(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'List not supported for access_tokens') + + def update(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'Update not supported for access_tokens') + + def get(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'Get not supported for access_tokens') + + def find(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'Find not supported for access_tokens') + + def put(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'Put not supported for access_tokens') + + def delete(self, **kwargs): + raise exceptions.MethodNotImplemented( + 'Delete not supported for access_tokens') + def _generate_json_request(self, consumer_id, consumer_secret, authorization_code, redirect_uri): body = { @@ -48,10 +84,11 @@ def _generate_json_request(self, consumer_id, consumer_secret, } } headers = { - 'Authorization': self._http_basic(consumer_id,consumer_secret) + 'Authorization': self._http_basic(consumer_id, consumer_secret) } return headers, body def _http_basic(self, consumer_id, consumer_secret): auth_string = consumer_id + ':' + consumer_secret - return 'Basic ' + base64.b64encode(auth_string) \ No newline at end of file + return 'Basic ' + base64.b64encode(auth_string) + From 59d3bb166e703622ee9b42e7968fe674fe7890d8 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 15 Apr 2015 11:13:45 +0200 Subject: [PATCH 061/102] basic acces token list for user --- .../tests/unit/v3/test_fiware_roles.py | 2 +- keystoneclient/tests/unit/v3/test_oauth2.py | 27 ++++++++++++++++--- .../tests/unit/v3/test_user_registration.py | 3 +-- .../v3/contrib/oauth2/access_tokens.py | 6 ++--- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_fiware_roles.py b/keystoneclient/tests/unit/v3/test_fiware_roles.py index 40b1950eb..e27e3c19b 100644 --- a/keystoneclient/tests/unit/v3/test_fiware_roles.py +++ b/keystoneclient/tests/unit/v3/test_fiware_roles.py @@ -15,7 +15,7 @@ import uuid from keystoneclient import exceptions -from keystoneclient.tests.v3 import utils +from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib.fiware_roles import allowed from keystoneclient.v3.contrib.fiware_roles import roles from keystoneclient.v3.contrib.fiware_roles import role_assignments diff --git a/keystoneclient/tests/unit/v3/test_oauth2.py b/keystoneclient/tests/unit/v3/test_oauth2.py index 33281e88f..1c36e6dd6 100644 --- a/keystoneclient/tests/unit/v3/test_oauth2.py +++ b/keystoneclient/tests/unit/v3/test_oauth2.py @@ -18,8 +18,8 @@ from keystoneclient import exceptions from keystoneclient import session -from keystoneclient.tests.v3 import client_fixtures -from keystoneclient.tests.v3 import utils +from keystoneclient.tests.unit.v3 import client_fixtures +from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib.oauth2 import auth from keystoneclient.v3.contrib.oauth2 import access_tokens from keystoneclient.v3.contrib.oauth2 import authorization_codes @@ -241,8 +241,27 @@ def test_create(self): self.assertRequestHeaderEqual('Authorization', expected_auth) def test_list_for_user(self): - # TODO(garcianavalon) implement - pass + user_id = uuid.uuid4().hex + access_tokens_ref = { + 'access_tokens': [ + { + 'id': uuid.uuid4().hex, + }, + { + 'id': uuid.uuid4().hex, + }, + ] + } + self.stub_url('GET', + [self.path_prefix, 'users', user_id, + self.collection_key], + status_code=204, + json=access_tokens_ref) + + result = self.manager.list_for_user(user=user_id) + + self.assertEqual(len(access_tokens_ref['access_tokens']), + len(result)) def test_list_params(self): # list not supported for access tokens diff --git a/keystoneclient/tests/unit/v3/test_user_registration.py b/keystoneclient/tests/unit/v3/test_user_registration.py index cc21647d2..c1c7b6182 100644 --- a/keystoneclient/tests/unit/v3/test_user_registration.py +++ b/keystoneclient/tests/unit/v3/test_user_registration.py @@ -14,8 +14,7 @@ import uuid -from keystoneclient import exceptions -from keystoneclient.tests.v3 import utils +from keystoneclient.tests.unit.v3 import utils from keystoneclient.v3.contrib.user_registration import users from keystoneclient.v3.contrib.user_registration import activation_key from keystoneclient.v3.contrib.user_registration import token diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index 81f637636..ac88cea37 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -44,11 +44,9 @@ def create(self, consumer_id, consumer_secret, authorization_code, def list_for_user(self, user, **kwargs): """lists all the created access token for a user.""" - base_url = self.base_url + base_url = self.base_url + '/users/{0}'.format(base.getid(user)) return super(AccessTokenManager, self).list( - base_url=base_url, - user=base.getid(user), - **kwargs) + base_url=base_url, **kwargs) def list(self, **kwargs): raise exceptions.MethodNotImplemented( From 98c43d4aa5d0bb97170243eaeb5be8d0e73a632b Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 15 Apr 2015 11:31:58 +0200 Subject: [PATCH 062/102] url changed --- keystoneclient/tests/unit/v3/test_oauth2.py | 4 ++-- keystoneclient/v3/contrib/oauth2/access_tokens.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_oauth2.py b/keystoneclient/tests/unit/v3/test_oauth2.py index 1c36e6dd6..5bea0b8e1 100644 --- a/keystoneclient/tests/unit/v3/test_oauth2.py +++ b/keystoneclient/tests/unit/v3/test_oauth2.py @@ -253,8 +253,8 @@ def test_list_for_user(self): ] } self.stub_url('GET', - [self.path_prefix, 'users', user_id, - self.collection_key], + ['users', user_id, + self.path_prefix, self.collection_key], status_code=204, json=access_tokens_ref) diff --git a/keystoneclient/v3/contrib/oauth2/access_tokens.py b/keystoneclient/v3/contrib/oauth2/access_tokens.py index ac88cea37..4ba479dbd 100644 --- a/keystoneclient/v3/contrib/oauth2/access_tokens.py +++ b/keystoneclient/v3/contrib/oauth2/access_tokens.py @@ -44,7 +44,7 @@ def create(self, consumer_id, consumer_secret, authorization_code, def list_for_user(self, user, **kwargs): """lists all the created access token for a user.""" - base_url = self.base_url + '/users/{0}'.format(base.getid(user)) + base_url = '/users/{0}'.format(base.getid(user)) + self.base_url return super(AccessTokenManager, self).list( base_url=base_url, **kwargs) From 91fb4f9b157ddb31a8606f657aa502280e19fd44 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 6 May 2015 11:34:14 +0200 Subject: [PATCH 063/102] basic endpoint group filters crud --- .../unit/v3/test_endpoint_groups_filters.py | 39 +++++++++++++++ keystoneclient/v3/client.py | 2 + keystoneclient/v3/contrib/endpoint_filter.py | 49 ++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 keystoneclient/tests/unit/v3/test_endpoint_groups_filters.py diff --git a/keystoneclient/tests/unit/v3/test_endpoint_groups_filters.py b/keystoneclient/tests/unit/v3/test_endpoint_groups_filters.py new file mode 100644 index 000000000..eb48c03bc --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_endpoint_groups_filters.py @@ -0,0 +1,39 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import uuid + +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3.contrib import endpoint_filter + + +EXTENSION_PATH = 'OS-EP-FILTER' + +class EndpointGroupsFiltersTests(utils.TestCase, utils.CrudTests): + + + def setUp(self): + super(EndpointGroupsFiltersTests, self).setUp() + self.key = 'endpoint_group' + self.collection_key = 'endpoint_groups' + self.model = endpoint_filter.EndpointGroupFilter + self.manager = self.client.endpoint_groups + self.path_prefix = EXTENSION_PATH + + def new_ref(self, **kwargs): + kwargs = super(EndpointGroupsFiltersTests, self).new_ref(**kwargs) + kwargs.setdefault('name', uuid.uuid4().hex) + kwargs.setdefault('description', uuid.uuid4().hex) + kwargs.setdefault('filters', {}) + return kwargs \ No newline at end of file diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index e6915a1aa..78b534fa1 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -183,6 +183,8 @@ def __init__(self, **kwargs): self.groups = groups.GroupManager(self._adapter) self.oauth1 = oauth1.create_oauth_manager(self._adapter) # TODO(garcianavalon) document this + self.endpoint_groups = endpoint_filter.EndpointGroupFilterManager( + self._adapter) self.fiware_roles = fiware_roles.FiwareRolesManager(self) self.oauth2 = oauth2.create_oauth_manager(self) self.user_registration = user_registration.UserRegistrationManager(self) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index 0da79b81b..ded12ba7e 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -18,10 +18,11 @@ from keystoneclient.v3 import endpoints from keystoneclient.v3 import projects +OS_EP_FILTER_EXT = 'OS-EP-FILTER' class EndpointFilterManager(base.Manager): """Manager class for manipulating project-endpoint associations.""" - OS_EP_FILTER_EXT = '/OS-EP-FILTER' + def _build_base_url(self, project=None, endpoint=None): project_id = base.getid(project) @@ -37,7 +38,7 @@ def _build_base_url(self, project=None, endpoint=None): msg = _('Must specify a project, an endpoint, or both') raise exceptions.ValidationError(msg) - return self.OS_EP_FILTER_EXT + api_path + return '/' + OS_EP_FILTER_EXT + api_path def add_endpoint_to_project(self, project, endpoint): """Create a project-endpoint association.""" @@ -87,3 +88,47 @@ def list_projects_for_endpoint(self, endpoint): base_url, projects.ProjectManager.collection_key, obj_class=projects.ProjectManager.resource_class) + + +class EndpointGroupFilter(base.Resource): + pass + +class EndpointGroupFilterManager(base.CrudManager): + """Manager class for Endpoint Group Filters.""" + + resource_class = EndpointGroupFilter + collection_key = 'endpoint_groups' + key = 'endpoint_group' + base_url = OS_EP_FILTER_EXT + + def create(self, name, description=None, filters=None, **kwargs): + filters = filters if filters else {} + + return super(EndpointGroupFilterManager, self).create( + name=name, + description=description, + filters=filters, + **kwargs) + + def get(self, endpoint_group): + return super(EndpointGroupFilterManager, self).get( + endpoint_group_id=base.getid(endpoint_group)) + + + def update(self, endpoint_group, name=None, description=None, filters=None, **kwargs): + return super(EndpointGroupFilterManager, self).update( + endpoint_group_id=base.getid(endpoint_group), + name=name, + description=description, + filters=filters, + **kwargs) + + + def delete(self, endpoint_group): + return super(EndpointGroupFilterManager, self).delete( + endpoint_group_id=base.getid(endpoint_group)) + + + def list(self, **kwargs): + base_url = self.base_url + return super(EndpointGroupFilterManager, self).list(base_url=base_url, **kwargs) \ No newline at end of file From 987920e45270e63dfca49c9c13fedffeb7df8f6b Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 6 May 2015 15:07:01 +0200 Subject: [PATCH 064/102] =?UTF-8?q?endpoint=20group=20project=20relationsh?= =?UTF-8?q?ips=C2=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- keystoneclient/v3/contrib/endpoint_filter.py | 67 +++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index ded12ba7e..7dd05f496 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -23,7 +23,6 @@ class EndpointFilterManager(base.Manager): """Manager class for manipulating project-endpoint associations.""" - def _build_base_url(self, project=None, endpoint=None): project_id = base.getid(project) endpoint_id = base.getid(endpoint) @@ -93,6 +92,7 @@ def list_projects_for_endpoint(self, endpoint): class EndpointGroupFilter(base.Resource): pass + class EndpointGroupFilterManager(base.CrudManager): """Manager class for Endpoint Group Filters.""" @@ -131,4 +131,67 @@ def delete(self, endpoint_group): def list(self, **kwargs): base_url = self.base_url - return super(EndpointGroupFilterManager, self).list(base_url=base_url, **kwargs) \ No newline at end of file + return super(EndpointGroupFilterManager, self).list(base_url=base_url, **kwargs) + + def _build_base_url(self, project=None, endpoint_group=None): + project_id = base.getid(project) + endpoint_group_id = base.getid(endpoint_group) + + if project_id and endpoint_group_id: + api_path = '/endpoint_groups/%s/projects/%s' % (project_id, endpoint_group_id) + elif project_id: + api_path = '/endpoint_groups/projects/%s' % (project_id) + elif endpoint_group_id: + api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id) + else: + msg = _('Must specify a project, an endpoint_group, or both') + raise exceptions.ValidationError(msg) + + return '/' + OS_EP_FILTER_EXT + api_path + + def add_endpoint_group_to_project(self, project, endpoint_group): + """Create a project-endpoint_group association. + PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} + """ + if not (project and endpoint_group): + raise ValueError(_('project and endpoint_group are required')) + + base_url = self._build_base_url(project=project, + endpoint_group=endpoint_group) + return super(EndpointGroupFilterManager, self)._put(url=base_url) + + def delete_endpoint_group_from_project(self, project, endpoint_group): + """Remove a project-endpoint_group association. + DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} + """ + if not (project and endpoint_group): + raise ValueError(_('project and endpoint_group are required')) + + base_url = self._build_base_url(project=project, + endpoint_group=endpoint_group) + return super(EndpointGroupFilterManager, self)._delete(url=base_url) + + def check_endpoint_group_in_project(self, project, endpoint_group): + """Checks if project-endpoint_group association exist. + HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects/{project_id} + """ + if not (project and endpoint_group): + raise ValueError(_('project and endpoint_group are required')) + + base_url = self._build_base_url(project=project, + endpoint_group=endpoint_group) + return super(EndpointGroupFilterManager, self)._head(url=base_url) + + def list_endpoint_groups_for_project(self, project): + """List all endpoints for a given project. + GET /OS-EP-FILTER/endpoint_groups/projects/{project_id} + + """ + if not project: + raise ValueError(_('project is required')) + + base_url = self._build_base_url(project=project) + return super(EndpointGroupFilterManager, self)._list( + base_url, + self.collection_key, + obj_class=self.resource_class) \ No newline at end of file From e0052cb30203a2c1f3174d6188f2f43c6cf38ffe Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 6 May 2015 15:32:25 +0200 Subject: [PATCH 065/102] fixed endpoint and project id ordering --- keystoneclient/v3/contrib/endpoint_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index 7dd05f496..53239c6b8 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -28,7 +28,7 @@ def _build_base_url(self, project=None, endpoint=None): endpoint_id = base.getid(endpoint) if project_id and endpoint_id: - api_path = '/projects/%s/endpoints/%s' % (project_id, endpoint_id) + api_path = '/projects/%s/endpoints/%s' % (endpoint_id, project_id) elif project_id: api_path = '/projects/%s/endpoints' % (project_id) elif endpoint_id: From 40ad69d4fc8291a3343735df348e348847bf11b0 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 6 May 2015 15:39:05 +0200 Subject: [PATCH 066/102] use format in build url --- keystoneclient/v3/contrib/endpoint_filter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index 53239c6b8..b07af2e2f 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -138,11 +138,12 @@ def _build_base_url(self, project=None, endpoint_group=None): endpoint_group_id = base.getid(endpoint_group) if project_id and endpoint_group_id: - api_path = '/endpoint_groups/%s/projects/%s' % (project_id, endpoint_group_id) + api_path = '/endpoint_groups/{0}/projects/{1}'.format(endpoint_group_id, + project_id) elif project_id: - api_path = '/endpoint_groups/projects/%s' % (project_id) + api_path = '/endpoint_groups/projects/{0}'.format(project_id) elif endpoint_group_id: - api_path = '/endpoint_groups/%s/projects' % (endpoint_group_id) + api_path = '/endpoint_groups/{0}/projects'.format(endpoint_group_id) else: msg = _('Must specify a project, an endpoint_group, or both') raise exceptions.ValidationError(msg) From 9f8787658d13a4167233c2a755d8bb13b4ef1c78 Mon Sep 17 00:00:00 2001 From: garcianavalon Date: Wed, 6 May 2015 19:01:56 +0200 Subject: [PATCH 067/102] not suported --- keystoneclient/v3/contrib/endpoint_filter.py | 27 ++++++++++---------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index b07af2e2f..bc1e25319 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -183,16 +183,17 @@ def check_endpoint_group_in_project(self, project, endpoint_group): endpoint_group=endpoint_group) return super(EndpointGroupFilterManager, self)._head(url=base_url) - def list_endpoint_groups_for_project(self, project): - """List all endpoints for a given project. - GET /OS-EP-FILTER/endpoint_groups/projects/{project_id} - - """ - if not project: - raise ValueError(_('project is required')) - - base_url = self._build_base_url(project=project) - return super(EndpointGroupFilterManager, self)._list( - base_url, - self.collection_key, - obj_class=self.resource_class) \ No newline at end of file + # NOTE(garcianavalon) this is documented but not suported in keystone... + # def list_endpoint_groups_for_project(self, project): + # """List all endpoints for a given project. + # GET /OS-EP-FILTER/endpoint_groups/projects/{project_id} + + # """ + # if not project: + # raise ValueError(_('project is required')) + + # base_url = self._build_base_url(project=project) + # return super(EndpointGroupFilterManager, self)._list( + # base_url, + # self.collection_key, + # obj_class=self.resource_class) \ No newline at end of file From f4573d72ef77272a48426f6f1869fd6d5cb52e2d Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 13 May 2015 09:40:46 +0200 Subject: [PATCH 068/102] added support for default organization option --- .../v3/contrib/fiware_roles/role_assignments.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/fiware_roles/role_assignments.py b/keystoneclient/v3/contrib/fiware_roles/role_assignments.py index 1a8033370..2289d563f 100644 --- a/keystoneclient/v3/contrib/fiware_roles/role_assignments.py +++ b/keystoneclient/v3/contrib/fiware_roles/role_assignments.py @@ -41,7 +41,7 @@ class RoleAssignmentManager(base.CrudManager): base_url = ROLES_PATH def list_user_role_assignments(self, user=None, organization=None, - application=None): + application=None, default_organization=False): """Lists role assignments for users. If no arguments are provided, all role assignments in the @@ -50,8 +50,10 @@ def list_user_role_assignments(self, user=None, organization=None, :param user: User to be used as query filter. (optional) :param organization: Project to be used as query filter. (optional) - :param application: Domain to be used as query + :param application: Application to be used as query filter. (optional) + :param default_organization: If set to true, the endpoint will filter role assignments + only in the default_project_id and the organization param is ignored. (optional) """ query_params = {} @@ -61,6 +63,8 @@ def list_user_role_assignments(self, user=None, organization=None, query_params['organization_id'] = base.getid(organization) if application: query_params['application_id'] = base.getid(application) + if default_organization: + query_params['default_organization'] = True base_url = self.base_url + '/users' return super(RoleAssignmentManager, self).list(base_url=base_url, From 8f9a317d25b0eda98e5283fe725353209388f278 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 24 Jun 2015 11:22:40 +0200 Subject: [PATCH 069/102] support for different response_types --- keystoneclient/v3/contrib/oauth2/authorization_codes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 483e13eba..59ce9e4bf 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -74,7 +74,7 @@ def authorize(self, consumer, scopes, redirect=False): return self.resource_class(self, authorization_code) - def request_authorization(self, consumer, redirect_uri, scope, state=None): + def request_authorization(self, consumer, redirect_uri, scope, response_type='code', state=None): """ Send the consumer credentials to the OAuth2 provider. The user then will be asked to authorize the client for the requested scopes. In @@ -101,7 +101,7 @@ def request_authorization(self, consumer, redirect_uri, scope, state=None): # NOTE(garcianavalon) we use a list of tuples to ensure param order # in the query string to be able to mock it during testing. credentials = [ - ('response_type', 'code'), + ('response_type', response_type), ('client_id', base.getid(consumer)), ('redirect_uri', redirect_uri), ('scope', scope_string), From 4b63f16f7bb43919e26d4cbe195a0ebe1fa5109d Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Wed, 24 Jun 2015 12:05:01 +0200 Subject: [PATCH 070/102] updated oslo.i18n to new namespace oslo_i18n --- keystoneclient/openstack/common/_i18n.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/keystoneclient/openstack/common/_i18n.py b/keystoneclient/openstack/common/_i18n.py index 52a5e8478..8ec37733c 100644 --- a/keystoneclient/openstack/common/_i18n.py +++ b/keystoneclient/openstack/common/_i18n.py @@ -16,7 +16,7 @@ """ -import oslo.i18n +import oslo_i18n # NOTE(dhellmann): This reference to o-s-l-o will be replaced by the @@ -24,7 +24,7 @@ # repository. It is OK to have more than one translation function # using the same domain, since there will still only be one message # catalog. -_translators = oslo.i18n.TranslatorFactory(domain='keystoneclient') +_translators = oslo_i18n.TranslatorFactory(domain='keystoneclient') # The primary translation function using the well-known name "_" _ = _translators.primary From 7dc26b39ea988d46669a11a6f6e10d648e4b9470 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Thu, 25 Jun 2015 10:49:32 +0200 Subject: [PATCH 071/102] support for token response in authorization code --- keystoneclient/v3/contrib/oauth2/authorization_codes.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/keystoneclient/v3/contrib/oauth2/authorization_codes.py b/keystoneclient/v3/contrib/oauth2/authorization_codes.py index 59ce9e4bf..e56ec9eff 100644 --- a/keystoneclient/v3/contrib/oauth2/authorization_codes.py +++ b/keystoneclient/v3/contrib/oauth2/authorization_codes.py @@ -68,9 +68,15 @@ def authorize(self, consumer, scopes, redirect=False): query = dict(urlparse.parse_qsl(parsed.query)) authorization_code = { 'redirect_uri':redirect_uri, - 'code': query['code'], 'state': query['state'] } + code = query.get('code', None) + if code: + authorization_code['code'] = code + + token = query.get('token', None) + if token: + authorization_code['token'] = token return self.resource_class(self, authorization_code) From e0106d01c92e2a3c8d9088d91fe5259baef02d44 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 29 Jun 2015 11:52:15 +0200 Subject: [PATCH 072/102] added project to endpoint_groups endpoint --- keystoneclient/v3/contrib/endpoint_filter.py | 25 ++++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/keystoneclient/v3/contrib/endpoint_filter.py b/keystoneclient/v3/contrib/endpoint_filter.py index bc1e25319..04e97fa82 100644 --- a/keystoneclient/v3/contrib/endpoint_filter.py +++ b/keystoneclient/v3/contrib/endpoint_filter.py @@ -141,7 +141,7 @@ def _build_base_url(self, project=None, endpoint_group=None): api_path = '/endpoint_groups/{0}/projects/{1}'.format(endpoint_group_id, project_id) elif project_id: - api_path = '/endpoint_groups/projects/{0}'.format(project_id) + api_path = '/projects/{0}/endpoint_groups'.format(project_id) elif endpoint_group_id: api_path = '/endpoint_groups/{0}/projects'.format(endpoint_group_id) else: @@ -183,17 +183,16 @@ def check_endpoint_group_in_project(self, project, endpoint_group): endpoint_group=endpoint_group) return super(EndpointGroupFilterManager, self)._head(url=base_url) - # NOTE(garcianavalon) this is documented but not suported in keystone... - # def list_endpoint_groups_for_project(self, project): - # """List all endpoints for a given project. - # GET /OS-EP-FILTER/endpoint_groups/projects/{project_id} + def list_endpoint_groups_for_project(self, project): + """List all endpoints for a given project. + GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups - # """ - # if not project: - # raise ValueError(_('project is required')) + """ + if not project: + raise ValueError(_('project is required')) - # base_url = self._build_base_url(project=project) - # return super(EndpointGroupFilterManager, self)._list( - # base_url, - # self.collection_key, - # obj_class=self.resource_class) \ No newline at end of file + base_url = self._build_base_url(project=project) + return super(EndpointGroupFilterManager, self)._list( + base_url, + self.collection_key, + obj_class=self.resource_class) \ No newline at end of file From 55c6be6cdffc1928e0354fe3b5b9c76e654dc94d Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Mon, 27 Jul 2015 14:44:54 +0200 Subject: [PATCH 073/102] update oslo.utils to oslo_utils --- keystoneclient/openstack/common/apiclient/base.py | 2 +- keystoneclient/openstack/common/apiclient/client.py | 4 ++-- keystoneclient/openstack/common/apiclient/utils.py | 2 +- keystoneclient/openstack/common/memorycache.py | 2 +- keystoneclient/tests/unit/client_fixtures.py | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/keystoneclient/openstack/common/apiclient/base.py b/keystoneclient/openstack/common/apiclient/base.py index 72d7999d1..fdfca59ea 100644 --- a/keystoneclient/openstack/common/apiclient/base.py +++ b/keystoneclient/openstack/common/apiclient/base.py @@ -26,7 +26,7 @@ import abc import copy -from oslo.utils import strutils +from oslo_utils import strutils import six from six.moves.urllib import parse diff --git a/keystoneclient/openstack/common/apiclient/client.py b/keystoneclient/openstack/common/apiclient/client.py index dd560aba5..1b4e14aad 100644 --- a/keystoneclient/openstack/common/apiclient/client.py +++ b/keystoneclient/openstack/common/apiclient/client.py @@ -34,8 +34,8 @@ except ImportError: import json -from oslo.utils import encodeutils -from oslo.utils import importutils +from oslo_utils import encodeutils +from oslo_utils import importutils import requests from keystoneclient.openstack.common._i18n import _ diff --git a/keystoneclient/openstack/common/apiclient/utils.py b/keystoneclient/openstack/common/apiclient/utils.py index 6aa2975aa..a45462134 100644 --- a/keystoneclient/openstack/common/apiclient/utils.py +++ b/keystoneclient/openstack/common/apiclient/utils.py @@ -11,7 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo.utils import encodeutils +from oslo_utils import encodeutils import six from keystoneclient.openstack.common._i18n import _ diff --git a/keystoneclient/openstack/common/memorycache.py b/keystoneclient/openstack/common/memorycache.py index 4826865a2..8eeef2a74 100644 --- a/keystoneclient/openstack/common/memorycache.py +++ b/keystoneclient/openstack/common/memorycache.py @@ -17,7 +17,7 @@ """Super simple fake memcache client.""" from oslo.config import cfg -from oslo.utils import timeutils +from oslo_utils import timeutils memcache_opts = [ cfg.ListOpt('memcached_servers', diff --git a/keystoneclient/tests/unit/client_fixtures.py b/keystoneclient/tests/unit/client_fixtures.py index b226e32bb..06351783e 100644 --- a/keystoneclient/tests/unit/client_fixtures.py +++ b/keystoneclient/tests/unit/client_fixtures.py @@ -544,12 +544,12 @@ class HackingCode(fixtures.Fixture): oslo_namespace_imports = { 'code': """ - import oslo.utils import oslo_utils - import oslo.utils.encodeutils + import oslo_utils + import oslo_utils.encodeutils import oslo_utils.encodeutils from oslo import utils - from oslo.utils import encodeutils + from oslo_utils import encodeutils from oslo_utils import encodeutils import oslo.serialization From 4575ee7d36ae0de8f9cadf00a170955e5c20b239 Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Tue, 27 Oct 2015 15:42:10 +0100 Subject: [PATCH 074/102] basic two factor methods --- keystoneclient/v3/client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keystoneclient/v3/client.py b/keystoneclient/v3/client.py index 78b534fa1..ebb239ae8 100644 --- a/keystoneclient/v3/client.py +++ b/keystoneclient/v3/client.py @@ -29,6 +29,7 @@ from keystoneclient.v3.contrib import oauth2 from keystoneclient.v3.contrib import trusts from keystoneclient.v3.contrib import user_registration +from keystoneclient.v3.contrib import two_factor from keystoneclient.v3 import credentials from keystoneclient.v3 import domains from keystoneclient.v3 import endpoints @@ -188,6 +189,7 @@ def __init__(self, **kwargs): self.fiware_roles = fiware_roles.FiwareRolesManager(self) self.oauth2 = oauth2.create_oauth_manager(self) self.user_registration = user_registration.UserRegistrationManager(self) + self.two_factor = two_factor.TwoFactorManager(self) self.policies = policies.PolicyManager(self._adapter) self.projects = projects.ProjectManager(self._adapter) self.regions = regions.RegionManager(self._adapter) From e046dd3c40b945b7e5269779885667e8e7ea82cd Mon Sep 17 00:00:00 2001 From: Enrique Garcia Navalon Date: Tue, 27 Oct 2015 16:50:18 +0100 Subject: [PATCH 075/102] basic two factor authentication and API --- .../tests/unit/v3/test_two_factor.py | 149 ++++++++++++++++++ .../v3/contrib/two_factor/__init__.py | 15 ++ keystoneclient/v3/contrib/two_factor/auth.py | 65 ++++++++ keystoneclient/v3/contrib/two_factor/core.py | 20 +++ keystoneclient/v3/contrib/two_factor/keys.py | 47 ++++++ 5 files changed, 296 insertions(+) create mode 100644 keystoneclient/tests/unit/v3/test_two_factor.py create mode 100644 keystoneclient/v3/contrib/two_factor/__init__.py create mode 100644 keystoneclient/v3/contrib/two_factor/auth.py create mode 100644 keystoneclient/v3/contrib/two_factor/core.py create mode 100644 keystoneclient/v3/contrib/two_factor/keys.py diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py new file mode 100644 index 000000000..6325062c4 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -0,0 +1,149 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import uuid + +from keystoneclient import session +from keystoneclient.tests.unit.v3 import client_fixtures +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3.contrib.two_factor import keys +from keystoneclient.v3.contrib.two_factor import auth + + +EXTENSION_PATH = 'OS-TWO-FACTOR-AUTHENTICATION' + +class UsersTests(utils.TestCase): + + def setUp(self): + super(UsersTests, self).setUp() + self.model = keys.Key + self.manager = self.client.two_factor.keys + self.path_prefix = EXTENSION_PATH + + def test_generate_new_key(self): + user_id = uuid.uuid4().hex + key_ref = { + 'key': { + 'key': uuid.uuid4().hex, + 'user': { + 'id': user_id, + } + } + } + self.stub_url('PUT', + [self.path_prefix, 'user/', user_id], + json=key_ref, + status_code=201) + + self.manager.generate_new_key(user=user_id) + + def test_deactivate_two_factor(self): + user_id = uuid.uuid4().hex + + self.stub_url('DELETE', + [self.path_prefix, 'user/', user_id], + status_code=204) + + self.manager.deactivate_two_factor(user=user_id) + + def test_check_activated_two_factor(self): + user_id = uuid.uuid4().hex + + self.stub_url('HEAD', + [self.path_prefix, 'user/', user_id], + status_code=204) + + self.manager.check_activated_two_factor(user=user_id) + + +class TwoFactorAuthTests(utils.TestCase): + + + def test_two_factor_authenticate_success(self): + verification_code = uuid.uuid4().hex + password = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to password, and add its section. + token = client_fixtures.unscoped_token() + + token['methods'] = ["password"] + token['password'] = { + "verification_code": verification_code, + 'password': password + } + self.stub_auth(json=token) + + a = auth.TwoFactor( + self.TEST_URL, + verification_code=verification_code, + password=password) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + TWO_FACTOR_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + 'user': { + "verification_code": verification_code, + 'password': password + } + } + } + } + } + + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) + + def test_two_factor_authenticate_scoped_success(self): + + verification_code = uuid.uuid4().hex + password = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to password, and add its section. + token = client_fixtures.project_scoped_token() + token['methods'] = ["password"] + token['password'] = { + "verification_code": verification_code, + 'password': password + } + self.stub_auth(json=token) + + a = auth.TwoFactor( + self.TEST_URL, + verification_code=verification_code, + password=password) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + TWO_FACTOR_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + 'user': { + "verification_code": verification_code, + 'password': password + } + } + } + } + } + + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) diff --git a/keystoneclient/v3/contrib/two_factor/__init__.py b/keystoneclient/v3/contrib/two_factor/__init__.py new file mode 100644 index 000000000..056aac325 --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/__init__.py @@ -0,0 +1,15 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.two_factor.core import * \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py new file mode 100644 index 000000000..32036a053 --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -0,0 +1,65 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + + +import copy + +from oslo_config import cfg + +from keystoneclient.auth.identity import v3 + + +class TwoFactorMethod(v3.PasswordMethod): + """Construct a User/Password based authentication method with an extra verification code. + + :param string password: Password for authentication. + :param string username: Username for authentication. + :param string user_id: User ID for authentication. + :param string user_domain_id: User's domain ID for authentication. + :param string user_domain_name: User's domain name for authentication. + :param string verification_code: Code generated through the key and the timestamp. + """ + + _method_parameters = [ + 'user_id', + 'username', + 'user_domain_id', + 'user_domain_name', + 'password', + 'verification_code', + ] + + + def get_auth_data(self, session, auth, headers, **kwargs): + method, payload = super(TwoFactorMethod, self).get_auth_data(session, auth, headers, **kwargs) + + if self.verification_code: + payload['user']['verification_code'] = self.verification_code + + return method, payload + + +class TwoFactor(v3.Password): + """AuthPlugin for TwoFactorMethod.""" + _auth_method_class = TwoFactorMethod + + @classmethod + def get_options(cls): + options = super(TwoFactor, cls).get_options() + + options.extend([ + cfg.StrOpt('verification-code', help='Generated ode by timestamp'), + ]) + + return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/core.py b/keystoneclient/v3/contrib/two_factor/core.py new file mode 100644 index 000000000..dacb1cd19 --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/core.py @@ -0,0 +1,20 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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.v3.contrib.two_factor import keys + + +class TwoFactorManager(object): + def __init__(self, api): + self.keys = keys.KeyManager(api) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py new file mode 100644 index 000000000..fec1c9971 --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -0,0 +1,47 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. +import logging + +from keystoneclient import base + + +LOG = logging.getLogger(__name__) + +EXTENSION_PATH = '/OS-TWO-FACTOR-AUTHENTICATION' + +class Key(base.Resource): + pass + + +class KeyManager(base.Manager): + """Manager class for creating/deleting two factor keys.""" + + resource_class = Key + base_url = EXTENSION_PATH + '/user/{user_id}' + + def _url(self, user): + return self.base_url.format(user_id=base.getid(user)) + + def generate_new_key(self, user): + + return super(KeyManager, self)._put(url=self._url(user)) + + + def deactivate_two_factor(self, user): + + return super(KeyManager, self)._delete(url=self._url(user)) + + def check_activated_two_factor(self, user): + + return super(KeyManager, self)._head(url=self._url(user)) From 25ecca1d22d0aa8215a008fc0d12bc80ff82a053 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 16 Nov 2015 16:59:14 +0100 Subject: [PATCH 076/102] Fix dependencies --- keystoneclient/openstack/common/memorycache.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/keystoneclient/openstack/common/memorycache.py b/keystoneclient/openstack/common/memorycache.py index 8eeef2a74..e7e105c73 100644 --- a/keystoneclient/openstack/common/memorycache.py +++ b/keystoneclient/openstack/common/memorycache.py @@ -16,7 +16,7 @@ """Super simple fake memcache client.""" -from oslo.config import cfg +from oslo_config import cfg from oslo_utils import timeutils memcache_opts = [ diff --git a/requirements.txt b/requirements.txt index caed2a785..780fe6cac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ argparse Babel>=1.3 iso8601>=0.1.9 netaddr>=0.7.12 -oslo.config>=1.6.0 # Apache-2.0 +oslo.config>=2.6.0 # Apache-2.0 oslo.i18n>=1.3.0 # Apache-2.0 oslo.serialization>=1.2.0 # Apache-2.0 oslo.utils>=1.2.0 # Apache-2.0 From 66c107b290f30d2663a68d3cc4a87cc640d336eb Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 17 Nov 2015 19:03:13 +0100 Subject: [PATCH 077/102] Two factor auth extension fixed --- .../tests/unit/v3/test_two_factor.py | 151 ++++++++++++++++++ keystoneclient/v3/contrib/two_factor/auth.py | 65 ++++++++ keystoneclient/v3/contrib/two_factor/keys.py | 52 ++++++ 3 files changed, 268 insertions(+) create mode 100644 keystoneclient/tests/unit/v3/test_two_factor.py create mode 100644 keystoneclient/v3/contrib/two_factor/auth.py create mode 100644 keystoneclient/v3/contrib/two_factor/keys.py diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py new file mode 100644 index 000000000..8c97815c4 --- /dev/null +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -0,0 +1,151 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + +import uuid + +from keystoneclient import session +from keystoneclient.tests.unit.v3 import client_fixtures +from keystoneclient.tests.unit.v3 import utils +from keystoneclient.v3.contrib.two_factor import keys +from keystoneclient.v3.contrib.two_factor import auth + + +EXTENSION_PATH = 'OS-TWO-FACTOR' + +class UsersTests(utils.TestCase): + + def setUp(self): + super(UsersTests, self).setUp() + self.model = keys.Key + self.manager = self.client.two_factor.keys + self.path_prefix = EXTENSION_PATH + + def test_generate_new_key(self): + user_id = uuid.uuid4().hex + key_ref = { + 'two_factor_auth_data': { + 'security_answer': 'Sample answer', + 'security_question': 'Sample question', + 'two_factor_key': uuid.uuid4().hex, + 'user_id': user_id, + } + } + self.stub_url('POST', + ['users/', user_id, self.path_prefix, '/two_factor_auth'], + json=key_ref, + status_code=201) + + self.manager.generate_new_key(user=user_id, + security_question='Sample question', + security_answer='Sample answer') + + def test_deactivate_two_factor(self): + user_id = uuid.uuid4().hex + + self.stub_url('DELETE', + ['users/', user_id, self.path_prefix, '/two_factor_auth'], + status_code=204) + + self.manager.deactivate_two_factor(user=user_id) + + def test_check_activated_two_factor(self): + user_id = uuid.uuid4().hex + + self.stub_url('HEAD', + [self.path_prefix, '/two_factor_auth'], + status_code=204) + + self.manager.check_activated_two_factor(user=user_id) + + +class TwoFactorAuthTests(utils.TestCase): + + + def test_two_factor_authenticate_success(self): + verification_code = uuid.uuid4().hex + password = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to password, and add its section. + token = client_fixtures.unscoped_token() + + token['methods'] = ["password"] + token['password'] = { + "verification_code": verification_code, + 'password': password + } + self.stub_auth(json=token) + + a = auth.TwoFactor( + self.TEST_URL, + verification_code=verification_code, + password=password) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + TWO_FACTOR_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + 'user': { + "verification_code": verification_code, + 'password': password + } + } + } + } + } + + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) + + def test_two_factor_authenticate_scoped_success(self): + + verification_code = uuid.uuid4().hex + password = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to password, and add its section. + token = client_fixtures.project_scoped_token() + token['methods'] = ["password"] + token['password'] = { + "verification_code": verification_code, + 'password': password + } + self.stub_auth(json=token) + + a = auth.TwoFactor( + self.TEST_URL, + verification_code=verification_code, + password=password) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + TWO_FACTOR_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + 'user': { + "verification_code": verification_code, + 'password': password + } + } + } + } + } + + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py new file mode 100644 index 000000000..0b00d86dd --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -0,0 +1,65 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. + + +import copy + +from oslo_config import cfg + +from keystoneclient.auth.identity import v3 + + +class TwoFactorMethod(v3.PasswordMethod): + """Construct a User/Password based authentication method with an extra verification code. + + :param string password: Password for authentication. + :param string username: Username for authentication. + :param string user_id: User ID for authentication. + :param string user_domain_id: User's domain ID for authentication. + :param string user_domain_name: User's domain name for authentication. + :param string verification_code: Code generated through the key and the timestamp. + """ + + _method_parameters = [ + 'user_id', + 'username', + 'user_domain_id', + 'user_domain_name', + 'password', + 'verification_code', + ] + + + def get_auth_data(self, session, auth, headers, **kwargs): + method, payload = super(TwoFactorMethod, self).get_auth_data(session, auth, headers, **kwargs) + + if self.verification_code: + payload['user']['verification_code'] = self.verification_code + + return method, payload + + +class TwoFactor(v3.Password): + """AuthPlugin for TwoFactorMethod.""" + _auth_method_class = TwoFactorMethod + + @classmethod + def get_options(cls): + options = super(TwoFactor, cls).get_options() + + options.extend([ + cfg.StrOpt('verification-code', help='Generated code by timestamp'), + ]) + + return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py new file mode 100644 index 000000000..88e1ba322 --- /dev/null +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -0,0 +1,52 @@ +# Copyright (C) 2014 Universidad Politecnica de Madrid +# 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. +import logging + +from keystoneclient import base + + +LOG = logging.getLogger(__name__) + +EXTENSION_PATH = '/OS-TWO-FACTOR' + +class Key(base.Resource): + pass + + +class KeyManager(base.Manager): + """Manager class for creating/deleting two factor keys.""" + + resource_class = Key + base_url = EXTENSION_PATH + '/two_factor_auth' + + def _url(self, user): + return '/users/{user_id}'.format(user_id=base.getid(user)) + self.base_url + + def generate_new_key(self, user, security_question, security_answer): + data = {} + data["two_factor_auth"] = {} + data["two_factor_auth"]["security_question"] = security_question + data["two_factor_auth"]["security_answer"] = security_answer + + return super(KeyManager, self)._post(body=data, + url=self._url(user), + response_key="two_factor_auth_data") + + def deactivate_two_factor(self, user): + + return super(KeyManager, self)._delete(url=self._url(user)) + + def check_activated_two_factor(self, user): + + return super(KeyManager, self)._head(url=self.base_url+'?user_id='+base.getid(user)) From 9bd9a1633d43a3f2d8d1488c74408741f33d8e0d Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Fri, 20 Nov 2015 14:32:05 +0100 Subject: [PATCH 078/102] Two factor integration test added --- integration_test_two_factor.py | 56 ++++++++++++++++++++ keystoneclient/v3/contrib/two_factor/auth.py | 2 +- keystoneclient/v3/contrib/two_factor/keys.py | 7 ++- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 integration_test_two_factor.py diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py new file mode 100644 index 000000000..32eb89cd9 --- /dev/null +++ b/integration_test_two_factor.py @@ -0,0 +1,56 @@ +from keystoneclient.v3 import client +from keystoneclient import session +from keystoneclient.auth.identity import v3 + +from keystoneclient.v3.contrib.two_factor import auth as authentication +url = 'http://127.0.0.1:5000/v3' + +def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this + """Encapsulates all the logic for communicating with the modified keystone server. + + The IdM has its own admin account in the keystone server, and uses it to perform + operations like create users, projects, etc. when there is no user with admin rights + (for example, when user registration) to overcome the Keystone limitations. + + Also adds the methods to operate with the OAuth2.0 extension. + """ + # TODO(garcianavalon) find a way to integrate this with the existng keystone api + # TODO(garcianavalon)caching and efficiency with the client object. + if not session: + session = _two_factor_session(user='idm', password='idm') + keystone = client.Client(session=session) + return keystone + +def _password_session(): + auth = v3.Password(auth_url="http://localhost:5000/v3", + user_id='idm', + password='idm', + project_name='idm', + project_domain_id='default') + return session.Session(auth=auth) + +def _two_factor_session(user, password, verification_code=None, project='idm', project_domain_id='default'): + auth = authentication.TwoFactor(auth_url="http://localhost:5000/v3", + user_id=user, + password=password, + verification_code=verification_code, + user_domain_name=domain, + user_domain_id=project_domain_id) + +keystone = fiwareclient() + +# RUN fab keystone.test_data + +keystone.two_factor.keys.generate_new_key(user='user0', security_question='Who?', security_answer='Me!') +print("Created key for user0.") + +if keystone.two_factor.keys.check_activated_two_factor(user='user0'): + print("Two factor is enabled for user0!") + + + +keystone.two_factor.keys.deactivate_two_factor(user='user0') +print("Disabling two factor.") + +if not keystone.two_factor.keys.check_activated_two_factor(user='user0'): + print("Two factor is disableed for user0!") \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py index 0b00d86dd..5acaa47a7 100644 --- a/keystoneclient/v3/contrib/two_factor/auth.py +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -59,7 +59,7 @@ def get_options(cls): options = super(TwoFactor, cls).get_options() options.extend([ - cfg.StrOpt('verification-code', help='Generated code by timestamp'), + cfg.StrOpt('verification_code', help='Generated code by timestamp'), ]) return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 88e1ba322..0b93f22b0 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -48,5 +48,8 @@ def deactivate_two_factor(self, user): return super(KeyManager, self)._delete(url=self._url(user)) def check_activated_two_factor(self, user): - - return super(KeyManager, self)._head(url=self.base_url+'?user_id='+base.getid(user)) + try: + super(KeyManager, self)._head(url=self.base_url+'?user_id='+base.getid(user)) + return True + except: + return False From 640d0077b21f16abeb8a13920f61ab5f12ffda00 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 23 Nov 2015 16:01:41 +0100 Subject: [PATCH 079/102] Two factor unit tests fixed Other minor changes also included --- keystoneclient/tests/unit/v3/test_two_factor.py | 16 +++++++++++----- keystoneclient/v3/contrib/two_factor/auth.py | 2 +- keystoneclient/v3/contrib/two_factor/keys.py | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index 8c97815c4..95f696de9 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -75,6 +75,7 @@ class TwoFactorAuthTests(utils.TestCase): def test_two_factor_authenticate_success(self): verification_code = uuid.uuid4().hex password = uuid.uuid4().hex + user_id = uuid.uuid4().hex # Just use an existing project scoped token and change # the methods to password, and add its section. @@ -90,7 +91,8 @@ def test_two_factor_authenticate_success(self): a = auth.TwoFactor( self.TEST_URL, verification_code=verification_code, - password=password) + password=password, + user_id=user_id) s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) @@ -102,7 +104,8 @@ def test_two_factor_authenticate_success(self): "password": { 'user': { "verification_code": verification_code, - 'password': password + 'password': password, + 'id': user_id } } } @@ -115,6 +118,7 @@ def test_two_factor_authenticate_scoped_success(self): verification_code = uuid.uuid4().hex password = uuid.uuid4().hex + user_id = uuid.uuid4().hex # Just use an existing project scoped token and change # the methods to password, and add its section. @@ -129,7 +133,8 @@ def test_two_factor_authenticate_scoped_success(self): a = auth.TwoFactor( self.TEST_URL, verification_code=verification_code, - password=password) + password=password, + user_id=user_id) s = session.Session(auth=a) t = s.get_token() self.assertEqual(self.TEST_TOKEN, t) @@ -140,8 +145,9 @@ def test_two_factor_authenticate_scoped_success(self): "methods": ["password"], "password": { 'user': { - "verification_code": verification_code, - 'password': password + 'verification_code': verification_code, + 'password': password, + 'id': user_id } } } diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py index 5acaa47a7..0b00d86dd 100644 --- a/keystoneclient/v3/contrib/two_factor/auth.py +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -59,7 +59,7 @@ def get_options(cls): options = super(TwoFactor, cls).get_options() options.extend([ - cfg.StrOpt('verification_code', help='Generated code by timestamp'), + cfg.StrOpt('verification-code', help='Generated code by timestamp'), ]) return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 0b93f22b0..33a433307 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -41,7 +41,7 @@ def generate_new_key(self, user, security_question, security_answer): return super(KeyManager, self)._post(body=data, url=self._url(user), - response_key="two_factor_auth_data") + response_key="two_factor_auth") def deactivate_two_factor(self, user): From a8684c3aef3727a80929ac0f3792e483f33263c6 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 23 Nov 2015 16:09:58 +0100 Subject: [PATCH 080/102] Improved two factor integration test --- integration_test_two_factor.py | 55 ++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index 32eb89cd9..127c9dc91 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -1,8 +1,9 @@ from keystoneclient.v3 import client from keystoneclient import session from keystoneclient.auth.identity import v3 +import pyotp -from keystoneclient.v3.contrib.two_factor import auth as authentication +from keystoneclient.v3.contrib.two_factor import auth url = 'http://127.0.0.1:5000/v3' def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this @@ -21,36 +22,38 @@ def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this keystone = client.Client(session=session) return keystone -def _password_session(): - auth = v3.Password(auth_url="http://localhost:5000/v3", - user_id='idm', - password='idm', - project_name='idm', - project_domain_id='default') - return session.Session(auth=auth) - -def _two_factor_session(user, password, verification_code=None, project='idm', project_domain_id='default'): - auth = authentication.TwoFactor(auth_url="http://localhost:5000/v3", - user_id=user, - password=password, - verification_code=verification_code, - user_domain_name=domain, - user_domain_id=project_domain_id) +def _two_factor_session(user, password, verification_code=None, domain_id='default'): + auth_object = auth.TwoFactor(auth_url=url, + user_id=user, + password=password, + user_domain_id=domain_id, + verification_code=verification_code) + return session.Session(auth=auth_object) keystone = fiwareclient() -# RUN fab keystone.test_data +# Create example user +keystone.users.create(name="exampleuser", password="example") -keystone.two_factor.keys.generate_new_key(user='user0', security_question='Who?', security_answer='Me!') -print("Created key for user0.") +# Enable two factor +key = keystone.two_factor.keys.generate_new_key(user='exampleuser', security_question='Who?', security_answer='Me!') +print "Created key for exampleuser.", key.two_factor_key -if keystone.two_factor.keys.check_activated_two_factor(user='user0'): - print("Two factor is enabled for user0!") +if keystone.two_factor.keys.check_activated_two_factor(user='exampleuser'): + print "Two factor is enabled for exampleuser!" + code = pyotp.TOTP(key.two_factor_key).now() + print code + keystone2 = fiwareclient(session=_two_factor_session(user="exampleuser", + password="exampleuser", + verification_code=code)) + print keystone2.users.list() +# Disable two factor +keystone.two_factor.keys.deactivate_two_factor(user='exampleuser') +print "Disabling two factor." +if not keystone.two_factor.keys.check_activated_two_factor(user='exampleuser'): + print "Two factor is disabled for exampleuser!" -keystone.two_factor.keys.deactivate_two_factor(user='user0') -print("Disabling two factor.") - -if not keystone.two_factor.keys.check_activated_two_factor(user='user0'): - print("Two factor is disableed for user0!") \ No newline at end of file +# Delete example user +keystone.users.delete(user="exampleuser") \ No newline at end of file From 2f3517ea0914e7af09ce3e869af26d8bae62e8f9 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 23 Nov 2015 16:48:33 +0100 Subject: [PATCH 081/102] Improved two factor integration test --- integration_test_two_factor.py | 66 +++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index 127c9dc91..d6f33fda9 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -1,9 +1,8 @@ from keystoneclient.v3 import client from keystoneclient import session -from keystoneclient.auth.identity import v3 +from keystoneclient.v3.contrib.two_factor import auth import pyotp -from keystoneclient.v3.contrib.two_factor import auth url = 'http://127.0.0.1:5000/v3' def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this @@ -18,11 +17,11 @@ def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this # TODO(garcianavalon) find a way to integrate this with the existng keystone api # TODO(garcianavalon)caching and efficiency with the client object. if not session: - session = _two_factor_session(user='idm', password='idm') + session = two_factor_session(user='idm', password='idm') keystone = client.Client(session=session) return keystone -def _two_factor_session(user, password, verification_code=None, domain_id='default'): +def two_factor_session(user, password, verification_code=None, domain_id='default'): auth_object = auth.TwoFactor(auth_url=url, user_id=user, password=password, @@ -30,30 +29,47 @@ def _two_factor_session(user, password, verification_code=None, domain_id='defau verification_code=verification_code) return session.Session(auth=auth_object) -keystone = fiwareclient() +def enable_two_factor(keystone, user, password): + key = keystone.two_factor.keys.generate_new_key(user=user.id, security_question='Who?', security_answer='Me!') + print "Created key for example_user: ", key.two_factor_key + + if keystone.two_factor.keys.check_activated_two_factor(user=user.id): + print "Two factor is enabled for example_user!" + code = pyotp.TOTP(key.two_factor_key).now() + print code + keystone2 = fiwareclient(session=two_factor_session(user=user.id, + password=password, + verification_code=code)) + try: + keystone2.users.get(user.id) + print "Auth worked" + except: + print "Auth didn't work" + +def disable_two_factor(keystone, user): + keystone.two_factor.keys.deactivate_two_factor(user=user.id) + print "Disabling two factor." -# Create example user -keystone.users.create(name="exampleuser", password="example") + if not keystone.two_factor.keys.check_activated_two_factor(user=user.id): + print "Two factor is disabled for exampleuser!" -# Enable two factor -key = keystone.two_factor.keys.generate_new_key(user='exampleuser', security_question='Who?', security_answer='Me!') -print "Created key for exampleuser.", key.two_factor_key +def main(): + keystone = fiwareclient() -if keystone.two_factor.keys.check_activated_two_factor(user='exampleuser'): - print "Two factor is enabled for exampleuser!" - code = pyotp.TOTP(key.two_factor_key).now() - print code - keystone2 = fiwareclient(session=_two_factor_session(user="exampleuser", - password="exampleuser", - verification_code=code)) - print keystone2.users.list() + # Create example user + project = keystone.projects.create(name="example_project", domain="default") + role = keystone.roles.create(name="example_role") + user = keystone.users.create(name="example_user", password="example_user", default_project=project) + keystone.roles.grant(role=role, user=user, project=project) -# Disable two factor -keystone.two_factor.keys.deactivate_two_factor(user='exampleuser') -print "Disabling two factor." + # Run tests + enable_two_factor(keystone, user, password="example_user") + disable_two_factor(keystone, user) -if not keystone.two_factor.keys.check_activated_two_factor(user='exampleuser'): - print "Two factor is disabled for exampleuser!" + # Delete example user + keystone.projects.delete(project) + keystone.roles.delete(role) + keystone.users.delete(user) -# Delete example user -keystone.users.delete(user="exampleuser") \ No newline at end of file +if __name__ == "__main__": + main() \ No newline at end of file From 45a40f5b5f3f7da9f36ea5b7ab1fbaf0da8179e4 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 23 Nov 2015 16:59:12 +0100 Subject: [PATCH 082/102] Improved two factor integration test --- integration_test_two_factor.py | 41 +++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index d6f33fda9..32e0ed064 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -29,26 +29,39 @@ def two_factor_session(user, password, verification_code=None, domain_id='defaul verification_code=verification_code) return session.Session(auth=auth_object) -def enable_two_factor(keystone, user, password): +def enable_two_factor(keystone, user): key = keystone.two_factor.keys.generate_new_key(user=user.id, security_question='Who?', security_answer='Me!') print "Created key for example_user: ", key.two_factor_key + return key - if keystone.two_factor.keys.check_activated_two_factor(user=user.id): - print "Two factor is enabled for example_user!" - code = pyotp.TOTP(key.two_factor_key).now() - print code - keystone2 = fiwareclient(session=two_factor_session(user=user.id, - password=password, - verification_code=code)) +def authenticate(keystone, user, password, key=None, use_two_factor=True): + if use_two_factor: + if keystone.two_factor.keys.check_activated_two_factor(user=user.id): + print "Two factor is enabled for example_user!" + code = pyotp.TOTP(key.two_factor_key).now() + print code + keystone2 = fiwareclient(session=two_factor_session(user=user.id, + password=password, + verification_code=code)) + try: + keystone2.users.get(user.id) + print "Auth with two factor worked" + except: + print "Auth with two factor didn't work" + else: + print "Two factor is disabled for exampleuser!" + else: + keystone3 = fiwareclient(session=two_factor_session(user=user.id, + password=password)) try: - keystone2.users.get(user.id) - print "Auth worked" + keystone3.users.get(user.id) + print "Auth without two factor worked" except: - print "Auth didn't work" + print "Auth without two factor didn't work" def disable_two_factor(keystone, user): keystone.two_factor.keys.deactivate_two_factor(user=user.id) - print "Disabling two factor." + print "Disabling two factor..." if not keystone.two_factor.keys.check_activated_two_factor(user=user.id): print "Two factor is disabled for exampleuser!" @@ -63,8 +76,10 @@ def main(): keystone.roles.grant(role=role, user=user, project=project) # Run tests - enable_two_factor(keystone, user, password="example_user") + key = enable_two_factor(keystone, user) + authenticate(keystone, user, password="example_user", key=key, use_two_factor=True) disable_two_factor(keystone, user) + authenticate(keystone, user, password="example_user", use_two_factor=False) # Delete example user keystone.projects.delete(project) From 8cfe4a29c074843bffa76c0fb2d236205c962408 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Wed, 25 Nov 2015 17:37:50 +0100 Subject: [PATCH 083/102] Check two factor using user_id or username/domain --- keystoneclient/v3/contrib/two_factor/keys.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 33a433307..1baf6c0c2 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -47,9 +47,12 @@ def deactivate_two_factor(self, user): return super(KeyManager, self)._delete(url=self._url(user)) - def check_activated_two_factor(self, user): + def check_activated_two_factor(self, user_id=None, username=None, domain=None): try: - super(KeyManager, self)._head(url=self.base_url+'?user_id='+base.getid(user)) + if user_id: + super(KeyManager, self)._head(url=self.base_url+'?user_id='+user_id) + else: + super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'?domain_id='+domain) return True except: return False From 77a0c22ab4f11e3d3ac8bd5a9c8a9361ac7c337a Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Wed, 25 Nov 2015 18:10:21 +0100 Subject: [PATCH 084/102] Minor fix regarding two factor --- keystoneclient/v3/contrib/two_factor/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 1baf6c0c2..6b83f3266 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -52,7 +52,7 @@ def check_activated_two_factor(self, user_id=None, username=None, domain=None): if user_id: super(KeyManager, self)._head(url=self.base_url+'?user_id='+user_id) else: - super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'?domain_id='+domain) + super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_id='+domain) return True except: return False From 7817f7f46ff12cbbf0a416afcb4b7a65aac91a7b Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Thu, 26 Nov 2015 17:27:44 +0100 Subject: [PATCH 085/102] Supporting domain name to check two factor --- keystoneclient/v3/contrib/two_factor/keys.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 6b83f3266..d5417da7b 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -47,12 +47,14 @@ def deactivate_two_factor(self, user): return super(KeyManager, self)._delete(url=self._url(user)) - def check_activated_two_factor(self, user_id=None, username=None, domain=None): + def check_activated_two_factor(self, user_id=None, username=None, domain_id=None, domain_name=None): try: if user_id: super(KeyManager, self)._head(url=self.base_url+'?user_id='+user_id) - else: - super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_id='+domain) + elif domain_id: + super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_id='+domain_id) + elif domain_name: + super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_name='+domain_name) return True except: return False From c360627033833c599419bd8b22b986ac7c90a3bf Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 7 Dec 2015 17:57:07 +0100 Subject: [PATCH 086/102] Two factor tests updated --- keystoneclient/tests/unit/v3/test_two_factor.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index 95f696de9..0b7f3e8a4 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -34,11 +34,12 @@ def setUp(self): def test_generate_new_key(self): user_id = uuid.uuid4().hex key_ref = { - 'two_factor_auth_data': { + 'two_factor_auth': { 'security_answer': 'Sample answer', 'security_question': 'Sample question', 'two_factor_key': uuid.uuid4().hex, 'user_id': user_id, + 'uri': 'otpauth://example' } } self.stub_url('POST', @@ -59,14 +60,24 @@ def test_deactivate_two_factor(self): self.manager.deactivate_two_factor(user=user_id) - def test_check_activated_two_factor(self): + def test_check_activated_two_factor_with_id(self): user_id = uuid.uuid4().hex self.stub_url('HEAD', [self.path_prefix, '/two_factor_auth'], status_code=204) - self.manager.check_activated_two_factor(user=user_id) + self.manager.check_activated_two_factor(user_id=user_id) + + def test_check_activated_two_factor_with_name_and_domain(self): + username = uuid.uuid4().hex + domain_id = uuid.uuid4().hex + + self.stub_url('HEAD', + [self.path_prefix, '/two_factor_auth'], + status_code=204) + + self.manager.check_activated_two_factor(username=username, domain_id=domain_id) class TwoFactorAuthTests(utils.TestCase): From 6d313c899e41b7434ca228419aae3f9e8f177b56 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Wed, 16 Dec 2015 18:38:00 +0100 Subject: [PATCH 087/102] Improvements in two factor extension Now a new key can be generated without providing security question/answer --- integration_test_two_factor.py | 4 ++-- keystoneclient/v3/contrib/two_factor/keys.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index 32e0ed064..ce09e4cf0 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -36,7 +36,7 @@ def enable_two_factor(keystone, user): def authenticate(keystone, user, password, key=None, use_two_factor=True): if use_two_factor: - if keystone.two_factor.keys.check_activated_two_factor(user=user.id): + if keystone.two_factor.keys.check_activated_two_factor(user_id=user.id): print "Two factor is enabled for example_user!" code = pyotp.TOTP(key.two_factor_key).now() print code @@ -63,7 +63,7 @@ def disable_two_factor(keystone, user): keystone.two_factor.keys.deactivate_two_factor(user=user.id) print "Disabling two factor..." - if not keystone.two_factor.keys.check_activated_two_factor(user=user.id): + if not keystone.two_factor.keys.check_activated_two_factor(user_id=user.id): print "Two factor is disabled for exampleuser!" def main(): diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index d5417da7b..b0b6b14ec 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -34,10 +34,13 @@ def _url(self, user): return '/users/{user_id}'.format(user_id=base.getid(user)) + self.base_url def generate_new_key(self, user, security_question, security_answer): - data = {} - data["two_factor_auth"] = {} - data["two_factor_auth"]["security_question"] = security_question - data["two_factor_auth"]["security_answer"] = security_answer + if security_question and security_answer: + data = {} + data["two_factor_auth"] = {} + data["two_factor_auth"]["security_question"] = security_question + data["two_factor_auth"]["security_answer"] = security_answer + else: + data = None return super(KeyManager, self)._post(body=data, url=self._url(user), From f1dda1f86f4c722cfb2d1929099d8e0585f5cc73 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 21 Dec 2015 17:20:25 +0100 Subject: [PATCH 088/102] Improvements in two factor auth extension Added support to get two factor data and check security question --- .../tests/unit/v3/test_two_factor.py | 26 +++++++++++++++ keystoneclient/v3/contrib/two_factor/keys.py | 32 ++++++++++++++----- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index 0b7f3e8a4..09a42d239 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -79,6 +79,32 @@ def test_check_activated_two_factor_with_name_and_domain(self): self.manager.check_activated_two_factor(username=username, domain_id=domain_id) + def test_get_two_factor_data(self): + user_id = uuid.uuid4().hex + key_ref = { + 'two_factor_auth': { + 'security_question': 'Sample question', + 'user_id': user_id, + } + } + self.stub_url('GET', + ['users/', user_id, self.path_prefix, '/two_factor_data'], + json=key_ref, + status_code=200) + + self.manager.get_two_factor_data(user=user_id) + + def test_check_security_question(self): + user_id = uuid.uuid4().hex + + self.stub_url('HEAD', + ['users/', user_id, self.path_prefix, '/sec_question'], + status_code=204) + + self.manager.check_security_question(user=user_id, + security_answer="Sample answer") + + class TwoFactorAuthTests(utils.TestCase): diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index b0b6b14ec..caa5a4aff 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -28,10 +28,15 @@ class KeyManager(base.Manager): """Manager class for creating/deleting two factor keys.""" resource_class = Key - base_url = EXTENSION_PATH + '/two_factor_auth' + base_url = '/two_factor_auth' + security_question_url = '/sec_question' + two_factor_data_url = '/two_factor_data' def _url(self, user): - return '/users/{user_id}'.format(user_id=base.getid(user)) + self.base_url + return '/users/{user_id}'.format(user_id=base.getid(user)) + EXTENSION_PATH + + def _base_url(self, user): + return self._url(user) + self.base_url def generate_new_key(self, user, security_question, security_answer): if security_question and security_answer: @@ -43,21 +48,32 @@ def generate_new_key(self, user, security_question, security_answer): data = None return super(KeyManager, self)._post(body=data, - url=self._url(user), + url=self._base_url(user), response_key="two_factor_auth") def deactivate_two_factor(self, user): - - return super(KeyManager, self)._delete(url=self._url(user)) + return super(KeyManager, self)._delete(url=self._base_url(user)) def check_activated_two_factor(self, user_id=None, username=None, domain_id=None, domain_name=None): try: if user_id: - super(KeyManager, self)._head(url=self.base_url+'?user_id='+user_id) + super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_id='+user_id) elif domain_id: - super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_id='+domain_id) + super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_name='+username+'&domain_id='+domain_id) elif domain_name: - super(KeyManager, self)._head(url=self.base_url+'?user_name='+username+'&domain_name='+domain_name) + super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_name='+username+'&domain_name='+domain_name) return True except: return False + + def get_two_factor_data(self, user): + return super(KeyManager, self)._get(url=self._url(user)+self.two_factor_data_url, + response_key="two_factor_auth") + + def check_security_question(self, user, security_answer): + data = {} + data["two_factor_auth"] = {} + data["two_factor_auth"]["security_answer"] = security_answer + + return super(KeyManager, self)._head(body=data, + url=self._url(user)+self.security_question_url) From a8d47846ad60a8a8be6619ee0065b6106e84982c Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 21 Dec 2015 17:48:49 +0100 Subject: [PATCH 089/102] URLs refactored in two factor auth extension --- keystoneclient/v3/contrib/two_factor/keys.py | 29 +++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index caa5a4aff..4c4d80ed7 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -28,15 +28,24 @@ class KeyManager(base.Manager): """Manager class for creating/deleting two factor keys.""" resource_class = Key - base_url = '/two_factor_auth' + auth_url = '/two_factor_auth' security_question_url = '/sec_question' two_factor_data_url = '/two_factor_data' def _url(self, user): return '/users/{user_id}'.format(user_id=base.getid(user)) + EXTENSION_PATH - def _base_url(self, user): - return self._url(user) + self.base_url + def _auth_url(self, user): + return self._url(user) + self.auth_url + + def _security_question_url(self, user): + return self._url(user) + self.security_question_url + + def _two_factor_data_url(self, user): + return self._url(user) + self.two_factor_data_url + + def _check_base_url(self): + return EXTENSION_PATH + self.auth_url def generate_new_key(self, user, security_question, security_answer): if security_question and security_answer: @@ -48,26 +57,26 @@ def generate_new_key(self, user, security_question, security_answer): data = None return super(KeyManager, self)._post(body=data, - url=self._base_url(user), + url=self._auth_url(user), response_key="two_factor_auth") def deactivate_two_factor(self, user): - return super(KeyManager, self)._delete(url=self._base_url(user)) + return super(KeyManager, self)._delete(url=self._auth_url(user)) def check_activated_two_factor(self, user_id=None, username=None, domain_id=None, domain_name=None): try: if user_id: - super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_id='+user_id) + super(KeyManager, self)._head(url=self._check_base_url()+'?user_id='+user_id) elif domain_id: - super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_name='+username+'&domain_id='+domain_id) + super(KeyManager, self)._head(url=self._check_base_url()+'?user_name='+username+'&domain_id='+domain_id) elif domain_name: - super(KeyManager, self)._head(url=EXTENSION_PATH+self.base_url+'?user_name='+username+'&domain_name='+domain_name) + super(KeyManager, self)._head(url=self._check_base_url()+'?user_name='+username+'&domain_name='+domain_name) return True except: return False def get_two_factor_data(self, user): - return super(KeyManager, self)._get(url=self._url(user)+self.two_factor_data_url, + return super(KeyManager, self)._get(url=self._two_factor_data_url(user), response_key="two_factor_auth") def check_security_question(self, user, security_answer): @@ -76,4 +85,4 @@ def check_security_question(self, user, security_answer): data["two_factor_auth"]["security_answer"] = security_answer return super(KeyManager, self)._head(body=data, - url=self._url(user)+self.security_question_url) + url=self._security_question_url(user)) From 6dab39aa70895be73937e47e0e229aa649216450 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 09:48:26 +0100 Subject: [PATCH 090/102] Fix dependencies --- requirements.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements.txt b/requirements.txt index 780fe6cac..848b15679 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,17 +2,17 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr>=0.6,!=0.7,<1.0 +pbr==0.11.0 argparse -Babel>=1.3 -iso8601>=0.1.9 -netaddr>=0.7.12 -oslo.config>=2.6.0 # Apache-2.0 -oslo.i18n>=1.3.0 # Apache-2.0 -oslo.serialization>=1.2.0 # Apache-2.0 -oslo.utils>=1.2.0 # Apache-2.0 -PrettyTable>=0.7,<0.8 -requests>=2.2.0,!=2.4.0 -six>=1.9.0 -stevedore>=1.1.0 # Apache-2.0 +Babel==2.1.1 +iso8601==0.1.10 +netaddr==0.7.13 +oslo.config==3.0.0 # Apache-2.0 +oslo.i18n==1.3.1 # Apache-2.0 +oslo.serialization==1.2.0 # Apache-2.0 +oslo.utils==1.4.2 # Apache-2.0 +prettytable==0.7.2 +requests==2.8.1 +six==1.9.0 +stevedore==1.9.0 # Apache-2.0 From 65cc975f34cf328469d9f3fe507207b642711f8f Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 11:43:16 +0100 Subject: [PATCH 091/102] Fix dependencies and egg name --- requirements.txt | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 848b15679..a722acc12 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr==0.11.0 +pbr==1.8.1 argparse Babel==2.1.1 diff --git a/setup.cfg b/setup.cfg index 962fedffc..ce2c66662 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = fiwareclient +name = python-keystoneclient summary = Modified Client Library for OpenStack Identity to work with FIWARE modified Keystone description-file = README.rst From 54ba7cad071e932440c443207cf2ebba38d6c74c Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:20:42 +0100 Subject: [PATCH 092/102] Fix stevedore dependency --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a722acc12..76facc8ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,4 @@ oslo.utils==1.4.2 # Apache-2.0 prettytable==0.7.2 requests==2.8.1 six==1.9.0 -stevedore==1.9.0 # Apache-2.0 +stevedore==1.3.0 # Apache-2.0 From a54e8ae2c70ddaa821fbe2459eaa3effc558a3fc Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:24:45 +0100 Subject: [PATCH 093/102] Fix requests dependency --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 76facc8ca..b1ed4341e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,6 @@ oslo.i18n==1.3.1 # Apache-2.0 oslo.serialization==1.2.0 # Apache-2.0 oslo.utils==1.4.2 # Apache-2.0 prettytable==0.7.2 -requests==2.8.1 +requests==2.2.1 six==1.9.0 stevedore==1.3.0 # Apache-2.0 From 11128a3fdbc9f4eb18e91acf7ec8c491d904ea2f Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:26:13 +0100 Subject: [PATCH 094/102] Fix oslo.utils dependency --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b1ed4341e..decadec0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ netaddr==0.7.13 oslo.config==3.0.0 # Apache-2.0 oslo.i18n==1.3.1 # Apache-2.0 oslo.serialization==1.2.0 # Apache-2.0 -oslo.utils==1.4.2 # Apache-2.0 +oslo.utils==1.4.1 # Apache-2.0 prettytable==0.7.2 requests==2.2.1 six==1.9.0 From 2821a281c35975ad685a5b7f86f042b7385ed60b Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:28:37 +0100 Subject: [PATCH 095/102] Fix dependencies --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index decadec0e..4f97629f6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,11 +8,11 @@ argparse Babel==2.1.1 iso8601==0.1.10 netaddr==0.7.13 -oslo.config==3.0.0 # Apache-2.0 +oslo.config>=1.6.0,<=3.0.0 # Apache-2.0 oslo.i18n==1.3.1 # Apache-2.0 oslo.serialization==1.2.0 # Apache-2.0 -oslo.utils==1.4.1 # Apache-2.0 +oslo.utils>=1.4.1,<=1.4.2 # Apache-2.0 prettytable==0.7.2 -requests==2.2.1 +requests>=2.2.1,<=2.8.1 six==1.9.0 -stevedore==1.3.0 # Apache-2.0 +stevedore>=1.2.0,<=1.9.0 # Apache-2.0 From 5f9723c95403e23de578ee5da0586595673aa25f Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:29:31 +0100 Subject: [PATCH 096/102] Fix dependencies --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4f97629f6..a8d71b22c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr==1.8.1 argparse -Babel==2.1.1 +Babel>=1.3,<=2.1.1 iso8601==0.1.10 netaddr==0.7.13 oslo.config>=1.6.0,<=3.0.0 # Apache-2.0 From 8360c59bd98e51866e21f17678d269026aada3e3 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 26 Jan 2016 13:30:16 +0100 Subject: [PATCH 097/102] Fix dependencies --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a8d71b22c..e6baec81b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -pbr==1.8.1 +pbr>=0.11.1,<=1.8.1 argparse Babel>=1.3,<=2.1.1 From 03be2d357516245de304f25394020ba5033bc6b7 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Thu, 11 Feb 2016 13:19:04 +0100 Subject: [PATCH 098/102] Changes in two factor --- integration_test_two_factor.py | 38 ++++++++++++++----- .../tests/unit/v3/test_two_factor.py | 32 +++++++++++++++- keystoneclient/v3/contrib/two_factor/auth.py | 5 ++- keystoneclient/v3/contrib/two_factor/keys.py | 15 ++++++++ 4 files changed, 77 insertions(+), 13 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index ce09e4cf0..451c3cfd7 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -17,15 +17,16 @@ def fiwareclient(session=None, request=None):# TODO(garcianavalon) use this # TODO(garcianavalon) find a way to integrate this with the existng keystone api # TODO(garcianavalon)caching and efficiency with the client object. if not session: - session = two_factor_session(user='idm', password='idm') + session = two_factor_session(user='idm_user', password='idm') keystone = client.Client(session=session) return keystone -def two_factor_session(user, password, verification_code=None, domain_id='default'): +def two_factor_session(user, password, verification_code=None, device_data=None, domain_id='default'): auth_object = auth.TwoFactor(auth_url=url, user_id=user, password=password, user_domain_id=domain_id, + device_data=device_data, verification_code=verification_code) return session.Session(auth=auth_object) @@ -34,20 +35,36 @@ def enable_two_factor(keystone, user): print "Created key for example_user: ", key.two_factor_key return key -def authenticate(keystone, user, password, key=None, use_two_factor=True): +def remember_device(keystone, user): + device_data = keystone.two_factor.keys.remember_device(user=user.id) + print "Remembering device for example_user: ", device_data.device_id + return device_data + +def authenticate(keystone, user, password, key=None, use_device_cookie=False, use_two_factor=True): if use_two_factor: if keystone.two_factor.keys.check_activated_two_factor(user_id=user.id): print "Two factor is enabled for example_user!" - code = pyotp.TOTP(key.two_factor_key).now() - print code - keystone2 = fiwareclient(session=two_factor_session(user=user.id, - password=password, - verification_code=code)) + + if use_device_cookie: + device_data = remember_device(keystone, user) + keystone2 = fiwareclient(session=two_factor_session(user=user.id, + password=password, + device_data={'device_id': device_data.device_id, + 'device_token': device_data.device_token, + 'user_id': device_data.user_id})) + used_device_message = ' (remembering device)' + else: + code = pyotp.TOTP(key.two_factor_key).now() + print code + keystone2 = fiwareclient(session=two_factor_session(user=user.id, + password=password, + verification_code=code)) + used_device_message = '' try: keystone2.users.get(user.id) - print "Auth with two factor worked" + print "Auth with two factor{used_device} worked".format(used_device=used_device_message) except: - print "Auth with two factor didn't work" + print "Auth with two factor{used_device} didn't work".format(used_device=used_device_message) else: print "Two factor is disabled for exampleuser!" else: @@ -78,6 +95,7 @@ def main(): # Run tests key = enable_two_factor(keystone, user) authenticate(keystone, user, password="example_user", key=key, use_two_factor=True) + authenticate(keystone, user, password="example_user", key=key, use_two_factor=True, use_device_cookie=True) disable_two_factor(keystone, user) authenticate(keystone, user, password="example_user", use_two_factor=False) diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index 09a42d239..ca90e1c12 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -104,6 +104,36 @@ def test_check_security_question(self): self.manager.check_security_question(user=user_id, security_answer="Sample answer") + def test_remember_device(self): + username = uuid.uuid4().hex + domain_name = uuid.uuid4().hex + user_id = uuid.uuid4().hex + device_id = uuid.uuid4().hex + device_token = uuid.uuid4().hex + + key_ref = { + 'two_factor_auth': { + 'device_id': device_id, + 'device_token': device_token, + 'user_id': user_id + } + } + self.stub_url('POST', + [self.path_prefix, '/devices'], + json=key_ref, + status_code=201) + + self.manager.remember_device(username=username, domain_name=domain_name) + + def test_delete_all_devices(self): + user_id = uuid.uuid4().hex + + self.stub_url('DELETE', + ['users/', user_id, self.path_prefix, '/devices'], + status_code=204) + + self.manager.delete_all_devices(user=user_id) + class TwoFactorAuthTests(utils.TestCase): @@ -191,4 +221,4 @@ def test_two_factor_authenticate_scoped_success(self): } } - self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py index 0b00d86dd..7f7b4a067 100644 --- a/keystoneclient/v3/contrib/two_factor/auth.py +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -29,6 +29,7 @@ class TwoFactorMethod(v3.PasswordMethod): :param string user_domain_id: User's domain ID for authentication. :param string user_domain_name: User's domain name for authentication. :param string verification_code: Code generated through the key and the timestamp. + :param dict device_data: Info from the client cookie to bypass verification code. """ _method_parameters = [ @@ -37,7 +38,7 @@ class TwoFactorMethod(v3.PasswordMethod): 'user_domain_id', 'user_domain_name', 'password', - 'verification_code', + 'verification_code' ] @@ -59,7 +60,7 @@ def get_options(cls): options = super(TwoFactor, cls).get_options() options.extend([ - cfg.StrOpt('verification-code', help='Generated code by timestamp'), + cfg.StrOpt('verification-code', help='Generated code by timestamp') ]) return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 4c4d80ed7..c94e0bd5a 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -31,6 +31,7 @@ class KeyManager(base.Manager): auth_url = '/two_factor_auth' security_question_url = '/sec_question' two_factor_data_url = '/two_factor_data' + devices_url = '/devices' def _url(self, user): return '/users/{user_id}'.format(user_id=base.getid(user)) + EXTENSION_PATH @@ -44,6 +45,9 @@ def _security_question_url(self, user): def _two_factor_data_url(self, user): return self._url(user) + self.two_factor_data_url + def _devices_url(self, user): + return self._url(user) + self.devices_url + def _check_base_url(self): return EXTENSION_PATH + self.auth_url @@ -86,3 +90,14 @@ def check_security_question(self, user, security_answer): return super(KeyManager, self)._head(body=data, url=self._security_question_url(user)) + + def remember_device(self, username, domain_name): + return super(KeyManager, self)._post(body={}, + url=EXTENSION_PATH+'/devices?user_name='+username+'&domain_name='+domain_name, + response_key="two_factor_auth") + + def delete_all_devices(self, user): + return super(KeyManager, self)._delete(url=self._devices_url(user)) + + def check_for_device(self, user_id, device_id, device_token): + return super(KeyManager, self)._get(url=EXTENSION_PATH+'/devices?user_id='+user_id+'?device_id='+device_id+'?device_token='+device_token) From 2f39400c7ea838310b9e7f450b88717d998eedd7 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Mon, 15 Feb 2016 13:10:07 +0100 Subject: [PATCH 099/102] Improvements in remembering devices for two factor auth Support for username/domain when remembering and checking a device. Tests updated. --- integration_test_two_factor.py | 6 +- .../tests/unit/v3/test_two_factor.py | 58 +++++++++++++++++-- keystoneclient/v3/contrib/two_factor/auth.py | 8 ++- keystoneclient/v3/contrib/two_factor/keys.py | 15 ++--- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index 451c3cfd7..65609c957 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -35,8 +35,8 @@ def enable_two_factor(keystone, user): print "Created key for example_user: ", key.two_factor_key return key -def remember_device(keystone, user): - device_data = keystone.two_factor.keys.remember_device(user=user.id) +def remember_device(keystone, **kwargs): + device_data = keystone.two_factor.keys.remember_device(**kwargs) print "Remembering device for example_user: ", device_data.device_id return device_data @@ -46,7 +46,7 @@ def authenticate(keystone, user, password, key=None, use_device_cookie=False, us print "Two factor is enabled for example_user!" if use_device_cookie: - device_data = remember_device(keystone, user) + device_data = remember_device(keystone=keystone, user_name=user.name, domain_id=user.domain_id) keystone2 = fiwareclient(session=two_factor_session(user=user.id, password=password, device_data={'device_id': device_data.device_id, diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index ca90e1c12..176e56fa0 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -70,14 +70,14 @@ def test_check_activated_two_factor_with_id(self): self.manager.check_activated_two_factor(user_id=user_id) def test_check_activated_two_factor_with_name_and_domain(self): - username = uuid.uuid4().hex + user_name = uuid.uuid4().hex domain_id = uuid.uuid4().hex self.stub_url('HEAD', [self.path_prefix, '/two_factor_auth'], status_code=204) - self.manager.check_activated_two_factor(username=username, domain_id=domain_id) + self.manager.check_activated_two_factor(user_name=user_name, domain_id=domain_id) def test_get_two_factor_data(self): user_id = uuid.uuid4().hex @@ -105,7 +105,7 @@ def test_check_security_question(self): security_answer="Sample answer") def test_remember_device(self): - username = uuid.uuid4().hex + user_name = uuid.uuid4().hex domain_name = uuid.uuid4().hex user_id = uuid.uuid4().hex device_id = uuid.uuid4().hex @@ -123,7 +123,7 @@ def test_remember_device(self): json=key_ref, status_code=201) - self.manager.remember_device(username=username, domain_name=domain_name) + self.manager.remember_device(user_name=user_name, domain_name=domain_name) def test_delete_all_devices(self): user_id = uuid.uuid4().hex @@ -221,4 +221,54 @@ def test_two_factor_authenticate_scoped_success(self): } } + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) + + def test_two_factor_device_authenticate_success(self): + password = uuid.uuid4().hex + user_id = uuid.uuid4().hex + + device_id = uuid.uuid4().hex + device_token = uuid.uuid4().hex + + # Just use an existing project scoped token and change + # the methods to password, and add its section. + token = client_fixtures.unscoped_token() + + token['methods'] = ["password"] + token['password'] = { + "device_data": { "device_id": device_id, + "device_token": device_token, + "user_id": user_id}, + 'password': password + } + self.stub_auth(json=token) + + a = auth.TwoFactor( + self.TEST_URL, + device_data={ "device_id": device_id, + "device_token": device_token, + "user_id": user_id}, + password=password, + user_id=user_id) + s = session.Session(auth=a) + t = s.get_token() + self.assertEqual(self.TEST_TOKEN, t) + + TWO_FACTOR_REQUEST_BODY = { + "auth": { + "identity": { + "methods": ["password"], + "password": { + 'user': { + "device_data": { "device_id": device_id, + "device_token": device_token, + "user_id": user_id}, + 'password': password, + 'id': user_id + } + } + } + } + } + self.assertRequestBodyIs(json=TWO_FACTOR_REQUEST_BODY) \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/auth.py b/keystoneclient/v3/contrib/two_factor/auth.py index 7f7b4a067..9a83e0665 100644 --- a/keystoneclient/v3/contrib/two_factor/auth.py +++ b/keystoneclient/v3/contrib/two_factor/auth.py @@ -38,7 +38,8 @@ class TwoFactorMethod(v3.PasswordMethod): 'user_domain_id', 'user_domain_name', 'password', - 'verification_code' + 'verification_code', + 'device_data' ] @@ -47,6 +48,8 @@ def get_auth_data(self, session, auth, headers, **kwargs): if self.verification_code: payload['user']['verification_code'] = self.verification_code + if self.device_data: + payload['user']['device_data'] = self.device_data return method, payload @@ -60,7 +63,8 @@ def get_options(cls): options = super(TwoFactor, cls).get_options() options.extend([ - cfg.StrOpt('verification-code', help='Generated code by timestamp') + cfg.StrOpt('verification-code', help='Generated code by timestamp'), + cfg.DictOpt('device_data', help='Cached device data') ]) return options \ No newline at end of file diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index c94e0bd5a..7800e3aed 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -13,6 +13,8 @@ # limitations under the License. import logging +import urllib + from keystoneclient import base @@ -67,14 +69,9 @@ def generate_new_key(self, user, security_question, security_answer): def deactivate_two_factor(self, user): return super(KeyManager, self)._delete(url=self._auth_url(user)) - def check_activated_two_factor(self, user_id=None, username=None, domain_id=None, domain_name=None): + def check_activated_two_factor(self, **kwargs): try: - if user_id: - super(KeyManager, self)._head(url=self._check_base_url()+'?user_id='+user_id) - elif domain_id: - super(KeyManager, self)._head(url=self._check_base_url()+'?user_name='+username+'&domain_id='+domain_id) - elif domain_name: - super(KeyManager, self)._head(url=self._check_base_url()+'?user_name='+username+'&domain_name='+domain_name) + super(KeyManager, self)._head(url=self._check_base_url() + '?' + urllib.urlencode(kwargs)) return True except: return False @@ -91,9 +88,9 @@ def check_security_question(self, user, security_answer): return super(KeyManager, self)._head(body=data, url=self._security_question_url(user)) - def remember_device(self, username, domain_name): + def remember_device(self, **kwargs): return super(KeyManager, self)._post(body={}, - url=EXTENSION_PATH+'/devices?user_name='+username+'&domain_name='+domain_name, + url=EXTENSION_PATH+'/devices?' + urllib.urlencode(kwargs), response_key="two_factor_auth") def delete_all_devices(self, user): From 461094b7f519d586ea104cad86edb391348d78e1 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Tue, 16 Feb 2016 12:59:53 +0100 Subject: [PATCH 100/102] Changed HTTP method of check_two_factor_device endpoint --- keystoneclient/v3/contrib/two_factor/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 7800e3aed..6bdef9c23 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -97,4 +97,4 @@ def delete_all_devices(self, user): return super(KeyManager, self)._delete(url=self._devices_url(user)) def check_for_device(self, user_id, device_id, device_token): - return super(KeyManager, self)._get(url=EXTENSION_PATH+'/devices?user_id='+user_id+'?device_id='+device_id+'?device_token='+device_token) + return super(KeyManager, self)._head(url=EXTENSION_PATH+'/devices?user_id='+user_id+'&device_id='+device_id+'?device_token='+device_token) From a3c851d415aad5d5b4f69eb7264ce656a92cb2d0 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Wed, 17 Feb 2016 15:36:38 +0100 Subject: [PATCH 101/102] Check for device with either username/domain or user_id --- integration_test_two_factor.py | 3 +-- keystoneclient/tests/unit/v3/test_two_factor.py | 12 ++++-------- keystoneclient/v3/contrib/two_factor/keys.py | 6 +++--- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/integration_test_two_factor.py b/integration_test_two_factor.py index 65609c957..623b3dff9 100644 --- a/integration_test_two_factor.py +++ b/integration_test_two_factor.py @@ -50,8 +50,7 @@ def authenticate(keystone, user, password, key=None, use_device_cookie=False, us keystone2 = fiwareclient(session=two_factor_session(user=user.id, password=password, device_data={'device_id': device_data.device_id, - 'device_token': device_data.device_token, - 'user_id': device_data.user_id})) + 'device_token': device_data.device_token})) used_device_message = ' (remembering device)' else: code = pyotp.TOTP(key.two_factor_key).now() diff --git a/keystoneclient/tests/unit/v3/test_two_factor.py b/keystoneclient/tests/unit/v3/test_two_factor.py index 176e56fa0..9654151bb 100644 --- a/keystoneclient/tests/unit/v3/test_two_factor.py +++ b/keystoneclient/tests/unit/v3/test_two_factor.py @@ -114,8 +114,7 @@ def test_remember_device(self): key_ref = { 'two_factor_auth': { 'device_id': device_id, - 'device_token': device_token, - 'user_id': user_id + 'device_token': device_token } } self.stub_url('POST', @@ -237,8 +236,7 @@ def test_two_factor_device_authenticate_success(self): token['methods'] = ["password"] token['password'] = { "device_data": { "device_id": device_id, - "device_token": device_token, - "user_id": user_id}, + "device_token": device_token}, 'password': password } self.stub_auth(json=token) @@ -246,8 +244,7 @@ def test_two_factor_device_authenticate_success(self): a = auth.TwoFactor( self.TEST_URL, device_data={ "device_id": device_id, - "device_token": device_token, - "user_id": user_id}, + "device_token": device_token}, password=password, user_id=user_id) s = session.Session(auth=a) @@ -261,8 +258,7 @@ def test_two_factor_device_authenticate_success(self): "password": { 'user': { "device_data": { "device_id": device_id, - "device_token": device_token, - "user_id": user_id}, + "device_token": device_token}, 'password': password, 'id': user_id } diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 6bdef9c23..04110beef 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -90,11 +90,11 @@ def check_security_question(self, user, security_answer): def remember_device(self, **kwargs): return super(KeyManager, self)._post(body={}, - url=EXTENSION_PATH+'/devices?' + urllib.urlencode(kwargs), + url=EXTENSION_PATH+'/devices?'+urllib.urlencode(kwargs), response_key="two_factor_auth") def delete_all_devices(self, user): return super(KeyManager, self)._delete(url=self._devices_url(user)) - def check_for_device(self, user_id, device_id, device_token): - return super(KeyManager, self)._head(url=EXTENSION_PATH+'/devices?user_id='+user_id+'&device_id='+device_id+'?device_token='+device_token) + def check_for_device(self, **kwargs): + return super(KeyManager, self)._head(url=EXTENSION_PATH+'/devices?'+urllib.urlencode(kwargs)) From cd904af136962f6b6770387552b68556143ed3a4 Mon Sep 17 00:00:00 2001 From: FedericoFdez Date: Thu, 5 May 2016 13:18:13 +0200 Subject: [PATCH 102/102] Meet new security answer API in two factor extension --- keystoneclient/v3/contrib/two_factor/keys.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/keystoneclient/v3/contrib/two_factor/keys.py b/keystoneclient/v3/contrib/two_factor/keys.py index 04110beef..aded34fc0 100644 --- a/keystoneclient/v3/contrib/two_factor/keys.py +++ b/keystoneclient/v3/contrib/two_factor/keys.py @@ -81,12 +81,8 @@ def get_two_factor_data(self, user): response_key="two_factor_auth") def check_security_question(self, user, security_answer): - data = {} - data["two_factor_auth"] = {} - data["two_factor_auth"]["security_answer"] = security_answer - return super(KeyManager, self)._head(body=data, - url=self._security_question_url(user)) + return super(KeyManager, self)._head(url=self._security_question_url(user) + '?sec_answer=' + security_answer) def remember_device(self, **kwargs): return super(KeyManager, self)._post(body={},