fix self-service credential APIs
Self-service credential APIs are broken in stable/rocky because it no longer building the target attributes with the entity data. Therefore, only "admin" can perform credential optionations. The lack of self-service capability limits the usefulness of these APIs. Arguably this is break backward compatibility as self-service used to work in stable/queens and older releases. Though we've never built the properly tests to guard this functionality. This patch re-enables self-service capability by conveying the entity data via the target attributes. It also build the proper tests for it. Change-Id: Ic7dddc4d2fe7b6c6ae3bf6aed6c4c048743b3eed Closes-Bug: 1815539
This commit is contained in:
parent
a2e307ed4d
commit
4420b78c01
@ -31,6 +31,20 @@ PROVIDERS = provider_api.ProviderAPIs
|
|||||||
ENFORCER = rbac_enforcer.RBACEnforcer
|
ENFORCER = rbac_enforcer.RBACEnforcer
|
||||||
|
|
||||||
|
|
||||||
|
def _build_target_enforcement():
|
||||||
|
target = {}
|
||||||
|
try:
|
||||||
|
target['credential'] = PROVIDERS.credential_api.get_credential(
|
||||||
|
flask.request.view_args.get('credential_id')
|
||||||
|
)
|
||||||
|
except exception.NotFound: # nosec
|
||||||
|
# Defer existance in the event the credential doesn't exist, we'll
|
||||||
|
# check this later anyway.
|
||||||
|
pass
|
||||||
|
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
class CredentialResource(ks_flask.ResourceBase):
|
class CredentialResource(ks_flask.ResourceBase):
|
||||||
collection_key = 'credentials'
|
collection_key = 'credentials'
|
||||||
member_key = 'credential'
|
member_key = 'credential'
|
||||||
@ -83,7 +97,10 @@ class CredentialResource(ks_flask.ResourceBase):
|
|||||||
return self.wrap_collection(refs, hints=hints)
|
return self.wrap_collection(refs, hints=hints)
|
||||||
|
|
||||||
def _get_credential(self, credential_id):
|
def _get_credential(self, credential_id):
|
||||||
ENFORCER.enforce_call(action='identity:get_credential')
|
ENFORCER.enforce_call(
|
||||||
|
action='identity:get_credential',
|
||||||
|
target_attr=_build_target_enforcement()
|
||||||
|
)
|
||||||
ref = PROVIDERS.credential_api.get_credential(credential_id)
|
ref = PROVIDERS.credential_api.get_credential(credential_id)
|
||||||
return self.wrap_member(self._blob_to_json(ref))
|
return self.wrap_member(self._blob_to_json(ref))
|
||||||
|
|
||||||
@ -108,7 +125,10 @@ class CredentialResource(ks_flask.ResourceBase):
|
|||||||
|
|
||||||
def patch(self, credential_id):
|
def patch(self, credential_id):
|
||||||
# Update Credential
|
# Update Credential
|
||||||
ENFORCER.enforce_call(action='identity:update_credential')
|
ENFORCER.enforce_call(
|
||||||
|
action='identity:update_credential',
|
||||||
|
target_attr=_build_target_enforcement()
|
||||||
|
)
|
||||||
credential = flask.request.json.get('credential', {})
|
credential = flask.request.json.get('credential', {})
|
||||||
validation.lazy_validate(schema.credential_update, credential)
|
validation.lazy_validate(schema.credential_update, credential)
|
||||||
self._require_matching_id(credential)
|
self._require_matching_id(credential)
|
||||||
@ -118,7 +138,11 @@ class CredentialResource(ks_flask.ResourceBase):
|
|||||||
|
|
||||||
def delete(self, credential_id):
|
def delete(self, credential_id):
|
||||||
# Delete credentials
|
# Delete credentials
|
||||||
ENFORCER.enforce_call(action='identity:delete_credential')
|
ENFORCER.enforce_call(
|
||||||
|
action='identity:delete_credential',
|
||||||
|
target_attr=_build_target_enforcement()
|
||||||
|
)
|
||||||
|
|
||||||
return (PROVIDERS.credential_api.delete_credential(credential_id),
|
return (PROVIDERS.credential_api.delete_credential(credential_id),
|
||||||
http_client.NO_CONTENT)
|
http_client.NO_CONTENT)
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
||||||
|
from oslo_serialization import jsonutils
|
||||||
from six.moves import http_client
|
from six.moves import http_client
|
||||||
from testtools import matchers
|
from testtools import matchers
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ from keystone.credential.providers import fernet as credential_fernet
|
|||||||
from keystone import exception
|
from keystone import exception
|
||||||
from keystone.tests import unit
|
from keystone.tests import unit
|
||||||
from keystone.tests.unit import ksfixtures
|
from keystone.tests.unit import ksfixtures
|
||||||
|
from keystone.tests.unit.ksfixtures import temporaryfile
|
||||||
from keystone.tests.unit import test_v3
|
from keystone.tests.unit import test_v3
|
||||||
|
|
||||||
|
|
||||||
@ -347,6 +349,95 @@ class CredentialTestCase(CredentialBaseTestCase):
|
|||||||
self.assertValidCredentialResponse(r, ref)
|
self.assertValidCredentialResponse(r, ref)
|
||||||
|
|
||||||
|
|
||||||
|
class CredentialSelfServiceTestCase(CredentialBaseTestCase):
|
||||||
|
"""Test self-service credential CRUD."""
|
||||||
|
|
||||||
|
def _policy_fixture(self):
|
||||||
|
return ksfixtures.Policy(self.tmpfilename, self.config_fixture)
|
||||||
|
|
||||||
|
def _set_policy(self, new_policy):
|
||||||
|
with open(self.tmpfilename, "w") as policyfile:
|
||||||
|
policyfile.write(jsonutils.dumps(new_policy))
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.tempfile = self.useFixture(temporaryfile.SecureTempFile())
|
||||||
|
self.tmpfilename = self.tempfile.file_name
|
||||||
|
super(CredentialSelfServiceTestCase, self).setUp()
|
||||||
|
|
||||||
|
# set the self-service credential policies
|
||||||
|
self_service_credential_policies = {
|
||||||
|
"identity:create_credential": "user_id:%(credential.user_id)s",
|
||||||
|
"identity:list_credentials": "user_id:%(user_id)s",
|
||||||
|
"identity:get_credential": "user_id:%(target.credential.user_id)s",
|
||||||
|
"identity:update_credential":
|
||||||
|
"user_id:%(target.credential.user_id)s",
|
||||||
|
"identity:delete_credential":
|
||||||
|
"user_id:%(target.credential.user_id)s"
|
||||||
|
}
|
||||||
|
self._set_policy(self_service_credential_policies)
|
||||||
|
|
||||||
|
# remove the 'admin' role from user and replace it with an
|
||||||
|
# arbitrary role
|
||||||
|
PROVIDERS.assignment_api.remove_role_from_user_and_project(
|
||||||
|
self.user_id, self.project_id, self.role_id)
|
||||||
|
self.arbitrary_role = unit.new_role_ref(name=uuid.uuid4().hex)
|
||||||
|
PROVIDERS.role_api.create_role(self.arbitrary_role['id'],
|
||||||
|
self.arbitrary_role)
|
||||||
|
PROVIDERS.assignment_api.add_role_to_user_and_project(
|
||||||
|
self.user_id, self.project_id, self.arbitrary_role['id'])
|
||||||
|
|
||||||
|
self.credential = unit.new_credential_ref(user_id=self.user['id'],
|
||||||
|
project_id=self.project_id)
|
||||||
|
|
||||||
|
PROVIDERS.credential_api.create_credential(
|
||||||
|
self.credential['id'],
|
||||||
|
self.credential)
|
||||||
|
|
||||||
|
def test_list_credentials_filtered_by_user_id(self):
|
||||||
|
"""Call ``GET /credentials?user_id={user_id}``."""
|
||||||
|
credential = unit.new_credential_ref(user_id=uuid.uuid4().hex)
|
||||||
|
PROVIDERS.credential_api.create_credential(
|
||||||
|
credential['id'], credential
|
||||||
|
)
|
||||||
|
|
||||||
|
r = self.get('/credentials?user_id=%s' % self.user['id'])
|
||||||
|
self.assertValidCredentialListResponse(r, ref=self.credential)
|
||||||
|
for cred in r.result['credentials']:
|
||||||
|
self.assertEqual(self.user['id'], cred['user_id'])
|
||||||
|
|
||||||
|
def test_create_credential(self):
|
||||||
|
"""Call ``POST /credentials``."""
|
||||||
|
ref = unit.new_credential_ref(user_id=self.user['id'])
|
||||||
|
r = self.post(
|
||||||
|
'/credentials',
|
||||||
|
body={'credential': ref})
|
||||||
|
self.assertValidCredentialResponse(r, ref)
|
||||||
|
|
||||||
|
def test_get_credential(self):
|
||||||
|
"""Call ``GET /credentials/{credential_id}``."""
|
||||||
|
r = self.get(
|
||||||
|
'/credentials/%(credential_id)s' % {
|
||||||
|
'credential_id': self.credential['id']})
|
||||||
|
self.assertValidCredentialResponse(r, self.credential)
|
||||||
|
|
||||||
|
def test_update_credential(self):
|
||||||
|
"""Call ``PATCH /credentials/{credential_id}``."""
|
||||||
|
ref = unit.new_credential_ref(user_id=self.user['id'],
|
||||||
|
project_id=self.project_id)
|
||||||
|
del ref['id']
|
||||||
|
r = self.patch(
|
||||||
|
'/credentials/%(credential_id)s' % {
|
||||||
|
'credential_id': self.credential['id']},
|
||||||
|
body={'credential': ref})
|
||||||
|
self.assertValidCredentialResponse(r, ref)
|
||||||
|
|
||||||
|
def test_delete_credential(self):
|
||||||
|
"""Call ``DELETE /credentials/{credential_id}``."""
|
||||||
|
self.delete(
|
||||||
|
'/credentials/%(credential_id)s' % {
|
||||||
|
'credential_id': self.credential['id']})
|
||||||
|
|
||||||
|
|
||||||
class TestCredentialTrustScoped(test_v3.RestfulTestCase):
|
class TestCredentialTrustScoped(test_v3.RestfulTestCase):
|
||||||
"""Test credential with trust scoped token."""
|
"""Test credential with trust scoped token."""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user