Change V3 router classes to provide JSON Home data
The V3 router classes are changed to provide JSON Home data for all of their resources. bp json-home Change-Id: Iea9ed41af352b6fb26895761785900eaac0f32a2
This commit is contained in:
committed by
Dolph Mathews
parent
8f566a7b85
commit
cfba7e1f7b
@@ -16,6 +16,7 @@
|
||||
"""WSGI Routers for the Assignment service."""
|
||||
|
||||
from keystone.assignment import controllers
|
||||
from keystone.common import json_home
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone import config
|
||||
@@ -70,7 +71,11 @@ class Routers(wsgi.RoutersBase):
|
||||
self._add_resource(
|
||||
mapper, project_controller,
|
||||
path='/users/{user_id}/projects',
|
||||
get_action='list_user_projects')
|
||||
get_action='list_user_projects',
|
||||
rel=json_home.build_v3_resource_relation('user_projects'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
role_controller = controllers.RoleV3()
|
||||
routers.append(
|
||||
@@ -81,41 +86,85 @@ class Routers(wsgi.RoutersBase):
|
||||
path='/projects/{project_id}/users/{user_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant')
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('project_user_role'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant')
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('project_group_role'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/projects/{project_id}/users/{user_id}/roles',
|
||||
get_action='list_grants')
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_user_roles'),
|
||||
path_vars={
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/projects/{project_id}/groups/{group_id}/roles',
|
||||
get_action='list_grants')
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('project_group_roles'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/domains/{domain_id}/users/{user_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant')
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('domain_user_role'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/domains/{domain_id}/groups/{group_id}/roles/{role_id}',
|
||||
get_head_action='check_grant',
|
||||
put_action='create_grant',
|
||||
delete_action='revoke_grant')
|
||||
delete_action='revoke_grant',
|
||||
rel=json_home.build_v3_resource_relation('domain_group_role'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/domains/{domain_id}/users/{user_id}/roles',
|
||||
get_action='list_grants')
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('domain_user_roles'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, role_controller,
|
||||
path='/domains/{domain_id}/groups/{group_id}/roles',
|
||||
get_action='list_grants')
|
||||
get_action='list_grants',
|
||||
rel=json_home.build_v3_resource_relation('domain_group_roles'),
|
||||
path_vars={
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
})
|
||||
|
||||
routers.append(
|
||||
router.Router(controllers.RoleAssignmentV3(),
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# under the License.
|
||||
|
||||
from keystone.auth import controllers
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
|
||||
|
||||
@@ -31,24 +32,30 @@ class Routers(wsgi.RoutersBase):
|
||||
get_action='validate_token',
|
||||
head_action='check_token',
|
||||
post_action='authenticate_for_token',
|
||||
delete_action='revoke_token')
|
||||
delete_action='revoke_token',
|
||||
rel=json_home.build_v3_resource_relation('auth_tokens'))
|
||||
|
||||
self._add_resource(
|
||||
mapper, auth_controller,
|
||||
path='/auth/tokens/OS-PKI/revoked',
|
||||
get_action='revocation_list')
|
||||
get_action='revocation_list',
|
||||
rel=json_home.build_v3_extension_resource_relation(
|
||||
'OS-PKI', '1.0', 'revocations'))
|
||||
|
||||
self._add_resource(
|
||||
mapper, auth_controller,
|
||||
path='/auth/catalog',
|
||||
get_action='get_auth_catalog')
|
||||
get_action='get_auth_catalog',
|
||||
rel=json_home.build_v3_resource_relation('auth_catalog'))
|
||||
|
||||
self._add_resource(
|
||||
mapper, auth_controller,
|
||||
path='/auth/projects',
|
||||
get_action='get_auth_projects')
|
||||
get_action='get_auth_projects',
|
||||
rel=json_home.build_v3_resource_relation('auth_projects'))
|
||||
|
||||
self._add_resource(
|
||||
mapper, auth_controller,
|
||||
path='/auth/domains',
|
||||
get_action='get_auth_domains')
|
||||
get_action='get_auth_domains',
|
||||
rel=json_home.build_v3_resource_relation('auth_domains'))
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 build_v3_resource_relation(resource_name):
|
||||
return ('http://docs.openstack.org/api/openstack-identity/3/rel/%s' %
|
||||
resource_name)
|
||||
|
||||
|
||||
def build_v3_extension_resource_relation(extension_name, extension_version,
|
||||
resource_name):
|
||||
return (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/ext/%s/%s/rel/%s' %
|
||||
(extension_name, extension_version, resource_name))
|
||||
|
||||
|
||||
def build_v3_parameter_relation(parameter_name):
|
||||
return ('http://docs.openstack.org/api/openstack-identity/3/param/%s' %
|
||||
parameter_name)
|
||||
|
||||
|
||||
def build_v3_extension_parameter_relation(extension_name, extension_version,
|
||||
parameter_name):
|
||||
return (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/ext/%s/%s/param/'
|
||||
'%s' % (extension_name, extension_version, parameter_name))
|
||||
|
||||
|
||||
class Parameters(object):
|
||||
"""Relationships for Common parameters."""
|
||||
|
||||
DOMAIN_ID = build_v3_parameter_relation('domain_id')
|
||||
GROUP_ID = build_v3_parameter_relation('group_id')
|
||||
PROJECT_ID = build_v3_parameter_relation('project_id')
|
||||
ROLE_ID = build_v3_parameter_relation('role_id')
|
||||
USER_ID = build_v3_parameter_relation('user_id')
|
||||
@@ -12,6 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""WSGI Routers for the Identity service."""
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import router
|
||||
from keystone.common import wsgi
|
||||
from keystone.identity import controllers
|
||||
@@ -38,19 +40,32 @@ class Routers(wsgi.RoutersBase):
|
||||
self._add_resource(
|
||||
mapper, user_controller,
|
||||
path='/users/{user_id}/password',
|
||||
post_action='change_password')
|
||||
post_action='change_password',
|
||||
rel=json_home.build_v3_resource_relation('user_change_password'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
self._add_resource(
|
||||
mapper, user_controller,
|
||||
path='/groups/{group_id}/users',
|
||||
get_action='list_users_in_group')
|
||||
get_action='list_users_in_group',
|
||||
rel=json_home.build_v3_resource_relation('group_users'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
})
|
||||
|
||||
self._add_resource(
|
||||
mapper, user_controller,
|
||||
path='/groups/{group_id}/users/{user_id}',
|
||||
put_action='add_user_to_group',
|
||||
get_head_action='check_user_in_group',
|
||||
delete_action='remove_user_from_group')
|
||||
delete_action='remove_user_from_group',
|
||||
rel=json_home.build_v3_resource_relation('group_user'),
|
||||
path_vars={
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
group_controller = controllers.GroupV3()
|
||||
routers.append(
|
||||
@@ -60,4 +75,8 @@ class Routers(wsgi.RoutersBase):
|
||||
self._add_resource(
|
||||
mapper, group_controller,
|
||||
path='/users/{user_id}/groups',
|
||||
get_action='list_groups_for_user')
|
||||
get_action='list_groups_for_user',
|
||||
rel=json_home.build_v3_resource_relation('user_groups'),
|
||||
path_vars={
|
||||
'user_id': json_home.Parameters.USER_ID,
|
||||
})
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import functools
|
||||
import random
|
||||
|
||||
import mock
|
||||
from testtools import matchers as tt_matchers
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone import config
|
||||
from keystone import controllers
|
||||
from keystone.openstack.common import jsonutils
|
||||
@@ -102,7 +104,105 @@ VERSIONS_RESPONSE = {
|
||||
}
|
||||
}
|
||||
|
||||
V3_JSON_HOME_RESOURCES = {}
|
||||
_build_trust_relation = functools.partial(
|
||||
json_home.build_v3_extension_resource_relation, extension_name='OS-TRUST',
|
||||
extension_version='1.0')
|
||||
|
||||
TRUST_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation(
|
||||
'OS-TRUST', '1.0', 'trust_id')
|
||||
|
||||
V3_JSON_HOME_RESOURCES = {
|
||||
json_home.build_v3_resource_relation('auth_tokens'): {
|
||||
'href': '/auth/tokens'},
|
||||
json_home.build_v3_resource_relation('auth_catalog'): {
|
||||
'href': '/auth/catalog'},
|
||||
json_home.build_v3_resource_relation('auth_projects'): {
|
||||
'href': '/auth/projects'},
|
||||
json_home.build_v3_resource_relation('auth_domains'): {
|
||||
'href': '/auth/domains'},
|
||||
json_home.build_v3_resource_relation('domain_group_role'): {
|
||||
'href-template':
|
||||
'/domains/{domain_id}/groups/{group_id}/roles/{role_id}',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID, }},
|
||||
json_home.build_v3_resource_relation('domain_group_roles'): {
|
||||
'href-template': '/domains/{domain_id}/groups/{group_id}/roles',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'group_id': json_home.Parameters.GROUP_ID}},
|
||||
json_home.build_v3_resource_relation('domain_user_role'): {
|
||||
'href-template':
|
||||
'/domains/{domain_id}/users/{user_id}/roles/{role_id}',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('domain_user_roles'): {
|
||||
'href-template': '/domains/{domain_id}/users/{user_id}/roles',
|
||||
'href-vars': {
|
||||
'domain_id': json_home.Parameters.DOMAIN_ID,
|
||||
'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_extension_resource_relation('OS-PKI', '1.0',
|
||||
'revocations'): {
|
||||
'href': '/auth/tokens/OS-PKI/revoked'},
|
||||
_build_trust_relation(resource_name='trust'):
|
||||
{
|
||||
'href-template': '/OS-TRUST/trusts/{trust_id}',
|
||||
'href-vars': {'trust_id': TRUST_ID_PARAMETER_RELATION, }},
|
||||
_build_trust_relation(resource_name='trust_role'): {
|
||||
'href-template': '/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
||||
'href-vars': {
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'trust_id': TRUST_ID_PARAMETER_RELATION, }},
|
||||
_build_trust_relation(resource_name='trust_roles'): {
|
||||
'href-template': '/OS-TRUST/trusts/{trust_id}/roles',
|
||||
'href-vars': {'trust_id': TRUST_ID_PARAMETER_RELATION, }},
|
||||
_build_trust_relation(resource_name='trusts'): {
|
||||
'href': '/OS-TRUST/trusts'},
|
||||
json_home.build_v3_resource_relation('group_user'): {
|
||||
'href-template': '/groups/{group_id}/users/{user_id}',
|
||||
'href-vars': {
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('group_users'): {
|
||||
'href-template': '/groups/{group_id}/users',
|
||||
'href-vars': {'group_id': json_home.Parameters.GROUP_ID, }},
|
||||
json_home.build_v3_resource_relation('project_group_role'): {
|
||||
'href-template':
|
||||
'/projects/{project_id}/groups/{group_id}/roles/{role_id}',
|
||||
'href-vars': {
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID, }},
|
||||
json_home.build_v3_resource_relation('project_group_roles'): {
|
||||
'href-template': '/projects/{project_id}/groups/{group_id}/roles',
|
||||
'href-vars': {
|
||||
'group_id': json_home.Parameters.GROUP_ID,
|
||||
'project_id': json_home.Parameters.PROJECT_ID, }},
|
||||
json_home.build_v3_resource_relation('project_user_role'): {
|
||||
'href-template':
|
||||
'/projects/{project_id}/users/{user_id}/roles/{role_id}',
|
||||
'href-vars': {
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('project_user_roles'): {
|
||||
'href-template': '/projects/{project_id}/users/{user_id}/roles',
|
||||
'href-vars': {
|
||||
'project_id': json_home.Parameters.PROJECT_ID,
|
||||
'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('user_change_password'): {
|
||||
'href-template': '/users/{user_id}/password',
|
||||
'href-vars': {'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('user_groups'): {
|
||||
'href-template': '/users/{user_id}/groups',
|
||||
'href-vars': {'user_id': json_home.Parameters.USER_ID, }},
|
||||
json_home.build_v3_resource_relation('user_projects'): {
|
||||
'href-template': '/users/{user_id}/projects',
|
||||
'href-vars': {'user_id': json_home.Parameters.USER_ID, }},
|
||||
}
|
||||
|
||||
|
||||
class VersionTestCase(tests.TestCase):
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 testtools import matchers
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone import tests
|
||||
|
||||
|
||||
class JsonHomeTest(tests.BaseTestCase):
|
||||
def test_build_v3_resource_relation(self):
|
||||
resource_name = self.getUniqueString()
|
||||
relation = json_home.build_v3_resource_relation(resource_name)
|
||||
exp_relation = (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/rel/%s' %
|
||||
resource_name)
|
||||
self.assertThat(relation, matchers.Equals(exp_relation))
|
||||
|
||||
def test_build_v3_extension_resource_relation(self):
|
||||
extension_name = self.getUniqueString()
|
||||
extension_version = self.getUniqueString()
|
||||
resource_name = self.getUniqueString()
|
||||
relation = json_home.build_v3_extension_resource_relation(
|
||||
extension_name, extension_version, resource_name)
|
||||
exp_relation = (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/ext/%s/%s/rel/'
|
||||
'%s' % (extension_name, extension_version, resource_name))
|
||||
self.assertThat(relation, matchers.Equals(exp_relation))
|
||||
|
||||
def test_build_v3_parameter_relation(self):
|
||||
parameter_name = self.getUniqueString()
|
||||
relation = json_home.build_v3_parameter_relation(parameter_name)
|
||||
exp_relation = (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/param/%s' %
|
||||
parameter_name)
|
||||
self.assertThat(relation, matchers.Equals(exp_relation))
|
||||
|
||||
def test_build_v3_extension_parameter_relation(self):
|
||||
extension_name = self.getUniqueString()
|
||||
extension_version = self.getUniqueString()
|
||||
parameter_name = self.getUniqueString()
|
||||
relation = json_home.build_v3_extension_parameter_relation(
|
||||
extension_name, extension_version, parameter_name)
|
||||
exp_relation = (
|
||||
'http://docs.openstack.org/api/openstack-identity/3/ext/%s/%s/'
|
||||
'param/%s' % (extension_name, extension_version, parameter_name))
|
||||
self.assertThat(relation, matchers.Equals(exp_relation))
|
||||
@@ -13,10 +13,21 @@
|
||||
# under the License.
|
||||
"""WSGI Routers for the Trust service."""
|
||||
|
||||
import functools
|
||||
|
||||
from keystone.common import json_home
|
||||
from keystone.common import wsgi
|
||||
from keystone.trust import controllers
|
||||
|
||||
|
||||
_build_resource_relation = functools.partial(
|
||||
json_home.build_v3_extension_resource_relation, extension_name='OS-TRUST',
|
||||
extension_version='1.0')
|
||||
|
||||
TRUST_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation(
|
||||
'OS-TRUST', '1.0', 'trust_id')
|
||||
|
||||
|
||||
class Routers(wsgi.RoutersBase):
|
||||
|
||||
def append_v3_routers(self, mapper, routers):
|
||||
@@ -26,17 +37,31 @@ class Routers(wsgi.RoutersBase):
|
||||
mapper, trust_controller,
|
||||
path='/OS-TRUST/trusts',
|
||||
get_action='list_trusts',
|
||||
post_action='create_trust')
|
||||
post_action='create_trust',
|
||||
rel=_build_resource_relation(resource_name='trusts'))
|
||||
self._add_resource(
|
||||
mapper, trust_controller,
|
||||
path='/OS-TRUST/trusts/{trust_id}',
|
||||
get_action='get_trust',
|
||||
delete_action='delete_trust')
|
||||
delete_action='delete_trust',
|
||||
rel=_build_resource_relation(resource_name='trust'),
|
||||
path_vars={
|
||||
'trust_id': TRUST_ID_PARAMETER_RELATION,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, trust_controller,
|
||||
path='/OS-TRUST/trusts/{trust_id}/roles',
|
||||
get_action='list_roles_for_trust')
|
||||
get_action='list_roles_for_trust',
|
||||
rel=_build_resource_relation(resource_name='trust_roles'),
|
||||
path_vars={
|
||||
'trust_id': TRUST_ID_PARAMETER_RELATION,
|
||||
})
|
||||
self._add_resource(
|
||||
mapper, trust_controller,
|
||||
path='/OS-TRUST/trusts/{trust_id}/roles/{role_id}',
|
||||
get_head_action='get_role_for_trust')
|
||||
get_head_action='get_role_for_trust',
|
||||
rel=_build_resource_relation(resource_name='trust_role'),
|
||||
path_vars={
|
||||
'trust_id': TRUST_ID_PARAMETER_RELATION,
|
||||
'role_id': json_home.Parameters.ROLE_ID,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user