1# Copyright 2012-2013 OpenStack Foundation 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may 4# not use this file except in compliance with the License. You may obtain 5# a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations 13# under the License. 14# 15 16"""Common identity code""" 17 18from keystoneclient import exceptions as identity_exc 19from keystoneclient.v3 import domains 20from keystoneclient.v3 import groups 21from keystoneclient.v3 import projects 22from keystoneclient.v3 import users 23from osc_lib import exceptions 24from osc_lib import utils 25 26from openstackclient.i18n import _ 27 28 29def find_service_in_list(service_list, service_id): 30 """Find a service by id in service list.""" 31 32 for service in service_list: 33 if service.id == service_id: 34 return service 35 raise exceptions.CommandError( 36 "No service with a type, name or ID of '%s' exists." % service_id) 37 38 39def find_service(identity_client, name_type_or_id): 40 """Find a service by id, name or type.""" 41 42 try: 43 # search for service id 44 return identity_client.services.get(name_type_or_id) 45 except identity_exc.NotFound: 46 # ignore NotFound exception, raise others 47 pass 48 49 try: 50 # search for service name 51 return identity_client.services.find(name=name_type_or_id) 52 except identity_exc.NotFound: 53 pass 54 except identity_exc.NoUniqueMatch: 55 msg = _("Multiple service matches found for '%s', " 56 "use an ID to be more specific.") 57 raise exceptions.CommandError(msg % name_type_or_id) 58 59 try: 60 # search for service type 61 return identity_client.services.find(type=name_type_or_id) 62 except identity_exc.NotFound: 63 msg = _("No service with a type, name or ID of '%s' exists.") 64 raise exceptions.CommandError(msg % name_type_or_id) 65 except identity_exc.NoUniqueMatch: 66 msg = _("Multiple service matches found for '%s', " 67 "use an ID to be more specific.") 68 raise exceptions.CommandError(msg % name_type_or_id) 69 70 71def get_resource(manager, name_type_or_id): 72 # NOTE (vishakha): Due to bug #1799153 and for any another related case 73 # where GET resource API does not support the filter by name, 74 # osc_lib.utils.find_resource() method cannot be used because that method 75 # try to fall back to list all the resource if requested resource cannot 76 # be get via name. Which ends up with NoUniqueMatch error. 77 # This new function is the replacement for osc_lib.utils.find_resource() 78 # for resources does not support GET by name. 79 # For example: identity GET /regions. 80 """Find a resource by id or name.""" 81 82 try: 83 return manager.get(name_type_or_id) 84 except identity_exc.NotFound: 85 # raise NotFound exception 86 msg = _("No resource with name or id of '%s' exists") 87 raise exceptions.CommandError(msg % name_type_or_id) 88 89 90def _get_token_resource(client, resource, parsed_name, parsed_domain=None): 91 """Peek into the user's auth token to get resource IDs 92 93 Look into a user's token to try and find the ID of a domain, project or 94 user, when given the name. Typically non-admin users will interact with 95 the CLI using names. However, by default, keystone does not allow look up 96 by name since it would involve listing all entities. Instead opt to use 97 the correct ID (from the token) instead. 98 :param client: An identity client 99 :param resource: A resource to look at in the token, this may be `domain`, 100 `project_domain`, `user_domain`, `project`, or `user`. 101 :param parsed_name: This is input from parsed_args that the user is hoping 102 to find in the token. 103 :param parsed_domain: This is domain filter from parsed_args that used to 104 filter the results. 105 106 :returns: The ID of the resource from the token, or the original value from 107 parsed_args if it does not match. 108 """ 109 110 try: 111 token = client.auth.client.get_token() 112 token_data = client.tokens.get_token_data(token) 113 token_dict = token_data['token'] 114 115 # NOTE(stevemar): If domain is passed, just look at the project domain. 116 if resource == 'domain': 117 token_dict = token_dict['project'] 118 obj = token_dict[resource] 119 120 # user/project under different domain may has a same name 121 if parsed_domain and parsed_domain not in obj['domain'].values(): 122 return parsed_name 123 if isinstance(obj, list): 124 for item in obj: 125 if item['name'] == parsed_name: 126 return item['id'] 127 if item['id'] == parsed_name: 128 return parsed_name 129 return parsed_name 130 return obj['id'] if obj['name'] == parsed_name else parsed_name 131 # diaper defense in case parsing the token fails 132 except Exception: # noqa 133 return parsed_name 134 135 136def _get_domain_id_if_requested(identity_client, domain_name_or_id): 137 if not domain_name_or_id: 138 return None 139 domain = find_domain(identity_client, domain_name_or_id) 140 return domain.id 141 142 143def find_domain(identity_client, name_or_id): 144 return _find_identity_resource(identity_client.domains, name_or_id, 145 domains.Domain) 146 147 148def find_group(identity_client, name_or_id, domain_name_or_id=None): 149 domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) 150 if not domain_id: 151 return _find_identity_resource(identity_client.groups, name_or_id, 152 groups.Group) 153 else: 154 return _find_identity_resource(identity_client.groups, name_or_id, 155 groups.Group, domain_id=domain_id) 156 157 158def find_project(identity_client, name_or_id, domain_name_or_id=None): 159 domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) 160 if not domain_id: 161 return _find_identity_resource(identity_client.projects, name_or_id, 162 projects.Project) 163 else: 164 return _find_identity_resource(identity_client.projects, name_or_id, 165 projects.Project, domain_id=domain_id) 166 167 168def find_user(identity_client, name_or_id, domain_name_or_id=None): 169 domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) 170 if not domain_id: 171 return _find_identity_resource(identity_client.users, name_or_id, 172 users.User) 173 else: 174 return _find_identity_resource(identity_client.users, name_or_id, 175 users.User, domain_id=domain_id) 176 177 178def _find_identity_resource(identity_client_manager, name_or_id, 179 resource_type, **kwargs): 180 """Find a specific identity resource. 181 182 Using keystoneclient's manager, attempt to find a specific resource by its 183 name or ID. If Forbidden to find the resource (a common case if the user 184 does not have permission), then return the resource by creating a local 185 instance of keystoneclient's Resource. 186 187 The parameter identity_client_manager is a keystoneclient manager, 188 for example: keystoneclient.v3.users or keystoneclient.v3.projects. 189 190 The parameter resource_type is a keystoneclient resource, for example: 191 keystoneclient.v3.users.User or keystoneclient.v3.projects.Project. 192 193 :param identity_client_manager: the manager that contains the resource 194 :type identity_client_manager: `keystoneclient.base.CrudManager` 195 :param name_or_id: the resources's name or ID 196 :type name_or_id: string 197 :param resource_type: class that represents the resource type 198 :type resource_type: `keystoneclient.base.Resource` 199 200 :returns: the resource in question 201 :rtype: `keystoneclient.base.Resource` 202 203 """ 204 205 try: 206 identity_resource = utils.find_resource(identity_client_manager, 207 name_or_id, **kwargs) 208 if identity_resource is not None: 209 return identity_resource 210 except exceptions.Forbidden: 211 pass 212 213 return resource_type(None, {'id': name_or_id, 'name': name_or_id}) 214 215 216def add_user_domain_option_to_parser(parser): 217 parser.add_argument( 218 '--user-domain', 219 metavar='<user-domain>', 220 help=_('Domain the user belongs to (name or ID). ' 221 'This can be used in case collisions between user names ' 222 'exist.'), 223 ) 224 225 226def add_group_domain_option_to_parser(parser): 227 parser.add_argument( 228 '--group-domain', 229 metavar='<group-domain>', 230 help=_('Domain the group belongs to (name or ID). ' 231 'This can be used in case collisions between group names ' 232 'exist.'), 233 ) 234 235 236def add_project_domain_option_to_parser(parser, enhance_help=lambda _h: _h): 237 parser.add_argument( 238 '--project-domain', 239 metavar='<project-domain>', 240 help=enhance_help(_('Domain the project belongs to (name or ID). This ' 241 'can be used in case collisions between project ' 242 'names exist.')), 243 ) 244 245 246def add_role_domain_option_to_parser(parser): 247 parser.add_argument( 248 '--role-domain', 249 metavar='<role-domain>', 250 help=_('Domain the role belongs to (name or ID). ' 251 'This must be specified when the name of a domain specific ' 252 'role is used.'), 253 ) 254 255 256def add_inherited_option_to_parser(parser): 257 parser.add_argument( 258 '--inherited', 259 action='store_true', 260 default=False, 261 help=_('Specifies if the role grant is inheritable to the sub ' 262 'projects'), 263 ) 264