summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--etc/keystone.conf.sample8
-rw-r--r--keystone/catalog/core.py137
-rw-r--r--keystone/common/controller.py45
-rw-r--r--keystone/exception.py4
-rw-r--r--keystone/identity/core.py464
-rw-r--r--keystone/policy/core.py100
-rw-r--r--keystone/service.py177
7 files changed, 867 insertions, 68 deletions
diff --git a/etc/keystone.conf.sample b/etc/keystone.conf.sample
index da0b5128..43b23e25 100644
--- a/etc/keystone.conf.sample
+++ b/etc/keystone.conf.sample
@@ -183,6 +183,9 @@ paste.filter_factory = keystone.contrib.stats:StatsExtension.factory
[app:public_service]
paste.app_factory = keystone.service:public_app_factory
+[app:service_v3]
+paste.app_factory = keystone.service:v3_app_factory
+
[app:admin_service]
paste.app_factory = keystone.service:admin_app_factory
@@ -192,6 +195,9 @@ pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body j
[pipeline:admin_api]
pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service
+[pipeline:api_v3]
+pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension service_v3
+
[app:public_version_service]
paste.app_factory = keystone.service:public_version_app_factory
@@ -207,9 +213,11 @@ pipeline = stats_monitoring url_normalize xml_body admin_version_service
[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
+/v3 = api_v3
/ = public_version_api
[composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
+/v3 = api_v3
/ = admin_version_api
diff --git a/keystone/catalog/core.py b/keystone/catalog/core.py
index 430cc47e..e5b8fe66 100644
--- a/keystone/catalog/core.py
+++ b/keystone/catalog/core.py
@@ -19,6 +19,7 @@
import uuid
+from keystone.common import controller
from keystone.common import logging
from keystone.common import manager
from keystone.common import wsgi
@@ -106,6 +107,14 @@ class Manager(manager.Manager):
class Driver(object):
"""Interface description for an Catalog driver."""
+ def create_service(self, service_id, service_ref):
+ """Creates a new service.
+
+ :raises: keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
def list_services(self):
"""List all service ids in catalog.
@@ -114,6 +123,14 @@ class Driver(object):
"""
raise exception.NotImplemented()
+ def get_all_services(self):
+ """List all services.
+
+ :returns: list of service_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
def get_service(self, service_id):
"""Get service by id.
@@ -123,18 +140,19 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def delete_service(self, service_id):
- """Deletes an existing service.
+ def update_service(self, service_id):
+ """Update service by id.
+ :returns: service_ref dict
:raises: keystone.exception.ServiceNotFound
"""
raise exception.NotImplemented()
- def create_service(self, service_id, service_ref):
- """Creates a new service.
+ def delete_service(self, service_id):
+ """Deletes an existing service.
- :raises: keystone.exception.Conflict
+ :raises: keystone.exception.ServiceNotFound
"""
raise exception.NotImplemented()
@@ -148,27 +166,45 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def delete_endpoint(self, endpoint_id):
- """Deletes an endpoint for a service.
+ def get_endpoint(self, endpoint_id):
+ """Get endpoint by id.
+ :returns: endpoint_ref dict
:raises: keystone.exception.EndpointNotFound
"""
raise exception.NotImplemented()
- def get_endpoint(self, endpoint_id):
+ def list_endpoints(self):
+ """List all endpoint ids in catalog.
+
+ :returns: list of endpoint_ids or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def get_all_endpoints(self):
+ """List all endpoints.
+
+ :returns: list of endpoint_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def update_endpoint(self, endpoint_id, endpoint_ref):
"""Get endpoint by id.
:returns: endpoint_ref dict
:raises: keystone.exception.EndpointNotFound
+ keystone.exception.ServiceNotFound
"""
raise exception.NotImplemented()
- def list_endpoints(self):
- """List all endpoint ids in catalog.
+ def delete_endpoint(self, endpoint_id):
+ """Deletes an endpoint for a service.
- :returns: list of endpoint_ids or an empty list.
+ :raises: keystone.exception.EndpointNotFound
"""
raise exception.NotImplemented()
@@ -261,3 +297,82 @@ class EndpointController(wsgi.Application):
def delete_endpoint(self, context, endpoint_id):
self.assert_admin(context)
self.catalog_api.delete_endpoint(context, endpoint_id)
+
+
+class ServiceControllerV3(controller.V3Controller):
+ def create_service(self, context, service):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(service))
+ self._require_attribute(ref, 'type')
+
+ ref = self.catalog_api.create_service(context, ref['id'], ref)
+ return {'service': ref}
+
+ def list_services(self, context):
+ self.assert_admin(context)
+
+ refs = self.catalog_api.get_all_services(context)
+ refs = self._filter_by_attribute(context, refs, 'type')
+ return {'services': self._paginate(context, refs)}
+
+ def get_service(self, context, service_id):
+ self.assert_admin(context)
+
+ ref = self.catalog_api.get_service(context, service_id)
+ return {'service': ref}
+
+ def update_service(self, context, service_id, service):
+ self.assert_admin(context)
+
+ self._require_matching_id(service_id, service)
+
+ ref = self.catalog_api.update_service(context, service_id, service)
+ return {'service': ref}
+
+ def delete_service(self, context, service_id):
+ self.assert_admin(context)
+
+ return self.catalog_api.delete_service(context, service_id)
+
+
+class EndpointControllerV3(controller.V3Controller):
+ def create_endpoint(self, context, endpoint):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(endpoint))
+ self._require_attribute(ref, 'service_id')
+ self._require_attribute(ref, 'interface')
+ self.catalog_api.get_service(context, ref['service_id'])
+
+ ref = self.catalog_api.create_endpoint(context, ref['id'], ref)
+ return {'endpoint': ref}
+
+ def list_endpoints(self, context):
+ self.assert_admin(context)
+
+ refs = self.catalog_api.get_all_endpoints(context)
+ refs = self._filter_by_attribute(context, refs, 'service_id')
+ refs = self._filter_by_attribute(context, refs, 'interface')
+ return {'endpoints': self._paginate(context, refs)}
+
+ def get_endpoint(self, context, endpoint_id):
+ self.assert_admin(context)
+
+ ref = self.catalog_api.get_endpoint(context, endpoint_id)
+ return {'endpoint': ref}
+
+ def update_endpoint(self, context, endpoint_id, endpoint):
+ self.assert_admin(context)
+
+ self._require_matching_id(endpoint_id, endpoint)
+
+ if 'service_id' in endpoint:
+ self.catalog_api.get_service(context, endpoint['service_id'])
+
+ ref = self.catalog_api.update_endpoint(context, endpoint_id, endpoint)
+ return {'endpoint': ref}
+
+ def delete_endpoint(self, context, endpoint_id):
+ self.assert_admin(context)
+ return self.catalog_api.delete_endpoint(context, endpoint_id)
diff --git a/keystone/common/controller.py b/keystone/common/controller.py
new file mode 100644
index 00000000..cb8d90d8
--- /dev/null
+++ b/keystone/common/controller.py
@@ -0,0 +1,45 @@
+import uuid
+
+from keystone.common import wsgi
+from keystone import exception
+
+
+class V3Controller(wsgi.Application):
+ """Base controller class for Identity API v3."""
+
+ def __init__(self, catalog_api, identity_api, token_api, policy_api):
+ self.catalog_api = catalog_api
+ self.identity_api = identity_api
+ self.policy_api = policy_api
+ self.token_api = token_api
+ super(V3Controller, self).__init__()
+
+ def _paginate(self, context, refs):
+ """Paginates a list of references by page & per_page query strings."""
+ page = context['query_string'].get('page', 1)
+ per_page = context['query_string'].get('per_page', 30)
+ return refs[per_page * (page - 1):per_page * page]
+
+ def _require_attribute(self, ref, attr):
+ """Ensures the reference contains the specified attribute."""
+ if ref.get(attr) is None or ref.get(attr) == '':
+ msg = '%s field is required and cannot be empty' % attr
+ raise exception.ValidationError(message=msg)
+
+ def _require_matching_id(self, value, ref):
+ """Ensures the value matches the reference's ID, if any."""
+ if 'id' in ref and ref['id'] != value:
+ raise exception.ValidationError('Cannot change ID')
+
+ def _assign_unique_id(self, ref):
+ """Generates and assigns a unique identifer to a reference."""
+ ref = ref.copy()
+ ref['id'] = uuid.uuid4().hex
+ return ref
+
+ def _filter_by_attribute(self, context, refs, attr):
+ """Filters a list of references by query string value."""
+ if attr in context['query_string']:
+ value = context['query_string'][attr]
+ return [r for r in refs if r[attr] == value]
+ return refs
diff --git a/keystone/exception.py b/keystone/exception.py
index c3b3ec82..cc61a632 100644
--- a/keystone/exception.py
+++ b/keystone/exception.py
@@ -83,6 +83,10 @@ class MetadataNotFound(NotFound):
# so this exception should not be exposed
+class PolicyNotFound(NotFound):
+ """Could not find policy: %(policy_id)s"""
+
+
class RoleNotFound(NotFound):
"""Could not find role: %(role_id)s"""
diff --git a/keystone/identity/core.py b/keystone/identity/core.py
index 3b4b3c8d..89663047 100644
--- a/keystone/identity/core.py
+++ b/keystone/identity/core.py
@@ -20,6 +20,7 @@ import urllib
import urlparse
import uuid
+from keystone.common import controller
from keystone.common import logging
from keystone.common import manager
from keystone.common import wsgi
@@ -89,15 +90,6 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def get_user(self, user_id):
- """Get a user by id.
-
- :returns: user_ref
- :raises: keystone.exception.UserNotFound
-
- """
- raise exception.NotImplemented()
-
def get_user_by_name(self, user_name):
"""Get a user by name.
@@ -107,36 +99,6 @@ class Driver(object):
"""
raise exception.NotImplemented()
- def get_role(self, role_id):
- """Get a role by id.
-
- :returns: role_ref
- :raises: keystone.exception.RoleNotFound
-
- """
- raise exception.NotImplemented()
-
- def list_users(self):
- """List all users in the system.
-
- NOTE(termie): I'd prefer if this listed only the users for a given
- tenant.
-
- :returns: a list of user_refs or an empty list
-
- """
- raise exception.NotImplemented()
-
- def list_roles(self):
- """List all roles in the system.
-
- :returns: a list of role_refs or an empty list.
-
- """
- raise exception.NotImplemented()
-
- # NOTE(termie): seven calls below should probably be exposed by the api
- # more clearly when the api redesign happens
def add_user_to_tenant(self, tenant_id, user_id):
"""Add user to a tenant without an explicit role relationship.
@@ -215,7 +177,132 @@ class Driver(object):
"""
raise exception.NotImplemented()
+ # tenant crud
+ def create_tenant(self, tenant_id, tenant):
+ """Creates a new tenant.
+
+ :raises: keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def update_tenant(self, tenant_id, tenant):
+ """Updates an existing tenant.
+
+ :raises: keystone.exception.TenantNotFound, keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_tenant(self, tenant_id):
+ """Deletes an existing tenant.
+
+ :raises: keystone.exception.TenantNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ # metadata crud
+ def get_metadata(self, user_id, tenant_id):
+ raise exception.NotImplemented()
+
+ def create_metadata(self, user_id, tenant_id, metadata):
+ raise exception.NotImplemented()
+
+ def update_metadata(self, user_id, tenant_id, metadata):
+ raise exception.NotImplemented()
+
+ def delete_metadata(self, user_id, tenant_id):
+ raise exception.NotImplemented()
+
+ # domain crud
+ def create_domain(self, domain_id, domain):
+ """Creates a new domain.
+
+ :raises: keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def list_domains(self):
+ """List all domains in the system.
+
+ :returns: a list of domain_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def get_domain(self, domain_id):
+ """Get a domain by ID.
+
+ :returns: user_ref
+ :raises: keystone.exception.DomainNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ def update_domain(self, domain_id, domain):
+ """Updates an existing domain.
+
+ :raises: keystone.exception.DomainNotFound,
+ keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_domain(self, domain_id):
+ """Deletes an existing domain.
+
+ :raises: keystone.exception.DomainNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ # project crud
+ def create_project(self, project_id, project):
+ """Creates a new project.
+
+ :raises: keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def list_projects(self):
+ """List all projects in the system.
+
+ :returns: a list of project_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def get_project(self):
+ """Get a project by ID.
+
+ :returns: user_ref
+ :raises: keystone.exception.ProjectNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ def update_project(self, project_id, project):
+ """Updates an existing project.
+
+ :raises: keystone.exception.ProjectNotFound,
+ keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_project(self, project_id):
+ """Deletes an existing project.
+
+ :raises: keystone.exception.ProjectNotFound
+
+ """
+ raise exception.NotImplemented()
+
# user crud
+
def create_user(self, user_id, user):
"""Creates a new user.
@@ -224,10 +311,28 @@ class Driver(object):
"""
raise exception.NotImplemented()
+ def list_users(self):
+ """List all users in the system.
+
+ :returns: a list of user_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def get_user(self, user_id):
+ """Get a user by ID.
+
+ :returns: user_ref
+ :raises: keystone.exception.UserNotFound
+
+ """
+ raise exception.NotImplemented()
+
def update_user(self, user_id, user):
"""Updates an existing user.
- :raises: keystone.exception.UserNotFound, keystone.exception.Conflict
+ :raises: keystone.exception.UserNotFound,
+ keystone.exception.Conflict
"""
raise exception.NotImplemented()
@@ -240,45 +345,52 @@ class Driver(object):
"""
raise exception.NotImplemented()
- # tenant crud
- def create_tenant(self, tenant_id, tenant):
- """Creates a new tenant.
+ # credential crud
+
+ def create_credential(self, credential_id, credential):
+ """Creates a new credential.
:raises: keystone.exception.Conflict
"""
raise exception.NotImplemented()
- def update_tenant(self, tenant_id, tenant):
- """Updates an existing tenant.
+ def list_credentials(self):
+ """List all credentials in the system.
- :raises: keystone.exception.TenantNotFound, keystone.exception.Conflict
+ :returns: a list of credential_refs or an empty list.
"""
raise exception.NotImplemented()
- def delete_tenant(self, tenant_id):
- """Deletes an existing tenant.
+ def get_credential(self, credential_id):
+ """Get a credential by ID.
- :raises: keystone.exception.TenantNotFound
+ :returns: credential_ref
+ :raises: keystone.exception.CredentialNotFound
"""
raise exception.NotImplemented()
- # metadata crud
- def get_metadata(self, user_id, tenant_id):
- raise exception.NotImplemented()
+ def update_credential(self, credential_id, credential):
+ """Updates an existing credential.
- def create_metadata(self, user_id, tenant_id, metadata):
- raise exception.NotImplemented()
+ :raises: keystone.exception.CredentialNotFound,
+ keystone.exception.Conflict
- def update_metadata(self, user_id, tenant_id, metadata):
+ """
raise exception.NotImplemented()
- def delete_metadata(self, user_id, tenant_id):
+ def delete_credential(self, credential_id):
+ """Deletes an existing credential.
+
+ :raises: keystone.exception.CredentialNotFound
+
+ """
raise exception.NotImplemented()
# role crud
+
def create_role(self, role_id, role):
"""Creates a new role.
@@ -287,10 +399,28 @@ class Driver(object):
"""
raise exception.NotImplemented()
+ def list_roles(self):
+ """List all roles in the system.
+
+ :returns: a list of role_refs or an empty list.
+
+ """
+ raise exception.NotImplemented()
+
+ def get_role(self, role_id):
+ """Get a role by ID.
+
+ :returns: role_ref
+ :raises: keystone.exception.RoleNotFound
+
+ """
+ raise exception.NotImplemented()
+
def update_role(self, role_id, role):
"""Updates an existing role.
- :raises: keystone.exception.RoleNotFound, keystone.exception.Conflict
+ :raises: keystone.exception.RoleNotFound,
+ keystone.exception.Conflict
"""
raise exception.NotImplemented()
@@ -721,3 +851,223 @@ class RoleController(wsgi.Application):
self.identity_api.remove_user_from_tenant(
context, tenant_id, user_id)
self.token_api.revoke_tokens(context, user_id, tenant_id)
+
+
+class DomainControllerV3(controller.V3Controller):
+ def create_domain(self, context, domain):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(domain))
+ ref = self.identity_api.create_domain(context, ref['id'], ref)
+ return {'domain': ref}
+
+ def list_domains(self, context):
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_domains(context)
+ return {'domains': self._paginate(context, refs)}
+
+ def get_domain(self, context, domain_id):
+ self.assert_admin(context)
+
+ ref = self.identity_api.get_domain(context, domain_id)
+ return {'domain': ref}
+
+ def update_domain(self, context, domain_id, domain):
+ self.assert_admin(context)
+
+ self._require_matching_id(domain_id, domain)
+
+ ref = self.identity_api.update_domain(context, domain_id, domain)
+ return {'domain': ref}
+
+ def delete_domain(self, context, domain_id):
+ self.assert_admin(context)
+ return self.identity_api.delete_domain(context, domain_id)
+
+
+class ProjectControllerV3(controller.V3Controller):
+ def create_project(self, context, project):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(project))
+ ref = self.identity_api.create_project(context, ref['id'], ref)
+ return {'project': ref}
+
+ def list_projects(self, context):
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_projects(context)
+ return {'projects': self._paginate(context, refs)}
+
+ def list_user_projects(self, context, user_id):
+ # FIXME(dolph): this should also be callable by user_id themselves
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_user_projects(context, user_id)
+ return {'projects': self._paginate(context, refs)}
+
+ def get_project(self, context, project_id):
+ self.assert_admin(context)
+
+ ref = self.identity_api.get_project(context, project_id)
+ return {'project': ref}
+
+ def update_project(self, context, project_id, project):
+ self.assert_admin(context)
+
+ self._require_matching_id(project_id, project)
+
+ ref = self.identity_api.update_project(context, project_id, project)
+ return {'project': ref}
+
+ def delete_project(self, context, project_id):
+ self.assert_admin(context)
+ return self.identity_api.delete_project(context, project_id)
+
+
+class UserControllerV3(controller.V3Controller):
+ def create_user(self, context, user):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(user))
+ ref = self.identity_api.create_user(context, ref['id'], ref)
+ return {'user': ref}
+
+ def list_users(self, context):
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_users(context)
+ return {'users': self._paginate(context, refs)}
+
+ def get_user(self, context, user_id):
+ self.assert_admin(context)
+
+ ref = self.identity_api.get_user(context, user_id)
+ return {'user': ref}
+
+ def update_user(self, context, user_id, user):
+ self.assert_admin(context)
+
+ self._require_matching_id(user_id, user)
+
+ ref = self.identity_api.update_user(context, user_id, user)
+ return {'user': ref}
+
+ def delete_user(self, context, user_id):
+ self.assert_admin(context)
+ return self.identity_api.delete_user(context, user_id)
+
+
+class CredentialControllerV3(controller.V3Controller):
+ def create_credential(self, context, credential):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(credential))
+ ref = self.identity_api.create_credential(context, ref['id'], ref)
+ return {'credential': ref}
+
+ def list_credentials(self, context):
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_credentials(context)
+ return {'credentials': self._paginate(context, refs)}
+
+ def get_credential(self, context, credential_id):
+ self.assert_admin(context)
+
+ ref = self.identity_api.get_credential(context, credential_id)
+ return {'credential': ref}
+
+ def update_credential(self, context, credential_id, credential):
+ self.assert_admin(context)
+
+ self._require_matching_id(credential_id, credential)
+
+ ref = self.identity_api.update_credential(
+ context,
+ credential_id,
+ credential)
+ return {'credential': ref}
+
+ def delete_credential(self, context, credential_id):
+ self.assert_admin(context)
+ return self.identity_api.delete_credential(context, credential_id)
+
+
+class RoleControllerV3(controller.V3Controller):
+ def create_role(self, context, role):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(role))
+ ref = self.identity_api.create_role(context, ref['id'], ref)
+ return {'role': ref}
+
+ def list_roles(self, context):
+ self.assert_admin(context)
+
+ refs = self.identity_api.list_roles(context)
+ return {'roles': self._paginate(context, refs)}
+
+ def get_role(self, context, role_id):
+ self.assert_admin(context)
+
+ ref = self.identity_api.get_role(context, role_id)
+ return {'role': ref}
+
+ def update_role(self, context, role_id, role):
+ self.assert_admin(context)
+
+ self._require_matching_id(role_id, role)
+
+ ref = self.identity_api.update_role(context, role_id, role)
+ return {'role': ref}
+
+ def delete_role(self, context, role_id):
+ self.assert_admin(context)
+ return self.identity_api.delete_role(context, role_id)
+
+ def _require_domain_or_project(self, domain_id, project_id):
+ if (domain_id and project_id) or (not domain_id and not project_id):
+ msg = 'Specify a domain or project, not both'
+ raise exception.ValidationError(msg)
+
+ def create_grant(self, context, role_id, user_id, domain_id=None,
+ project_id=None):
+ """Grants a role to a user on either a domain or project."""
+ self.assert_admin(context)
+
+ self._require_domain_or_project(domain_id, project_id)
+
+ return self.identity_api.create_grant(
+ context, role_id, user_id, domain_id, project_id)
+
+ def list_grants(self, context, user_id, domain_id=None,
+ project_id=None):
+ """Lists roles granted to a user on either a domain or project."""
+ self.assert_admin(context)
+
+ self._require_domain_or_project(domain_id, project_id)
+
+ return self.identity_api.list_grants(
+ context, user_id, domain_id, project_id)
+
+ def check_grant(self, context, role_id, user_id, domain_id=None,
+ project_id=None):
+ """Checks if a role has been granted on either a domain or project."""
+ self.assert_admin(context)
+
+ self._require_domain_or_project(domain_id, project_id)
+
+ self.identity_api.get_grant(
+ context, role_id, user_id, domain_id, project_id)
+
+ def revoke_grant(self, context, role_id, user_id, domain_id=None,
+ project_id=None):
+ """Revokes a role from a user on either a domain or project."""
+ self.assert_admin(context)
+
+ self._require_domain_or_project(domain_id, project_id)
+
+ self.identity_api.delete_grant(
+ context, role_id, user_id, domain_id, project_id)
diff --git a/keystone/policy/core.py b/keystone/policy/core.py
index 5b60443b..498e027b 100644
--- a/keystone/policy/core.py
+++ b/keystone/policy/core.py
@@ -16,7 +16,9 @@
"""Main entry point into the Policy service."""
+
from keystone.common import manager
+from keystone.common import controller
from keystone import config
from keystone import exception
@@ -35,6 +37,26 @@ class Manager(manager.Manager):
def __init__(self):
super(Manager, self).__init__(CONF.policy.driver)
+ def get_policy(self, context, policy_id):
+ try:
+ return self.driver.get_policy(policy_id)
+ except exception.NotFound:
+ raise exception.PolicyNotFound(policy_id=policy_id)
+
+ def update_policy(self, context, policy_id, policy):
+ if 'id' in policy and policy_id != policy['id']:
+ raise exception.ValidationError('Cannot change policy ID')
+ try:
+ return self.driver.update_policy(policy_id, policy)
+ except exception.NotFound:
+ raise exception.PolicyNotFound(policy_id=policy_id)
+
+ def delete_policy(self, context, policy_id):
+ try:
+ return self.driver.delete_policy(policy_id)
+ except exception.NotFound:
+ raise exception.PolicyNotFound(policy_id=policy_id)
+
class Driver(object):
def enforce(context, credentials, action, target):
@@ -44,3 +66,81 @@ class Driver(object):
`keystone.common.policy.enforce`.
"""
raise exception.NotImplemented()
+
+ def create_policy(self, policy_id, policy):
+ """Store a policy blob for a particular endpoint.
+
+ :raises: keystone.exception.EndpointNotFound,
+ keystone.exception.Conflict
+
+ """
+ raise exception.NotImplemented()
+
+ def list_policies(self):
+ """List all policies."""
+ raise exception.NotImplemented()
+
+ def get_policy(self, policy_id):
+ """Retrieve a specific policy blob.
+
+ :raises: keystone.exception.PolicyNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ def update_policy(self, policy_id, policy):
+ """Update a policy blob.
+
+ :raises: keystone.exception.PolicyNotFound,
+ keystone.exception.EndpointNotFound
+
+ """
+ raise exception.NotImplemented()
+
+ def delete_policy(self, policy_id):
+ """Remove a policy blob.
+
+ :raises: keystone.exception.PolicyNotFound
+
+ """
+ raise exception.NotImplemented()
+
+
+class PolicyControllerV3(controller.V3Controller):
+ def create_policy(self, context, policy):
+ self.assert_admin(context)
+
+ ref = self._assign_unique_id(self._normalize_dict(policy))
+ self._require_attribute(ref, 'blob')
+ self._require_attribute(ref, 'type')
+ self._require_attribute(ref, 'endpoint_id')
+
+ self.catalog_api.get_endpoint(context, ref['endpoint_id'])
+
+ ref = self.policy_api.create_policy(context, ref['id'], ref)
+ return {'policy': ref}
+
+ def list_policies(self, context):
+ self.assert_admin(context)
+ refs = self.policy_api.list_policies(context)
+ refs = self._filter_by_attribute(context, refs, 'endpoint_id')
+ refs = self._filter_by_attribute(context, refs, 'type')
+ return {'policies': self._paginate(context, refs)}
+
+ def get_policy(self, context, policy_id):
+ self.assert_admin(context)
+ ref = self.policy_api.get_policy(context, policy_id)
+ return {'policy': ref}
+
+ def update_policy(self, context, policy_id, policy):
+ self.assert_admin(context)
+
+ if 'endpoint_id' in policy:
+ self.catalog_api.get_endpoint(context, policy['endpoint_id'])
+
+ ref = self.policy_api.update_policy(context, policy_id, policy)
+ return {'policy': ref}
+
+ def delete_policy(self, context, policy_id):
+ self.assert_admin(context)
+ return self.policy_api.delete_policy(context, policy_id)
diff --git a/keystone/service.py b/keystone/service.py
index b6443a70..334f6150 100644
--- a/keystone/service.py
+++ b/keystone/service.py
@@ -33,6 +33,176 @@ from keystone import token
LOG = logging.getLogger(__name__)
+class V3Router(wsgi.ComposingRouter):
+ def crud_routes(self, mapper, controller, collection_key, key):
+ collection_path = '/%(collection_key)s' % {
+ 'collection_key': collection_key}
+ entity_path = '/%(collection_key)s/{%(key)s_id}' % {
+ 'collection_key': collection_key,
+ 'key': key}
+
+ mapper.connect(
+ collection_path,
+ controller=controller,
+ action='create_%s' % key,
+ conditions=dict(method=['POST']))
+ mapper.connect(
+ collection_path,
+ controller=controller,
+ action='list_%s' % collection_key,
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ entity_path,
+ controller=controller,
+ action='get_%s' % key,
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ entity_path,
+ controller=controller,
+ action='update_%s' % key,
+ conditions=dict(method=['PATCH']))
+ mapper.connect(
+ entity_path,
+ controller=controller,
+ action='delete_%s' % key,
+ conditions=dict(method=['DELETE']))
+
+ def __init__(self):
+ mapper = routes.Mapper()
+
+ apis = dict(
+ catalog_api=catalog.Manager(),
+ identity_api=identity.Manager(),
+ policy_api=policy.Manager(),
+ token_api=token.Manager())
+
+ # Catalog
+
+ self.crud_routes(
+ mapper,
+ catalog.ServiceControllerV3(**apis),
+ 'services',
+ 'service')
+
+ self.crud_routes(
+ mapper,
+ catalog.EndpointControllerV3(**apis),
+ 'endpoints',
+ 'endpoint')
+
+ # Identity
+
+ self.crud_routes(
+ mapper,
+ identity.DomainControllerV3(**apis),
+ 'domains',
+ 'domain')
+
+ project_controller = identity.ProjectControllerV3(**apis)
+ self.crud_routes(
+ mapper,
+ project_controller,
+ 'projects',
+ 'project')
+ mapper.connect(
+ '/users/{user_id}/projects',
+ controller=project_controller,
+ action='list_user_projects',
+ conditions=dict(method=['GET']))
+
+ self.crud_routes(
+ mapper,
+ identity.UserControllerV3(**apis),
+ 'users',
+ 'user')
+
+ self.crud_routes(
+ mapper,
+ identity.CredentialControllerV3(**apis),
+ 'credentials',
+ 'credential')
+
+ role_controller = identity.RoleControllerV3(**apis)
+ self.crud_routes(
+ mapper,
+ role_controller,
+ 'roles',
+ 'role')
+ mapper.connect(
+ '/projects/{project_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='create_grant',
+ conditions=dict(method=['PUT']))
+ mapper.connect(
+ '/projects/{project_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='check_grant',
+ conditions=dict(method=['HEAD']))
+ mapper.connect(
+ '/projects/{project_id}/users/{user_id}/roles',
+ controller=role_controller,
+ action='list_grants',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/projects/{project_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='revoke_grant',
+ conditions=dict(method=['DELETE']))
+ mapper.connect(
+ '/domains/{domain_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='create_grant',
+ conditions=dict(method=['PUT']))
+ mapper.connect(
+ '/domains/{domain_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='check_grant',
+ conditions=dict(method=['HEAD']))
+ mapper.connect(
+ '/domains/{domain_id}/users/{user_id}/roles',
+ controller=role_controller,
+ action='list_grants',
+ conditions=dict(method=['GET']))
+ mapper.connect(
+ '/domains/{domain_id}/users/{user_id}/roles/{role_id}',
+ controller=role_controller,
+ action='revoke_grant',
+ conditions=dict(method=['DELETE']))
+
+ # Policy
+
+ policy_controller = policy.PolicyControllerV3(**apis)
+ self.crud_routes(
+ mapper,
+ policy_controller,
+ 'policies',
+ 'policy')
+
+ # Token
+
+ """
+ # v2.0 LEGACY
+ mapper.connect('/tokens/{token_id}',
+ controller=auth_controller,
+ action='validate_token',
+ conditions=dict(method=['GET']))
+ mapper.connect('/tokens/{token_id}',
+ controller=auth_controller,
+ action='validate_token_head',
+ conditions=dict(method=['HEAD']))
+ mapper.connect('/tokens/{token_id}',
+ controller=auth_controller,
+ action='delete_token',
+ conditions=dict(method=['DELETE']))
+ mapper.connect('/tokens/{token_id}/endpoints',
+ controller=auth_controller,
+ action='endpoints',
+ conditions=dict(method=['GET']))
+ """
+
+ super(V3Router, self).__init__(mapper, [])
+
+
class AdminRouter(wsgi.ComposingRouter):
def __init__(self):
mapper = routes.Mapper()
@@ -808,3 +978,10 @@ def admin_version_app_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
return AdminVersionRouter()
+
+
+@logging.fail_gracefully
+def v3_app_factory(global_conf, **local_conf):
+ conf = global_conf.copy()
+ conf.update(local_conf)
+ return V3Router()