Implement GET /v3/auth/system
Keystone has APIs for retrieving projects and domains based on the role assignments a user has on projects and domains. We should introduce similar functionality for system assignments. This will make discovering system access for users and client easier. bp system-scope Change-Id: Iab577fcd1b57b8b5593c3f9d50a772466383a999
This commit is contained in:
parent
9cd5f198da
commit
a50fafd246
|
@ -921,4 +921,55 @@ Example
|
|||
~~~~~~~
|
||||
|
||||
.. literalinclude:: ./samples/admin/get-available-domain-scopes-response.json
|
||||
:language: javascript
|
||||
:language: javascript
|
||||
|
||||
Get available system scopes
|
||||
===========================
|
||||
|
||||
.. rest_method:: GET /v3/auth/system
|
||||
|
||||
New in version 3.10
|
||||
|
||||
This call returns the list of systems that are available to be scoped
|
||||
to based on the X-Auth-Token provided in the request.
|
||||
|
||||
Relationship: ``https://docs.openstack.org/api/openstack-identity/3/rel/auth_system``
|
||||
|
||||
Request
|
||||
-------
|
||||
|
||||
Parameters
|
||||
~~~~~~~~~~
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- X-Auth-Token: X-Auth-Token
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
Parameters
|
||||
~~~~~~~~~~
|
||||
|
||||
.. rest_parameters:: parameters.yaml
|
||||
|
||||
- links: domain_link_response_body
|
||||
- system: response_body_system_required
|
||||
|
||||
Status Codes
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. rest_status_code:: success status.yaml
|
||||
|
||||
- 200
|
||||
|
||||
.. rest_status_code:: error status.yaml
|
||||
|
||||
- 401
|
||||
- 400
|
||||
|
||||
Example
|
||||
~~~~~~~
|
||||
|
||||
.. literalinclude:: ./samples/admin/get-available-system-scopes-response.json
|
||||
:language: javascript
|
||||
|
|
|
@ -1315,6 +1315,12 @@ response_body_project_tags_required:
|
|||
in: body
|
||||
required: true
|
||||
type: array
|
||||
response_body_system_required:
|
||||
description: |
|
||||
A list of systems to access based on role assignments.
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
role:
|
||||
description: |
|
||||
A ``role`` object, containing:
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"system": [
|
||||
{
|
||||
"all": true
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"self": "https://example.com/identity/v3/auth/system"
|
||||
}
|
||||
}
|
|
@ -190,6 +190,7 @@ identity:delete_service_provider DELETE /v3/OS-FEDERAT
|
|||
identity:get_auth_catalog GET /v3/auth/catalog
|
||||
identity:get_auth_projects GET /v3/auth/projects
|
||||
identity:get_auth_domains GET /v3/auth/domains
|
||||
identity:get_auth_system GET /v3/auth/system
|
||||
|
||||
identity:list_projects_for_user GET /v3/OS-FEDERATION/projects
|
||||
identity:list_domains_for_user GET /v3/OS-FEDERATION/domains
|
||||
|
|
|
@ -215,6 +215,7 @@
|
|||
"identity:get_auth_catalog": "",
|
||||
"identity:get_auth_projects": "",
|
||||
"identity:get_auth_domains": "",
|
||||
"identity:get_auth_system": "",
|
||||
|
||||
"identity:list_projects_for_user": "",
|
||||
"identity:list_domains_for_user": "",
|
||||
|
|
|
@ -21,6 +21,7 @@ from keystone.auth import core
|
|||
from keystone.auth import schema
|
||||
from keystone.common import authorization
|
||||
from keystone.common import controller
|
||||
from keystone.common import provider_api
|
||||
from keystone.common import utils
|
||||
from keystone.common import validation
|
||||
from keystone.common import wsgi
|
||||
|
@ -34,6 +35,7 @@ from keystone.resource import controllers as resource_controllers
|
|||
LOG = log.getLogger(__name__)
|
||||
|
||||
CONF = keystone.conf.CONF
|
||||
PROVIDERS = provider_api.ProviderAPIs
|
||||
|
||||
|
||||
def validate_issue_token_auth(auth=None):
|
||||
|
@ -398,6 +400,54 @@ class Auth(controller.V3Controller):
|
|||
return resource_controllers.DomainV3.wrap_collection(
|
||||
request.context_dict, refs)
|
||||
|
||||
@controller.protected()
|
||||
def get_auth_system(self, request):
|
||||
user_id = request.auth_context.get('user_id')
|
||||
group_ids = request.auth_context.get('group_ids')
|
||||
|
||||
user_assignments = []
|
||||
if user_id:
|
||||
try:
|
||||
user_assignments = (
|
||||
PROVIDERS.assignment_api.list_system_grants_for_user(
|
||||
user_id
|
||||
)
|
||||
)
|
||||
except exception.UserNotFound: # nosec
|
||||
# federated users have an id but they don't link to anything
|
||||
pass
|
||||
|
||||
group_assignments = []
|
||||
if group_ids:
|
||||
group_assignments = (
|
||||
PROVIDERS.assignment_api.list_system_grants_for_group(
|
||||
group_ids
|
||||
)
|
||||
)
|
||||
|
||||
assignments = self._combine_lists_uniquely(
|
||||
user_assignments, group_assignments
|
||||
)
|
||||
if assignments:
|
||||
response = {
|
||||
'system': [{'all': True}],
|
||||
'links': {
|
||||
'self': self.base_url(
|
||||
request.context_dict, path='auth/system'
|
||||
)
|
||||
}
|
||||
}
|
||||
else:
|
||||
response = {
|
||||
'system': [],
|
||||
'links': {
|
||||
'self': self.base_url(
|
||||
request.context_dict, path='auth/system'
|
||||
)
|
||||
}
|
||||
}
|
||||
return response
|
||||
|
||||
@controller.protected()
|
||||
def get_auth_catalog(self, request):
|
||||
user_id = request.auth_context.get('user_id')
|
||||
|
|
|
@ -55,3 +55,9 @@ class Routers(wsgi.RoutersBase):
|
|||
path='/auth/domains',
|
||||
get_head_action='get_auth_domains',
|
||||
rel=json_home.build_v3_resource_relation('auth_domains'))
|
||||
|
||||
self._add_resource(
|
||||
mapper, auth_controller,
|
||||
path='/auth/system',
|
||||
get_head_action='get_auth_system',
|
||||
rel=json_home.build_v3_resource_relation('auth_system'))
|
||||
|
|
|
@ -61,6 +61,21 @@ auth_policies = [
|
|||
'method': 'HEAD'
|
||||
}
|
||||
]
|
||||
),
|
||||
policy.DocumentedRuleDefault(
|
||||
name=base.IDENTITY % 'get_auth_system',
|
||||
check_str='',
|
||||
description='List systems a user has access to via role assignments.',
|
||||
operations=[
|
||||
{
|
||||
'path': '/v3/auth/system',
|
||||
'method': 'GET'
|
||||
},
|
||||
{
|
||||
'path': '/v3/auth/system',
|
||||
'method': 'HEAD'
|
||||
}
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
|
|
|
@ -4703,6 +4703,102 @@ class TestAuthSpecificData(test_v3.RestfulTestCase):
|
|||
|
||||
self.head('/auth/domains', expected_status=http_client.OK)
|
||||
|
||||
def test_get_system_roles_with_unscoped_token(self):
|
||||
path = '/system/users/%(user_id)s/roles/%(role_id)s' % {
|
||||
'user_id': self.user['id'],
|
||||
'role_id': self.role_id
|
||||
}
|
||||
self.put(path=path)
|
||||
|
||||
unscoped_request = self.build_authentication_request(
|
||||
user_id=self.user['id'], password=self.user['password']
|
||||
)
|
||||
r = self.post('/auth/tokens', body=unscoped_request)
|
||||
unscoped_token = r.headers.get('X-Subject-Token')
|
||||
self.assertValidUnscopedTokenResponse(r)
|
||||
response = self.get('/auth/system', token=unscoped_token)
|
||||
self.assertTrue(response.json_body['system'][0]['all'])
|
||||
self.head(
|
||||
'/auth/system', token=unscoped_token,
|
||||
expected_status=http_client.OK
|
||||
)
|
||||
|
||||
def test_get_system_roles_returns_empty_list_without_system_roles(self):
|
||||
# A user without a system role assignment shouldn't expect an empty
|
||||
# list when calling /v3/auth/system regardless of calling the API with
|
||||
# an unscoped token or a project-scoped token.
|
||||
unscoped_request = self.build_authentication_request(
|
||||
user_id=self.user['id'], password=self.user['password']
|
||||
)
|
||||
r = self.post('/auth/tokens', body=unscoped_request)
|
||||
unscoped_token = r.headers.get('X-Subject-Token')
|
||||
self.assertValidUnscopedTokenResponse(r)
|
||||
response = self.get('/auth/system', token=unscoped_token)
|
||||
self.assertEqual(response.json_body['system'], [])
|
||||
self.head(
|
||||
'/auth/system', token=unscoped_token,
|
||||
expected_status=http_client.OK
|
||||
)
|
||||
|
||||
project_scoped_request = self.build_authentication_request(
|
||||
user_id=self.user['id'], password=self.user['password'],
|
||||
project_id=self.project_id
|
||||
)
|
||||
r = self.post('/auth/tokens', body=project_scoped_request)
|
||||
project_scoped_token = r.headers.get('X-Subject-Token')
|
||||
self.assertValidProjectScopedTokenResponse(r)
|
||||
response = self.get('/auth/system', token=project_scoped_token)
|
||||
self.assertEqual(response.json_body['system'], [])
|
||||
self.head(
|
||||
'/auth/system', token=project_scoped_token,
|
||||
expected_status=http_client.OK
|
||||
)
|
||||
|
||||
def test_get_system_roles_with_project_scoped_token(self):
|
||||
path = '/system/users/%(user_id)s/roles/%(role_id)s' % {
|
||||
'user_id': self.user['id'],
|
||||
'role_id': self.role_id
|
||||
}
|
||||
self.put(path=path)
|
||||
|
||||
self.put(path='/domains/%s/users/%s/roles/%s' % (
|
||||
self.domain['id'], self.user['id'], self.role['id']))
|
||||
|
||||
domain_scoped_request = self.build_authentication_request(
|
||||
user_id=self.user['id'], password=self.user['password'],
|
||||
domain_id=self.domain['id']
|
||||
)
|
||||
r = self.post('/auth/tokens', body=domain_scoped_request)
|
||||
domain_scoped_token = r.headers.get('X-Subject-Token')
|
||||
self.assertValidDomainScopedTokenResponse(r)
|
||||
response = self.get('/auth/system', token=domain_scoped_token)
|
||||
self.assertTrue(response.json_body['system'][0]['all'])
|
||||
self.head(
|
||||
'/auth/system', token=domain_scoped_token,
|
||||
expected_status=http_client.OK
|
||||
)
|
||||
|
||||
def test_get_system_roles_with_domain_scoped_token(self):
|
||||
path = '/system/users/%(user_id)s/roles/%(role_id)s' % {
|
||||
'user_id': self.user['id'],
|
||||
'role_id': self.role_id
|
||||
}
|
||||
self.put(path=path)
|
||||
|
||||
project_scoped_request = self.build_authentication_request(
|
||||
user_id=self.user['id'], password=self.user['password'],
|
||||
project_id=self.project_id
|
||||
)
|
||||
r = self.post('/auth/tokens', body=project_scoped_request)
|
||||
project_scoped_token = r.headers.get('X-Subject-Token')
|
||||
self.assertValidProjectScopedTokenResponse(r)
|
||||
response = self.get('/auth/system', token=project_scoped_token)
|
||||
self.assertTrue(response.json_body['system'][0]['all'])
|
||||
self.head(
|
||||
'/auth/system', token=project_scoped_token,
|
||||
expected_status=http_client.OK
|
||||
)
|
||||
|
||||
|
||||
class TestTrustAuthFernetTokenProvider(TrustAPIBehavior, TestTrustChain):
|
||||
def config_overrides(self):
|
||||
|
|
|
@ -71,7 +71,7 @@ v3_MEDIA_TYPES = [
|
|||
]
|
||||
|
||||
v3_EXPECTED_RESPONSE = {
|
||||
"id": "v3.9",
|
||||
"id": "v3.10",
|
||||
"status": "stable",
|
||||
"updated": "2018-02-28T00:00:00Z",
|
||||
"links": [
|
||||
|
@ -184,6 +184,8 @@ V3_JSON_HOME_RESOURCES = {
|
|||
'href': '/auth/projects'},
|
||||
json_home.build_v3_resource_relation('auth_domains'): {
|
||||
'href': '/auth/domains'},
|
||||
json_home.build_v3_resource_relation('auth_system'): {
|
||||
'href': '/auth/system'},
|
||||
json_home.build_v3_resource_relation('credential'): {
|
||||
'href-template': '/credentials/{credential_id}',
|
||||
'href-vars': {
|
||||
|
|
|
@ -12,4 +12,4 @@
|
|||
|
||||
|
||||
def release_string():
|
||||
return 'v3.9'
|
||||
return 'v3.10'
|
||||
|
|
|
@ -141,7 +141,7 @@ class Version(wsgi.Application):
|
|||
|
||||
if 'v3' in _VERSIONS:
|
||||
versions['v3'] = {
|
||||
'id': 'v3.9',
|
||||
'id': 'v3.10',
|
||||
'status': 'stable',
|
||||
'updated': '2018-02-28T00:00:00Z',
|
||||
'links': [
|
||||
|
|
Loading…
Reference in New Issue