Added support for a ``description`` attribute for Identity Roles

Now user can add the description to the role when user creates the role.
Added support for a ``description`` attribute for V3 Identity Roles.

Co-Authored-By: wangxiyuan<wangxiyuan@huawei.com>
Co-Authored-By: Deepak Mourya<deepakmoriya7@gmail.com>
Change-Id: I230af9cc833af13064636b5d9a7ce6334c3f6e9a
Closes-Bug: #1669080
This commit is contained in:
M V P Nitesh 2017-08-07 18:41:35 +05:30 committed by Deepak Mourya
parent 9e107fdd64
commit 430d7f765a
21 changed files with 185 additions and 2 deletions

View File

@ -1665,6 +1665,24 @@ role_assignments:
in: body
required: true
type: array
role_description_create_body:
description: |
Add description about the role.
in: body
required: false
type: string
role_description_response_body:
description: |
The role description.
in: body
required: false
type: string
role_description_update_body:
description: |
The new role description.
in: body
required: false
type: string
role_domain_id_request_body:
description: |
The ID of the domain of the role.

View File

@ -133,6 +133,7 @@ Parameters
- id: role_id_response_body
- links: link_response_body
- name: role_name_response_body
- description: role_description_response_body
Status Codes
~~~~~~~~~~~~
@ -174,6 +175,7 @@ Parameters
- role: role
- name: role_name_create_body
- domain_id: role_domain_id_request_body
- description: role_description_create_body
Example
~~~~~~~
@ -200,6 +202,7 @@ Parameters
- id: role_id_response_body
- links: link_response_body
- name: role_name_response_body
- description: role_description_response_body
Status Codes
~~~~~~~~~~~~
@ -248,6 +251,7 @@ Parameters
- id: role_id_response_body
- links: link_response_body
- name: role_name_response_body
- description: role_description_response_body
Status Codes
~~~~~~~~~~~~
@ -290,6 +294,7 @@ Parameters
- role_id: role_id_path
- role: role
- name: role_name_update_body
- description: role_description_update_body
Example
~~~~~~~
@ -310,6 +315,7 @@ Parameters
- id: role_id_response_body
- links: link_response_body
- name: role_name_response_body
- description: role_description_response_body
Status Codes
~~~~~~~~~~~~

View File

@ -1,5 +1,6 @@
{
"role": {
"description": "My new role"
"domain_id": "92e782c4988642d783a95f4a87c3fdd7",
"name": "developer"
}

View File

@ -1,5 +1,6 @@
{
"role": {
"description": "My new role",
"name": "developer"
}
}

View File

@ -4,6 +4,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/1e443fa8cee3482a8a2b6954dd5c8f12"
},
"description": "My new role",
"name": "developer"
}
}

View File

@ -6,6 +6,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/1acd3c5aa0e246b9a7427d252160dcd1"
},
"description": "My new role",
"name": "prior role name"
},
"implies": [
@ -14,6 +15,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/3602510e2e1f499589f78a0724dcf614"
},
"description": "My new role",
"name": "implied role1 name"
},
{
@ -21,6 +23,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/738289aeef684e73a987f7cf2ec6d925"
},
"description": "My new role",
"name": "implied role2 name"
}
]
@ -31,6 +34,7 @@
"links": {
"self" : "http://example.com/identity/v3/roles/bbf7a5098bb34407b7164eb6ff9f144e"
},
"description": "My new role",
"name": "prior role name"
},
"implies": [
@ -39,6 +43,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/872b20ad124c4c1bafaef2b1aae316ab"
},
"description": null,
"name": "implied role1 name"
},
{
@ -46,6 +51,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/1d865b1b2da14cb7b05254677e5f36a2"
},
"description": null,
"name": "implied role2 name"
}
]

View File

@ -5,6 +5,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/1e443fa8cee3482a8a2b6954dd5c8f12"
},
"description": "My new role",
"name": "Developer"
}
}

View File

@ -1,5 +1,6 @@
{
"role": {
"description": "My new role",
"name": "Developer"
}
}

View File

@ -5,6 +5,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/1e443fa8cee3482a8a2b6954dd5c8f12"
},
"description": "My new role",
"name": "Developer"
}
}

View File

@ -10,6 +10,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/5318e65d75574c17bf5339d3df33a5a3"
},
"description": "My new role",
"name": "admin"
},
{
@ -17,6 +18,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/642bcfc75c384fd181adf34d9b2df897"
},
"description": "My new role",
"name": "anotherrole"
},
{
@ -24,6 +26,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/779a76d74f544224a7ef8762ca0de627"
},
"description": "My new role",
"name": "Member"
},
{
@ -38,6 +41,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/ba2dfba61c934ee89e3110de36273229"
},
"description": "My new role",
"name": "ResellerAdmin"
},
{
@ -45,6 +49,7 @@
"links": {
"self": "http://example.com/identity/v3/roles/f127b97616f24d3ebceb7be840210adc"
},
"description": null,
"name": "service"
}
]

View File

@ -83,6 +83,7 @@ class Role(base.RoleDriverBase):
if attr != 'id':
setattr(ref, attr, getattr(new_role, attr))
ref.extra = new_role.extra
ref.description = new_role.description
return ref.to_dict()
def delete_role(self, role_id):
@ -193,10 +194,11 @@ class RoleTable(sql.ModelBase, sql.ModelDictMixinWithExtras):
return super(RoleTable, cls).from_dict(new_dict)
__tablename__ = 'role'
attributes = ['id', 'name', 'domain_id']
attributes = ['id', 'name', 'domain_id', 'description']
id = sql.Column(sql.String(64), primary_key=True)
name = sql.Column(sql.String(255), nullable=False)
domain_id = sql.Column(sql.String(64), nullable=False,
server_default=NULL_DOMAIN_ID)
description = sql.Column(sql.String(255), nullable=True)
extra = sql.Column(sql.JsonBlob())
__table_args__ = (sql.UniqueConstraint('name', 'domain_id'),)

View File

@ -15,7 +15,8 @@ from keystone.common.validation import parameter_types
# Schema for Identity v3 API
_role_properties = {
'name': parameter_types.name
'name': parameter_types.name,
'description': parameter_types.description
}
role_create = {

View File

@ -0,0 +1,15 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -0,0 +1,15 @@
# 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.
def upgrade(migrate_engine):
pass

View File

@ -0,0 +1,23 @@
# 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 sqlalchemy as sql
def upgrade(migrate_engine):
meta = sql.MetaData()
meta.bind = migrate_engine
role_table = sql.Table('role', meta, autoload=True)
description = sql.Column('description', sql.String(255),
nullable=True)
role_table.create_column(description)

View File

@ -82,6 +82,31 @@ class RoleTests(object):
PROVIDERS.role_api.get_role,
role['id'])
def test_role_crud_without_description(self):
role = {
'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
'domain_id': None,
}
self.role_api.create_role(role['id'], role)
role_ref = self.role_api.get_role(role['id'])
role_ref_dict = {x: role_ref[x] for x in role_ref}
self.assertIsNone(role_ref_dict['description'])
role_ref_dict.pop('description')
self.assertDictEqual(role, role_ref_dict)
role['name'] = uuid.uuid4().hex
updated_role_ref = self.role_api.update_role(role['id'], role)
role_ref = self.role_api.get_role(role['id'])
role_ref_dict = {x: role_ref[x] for x in role_ref}
self.assertIsNone(updated_role_ref['description'])
self.assertDictEqual(role_ref_dict, updated_role_ref)
self.role_api.delete_role(role['id'])
self.assertRaises(exception.RoleNotFound,
self.role_api.get_role,
role['id'])
def test_update_role_returns_not_found(self):
role = unit.new_role_ref()
self.assertRaises(exception.RoleNotFound,

View File

@ -385,6 +385,7 @@ def new_role_ref(**kwargs):
ref = {
'id': uuid.uuid4().hex,
'name': uuid.uuid4().hex,
'description': uuid.uuid4().hex,
'domain_id': None
}
ref.update(kwargs)

View File

@ -3140,6 +3140,45 @@ class FullMigration(SqlMigrateBase, unit.TestCase):
)
self.assertTrue(self.does_fk_exist('limit', 'registered_limit_id'))
def test_migration_053_adds_description_to_role(self):
self.expand(52)
self.migrate(52)
self.contract(52)
role_table_name = 'role'
self.assertTableColumns(
role_table_name,
['id', 'name', 'domain_id', 'extra']
)
self.expand(53)
self.migrate(53)
self.contract(53)
self.assertTableColumns(
role_table_name,
['id', 'name', 'domain_id', 'extra', 'description']
)
role_table = sqlalchemy.Table(
role_table_name, self.metadata, autoload=True
)
role = {
'id': uuid.uuid4().hex,
'name': "test",
'domain_id': resource_base.NULL_DOMAIN_ID,
'description': "This is a string"
}
role_table.insert().values(role).execute()
role_without_description = {
'id': uuid.uuid4().hex,
'name': "test1",
'domain_id': resource_base.NULL_DOMAIN_ID
}
role_table.insert().values(role_without_description).execute()
class MySQLOpportunisticFullMigration(FullMigration):
FIXTURE = db_fixtures.MySQLOpportunisticFixture

View File

@ -53,6 +53,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase,
'properties': {
'id': {'type': 'string', },
'name': {'type': 'string', },
'description': {'type': 'string', },
},
'required': ['id', 'name', ],
'additionalProperties': False,

View File

@ -755,6 +755,20 @@ class RoleValidationTestCase(unit.BaseTestCase):
self.create_role_validator.validate,
request_to_validate)
def test_validate_role_request_with_valid_description(self):
"""Test we can validate`description` in create role request."""
request_to_validate = {'name': self.role_name,
'description': 'My Role'}
self.create_role_validator.validate(request_to_validate)
def test_validate_role_request_fails_with_invalid_description(self):
"""Exception is raised when `description` as a non-string value."""
request_to_validate = {'name': self.role_name,
'description': False}
self.assertRaises(exception.SchemaValidationError,
self.create_role_validator.validate,
request_to_validate)
def test_validate_role_update_request(self):
"""Test that we validate a role update request."""
request_to_validate = {'name': 'My New Role'}

View File

@ -0,0 +1,6 @@
---
features:
- |
[`bug 1669080 <https://bugs.launchpad.net/keystone/+bug/1669080>`_]
Added support for a ``description`` attribute for V3 Identity Roles, see
API docs for details.