**** CubicPower OpenStack Study ****
# Copyright 2013 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.
"""Extensions supporting OAuth1."""
from keystone.common import controller
from keystone.common import dependency
from keystone.common import wsgi
from keystone import config
from keystone.contrib.oauth1 import core as oauth1
from keystone.contrib.oauth1 import validator
from keystone import exception
from keystone.openstack.common.gettextutils import _
from keystone.openstack.common import jsonutils
from keystone.openstack.common import timeutils
CONF = config.CONF
@dependency.requires('oauth_api', 'token_api')
**** CubicPower OpenStack Study ****
class ConsumerCrudV3(controller.V3Controller):
collection_name = 'consumers'
member_name = 'consumer'
@classmethod
**** CubicPower OpenStack Study ****
def base_url(cls, context, path=None):
"""Construct a path and pass it to V3Controller.base_url method."""
# NOTE(stevemar): Overriding path to /OS-OAUTH1/consumers so that
# V3Controller.base_url handles setting the self link correctly.
path = '/OS-OAUTH1/' + cls.collection_name
return controller.V3Controller.base_url(context, path=path)
@controller.protected()
**** CubicPower OpenStack Study ****
def create_consumer(self, context, consumer):
ref = self._assign_unique_id(self._normalize_dict(consumer))
consumer_ref = self.oauth_api.create_consumer(ref)
return ConsumerCrudV3.wrap_member(context, consumer_ref)
@controller.protected()
**** CubicPower OpenStack Study ****
def update_consumer(self, context, consumer_id, consumer):
self._require_matching_id(consumer_id, consumer)
ref = self._normalize_dict(consumer)
self._validate_consumer_ref(ref)
ref = self.oauth_api.update_consumer(consumer_id, ref)
return ConsumerCrudV3.wrap_member(context, ref)
@controller.protected()
**** CubicPower OpenStack Study ****
def list_consumers(self, context):
ref = self.oauth_api.list_consumers()
return ConsumerCrudV3.wrap_collection(context, ref)
@controller.protected()
**** CubicPower OpenStack Study ****
def get_consumer(self, context, consumer_id):
ref = self.oauth_api.get_consumer(consumer_id)
return ConsumerCrudV3.wrap_member(context, ref)
@controller.protected()
**** CubicPower OpenStack Study ****
def delete_consumer(self, context, consumer_id):
user_token_ref = self.token_api.get_token(context['token_id'])
user_id = user_token_ref['user'].get('id')
self.token_api.delete_tokens(user_id, consumer_id=consumer_id)
self.oauth_api.delete_consumer(consumer_id)
**** CubicPower OpenStack Study ****
def _validate_consumer_ref(self, consumer):
if 'secret' in consumer:
msg = _('Cannot change consumer secret')
raise exception.ValidationError(message=msg)
@dependency.requires('oauth_api', 'token_api')
**** CubicPower OpenStack Study ****
class AccessTokenCrudV3(controller.V3Controller):
collection_name = 'access_tokens'
member_name = 'access_token'
@controller.protected()
**** CubicPower OpenStack Study ****
def get_access_token(self, context, user_id, access_token_id):
access_token = self.oauth_api.get_access_token(access_token_id)
if access_token['authorizing_user_id'] != user_id:
raise exception.NotFound()
access_token = self._format_token_entity(context, access_token)
return AccessTokenCrudV3.wrap_member(context, access_token)
@controller.protected()
**** CubicPower OpenStack Study ****
def list_access_tokens(self, context, user_id):
refs = self.oauth_api.list_access_tokens(user_id)
formatted_refs = ([self._format_token_entity(context, x)
for x in refs])
return AccessTokenCrudV3.wrap_collection(context, formatted_refs)
@controller.protected()
**** CubicPower OpenStack Study ****
def delete_access_token(self, context, user_id, access_token_id):
access_token = self.oauth_api.get_access_token(access_token_id)
consumer_id = access_token['consumer_id']
self.token_api.delete_tokens(user_id, consumer_id=consumer_id)
return self.oauth_api.delete_access_token(
user_id, access_token_id)
**** CubicPower OpenStack Study ****
def _format_token_entity(self, context, entity):
formatted_entity = entity.copy()
access_token_id = formatted_entity['id']
user_id = ""
if 'role_ids' in entity:
formatted_entity.pop('role_ids')
if 'access_secret' in entity:
formatted_entity.pop('access_secret')
if 'authorizing_user_id' in entity:
user_id = formatted_entity['authorizing_user_id']
url = ('/users/%(user_id)s/OS-OAUTH1/access_tokens/%(access_token_id)s'
'/roles' % {'user_id': user_id,
'access_token_id': access_token_id})
formatted_entity.setdefault('links', {})
formatted_entity['links']['roles'] = (self.base_url(context, url))
return formatted_entity
@dependency.requires('assignment_api', 'oauth_api')
**** CubicPower OpenStack Study ****
class AccessTokenRolesV3(controller.V3Controller):
collection_name = 'roles'
member_name = 'role'
@controller.protected()
**** CubicPower OpenStack Study ****
def list_access_token_roles(self, context, user_id, access_token_id):
access_token = self.oauth_api.get_access_token(access_token_id)
if access_token['authorizing_user_id'] != user_id:
raise exception.NotFound()
authed_role_ids = access_token['role_ids']
authed_role_ids = jsonutils.loads(authed_role_ids)
refs = ([self._format_role_entity(x) for x in authed_role_ids])
return AccessTokenRolesV3.wrap_collection(context, refs)
@controller.protected()
**** CubicPower OpenStack Study ****
def get_access_token_role(self, context, user_id,
access_token_id, role_id):
access_token = self.oauth_api.get_access_token(access_token_id)
if access_token['authorizing_user_id'] != user_id:
raise exception.Unauthorized(_('User IDs do not match'))
authed_role_ids = access_token['role_ids']
authed_role_ids = jsonutils.loads(authed_role_ids)
for authed_role_id in authed_role_ids:
if authed_role_id == role_id:
role = self._format_role_entity(role_id)
return AccessTokenRolesV3.wrap_member(context, role)
raise exception.RoleNotFound(_('Could not find role'))
**** CubicPower OpenStack Study ****
def _format_role_entity(self, role_id):
role = self.assignment_api.get_role(role_id)
formatted_entity = role.copy()
if 'description' in role:
formatted_entity.pop('description')
if 'enabled' in role:
formatted_entity.pop('enabled')
return formatted_entity
@dependency.requires('assignment_api', 'oauth_api', 'token_api')
**** CubicPower OpenStack Study ****
class OAuthControllerV3(controller.V3Controller):
collection_name = 'not_used'
member_name = 'not_used'
**** CubicPower OpenStack Study ****
def create_request_token(self, context):
headers = context['headers']
oauth_headers = oauth1.get_oauth_headers(headers)
consumer_id = oauth_headers.get('oauth_consumer_key')
requested_project_id = headers.get('Requested-Project-Id')
if not consumer_id:
raise exception.ValidationError(
attribute='oauth_consumer_key', target='request')
if not requested_project_id:
raise exception.ValidationError(
attribute='requested_project_id', target='request')
url = self.base_url(context, context['path'])
req_headers = {'Requested-Project-Id': requested_project_id}
req_headers.update(headers)
request_verifier = oauth1.RequestTokenEndpoint(
request_validator=validator.OAuthValidator(),
token_generator=oauth1.token_generator)
h, b, s = request_verifier.create_request_token_response(
url,
http_method='POST',
body=context['query_string'],
headers=req_headers)
if (not b) or int(s) > 399:
msg = _('Invalid signature')
raise exception.Unauthorized(message=msg)
request_token_duration = CONF.oauth1.request_token_duration
token_ref = self.oauth_api.create_request_token(consumer_id,
requested_project_id,
request_token_duration)
result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
% {'key': token_ref['id'],
'secret': token_ref['request_secret']})
if CONF.oauth1.request_token_duration:
expiry_bit = '&oauth_expires_at=%s' % token_ref['expires_at']
result += expiry_bit
headers = [('Content-Type', 'application/x-www-urlformencoded')]
response = wsgi.render_response(result,
status=(201, 'Created'),
headers=headers)
return response
**** CubicPower OpenStack Study ****
def create_access_token(self, context):
headers = context['headers']
oauth_headers = oauth1.get_oauth_headers(headers)
consumer_id = oauth_headers.get('oauth_consumer_key')
request_token_id = oauth_headers.get('oauth_token')
oauth_verifier = oauth_headers.get('oauth_verifier')
if not consumer_id:
raise exception.ValidationError(
attribute='oauth_consumer_key', target='request')
if not request_token_id:
raise exception.ValidationError(
attribute='oauth_token', target='request')
if not oauth_verifier:
raise exception.ValidationError(
attribute='oauth_verifier', target='request')
req_token = self.oauth_api.get_request_token(
request_token_id)
expires_at = req_token['expires_at']
if expires_at:
now = timeutils.utcnow()
expires = timeutils.normalize_time(
timeutils.parse_isotime(expires_at))
if now > expires:
raise exception.Unauthorized(_('Request token is expired'))
url = self.base_url(context, context['path'])
access_verifier = oauth1.AccessTokenEndpoint(
request_validator=validator.OAuthValidator(),
token_generator=oauth1.token_generator)
h, b, s = access_verifier.create_access_token_response(
url,
http_method='POST',
body=context['query_string'],
headers=headers)
params = oauth1.extract_non_oauth_params(b)
if len(params) != 0:
msg = _('There should not be any non-oauth parameters')
raise exception.Unauthorized(message=msg)
if req_token['consumer_id'] != consumer_id:
msg = _('provided consumer key does not match stored consumer key')
raise exception.Unauthorized(message=msg)
if req_token['verifier'] != oauth_verifier:
msg = _('provided verifier does not match stored verifier')
raise exception.Unauthorized(message=msg)
if req_token['id'] != request_token_id:
msg = _('provided request key does not match stored request key')
raise exception.Unauthorized(message=msg)
if not req_token.get('authorizing_user_id'):
msg = _('Request Token does not have an authorizing user id')
raise exception.Unauthorized(message=msg)
access_token_duration = CONF.oauth1.access_token_duration
token_ref = self.oauth_api.create_access_token(request_token_id,
access_token_duration)
result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
% {'key': token_ref['id'],
'secret': token_ref['access_secret']})
if CONF.oauth1.access_token_duration:
expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at'])
result += expiry_bit
headers = [('Content-Type', 'application/x-www-urlformencoded')]
response = wsgi.render_response(result,
status=(201, 'Created'),
headers=headers)
return response
@controller.protected()
**** CubicPower OpenStack Study ****
def authorize_request_token(self, context, request_token_id, roles):
"""An authenticated user is going to authorize a request token.
As a security precaution, the requested roles must match those in
the request token. Because this is in a CLI-only world at the moment,
there is not another easy way to make sure the user knows which roles
are being requested before authorizing.
"""
req_token = self.oauth_api.get_request_token(request_token_id)
expires_at = req_token['expires_at']
if expires_at:
now = timeutils.utcnow()
expires = timeutils.normalize_time(
timeutils.parse_isotime(expires_at))
if now > expires:
raise exception.Unauthorized(_('Request token is expired'))
# put the roles in a set for easy comparison
authed_roles = set()
for role in roles:
authed_roles.add(role['id'])
# verify the authorizing user has the roles
user_token = self.token_api.get_token(context['token_id'])
user_id = user_token['user'].get('id')
project_id = req_token['requested_project_id']
user_roles = self.assignment_api.get_roles_for_user_and_project(
user_id, project_id)
cred_set = set(user_roles)
if not cred_set.issuperset(authed_roles):
msg = _('authorizing user does not have role required')
raise exception.Unauthorized(message=msg)
# create list of just the id's for the backend
role_list = list(authed_roles)
# verify the user has the project too
req_project_id = req_token['requested_project_id']
user_projects = self.assignment_api.list_projects_for_user(user_id)
found = False
for user_project in user_projects:
if user_project['id'] == req_project_id:
found = True
break
if not found:
msg = _("User is not a member of the requested project")
raise exception.Unauthorized(message=msg)
# finally authorize the token
authed_token = self.oauth_api.authorize_request_token(
request_token_id, user_id, role_list)
to_return = {'token': {'oauth_verifier': authed_token['verifier']}}
return to_return