keystone  18.0.0
About: OpenStack Keystone (Core Service: Identity) provides an authentication and authorization service for other OpenStack services. Provides a catalog of endpoints for all OpenStack services.
The "Victoria" series (maintained release).
  Fossies Dox: keystone-18.0.0.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

auth.py
Go to the documentation of this file.
1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
11 # under the License.
12 
13 # This file handles all flask-restful resources for /v3/auth
14 import string
15 
16 import flask
17 import flask_restful
18 import http.client
19 from oslo_log import log
20 from oslo_serialization import jsonutils
21 from oslo_utils import strutils
22 import urllib
23 import werkzeug.exceptions
24 
25 from keystone.api._shared import authentication
26 from keystone.api._shared import json_home_relations
27 from keystone.api._shared import saml
28 from keystone.auth import schema as auth_schema
29 from keystone.common import authorization
30 from keystone.common import provider_api
31 from keystone.common import rbac_enforcer
32 from keystone.common import render_token
33 from keystone.common import utils as k_utils
34 from keystone.common import validation
35 import keystone.conf
36 from keystone import exception
37 from keystone.federation import idp as keystone_idp
38 from keystone.federation import schema as federation_schema
39 from keystone.federation import utils as federation_utils
40 from keystone.i18n import _
41 from keystone.server import flask as ks_flask
42 
43 
44 CONF = keystone.conf.CONF
45 ENFORCER = rbac_enforcer.RBACEnforcer
46 LOG = log.getLogger(__name__)
47 PROVIDERS = provider_api.ProviderAPIs
48 
49 
51  # it's most likely that only one of these will be filled so avoid
52  # the combination if possible.
53  if a and b:
54  return {x['id']: x for x in a + b}.values()
55  else:
56  return a or b
57 
58 
59 def _build_response_headers(service_provider):
60  # URLs in header are encoded into bytes
61  return [('Content-Type', 'text/xml'),
62  ('X-sp-url', service_provider['sp_url'].encode('utf-8')),
63  ('X-auth-url', service_provider['auth_url'].encode('utf-8'))]
64 
65 
67  """Validate and return originating dashboard URL.
68 
69  Make sure the parameter is specified in the request's URL as well its
70  value belongs to a list of trusted dashboards.
71 
72  :raises keystone.exception.ValidationError: ``origin`` query parameter
73  was not specified. The URL is deemed invalid.
74  :raises keystone.exception.Unauthorized: URL specified in origin query
75  parameter does not exist in list of websso trusted dashboards.
76  :returns: URL with the originating dashboard
77 
78  """
79  origin = flask.request.args.get('origin')
80 
81  if not origin:
82  msg = 'Request must have an origin query parameter'
83  tr_msg = _('Request must have an origin query parameter')
84  LOG.error(msg)
85  raise exception.ValidationError(tr_msg)
86 
87  host = urllib.parse.unquote_plus(origin)
88 
89  # change trusted_dashboard hostnames to lowercase before comparison
90  trusted_dashboards = [k_utils.lower_case_hostname(trusted)
91  for trusted in CONF.federation.trusted_dashboard]
92 
93  if host not in trusted_dashboards:
94  msg = '%(host)s is not a trusted dashboard host' % {'host': host}
95  tr_msg = _('%(host)s is not a trusted dashboard host') % {
96  'host': host}
97  LOG.error(msg)
98  raise exception.Unauthorized(tr_msg)
99 
100  return host
101 
102 
103 class _AuthFederationWebSSOBase(ks_flask.ResourceBase):
104  @staticmethod
105  def _render_template_response(host, token_id):
106  with open(CONF.federation.sso_callback_template) as template:
107  src = string.Template(template.read())
108  subs = {'host': host, 'token': token_id}
109  body = src.substitute(subs)
110  resp = flask.make_response(body, http.client.OK)
111  resp.charset = 'utf-8'
112  resp.headers['Content-Type'] = 'text/html'
113  return resp
114 
115 
116 class AuthProjectsResource(ks_flask.ResourceBase):
117  collection_key = 'projects'
118  member_key = 'project'
119 
120  def get(self):
121  """Get possible project scopes for token.
122 
123  GET/HEAD /v3/auth/projects
124  GET/HEAD /v3/OS-FEDERATION/projects
125  """
126  ENFORCER.enforce_call(action='identity:get_auth_projects')
127  user_id = self.auth_context.get('user_id')
128  group_ids = self.auth_context.get('group_ids')
129 
130  user_p_refs = []
131  grp_p_refs = []
132 
133  if user_id:
134  try:
135  user_p_refs = PROVIDERS.assignment_api.list_projects_for_user(
136  user_id)
137  except exception.UserNotFound: # nosec
138  # federated users have an id but they don't link to anything
139  pass
140 
141  if group_ids:
142  grp_p_refs = PROVIDERS.assignment_api.list_projects_for_groups(
143  group_ids)
144  refs = _combine_lists_uniquely(user_p_refs, grp_p_refs)
145  return self.wrap_collection(refs)
146 
147 
148 class AuthDomainsResource(ks_flask.ResourceBase):
149  collection_key = 'domains'
150  member_key = 'domain'
151 
152  def get(self):
153  """Get possible domain scopes for token.
154 
155  GET/HEAD /v3/auth/domains
156  GET/HEAD /v3/OS-FEDERATION/domains
157  """
158  ENFORCER.enforce_call(action='identity:get_auth_domains')
159  user_id = self.auth_context.get('user_id')
160  group_ids = self.auth_context.get('group_ids')
161 
162  user_d_refs = []
163  grp_d_refs = []
164 
165  if user_id:
166  try:
167  user_d_refs = PROVIDERS.assignment_api.list_domains_for_user(
168  user_id)
169  except exception.UserNotFound: # nosec
170  # federated users have an id but they don't link to anything
171  pass
172 
173  if group_ids:
174  grp_d_refs = PROVIDERS.assignment_api.list_domains_for_groups(
175  group_ids)
176 
177  refs = _combine_lists_uniquely(user_d_refs, grp_d_refs)
178  return self.wrap_collection(refs)
179 
180 
182  def get(self):
183  """Get possible system scopes for token.
184 
185  GET/HEAD /v3/auth/system
186  """
187  ENFORCER.enforce_call(action='identity:get_auth_system')
188  user_id = self.auth_context.get('user_id')
189  group_ids = self.auth_context.get('group_ids')
190 
191  user_assignments = []
192  group_assignments = []
193 
194  if user_id:
195  try:
196  user_assignments = (
197  PROVIDERS.assignment_api.list_system_grants_for_user(
198  user_id)
199  )
200  except exception.UserNotFound: # nosec
201  # federated users have an id but they don't link to anything
202  pass
203 
204  if group_ids:
205  group_assignments = (
206  PROVIDERS.assignment_api.list_system_grants_for_groups(
207  group_ids)
208  )
209 
210  assignments = _combine_lists_uniquely(
211  user_assignments, group_assignments)
212 
213  if assignments:
214  response = {
215  'system': [{'all': True}],
216  'links': {
217  'self': ks_flask.base_url(path='auth/system')
218  }
219  }
220  else:
221  response = {
222  'system': [],
223  'links': {
224  'self': ks_flask.base_url(path='auth/system')
225  }
226  }
227  return response
228 
229 
231  def get(self):
232  """Get service catalog for token.
233 
234  GET/HEAD /v3/auth/catalog
235  """
236  ENFORCER.enforce_call(action='identity:get_auth_catalog')
237  user_id = self.auth_context.get('user_id')
238  project_id = self.auth_context.get('project_id')
239 
240  if not project_id:
241  raise exception.Forbidden(
242  _('A project-scoped token is required to produce a '
243  'service catalog.'))
244 
245  return {
246  'catalog': PROVIDERS.catalog_api.get_v3_catalog(
247  user_id, project_id
248  ),
249  'links': {
250  'self': ks_flask.base_url(path='auth/catalog')
251  }
252  }
253 
254 
255 class AuthTokenOSPKIResource(flask_restful.Resource):
256  @ks_flask.unenforced_api
257  def get(self):
258  """Deprecated; get revoked token list.
259 
260  GET/HEAD /v3/auth/tokens/OS-PKI/revoked
261  """
262  if not CONF.token.revoke_by_id:
263  raise exception.Gone()
264  # NOTE(lbragstad): This API is deprecated and isn't supported. Keystone
265  # also doesn't store tokens, so returning a list of revoked tokens
266  # would require keystone to write invalid tokens to disk, which defeats
267  # the purpose. Return a 403 instead of removing the API altogether.
268  raise exception.Forbidden()
269 
270 
272  def get(self):
273  """Validate a token.
274 
275  HEAD/GET /v3/auth/tokens
276  """
277  # TODO(morgan): eliminate the check_token action only use validate
278  # NOTE(morgan): Well lookie here, we have different enforcements
279  # for no good reason (historical), because the methods previously
280  # had to be named different names. Check which method and do the
281  # correct enforcement.
282  if flask.request.method == 'HEAD':
283  ENFORCER.enforce_call(action='identity:check_token')
284  else:
285  ENFORCER.enforce_call(action='identity:validate_token')
286 
287  token_id = flask.request.headers.get(
288  authorization.SUBJECT_TOKEN_HEADER)
289  access_rules_support = flask.request.headers.get(
290  authorization.ACCESS_RULES_HEADER)
291  allow_expired = strutils.bool_from_string(
292  flask.request.args.get('allow_expired'))
293  window_secs = CONF.token.allow_expired_window if allow_expired else 0
294  include_catalog = 'nocatalog' not in flask.request.args
295  token = PROVIDERS.token_provider_api.validate_token(
296  token_id, window_seconds=window_secs,
297  access_rules_support=access_rules_support)
298  token_resp = render_token.render_token_response_from_model(
299  token, include_catalog=include_catalog)
300  resp_body = jsonutils.dumps(token_resp)
301  response = flask.make_response(resp_body, http.client.OK)
302  response.headers['X-Subject-Token'] = token_id
303  response.headers['Content-Type'] = 'application/json'
304  return response
305 
306  @ks_flask.unenforced_api
307  def post(self):
308  """Issue a token.
309 
310  POST /v3/auth/tokens
311  """
312  include_catalog = 'nocatalog' not in flask.request.args
313  auth_data = self.request_body_json.get('auth')
314  auth_schema.validate_issue_token_auth(auth_data)
315  token = authentication.authenticate_for_token(auth_data)
316  resp_data = render_token.render_token_response_from_model(
317  token, include_catalog=include_catalog
318  )
319  resp_body = jsonutils.dumps(resp_data)
320  response = flask.make_response(resp_body, http.client.CREATED)
321  response.headers['X-Subject-Token'] = token.id
322  response.headers['Content-Type'] = 'application/json'
323  return response
324 
325  def delete(self):
326  """Revoke a token.
327 
328  DELETE /v3/auth/tokens
329  """
330  ENFORCER.enforce_call(action='identity:revoke_token')
331  token_id = flask.request.headers.get(
332  authorization.SUBJECT_TOKEN_HEADER)
333  PROVIDERS.token_provider_api.revoke_token(token_id)
334  return None, http.client.NO_CONTENT
335 
336 
338  @classmethod
339  def _perform_auth(cls, protocol_id):
340  idps = PROVIDERS.federation_api.list_idps()
341  remote_id = None
342  for idp in idps:
343  try:
344  remote_id_name = federation_utils.get_remote_id_parameter(
345  idp, protocol_id)
347  # no protocol for this IdP, so this can't be the IdP we're
348  # looking for
349  continue
350  remote_id = flask.request.environ.get(remote_id_name)
351  if remote_id:
352  break
353  if not remote_id:
354  msg = 'Missing entity ID from environment'
355  tr_msg = _('Missing entity ID from environment')
356  LOG.error(msg)
357  raise exception.Unauthorized(tr_msg)
358 
359  host = _get_sso_origin_host()
360  ref = PROVIDERS.federation_api.get_idp_from_remote_id(remote_id)
361  identity_provider = ref['idp_id']
362  token = authentication.federated_authenticate_for_token(
363  identity_provider=identity_provider, protocol_id=protocol_id)
364  return cls._render_template_response(host, token.id)
365 
366  @ks_flask.unenforced_api
367  def get(self, protocol_id):
368  return self._perform_auth(protocol_id)
369 
370  @ks_flask.unenforced_api
371  def post(self, protocol_id):
372  return self._perform_auth(protocol_id)
373 
374 
376  @classmethod
377  def _perform_auth(cls, idp_id, protocol_id):
378  host = _get_sso_origin_host()
379 
380  token = authentication.federated_authenticate_for_token(
381  identity_provider=idp_id, protocol_id=protocol_id)
382  return cls._render_template_response(host, token.id)
383 
384  @ks_flask.unenforced_api
385  def get(self, idp_id, protocol_id):
386  return self._perform_auth(idp_id, protocol_id)
387 
388  @ks_flask.unenforced_api
389  def post(self, idp_id, protocol_id):
390  return self._perform_auth(idp_id, protocol_id)
391 
392 
394  def get(self):
395  raise werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
396 
397  @ks_flask.unenforced_api
398  def post(self):
399  """Exchange a scoped token for a SAML assertion.
400 
401  POST /v3/auth/OS-FEDERATION/saml2
402  """
403  auth = self.request_body_json.get('auth')
404  validation.lazy_validate(federation_schema.saml_create, auth)
405  response, service_provider = saml.create_base_saml_assertion(auth)
406  headers = _build_response_headers(service_provider)
407  response = flask.make_response(response.to_string(), http.client.OK)
408  for header, value in headers:
409  response.headers[header] = value
410  return response
411 
412 
414  def get(self):
415  raise werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
416 
417  @ks_flask.unenforced_api
418  def post(self):
419  """Exchange a scoped token for an ECP assertion.
420 
421  POST /v3/auth/OS-FEDERATION/saml2/ecp
422  """
423  auth = self.request_body_json.get('auth')
424  validation.lazy_validate(federation_schema.saml_create, auth)
425  saml_assertion, service_provider = saml.create_base_saml_assertion(
426  auth)
427  relay_state_prefix = service_provider['relay_state_prefix']
428 
429  generator = keystone_idp.ECPGenerator()
430  ecp_assertion = generator.generate_ecp(
431  saml_assertion, relay_state_prefix)
432  headers = _build_response_headers(service_provider)
433  response = flask.make_response(
434  ecp_assertion.to_string(), http.client.OK)
435  for header, value in headers:
436  response.headers[header] = value
437  return response
438 
439 
440 class AuthAPI(ks_flask.APIBase):
441  _name = 'auth'
442  _import_name = __name__
443  resources = []
444  resource_mapping = [
445  ks_flask.construct_resource_map(
446  resource=AuthProjectsResource,
447  url='/auth/projects',
448  alternate_urls=[dict(
449  url='/OS-FEDERATION/projects',
450  json_home=ks_flask.construct_json_home_data(
451  rel='projects',
452  resource_relation_func=(
453  json_home_relations.os_federation_resource_rel_func)
454  )
455  )],
456 
457  rel='auth_projects',
458  resource_kwargs={}
459  ),
460  ks_flask.construct_resource_map(
461  resource=AuthDomainsResource,
462  url='/auth/domains',
463  alternate_urls=[dict(
464  url='/OS-FEDERATION/domains',
465  json_home=ks_flask.construct_json_home_data(
466  rel='domains',
467  resource_relation_func=(
468  json_home_relations.os_federation_resource_rel_func)
469  )
470  )],
471  rel='auth_domains',
472  resource_kwargs={},
473  ),
474  ks_flask.construct_resource_map(
475  resource=AuthSystemResource,
476  url='/auth/system',
477  resource_kwargs={},
478  rel='auth_system'
479  ),
480  ks_flask.construct_resource_map(
481  resource=AuthCatalogResource,
482  url='/auth/catalog',
483  resource_kwargs={},
484  rel='auth_catalog'
485  ),
486  ks_flask.construct_resource_map(
487  resource=AuthTokenOSPKIResource,
488  url='/auth/tokens/OS-PKI/revoked',
489  resource_kwargs={},
490  rel='revocations',
491  resource_relation_func=json_home_relations.os_pki_resource_rel_func
492  ),
493  ks_flask.construct_resource_map(
494  resource=AuthTokenResource,
495  url='/auth/tokens',
496  resource_kwargs={},
497  rel='auth_tokens'
498  )
499  ]
500 
501 
502 class AuthFederationAPI(ks_flask.APIBase):
503  _name = 'auth/OS-FEDERATION'
504  _import_name = __name__
505  resources = []
506  resource_mapping = [
507  ks_flask.construct_resource_map(
508  resource=AuthFederationSaml2Resource,
509  url='/auth/OS-FEDERATION/saml2',
510  resource_kwargs={},
511  resource_relation_func=(
512  json_home_relations.os_federation_resource_rel_func),
513  rel='saml2'
514  ),
515  ks_flask.construct_resource_map(
516  resource=AuthFederationSaml2ECPResource,
517  url='/auth/OS-FEDERATION/saml2/ecp',
518  resource_kwargs={},
519  resource_relation_func=(
520  json_home_relations.os_federation_resource_rel_func),
521  rel='ecp'
522  ),
523  ks_flask.construct_resource_map(
524  resource=AuthFederationWebSSOResource,
525  url='/auth/OS-FEDERATION/websso/<string:protocol_id>',
526  resource_kwargs={},
527  rel='websso',
528  resource_relation_func=(
529  json_home_relations.os_federation_resource_rel_func),
530  path_vars={
531  'protocol_id': (
532  json_home_relations.os_federation_parameter_rel_func(
533  parameter_name='protocol_id'))}
534  ),
535  ks_flask.construct_resource_map(
536  resource=AuthFederationWebSSOIDPsResource,
537  url=('/auth/OS-FEDERATION/identity_providers/<string:idp_id>/'
538  'protocols/<string:protocol_id>/websso'),
539  resource_kwargs={},
540  rel='identity_providers_websso',
541  resource_relation_func=(
542  json_home_relations.os_federation_resource_rel_func),
543  path_vars={
544  'idp_id': (
545  json_home_relations.os_federation_parameter_rel_func(
546  parameter_name='idp_id')),
547  'protocol_id': (
548  json_home_relations.os_federation_parameter_rel_func(
549  parameter_name='protocol_id'))}
550  )
551  ]
552 
553 
554 APIs = (
555  AuthAPI,
556  AuthFederationAPI,
557 )
keystone.api.auth.AuthCatalogResource
Definition: auth.py:230
keystone.api.auth.AuthFederationWebSSOIDPsResource.get
def get(self, idp_id, protocol_id)
Definition: auth.py:385
keystone.exception.UserNotFound
Definition: exception.py:469
keystone.api.auth.AuthFederationSaml2Resource.get
def get(self)
Definition: auth.py:394
keystone.exception.FederatedProtocolNotFound
Definition: exception.py:509
keystone.api.auth.AuthSystemResource
Definition: auth.py:181
keystone.exception.Unauthorized
Definition: exception.py:283
keystone.api.auth.AuthFederationSaml2Resource
Definition: auth.py:393
keystone.api.auth._AuthFederationWebSSOBase._render_template_response
def _render_template_response(host, token_id)
Definition: auth.py:105
keystone.auth
Definition: __init__.py:1
keystone.api.auth._build_response_headers
def _build_response_headers(service_provider)
Definition: auth.py:59
keystone.api.auth.AuthTokenResource
Definition: auth.py:271
keystone.api.auth.AuthFederationAPI
Definition: auth.py:502
keystone.api.auth.AuthTokenResource.post
def post(self)
Definition: auth.py:307
keystone.api.auth.AuthFederationWebSSOResource._perform_auth
def _perform_auth(cls, protocol_id)
Definition: auth.py:339
keystone.api.auth.AuthFederationWebSSOResource.get
def get(self, protocol_id)
Definition: auth.py:367
keystone.exception.ValidationError
Definition: exception.py:98
keystone.api.auth.AuthFederationSaml2ECPResource.get
def get(self)
Definition: auth.py:414
keystone.api.auth.AuthTokenResource.delete
def delete(self)
Definition: auth.py:325
keystone.federation
Definition: __init__.py:1
keystone.api.auth.AuthSystemResource.get
def get(self)
Definition: auth.py:182
keystone.exception.Gone
Definition: exception.py:628
keystone.api.auth.AuthFederationSaml2ECPResource
Definition: auth.py:413
keystone.api.auth._get_sso_origin_host
def _get_sso_origin_host()
Definition: auth.py:66
keystone.api.auth.AuthCatalogResource.get
def get(self)
Definition: auth.py:231
keystone.api.auth.AuthTokenOSPKIResource.get
def get(self)
Definition: auth.py:257
keystone.api.auth._AuthFederationWebSSOBase
Definition: auth.py:103
keystone.api.auth.AuthFederationWebSSOIDPsResource.post
def post(self, idp_id, protocol_id)
Definition: auth.py:389
keystone.api.auth.AuthDomainsResource.get
def get(self)
Definition: auth.py:152
keystone.api.auth.AuthAPI
Definition: auth.py:440
keystone.api.auth.AuthFederationWebSSOIDPsResource
Definition: auth.py:375
keystone.api.auth.AuthFederationWebSSOResource.post
def post(self, protocol_id)
Definition: auth.py:371
keystone.api.auth.AuthProjectsResource.get
def get(self)
Definition: auth.py:120
keystone.server
Definition: __init__.py:1
keystone.exception.Forbidden
Definition: exception.py:352
keystone.api.auth.AuthFederationWebSSOResource
Definition: auth.py:337
keystone.conf
Definition: __init__.py:1
keystone.api.auth.AuthFederationWebSSOIDPsResource._perform_auth
def _perform_auth(cls, idp_id, protocol_id)
Definition: auth.py:377
keystone.api.auth.AuthDomainsResource
Definition: auth.py:148
keystone.i18n._
_
Definition: i18n.py:29
keystone.api.auth.AuthTokenOSPKIResource
Definition: auth.py:255
keystone.api.auth.AuthFederationSaml2ECPResource.post
def post(self)
Definition: auth.py:418
keystone.api.auth.AuthFederationSaml2Resource.post
def post(self)
Definition: auth.py:398
keystone.common
Definition: __init__.py:1
keystone.i18n
Definition: i18n.py:1
keystone.api._shared
Definition: __init__.py:1
keystone.api.auth._combine_lists_uniquely
def _combine_lists_uniquely(a, b)
Definition: auth.py:50
keystone.api.auth.AuthProjectsResource
Definition: auth.py:116
keystone.api.auth.AuthTokenResource.get
def get(self)
Definition: auth.py:272