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)  

os_oauth1.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/OS-OAUTH1/
14 
15 import flask
16 import flask_restful
17 import http.client
18 from oslo_log import log
19 from oslo_utils import timeutils
20 from urllib import parse as urlparse
21 from werkzeug import exceptions
22 
23 from keystone.api._shared import json_home_relations
24 from keystone.common import authorization
25 from keystone.common import context
26 from keystone.common import provider_api
27 from keystone.common import rbac_enforcer
28 from keystone.common import validation
29 import keystone.conf
30 from keystone import exception
31 from keystone.i18n import _
32 from keystone import notifications
33 from keystone.oauth1 import core as oauth1
34 from keystone.oauth1 import schema
35 from keystone.oauth1 import validator
36 from keystone.server import flask as ks_flask
37 
38 
39 LOG = log.getLogger(__name__)
40 PROVIDERS = provider_api.ProviderAPIs
41 ENFORCER = rbac_enforcer.RBACEnforcer
42 CONF = keystone.conf.CONF
43 
44 
45 _build_resource_relation = json_home_relations.os_oauth1_resource_rel_func
46 _build_parameter_relation = json_home_relations.os_oauth1_parameter_rel_func
47 
48 _ACCESS_TOKEN_ID_PARAMETER_RELATION = _build_parameter_relation(
49  parameter_name='access_token_id')
50 
51 
52 def _normalize_role_list(authorize_roles):
53  roles = set()
54  for role in authorize_roles:
55  if role.get('id'):
56  roles.add(role['id'])
57  else:
58  roles.add(PROVIDERS.role_api.get_unique_role_by_name(
59  role['name'])['id'])
60  return roles
61 
62 
64  """Update request url scheme with base url scheme."""
65  url = ks_flask.base_url()
66  url_scheme = list(urlparse.urlparse(url))[0]
67  req_url_list = list(urlparse.urlparse(flask.request.url))
68  req_url_list[0] = url_scheme
69  req_url = urlparse.urlunparse(req_url_list)
70  return req_url
71 
72 
73 class _OAuth1ResourceBase(flask_restful.Resource):
74  def get(self):
75  # GET is not allowed, however flask restful doesn't handle "GET" not
76  # being allowed cleanly. Here we explicitly mark is as not allowed. All
77  # other methods not defined would raise a method NotAllowed error and
78  # this would not be needed.
79  raise exceptions.MethodNotAllowed(valid_methods=['POST'])
80 
81 
82 class ConsumerResource(ks_flask.ResourceBase):
83  collection_key = 'consumers'
84  member_key = 'consumer'
85  api_prefix = '/OS-OAUTH1'
86  json_home_resource_rel_func = _build_resource_relation
87  json_home_parameter_rel_func = _build_parameter_relation
88 
89  def _list_consumers(self):
90  ENFORCER.enforce_call(action='identity:list_consumers')
91  return self.wrap_collection(PROVIDERS.oauth_api.list_consumers())
92 
93  def _get_consumer(self, consumer_id):
94  ENFORCER.enforce_call(action='identity:get_consumer')
95  return self.wrap_member(PROVIDERS.oauth_api.get_consumer(consumer_id))
96 
97  def get(self, consumer_id=None):
98  if consumer_id is None:
99  return self._list_consumers()
100  return self._get_consumer(consumer_id)
101 
102  def post(self):
103  ENFORCER.enforce_call(action='identity:create_consumer')
104  consumer = (flask.request.get_json(force=True, silent=True) or {}).get(
105  'consumer', {})
106  consumer = self._normalize_dict(consumer)
107  validation.lazy_validate(schema.consumer_create, consumer)
108  consumer = self._assign_unique_id(consumer)
109  ref = PROVIDERS.oauth_api.create_consumer(
110  consumer, initiator=self.audit_initiator)
111  return self.wrap_member(ref), http.client.CREATED
112 
113  def delete(self, consumer_id):
114  ENFORCER.enforce_call(action='identity:delete_consumer')
115  reason = (
116  'Invalidating token cache because consumer %(consumer_id)s has '
117  'been deleted. Authorization for users with OAuth tokens will be '
118  'recalculated and enforced accordingly the next time they '
119  'authenticate or validate a token.' %
120  {'consumer_id': consumer_id}
121  )
122  notifications.invalidate_token_cache_notification(reason)
123  PROVIDERS.oauth_api.delete_consumer(
124  consumer_id, initiator=self.audit_initiator)
125  return None, http.client.NO_CONTENT
126 
127  def patch(self, consumer_id):
128  ENFORCER.enforce_call(action='identity:update_consumer')
129  consumer = (flask.request.get_json(force=True, silent=True) or {}).get(
130  'consumer', {})
131  validation.lazy_validate(schema.consumer_update, consumer)
132  consumer = self._normalize_dict(consumer)
133  self._require_matching_id(consumer)
134  ref = PROVIDERS.oauth_api.update_consumer(
135  consumer_id, consumer, initiator=self.audit_initiator)
136  return self.wrap_member(ref)
137 
138 
140  @ks_flask.unenforced_api
141  def post(self):
142  oauth_headers = oauth1.get_oauth_headers(flask.request.headers)
143  consumer_id = oauth_headers.get('oauth_consumer_key')
144  requested_project_id = flask.request.headers.get(
145  'Requested-Project-Id')
146 
147  if not consumer_id:
149  attribute='oauth_consumer_key', target='request')
150  if not requested_project_id:
152  attribute='Requested-Project-Id', target='request')
153 
154  # NOTE(stevemar): Ensure consumer and requested project exist
155  PROVIDERS.resource_api.get_project(requested_project_id)
156  PROVIDERS.oauth_api.get_consumer(consumer_id)
157 
158  url = _update_url_scheme()
159  req_headers = {'Requested-Project-Id': requested_project_id}
160  req_headers.update(flask.request.headers)
161  request_verifier = oauth1.RequestTokenEndpoint(
162  request_validator=validator.OAuthValidator(),
163  token_generator=oauth1.token_generator)
164  h, b, s = request_verifier.create_request_token_response(
165  url, http_method='POST', body=flask.request.args,
166  headers=req_headers)
167  if not b:
168  msg = _('Invalid signature')
169  raise exception.Unauthorized(message=msg)
170  # show the details of the failure.
171  oauth1.validate_oauth_params(b)
172  request_token_duration = CONF.oauth1.request_token_duration
173  token_ref = PROVIDERS.oauth_api.create_request_token(
174  consumer_id,
175  requested_project_id,
176  request_token_duration,
177  initiator=notifications.build_audit_initiator())
178 
179  result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
180  % {'key': token_ref['id'],
181  'secret': token_ref['request_secret']})
182 
183  if CONF.oauth1.request_token_duration > 0:
184  expiry_bit = '&oauth_expires_at=%s' % token_ref['expires_at']
185  result += expiry_bit
186 
187  resp = flask.make_response(result, http.client.CREATED)
188  resp.headers['Content-Type'] = 'application/x-www-form-urlencoded'
189  return resp
190 
191 
193  @ks_flask.unenforced_api
194  def post(self):
195  oauth_headers = oauth1.get_oauth_headers(flask.request.headers)
196  consumer_id = oauth_headers.get('oauth_consumer_key')
197  request_token_id = oauth_headers.get('oauth_token')
198  oauth_verifier = oauth_headers.get('oauth_verifier')
199 
200  if not consumer_id:
202  attribute='oauth_consumer_key', target='request')
203  if not request_token_id:
205  attribute='oauth_token', target='request')
206  if not oauth_verifier:
208  attribute='oauth_verifier', target='request')
209 
210  req_token = PROVIDERS.oauth_api.get_request_token(
211  request_token_id)
212 
213  expires_at = req_token['expires_at']
214  if expires_at:
215  now = timeutils.utcnow()
216  expires = timeutils.normalize_time(
217  timeutils.parse_isotime(expires_at))
218  if now > expires:
219  raise exception.Unauthorized(_('Request token is expired'))
220 
221  url = _update_url_scheme()
222  access_verifier = oauth1.AccessTokenEndpoint(
223  request_validator=validator.OAuthValidator(),
224  token_generator=oauth1.token_generator)
225  try:
226  h, b, s = access_verifier.create_access_token_response(
227  url,
228  http_method='POST',
229  body=flask.request.args,
230  headers=dict(flask.request.headers))
231  except NotImplementedError:
232  # Client key or request token validation failed, since keystone
233  # does not yet support dummy client or dummy request token,
234  # so we will raise unauthorized exception instead.
235  try:
236  PROVIDERS.oauth_api.get_consumer(consumer_id)
237  except exception.NotFound:
238  msg = _('Provided consumer does not exist.')
239  LOG.warning('Provided consumer does not exist.')
240  raise exception.Unauthorized(message=msg)
241  if req_token['consumer_id'] != consumer_id:
242  msg = ('Provided consumer key does not match stored consumer '
243  'key.')
244  tr_msg = _('Provided consumer key does not match stored '
245  'consumer key.')
246  LOG.warning(msg)
247  raise exception.Unauthorized(message=tr_msg)
248  # The response body is empty since either one of the following reasons
249  if not b:
250  if req_token['verifier'] != oauth_verifier:
251  msg = 'Provided verifier does not match stored verifier'
252  tr_msg = _('Provided verifier does not match stored verifier')
253  else:
254  msg = 'Invalid signature'
255  tr_msg = _('Invalid signature')
256  LOG.warning(msg)
257  raise exception.Unauthorized(message=tr_msg)
258  # show the details of the failure
259  oauth1.validate_oauth_params(b)
260  if not req_token.get('authorizing_user_id'):
261  msg = _('Request Token does not have an authorizing user id.')
262  LOG.warning('Request Token does not have an authorizing user id.')
263  raise exception.Unauthorized(message=msg)
264 
265  access_token_duration = CONF.oauth1.access_token_duration
266  token_ref = PROVIDERS.oauth_api.create_access_token(
267  request_token_id,
268  access_token_duration,
269  initiator=notifications.build_audit_initiator())
270 
271  result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
272  % {'key': token_ref['id'],
273  'secret': token_ref['access_secret']})
274 
275  if CONF.oauth1.access_token_duration > 0:
276  expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at'])
277  result += expiry_bit
278 
279  resp = flask.make_response(result, http.client.CREATED)
280  resp.headers['Content-Type'] = 'application/x-www-form-urlencoded'
281  return resp
282 
283 
285  def put(self, request_token_id):
286  ENFORCER.enforce_call(action='identity:authorize_request_token')
287  roles = (flask.request.get_json(force=True, silent=True) or {}).get(
288  'roles', [])
289  validation.lazy_validate(schema.request_token_authorize, roles)
290  ctx = flask.request.environ[context.REQUEST_CONTEXT_ENV]
291  if ctx.is_delegated_auth:
292  raise exception.Forbidden(
293  _('Cannot authorize a request token with a token issued via '
294  'delegation.'))
295 
296  req_token = PROVIDERS.oauth_api.get_request_token(request_token_id)
297 
298  expires_at = req_token['expires_at']
299  if expires_at:
300  now = timeutils.utcnow()
301  expires = timeutils.normalize_time(
302  timeutils.parse_isotime(expires_at))
303  if now > expires:
304  raise exception.Unauthorized(_('Request token is expired'))
305 
306  authed_roles = _normalize_role_list(roles)
307 
308  # verify the authorizing user has the roles
309  try:
310  auth_context = flask.request.environ[
311  authorization.AUTH_CONTEXT_ENV]
312  user_token_ref = auth_context['token']
313  except KeyError:
314  LOG.warning("Couldn't find the auth context.")
315  raise exception.Unauthorized()
316 
317  user_id = user_token_ref.user_id
318  project_id = req_token['requested_project_id']
319  user_roles = PROVIDERS.assignment_api.get_roles_for_user_and_project(
320  user_id, project_id)
321  cred_set = set(user_roles)
322 
323  if not cred_set.issuperset(authed_roles):
324  msg = _('authorizing user does not have role required')
325  raise exception.Unauthorized(message=msg)
326 
327  # create least of just the id's for the backend
328  role_ids = list(authed_roles)
329 
330  # finally authorize the token
331  authed_token = PROVIDERS.oauth_api.authorize_request_token(
332  request_token_id, user_id, role_ids)
333 
334  to_return = {'token': {'oauth_verifier': authed_token['verifier']}}
335  return to_return
336 
337 
338 class OSAuth1API(ks_flask.APIBase):
339  _name = 'OS-OAUTH1'
340  _import_name = __name__
341  _api_url_prefix = '/OS-OAUTH1'
342  resources = [ConsumerResource]
343  resource_mapping = [
344  ks_flask.construct_resource_map(
345  resource=RequestTokenResource,
346  url='/request_token',
347  resource_kwargs={},
348  rel='request_tokens',
349  resource_relation_func=_build_resource_relation
350  ),
351  ks_flask.construct_resource_map(
352  resource=AccessTokenResource,
353  url='/access_token',
354  rel='access_tokens',
355  resource_kwargs={},
356  resource_relation_func=_build_resource_relation
357  ),
358  ks_flask.construct_resource_map(
359  resource=AuthorizeResource,
360  url='/authorize/<string:request_token_id>',
361  resource_kwargs={},
362  rel='authorize_request_token',
363  resource_relation_func=_build_resource_relation,
364  path_vars={
365  'request_token_id': _build_parameter_relation(
366  parameter_name='request_token_id')
367  })]
368 
369 
370 APIs = (OSAuth1API,)
keystone.api.os_oauth1.ConsumerResource.patch
def patch(self, consumer_id)
Definition: os_oauth1.py:127
keystone.api.os_oauth1._OAuth1ResourceBase.get
def get(self)
Definition: os_oauth1.py:74
keystone.api.os_oauth1.ConsumerResource._get_consumer
def _get_consumer(self, consumer_id)
Definition: os_oauth1.py:93
keystone.api.os_oauth1.AccessTokenResource.post
def post(self)
Definition: os_oauth1.py:194
keystone.api.os_oauth1.AuthorizeResource
Definition: os_oauth1.py:284
keystone.exception.Unauthorized
Definition: exception.py:283
keystone.exception.NotFound
Definition: exception.py:395
keystone.api.os_oauth1.ConsumerResource.get
def get(self, consumer_id=None)
Definition: os_oauth1.py:97
keystone.api.os_oauth1.RequestTokenResource
Definition: os_oauth1.py:139
keystone.exception.ValidationError
Definition: exception.py:98
keystone.api.os_oauth1.ConsumerResource
Definition: os_oauth1.py:82
keystone.api.os_oauth1.AuthorizeResource.put
def put(self, request_token_id)
Definition: os_oauth1.py:285
keystone.api.os_oauth1._update_url_scheme
def _update_url_scheme()
Definition: os_oauth1.py:63
keystone.api.os_oauth1._OAuth1ResourceBase
Definition: os_oauth1.py:73
keystone.oauth1
Definition: __init__.py:1
keystone.api.os_oauth1.ConsumerResource.post
def post(self)
Definition: os_oauth1.py:102
keystone.api.os_oauth1.RequestTokenResource.post
def post(self)
Definition: os_oauth1.py:141
keystone.api.os_oauth1.ConsumerResource.delete
def delete(self, consumer_id)
Definition: os_oauth1.py:113
keystone.server
Definition: __init__.py:1
keystone.exception.Forbidden
Definition: exception.py:352
keystone.conf
Definition: __init__.py:1
keystone.api.os_oauth1.AccessTokenResource
Definition: os_oauth1.py:192
keystone.i18n._
_
Definition: i18n.py:29
keystone.api.os_oauth1.OSAuth1API
Definition: os_oauth1.py:338
keystone.api.os_oauth1.ConsumerResource._list_consumers
def _list_consumers(self)
Definition: os_oauth1.py:89
keystone.common
Definition: __init__.py:1
keystone.i18n
Definition: i18n.py:1
keystone.api._shared
Definition: __init__.py:1
keystone.api.os_oauth1._normalize_role_list
def _normalize_role_list(authorize_roles)
Definition: os_oauth1.py:52
keystone.api.os_oauth1._build_parameter_relation
_build_parameter_relation
Definition: os_oauth1.py:46