# Copyright 2012 OpenStack Foundation
#
# 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 copy
import functools
import http.client
import random

from oslo_serialization import jsonutils
from testtools import matchers as tt_matchers
import webob

from keystone.api import discovery
from keystone.common import json_home
from keystone.tests import unit

v3_MEDIA_TYPES = [
    {
        "base": "application/json",
        "type": "application/vnd.openstack.identity-v3+json",
    }
]

v3_EXPECTED_RESPONSE = {
    "id": "v3.14",
    "status": "stable",
    "updated": "2020-04-07T00:00:00Z",
    "links": [
        {
            "rel": "self",
            "href": "",  # Will get filled in after initialization
        }
    ],
    "media-types": v3_MEDIA_TYPES,
}

v3_VERSION_RESPONSE = {"version": v3_EXPECTED_RESPONSE}

VERSIONS_RESPONSE = {"versions": {"values": [v3_EXPECTED_RESPONSE]}}

_build_ec2tokens_relation = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-EC2',
    extension_version='1.0',
)

REVOCATIONS_RELATION = json_home.build_v3_extension_resource_relation(
    'OS-PKI', '1.0', 'revocations'
)

_build_simple_cert_relation = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-SIMPLE-CERT',
    extension_version='1.0',
)

_build_trust_relation = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-TRUST',
    extension_version='1.0',
)

_build_federation_rel = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-FEDERATION',
    extension_version='1.0',
)

_build_oauth1_rel = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-OAUTH1',
    extension_version='1.0',
)

_build_ep_policy_rel = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-ENDPOINT-POLICY',
    extension_version='1.0',
)

_build_ep_filter_rel = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-EP-FILTER',
    extension_version='1.0',
)

_build_os_inherit_rel = functools.partial(
    json_home.build_v3_extension_resource_relation,
    extension_name='OS-INHERIT',
    extension_version='1.0',
)

TRUST_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation(
    'OS-TRUST', '1.0', 'trust_id'
)

IDP_ID_PARAMETER_RELATION = json_home.build_v3_extension_parameter_relation(
    'OS-FEDERATION', '1.0', 'idp_id'
)

PROTOCOL_ID_PARAM_RELATION = json_home.build_v3_extension_parameter_relation(
    'OS-FEDERATION', '1.0', 'protocol_id'
)

MAPPING_ID_PARAM_RELATION = json_home.build_v3_extension_parameter_relation(
    'OS-FEDERATION', '1.0', 'mapping_id'
)

SERVICE_PROVIDER_ID_PARAMETER_RELATION = (
    json_home.build_v3_extension_parameter_relation(
        'OS-FEDERATION', '1.0', 'service_provider_id'
    )
)

CONSUMER_ID_PARAMETER_RELATION = (
    json_home.build_v3_extension_parameter_relation(
        'OS-OAUTH1', '1.0', 'consumer_id'
    )
)

REQUEST_TOKEN_ID_PARAMETER_RELATION = (
    json_home.build_v3_extension_parameter_relation(
        'OS-OAUTH1', '1.0', 'request_token_id'
    )
)

ACCESS_TOKEN_ID_PARAMETER_RELATION = (
    json_home.build_v3_extension_parameter_relation(
        'OS-OAUTH1', '1.0', 'access_token_id'
    )
)

ENDPOINT_GROUP_ID_PARAMETER_RELATION = (
    json_home.build_v3_extension_parameter_relation(
        'OS-EP-FILTER', '1.0', 'endpoint_group_id'
    )
)

BASE_IDP_PROTOCOL = '/OS-FEDERATION/identity_providers/{idp_id}/protocols'
BASE_EP_POLICY = '/policies/{policy_id}/OS-ENDPOINT-POLICY'
BASE_EP_FILTER_PREFIX = '/OS-EP-FILTER'
BASE_EP_FILTER = BASE_EP_FILTER_PREFIX + '/endpoint_groups/{endpoint_group_id}'
BASE_ACCESS_TOKEN = (
    '/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}'
)

FEDERATED_AUTH_URL = (
    '/OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}/auth'
)
FEDERATED_IDP_SPECIFIC_WEBSSO = (
    '/auth/OS-FEDERATION/identity_providers/'
    '{idp_id}/protocols/{protocol_id}/websso'
)

APPLICATION_CREDENTIAL = (
    '/users/{user_id}/application_credentials/{application_credential_id}'
)
APPLICATION_CREDENTIALS = '/users/{user_id}/application_credentials'
APPLICATION_CREDENTIAL_RELATION = json_home.build_v3_parameter_relation(
    'application_credential_id'
)

ACCESS_RULE = '/users/{user_id}/access_rules/{access_rule_id}'
ACCESS_RULES = '/users/{user_id}/access_rules'
ACCESS_RULE_RELATION = json_home.build_v3_parameter_relation('access_rule_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('auth_system'): {
        'href': '/auth/system'
    },
    json_home.build_v3_resource_relation('credential'): {
        'href-template': '/credentials/{credential_id}',
        'href-vars': {
            'credential_id': json_home.build_v3_parameter_relation(
                'credential_id'
            )
        },
    },
    json_home.build_v3_resource_relation('credentials'): {
        'href': '/credentials'
    },
    json_home.build_v3_resource_relation('system_user_role'): {
        'href-template': '/system/users/{user_id}/roles/{role_id}',
        'href-vars': {
            'user_id': json_home.Parameters.USER_ID,
            'role_id': json_home.Parameters.ROLE_ID,
        },
    },
    json_home.build_v3_resource_relation('system_user_roles'): {
        'href-template': '/system/users/{user_id}/roles',
        'href-vars': {'user_id': json_home.Parameters.USER_ID},
    },
    json_home.build_v3_resource_relation('system_group_role'): {
        'href-template': '/system/groups/{group_id}/roles/{role_id}',
        'href-vars': {
            'group_id': json_home.Parameters.GROUP_ID,
            'role_id': json_home.Parameters.ROLE_ID,
        },
    },
    json_home.build_v3_resource_relation('system_group_roles'): {
        'href-template': '/system/groups/{group_id}/roles',
        'href-vars': {'group_id': json_home.Parameters.GROUP_ID},
    },
    json_home.build_v3_resource_relation('domain'): {
        'href-template': '/domains/{domain_id}',
        'href-vars': {'domain_id': json_home.Parameters.DOMAIN_ID},
    },
    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_resource_relation('domains'): {'href': '/domains'},
    json_home.build_v3_resource_relation('endpoint'): {
        'href-template': '/endpoints/{endpoint_id}',
        'href-vars': {
            'endpoint_id': json_home.build_v3_parameter_relation('endpoint_id')
        },
    },
    json_home.build_v3_resource_relation('endpoints'): {'href': '/endpoints'},
    _build_ec2tokens_relation(resource_name='ec2tokens'): {
        'href': '/ec2tokens'
    },
    _build_ec2tokens_relation(resource_name='user_credential'): {
        'href-template': '/users/{user_id}/credentials/OS-EC2/{credential_id}',
        'href-vars': {
            'credential_id': json_home.build_v3_parameter_relation(
                'credential_id'
            ),
            'user_id': json_home.Parameters.USER_ID,
        },
    },
    _build_ec2tokens_relation(resource_name='user_credentials'): {
        'href-template': '/users/{user_id}/credentials/OS-EC2',
        'href-vars': {'user_id': json_home.Parameters.USER_ID},
    },
    REVOCATIONS_RELATION: {'href': '/auth/tokens/OS-PKI/revoked'},
    'https://docs.openstack.org/api/openstack-identity/3/ext/OS-REVOKE/1.0/rel'
    '/events': {'href': '/OS-REVOKE/events'},
    _build_simple_cert_relation(resource_name='ca_certificate'): {
        'href': '/OS-SIMPLE-CERT/ca'
    },
    _build_simple_cert_relation(resource_name='certificates'): {
        'href': '/OS-SIMPLE-CERT/certificates'
    },
    _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'
    },
    'https://docs.openstack.org/api/openstack-identity/3/ext/s3tokens/1.0/rel/'
    's3tokens': {'href': '/s3tokens'},
    json_home.build_v3_resource_relation('group'): {
        'href-template': '/groups/{group_id}',
        'href-vars': {'group_id': json_home.Parameters.GROUP_ID},
    },
    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('groups'): {'href': '/groups'},
    json_home.build_v3_resource_relation('policies'): {'href': '/policies'},
    json_home.build_v3_resource_relation('policy'): {
        'href-template': '/policies/{policy_id}',
        'href-vars': {
            'policy_id': json_home.build_v3_parameter_relation('policy_id')
        },
    },
    json_home.build_v3_resource_relation('project'): {
        'href-template': '/projects/{project_id}',
        'href-vars': {'project_id': json_home.Parameters.PROJECT_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_tags'): {
        'href-template': '/projects/{project_id}/tags/{value}',
        'href-vars': {
            'project_id': json_home.Parameters.PROJECT_ID,
            'value': json_home.Parameters.TAG_VALUE,
        },
    },
    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('projects'): {'href': '/projects'},
    json_home.build_v3_resource_relation('region'): {
        'href-template': '/regions/{region_id}',
        'href-vars': {
            'region_id': json_home.build_v3_parameter_relation('region_id')
        },
    },
    json_home.build_v3_resource_relation('regions'): {'href': '/regions'},
    json_home.build_v3_resource_relation('role'): {
        'href-template': '/roles/{role_id}',
        'href-vars': {'role_id': json_home.Parameters.ROLE_ID},
    },
    json_home.build_v3_resource_relation('implied_roles'): {
        'href-template': '/roles/{prior_role_id}/implies',
        'href-vars': {'prior_role_id': json_home.Parameters.ROLE_ID},
    },
    json_home.build_v3_resource_relation('implied_role'): {
        'href-template': '/roles/{prior_role_id}/implies/{implied_role_id}',
        'href-vars': {
            'prior_role_id': json_home.Parameters.ROLE_ID,
            'implied_role_id': json_home.Parameters.ROLE_ID,
        },
    },
    json_home.build_v3_resource_relation('role_inferences'): {
        'href': '/role_inferences'
    },
    json_home.build_v3_resource_relation('role_assignments'): {
        'href': '/role_assignments'
    },
    json_home.build_v3_resource_relation('roles'): {'href': '/roles'},
    json_home.build_v3_resource_relation('service'): {
        'href-template': '/services/{service_id}',
        'href-vars': {
            'service_id': json_home.build_v3_parameter_relation('service_id')
        },
    },
    json_home.build_v3_resource_relation('services'): {'href': '/services'},
    json_home.build_v3_resource_relation('user'): {
        'href-template': '/users/{user_id}',
        'href-vars': {'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},
    },
    json_home.build_v3_resource_relation('users'): {'href': '/users'},
    json_home.build_v3_extension_resource_relation(
        'OS-OAUTH2', '1.0', 'token'
    ): {'href': '/OS-OAUTH2/token'},
    _build_federation_rel(resource_name='domains'): {'href': '/auth/domains'},
    _build_federation_rel(resource_name='websso'): {
        'href-template': '/auth/OS-FEDERATION/websso/{protocol_id}',
        'href-vars': {'protocol_id': PROTOCOL_ID_PARAM_RELATION},
    },
    _build_federation_rel(resource_name='projects'): {
        'href': '/auth/projects'
    },
    _build_federation_rel(resource_name='saml2'): {
        'href': '/auth/OS-FEDERATION/saml2'
    },
    _build_federation_rel(resource_name='ecp'): {
        'href': '/auth/OS-FEDERATION/saml2/ecp'
    },
    _build_federation_rel(resource_name='metadata'): {
        'href': '/OS-FEDERATION/saml2/metadata'
    },
    _build_federation_rel(resource_name='identity_providers'): {
        'href': '/OS-FEDERATION/identity_providers'
    },
    _build_federation_rel(resource_name='service_providers'): {
        'href': '/OS-FEDERATION/service_providers'
    },
    _build_federation_rel(resource_name='mappings'): {
        'href': '/OS-FEDERATION/mappings'
    },
    _build_federation_rel(resource_name='identity_provider'): {
        'href-template': '/OS-FEDERATION/identity_providers/{idp_id}',
        'href-vars': {'idp_id': IDP_ID_PARAMETER_RELATION},
    },
    _build_federation_rel(resource_name='identity_providers_websso'): {
        'href-template': FEDERATED_IDP_SPECIFIC_WEBSSO,
        'href-vars': {
            'idp_id': IDP_ID_PARAMETER_RELATION,
            'protocol_id': PROTOCOL_ID_PARAM_RELATION,
        },
    },
    _build_federation_rel(resource_name='service_provider'): {
        'href-template': '/OS-FEDERATION/service_providers/{service_provider_id}',
        'href-vars': {
            'service_provider_id': SERVICE_PROVIDER_ID_PARAMETER_RELATION
        },
    },
    _build_federation_rel(resource_name='mapping'): {
        'href-template': '/OS-FEDERATION/mappings/{mapping_id}',
        'href-vars': {'mapping_id': MAPPING_ID_PARAM_RELATION},
    },
    _build_federation_rel(resource_name='identity_provider_protocol'): {
        'href-template': BASE_IDP_PROTOCOL + '/{protocol_id}',
        'href-vars': {
            'idp_id': IDP_ID_PARAMETER_RELATION,
            'protocol_id': PROTOCOL_ID_PARAM_RELATION,
        },
    },
    _build_federation_rel(resource_name='identity_provider_protocols'): {
        'href-template': BASE_IDP_PROTOCOL,
        'href-vars': {'idp_id': IDP_ID_PARAMETER_RELATION},
    },
    _build_federation_rel(resource_name='identity_provider_protocol_auth'): {
        'href-template': FEDERATED_AUTH_URL,
        'href-vars': {
            'idp_id': IDP_ID_PARAMETER_RELATION,
            'protocol_id': PROTOCOL_ID_PARAM_RELATION,
        },
    },
    _build_oauth1_rel(resource_name='access_tokens'): {
        'href': '/OS-OAUTH1/access_token'
    },
    _build_oauth1_rel(resource_name='request_tokens'): {
        'href': '/OS-OAUTH1/request_token'
    },
    _build_oauth1_rel(resource_name='consumers'): {
        'href': '/OS-OAUTH1/consumers'
    },
    _build_oauth1_rel(resource_name='authorize_request_token'): {
        'href-template': '/OS-OAUTH1/authorize/{request_token_id}',
        'href-vars': {'request_token_id': REQUEST_TOKEN_ID_PARAMETER_RELATION},
    },
    _build_oauth1_rel(resource_name='consumer'): {
        'href-template': '/OS-OAUTH1/consumers/{consumer_id}',
        'href-vars': {'consumer_id': CONSUMER_ID_PARAMETER_RELATION},
    },
    _build_oauth1_rel(resource_name='user_access_token'): {
        'href-template': BASE_ACCESS_TOKEN,
        'href-vars': {
            'user_id': json_home.Parameters.USER_ID,
            'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
        },
    },
    _build_oauth1_rel(resource_name='user_access_tokens'): {
        'href-template': '/users/{user_id}/OS-OAUTH1/access_tokens',
        'href-vars': {'user_id': json_home.Parameters.USER_ID},
    },
    _build_oauth1_rel(resource_name='user_access_token_role'): {
        'href-template': BASE_ACCESS_TOKEN + '/roles/{role_id}',
        'href-vars': {
            'user_id': json_home.Parameters.USER_ID,
            'role_id': json_home.Parameters.ROLE_ID,
            'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
        },
    },
    _build_oauth1_rel(resource_name='user_access_token_roles'): {
        'href-template': BASE_ACCESS_TOKEN + '/roles',
        'href-vars': {
            'user_id': json_home.Parameters.USER_ID,
            'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
        },
    },
    _build_ep_policy_rel(resource_name='endpoint_policy'): {
        'href-template': '/endpoints/{endpoint_id}/OS-ENDPOINT-POLICY/policy',
        'href-vars': {'endpoint_id': json_home.Parameters.ENDPOINT_ID},
    },
    _build_ep_policy_rel(resource_name='endpoint_policy_association'): {
        'href-template': BASE_EP_POLICY + '/endpoints/{endpoint_id}',
        'href-vars': {
            'endpoint_id': json_home.Parameters.ENDPOINT_ID,
            'policy_id': json_home.Parameters.POLICY_ID,
        },
    },
    _build_ep_policy_rel(resource_name='policy_endpoints'): {
        'href-template': BASE_EP_POLICY + '/endpoints',
        'href-vars': {'policy_id': json_home.Parameters.POLICY_ID},
    },
    _build_ep_policy_rel(
        resource_name='region_and_service_policy_association'
    ): {
        'href-template': (
            BASE_EP_POLICY + '/services/{service_id}/regions/{region_id}'
        ),
        'href-vars': {
            'policy_id': json_home.Parameters.POLICY_ID,
            'service_id': json_home.Parameters.SERVICE_ID,
            'region_id': json_home.Parameters.REGION_ID,
        },
    },
    _build_ep_policy_rel(resource_name='service_policy_association'): {
        'href-template': BASE_EP_POLICY + '/services/{service_id}',
        'href-vars': {
            'policy_id': json_home.Parameters.POLICY_ID,
            'service_id': json_home.Parameters.SERVICE_ID,
        },
    },
    _build_ep_filter_rel(resource_name='endpoint_group'): {
        'href-template': '/OS-EP-FILTER/endpoint_groups/{endpoint_group_id}',
        'href-vars': {
            'endpoint_group_id': ENDPOINT_GROUP_ID_PARAMETER_RELATION
        },
    },
    _build_ep_filter_rel(
        resource_name='endpoint_group_to_project_association'
    ): {
        'href-template': BASE_EP_FILTER + '/projects/{project_id}',
        'href-vars': {
            'endpoint_group_id': ENDPOINT_GROUP_ID_PARAMETER_RELATION,
            'project_id': json_home.Parameters.PROJECT_ID,
        },
    },
    _build_ep_filter_rel(resource_name='endpoint_groups'): {
        'href': '/OS-EP-FILTER/endpoint_groups'
    },
    _build_ep_filter_rel(resource_name='endpoint_projects'): {
        'href-template': '/OS-EP-FILTER/endpoints/{endpoint_id}/projects',
        'href-vars': {'endpoint_id': json_home.Parameters.ENDPOINT_ID},
    },
    _build_ep_filter_rel(resource_name='endpoints_in_endpoint_group'): {
        'href-template': BASE_EP_FILTER + '/endpoints',
        'href-vars': {
            'endpoint_group_id': ENDPOINT_GROUP_ID_PARAMETER_RELATION
        },
    },
    _build_ep_filter_rel(resource_name='project_endpoint_groups'): {
        'href-template': (
            BASE_EP_FILTER_PREFIX
            + '/projects/{project_id}'
            + '/endpoint_groups'
        ),
        'href-vars': {'project_id': json_home.Parameters.PROJECT_ID},
    },
    _build_ep_filter_rel(resource_name='project_endpoint'): {
        'href-template': (
            '/OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}'
        ),
        'href-vars': {
            'endpoint_id': json_home.Parameters.ENDPOINT_ID,
            'project_id': json_home.Parameters.PROJECT_ID,
        },
    },
    _build_ep_filter_rel(resource_name='project_endpoints'): {
        'href-template': '/OS-EP-FILTER/projects/{project_id}/endpoints',
        'href-vars': {'project_id': json_home.Parameters.PROJECT_ID},
    },
    _build_ep_filter_rel(
        resource_name='projects_associated_with_endpoint_group'
    ): {
        'href-template': BASE_EP_FILTER + '/projects',
        'href-vars': {
            'endpoint_group_id': ENDPOINT_GROUP_ID_PARAMETER_RELATION
        },
    },
    _build_os_inherit_rel(
        resource_name='domain_user_role_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/domains/{domain_id}/users/'
        '{user_id}/roles/{role_id}/inherited_to_projects',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'role_id': json_home.Parameters.ROLE_ID,
            'user_id': json_home.Parameters.USER_ID,
        },
    },
    _build_os_inherit_rel(
        resource_name='domain_group_role_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/domains/{domain_id}/groups/'
        '{group_id}/roles/{role_id}/inherited_to_projects',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'group_id': json_home.Parameters.GROUP_ID,
            'role_id': json_home.Parameters.ROLE_ID,
        },
    },
    _build_os_inherit_rel(
        resource_name='domain_user_roles_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/domains/{domain_id}/users/'
        '{user_id}/roles/inherited_to_projects',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'user_id': json_home.Parameters.USER_ID,
        },
    },
    _build_os_inherit_rel(
        resource_name='domain_group_roles_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/domains/{domain_id}/groups/'
        '{group_id}/roles/inherited_to_projects',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'group_id': json_home.Parameters.GROUP_ID,
        },
    },
    _build_os_inherit_rel(
        resource_name='project_user_role_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/projects/{project_id}/users/'
        '{user_id}/roles/{role_id}/inherited_to_projects',
        'href-vars': {
            'project_id': json_home.Parameters.PROJECT_ID,
            'role_id': json_home.Parameters.ROLE_ID,
            'user_id': json_home.Parameters.USER_ID,
        },
    },
    _build_os_inherit_rel(
        resource_name='project_group_role_inherited_to_projects'
    ): {
        'href-template': '/OS-INHERIT/projects/{project_id}/groups/'
        '{group_id}/roles/{role_id}/inherited_to_projects',
        'href-vars': {
            'project_id': json_home.Parameters.PROJECT_ID,
            'group_id': json_home.Parameters.GROUP_ID,
            'role_id': json_home.Parameters.ROLE_ID,
        },
    },
    json_home.build_v3_resource_relation('domain_config'): {
        'href-template': '/domains/{domain_id}/config',
        'href-vars': {'domain_id': json_home.Parameters.DOMAIN_ID},
    },
    json_home.build_v3_resource_relation('domain_config_group'): {
        'href-template': '/domains/{domain_id}/config/{group}',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'group': json_home.build_v3_parameter_relation('config_group'),
        },
    },
    json_home.build_v3_resource_relation('domain_config_option'): {
        'href-template': '/domains/{domain_id}/config/{group}/{option}',
        'href-vars': {
            'domain_id': json_home.Parameters.DOMAIN_ID,
            'group': json_home.build_v3_parameter_relation('config_group'),
            'option': json_home.build_v3_parameter_relation('config_option'),
        },
    },
    json_home.build_v3_resource_relation('domain_config_default'): {
        'href': '/domains/config/default'
    },
    json_home.build_v3_resource_relation('domain_config_default_group'): {
        'href-template': '/domains/config/{group}/default',
        'href-vars': {
            'group': json_home.build_v3_parameter_relation('config_group')
        },
    },
    json_home.build_v3_resource_relation('domain_config_default_option'): {
        'href-template': '/domains/config/{group}/{option}/default',
        'href-vars': {
            'group': json_home.build_v3_parameter_relation('config_group'),
            'option': json_home.build_v3_parameter_relation('config_option'),
        },
    },
    json_home.build_v3_resource_relation('registered_limits'): {
        'hints': {'status': 'experimental'},
        'href': '/registered_limits',
    },
    json_home.build_v3_resource_relation('registered_limit'): {
        'href-template': '/registered_limits/{registered_limit_id}',
        'href-vars': {
            'registered_limit_id': json_home.build_v3_parameter_relation(
                'registered_limit_id'
            )
        },
        'hints': {'status': 'experimental'},
    },
    json_home.build_v3_resource_relation('limits'): {
        'hints': {'status': 'experimental'},
        'href': '/limits',
    },
    json_home.build_v3_resource_relation('limit'): {
        'href-template': '/limits/{limit_id}',
        'href-vars': {
            'limit_id': json_home.build_v3_parameter_relation('limit_id')
        },
        'hints': {'status': 'experimental'},
    },
    json_home.build_v3_resource_relation('limit_model'): {
        'href': '/limits/model',
        'hints': {'status': 'experimental'},
    },
    json_home.build_v3_resource_relation('application_credentials'): {
        'href-template': APPLICATION_CREDENTIALS,
        'href-vars': {
            'user_id': json_home.build_v3_parameter_relation('user_id')
        },
    },
    json_home.build_v3_resource_relation('application_credential'): {
        'href-template': APPLICATION_CREDENTIAL,
        'href-vars': {
            'application_credential_id': APPLICATION_CREDENTIAL_RELATION,
            'user_id': json_home.build_v3_parameter_relation('user_id'),
        },
    },
    json_home.build_v3_resource_relation('access_rules'): {
        'href-template': ACCESS_RULES,
        'href-vars': {
            'user_id': json_home.build_v3_parameter_relation('user_id')
        },
    },
    json_home.build_v3_resource_relation('access_rule'): {
        'href-template': ACCESS_RULE,
        'href-vars': {
            'access_rule_id': ACCESS_RULE_RELATION,
            'user_id': json_home.build_v3_parameter_relation('user_id'),
        },
    },
}


class TestClient:
    def __init__(self, app=None, token=None):
        self.app = app
        self.token = token

    def request(self, method, path, headers=None, body=None):
        if headers is None:
            headers = {}

        if self.token:
            headers.setdefault('X-Auth-Token', self.token)

        req = webob.Request.blank(path)
        req.method = method
        for k, v in headers.items():
            req.headers[k] = v
        if body:
            req.body = body
        return req.get_response(self.app)

    def get(self, path, headers=None):
        return self.request('GET', path=path, headers=headers)

    def post(self, path, headers=None, body=None):
        return self.request('POST', path=path, headers=headers, body=body)

    def put(self, path, headers=None, body=None):
        return self.request('PUT', path=path, headers=headers, body=body)


class _VersionsEqual(tt_matchers.MatchesListwise):
    def __init__(self, expected):
        super().__init__(
            [
                tt_matchers.KeysEqual(expected),
                tt_matchers.KeysEqual(expected['versions']),
                tt_matchers.HasLength(len(expected['versions']['values'])),
                tt_matchers.ContainsAll(expected['versions']['values']),
            ]
        )

    def match(self, other):
        return super().match(
            [
                other,
                other['versions'],
                other['versions']['values'],
                other['versions']['values'],
            ]
        )


class VersionTestCase(unit.TestCase):
    def setUp(self):
        super().setUp()
        self.load_backends()
        self.public_app = self.loadapp('public')
        self.public_port = random.randint(40000, 60000)

        self.config_fixture.config(
            public_endpoint=f"http://localhost:{self.public_port}"
        )

    def config_overrides(self):
        super().config_overrides()

    def _paste_in_port(self, response, port):
        for link in response['links']:
            if link['rel'] == 'self':
                link['href'] = port

    def test_public_versions(self):
        client = TestClient(self.public_app)
        resp = client.get('/')
        self.assertEqual(300, resp.status_int)
        data = jsonutils.loads(resp.body)
        expected = VERSIONS_RESPONSE
        for version in expected['versions']['values']:
            if version['id'].startswith('v3'):
                self._paste_in_port(
                    version, f'http://localhost:{self.public_port}/v3/'
                )
        self.assertThat(data, _VersionsEqual(expected))

    def test_use_site_url_if_endpoint_unset(self):
        self.config_fixture.config(public_endpoint=None)

        for app in (self.public_app,):
            client = TestClient(app)
            resp = client.get('/')
            self.assertEqual(300, resp.status_int)
            data = jsonutils.loads(resp.body)
            expected = VERSIONS_RESPONSE
            for version in expected['versions']['values']:
                # localhost happens to be the site url for tests
                if version['id'].startswith('v3'):
                    self._paste_in_port(version, 'http://localhost/v3/')
            self.assertThat(data, _VersionsEqual(expected))

    def test_public_version_v3(self):
        client = TestClient(self.public_app)
        resp = client.get('/v3/')
        self.assertEqual(http.client.OK, resp.status_int)
        data = jsonutils.loads(resp.body)
        expected = v3_VERSION_RESPONSE
        self._paste_in_port(
            expected['version'], f'http://localhost:{self.public_port}/v3/'
        )
        self.assertEqual(expected, data)

    def test_use_site_url_if_endpoint_unset_v3(self):
        self.config_fixture.config(public_endpoint=None)
        for app in (self.public_app,):
            client = TestClient(app)
            resp = client.get('/v3/')
            self.assertEqual(http.client.OK, resp.status_int)
            data = jsonutils.loads(resp.body)
            expected = v3_VERSION_RESPONSE
            self._paste_in_port(expected['version'], 'http://localhost/v3/')
            self.assertEqual(expected, data)

    def test_v2_disabled(self):
        # NOTE(morgan): This test should be kept, v2.0 is removed and should
        # never return, this prevents regression[s]/v2.0 discovery doc
        # slipping back in.
        client = TestClient(self.public_app)
        # request to /v2.0 should fail
        resp = client.get('/v2.0/')
        # NOTE(morgan): getting a 418 here is indicative of a 404, but from
        # the flask app itself (not a handled 404 such as UserNotFound)
        self.assertEqual(418, resp.status_int)

        # request to /v3 should pass
        resp = client.get('/v3/')
        self.assertEqual(http.client.OK, resp.status_int)
        data = jsonutils.loads(resp.body)
        expected = v3_VERSION_RESPONSE
        self._paste_in_port(
            expected['version'], f'http://localhost:{self.public_port}/v3/'
        )
        self.assertEqual(expected, data)

        # only v3 information should be displayed by requests to /
        v3_only_response = {"versions": {"values": [v3_EXPECTED_RESPONSE]}}
        self._paste_in_port(
            v3_only_response['versions']['values'][0],
            f'http://localhost:{self.public_port}/v3/',
        )
        resp = client.get('/')
        self.assertEqual(300, resp.status_int)
        data = jsonutils.loads(resp.body)
        self.assertEqual(v3_only_response, data)

    def _test_json_home(self, path, exp_json_home_data):
        client = TestClient(self.public_app)
        resp = client.get(path, headers={'Accept': 'application/json-home'})

        self.assertThat(resp.status, tt_matchers.Equals('200 OK'))
        self.assertThat(
            resp.headers['Content-Type'],
            tt_matchers.Equals('application/json-home'),
        )
        maxDiff = self.maxDiff
        self.maxDiff = None
        # NOTE(morgan): Changed from tt_matchers.Equals to make it easier to
        # determine issues. Reset maxDiff to the original value at the end
        # of the assert.
        self.assertDictEqual(exp_json_home_data, jsonutils.loads(resp.body))
        self.maxDiff = maxDiff

    def test_json_home_v3(self):
        # If the request is /v3 and the Accept header is application/json-home
        # then the server responds with a JSON Home document.

        exp_json_home_data = {'resources': V3_JSON_HOME_RESOURCES}

        self._test_json_home('/v3', exp_json_home_data)

    def test_json_home_root(self):
        # If the request is / and the Accept header is application/json-home
        # then the server responds with a JSON Home document.

        exp_json_home_data = copy.deepcopy(
            {'resources': V3_JSON_HOME_RESOURCES}
        )
        json_home.translate_urls(exp_json_home_data, '/v3')

        self._test_json_home('/', exp_json_home_data)

    def test_accept_type_handling(self):
        # Accept headers with multiple types and qvalues are handled.

        def make_request(accept_types=None):
            client = TestClient(self.public_app)
            headers = None
            if accept_types:
                headers = {'Accept': accept_types}
            resp = client.get('/v3', headers=headers)
            self.assertThat(resp.status, tt_matchers.Equals('200 OK'))
            return resp.headers['Content-Type']

        JSON = discovery.MimeTypes.JSON
        JSON_HOME = discovery.MimeTypes.JSON_HOME

        JSON_MATCHER = tt_matchers.Equals(JSON)
        JSON_HOME_MATCHER = tt_matchers.Equals(JSON_HOME)

        # Default is JSON.
        self.assertThat(make_request(), JSON_MATCHER)

        # Can request JSON and get JSON.
        self.assertThat(make_request(JSON), JSON_MATCHER)

        # Can request JSONHome and get JSONHome.
        self.assertThat(make_request(JSON_HOME), JSON_HOME_MATCHER)

        # If request JSON, JSON Home get JSON.
        accept_types = f'{JSON}, {JSON_HOME}'
        self.assertThat(make_request(accept_types), JSON_MATCHER)

        # If request JSON Home, JSON get JSON.
        accept_types = f'{JSON_HOME}, {JSON}'
        self.assertThat(make_request(accept_types), JSON_MATCHER)

        # If request JSON Home, JSON;q=0.5 get JSON Home.
        accept_types = f'{JSON_HOME}, {JSON};q=0.5'
        self.assertThat(make_request(accept_types), JSON_HOME_MATCHER)

        # If request some unknown mime-type, get JSON.
        self.assertThat(make_request(self.getUniqueString()), JSON_MATCHER)


class VersionSingleAppTestCase(unit.TestCase):
    """Test running with a single application loaded.

    These are important because when Keystone is running in Apache httpd
    there's only one application loaded for each instance.

    """

    def setUp(self):
        super().setUp()
        self.load_backends()

        self.public_port = random.randint(40000, 60000)

        self.config_fixture.config(
            public_endpoint=f"http://localhost:{self.public_port}"
        )

    def config_overrides(self):
        super().config_overrides()

    def _paste_in_port(self, response, port):
        for link in response['links']:
            if link['rel'] == 'self':
                link['href'] = port

    def _test_version(self, app_name):
        app = self.loadapp(app_name)
        client = TestClient(app)
        resp = client.get('/')
        self.assertEqual(300, resp.status_int)
        data = jsonutils.loads(resp.body)
        expected = VERSIONS_RESPONSE
        url_with_port = f'http://localhost:{self.public_port}/v3/'
        for version in expected['versions']['values']:
            # TODO(morgan): Eliminate the need to do the "paste-in-port" part
            # of the tests. Ultimately, this is very hacky and shows we are
            # not setting up the test case sanely.
            if version['id'].startswith('v3'):
                self._paste_in_port(version, url_with_port)
        # Explicitly check that a location header is set and it is pointing
        # to v3 (The preferred location for now)!
        self.assertIn('Location', resp.headers)
        self.assertEqual(url_with_port, resp.headers['Location'])
        self.assertThat(data, _VersionsEqual(expected))

    def test_public(self):
        self._test_version('public')

    def test_admin(self):
        self._test_version('admin')
