Move oauth1 extension into core

Remove oauth1 as an extension and move it to a core resource.
For now we leave the database migrations in the extension directory
until we have a general policy for merging these into core.

DocImpact: update keystone-paste and remove oauth1 from pipeline

Change-Id: I0ed1ec44d42c3b379a5c2a40e3e6298842dfc01d
Implements: bp move-extensions
This commit is contained in:
Steve Martinelli 2015-10-14 02:47:25 -04:00 committed by Dave Chen
parent 9f3abc6983
commit 78e256273a
16 changed files with 491 additions and 418 deletions

View File

@ -30,9 +30,6 @@ use = egg:keystone#ec2_extension
[filter:ec2_extension_v3] [filter:ec2_extension_v3]
use = egg:keystone#ec2_extension_v3 use = egg:keystone#ec2_extension_v3
[filter:oauth1_extension]
use = egg:keystone#oauth1_extension
[filter:s3_extension] [filter:s3_extension]
use = egg:keystone#s3_extension use = egg:keystone#s3_extension
@ -73,7 +70,7 @@ pipeline = sizelimit url_normalize request_id build_auth_context token_auth admi
[pipeline:api_v3] [pipeline:api_v3]
# The last item in this pipeline must be service_v3 or an equivalent # The last item in this pipeline must be service_v3 or an equivalent
# application. It cannot be a filter. # application. It cannot be a filter.
pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension revoke_extension oauth1_extension endpoint_filter_extension service_v3 pipeline = sizelimit url_normalize request_id build_auth_context token_auth admin_token_auth json_body ec2_extension_v3 s3_extension simple_cert_extension revoke_extension endpoint_filter_extension service_v3
[app:public_version_service] [app:public_version_service]
use = egg:keystone#public_version_service use = egg:keystone#public_version_service

View File

@ -18,10 +18,10 @@ from oslo_utils import timeutils
from keystone import auth from keystone import auth
from keystone.common import controller from keystone.common import controller
from keystone.common import dependency from keystone.common import dependency
from keystone.contrib.oauth1 import core as oauth
from keystone.contrib.oauth1 import validator
from keystone import exception from keystone import exception
from keystone.i18n import _ from keystone.i18n import _
from keystone.oauth1 import core as oauth
from keystone.oauth1 import validator
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)

View File

@ -1,15 +0,0 @@
# 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.
from keystone.contrib.oauth1.core import * # noqa

View File

@ -12,261 +12,19 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import datetime from oslo_log import versionutils
import random as _random
import uuid
from oslo_serialization import jsonutils from keystone.oauth1.backends import sql
from oslo_utils import timeutils
from keystone.common import sql
from keystone.common import utils
from keystone.contrib.oauth1 import core
from keystone import exception
from keystone.i18n import _
random = _random.SystemRandom() _OLD = "keystone.contrib.oauth1.backends.sql.OAuth1"
_NEW = "sql"
class Consumer(sql.ModelBase, sql.DictBase): class OAuth1(sql.OAuth1):
__tablename__ = 'consumer'
attributes = ['id', 'description', 'secret']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
description = sql.Column(sql.String(64), nullable=True)
secret = sql.Column(sql.String(64), nullable=False)
extra = sql.Column(sql.JsonBlob(), nullable=False)
@versionutils.deprecated(versionutils.deprecated.MITAKA,
class RequestToken(sql.ModelBase, sql.DictBase): in_favor_of=_NEW,
__tablename__ = 'request_token' what=_OLD)
attributes = ['id', 'request_secret', def __init__(self, *args, **kwargs):
'verifier', 'authorizing_user_id', 'requested_project_id', super(OAuth1, self).__init__(*args, **kwargs)
'role_ids', 'consumer_id', 'expires_at']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
request_secret = sql.Column(sql.String(64), nullable=False)
verifier = sql.Column(sql.String(64), nullable=True)
authorizing_user_id = sql.Column(sql.String(64), nullable=True)
requested_project_id = sql.Column(sql.String(64), nullable=False)
role_ids = sql.Column(sql.Text(), nullable=True)
consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'),
nullable=False, index=True)
expires_at = sql.Column(sql.String(64), nullable=True)
@classmethod
def from_dict(cls, user_dict):
return cls(**user_dict)
def to_dict(self):
return dict(self.items())
class AccessToken(sql.ModelBase, sql.DictBase):
__tablename__ = 'access_token'
attributes = ['id', 'access_secret', 'authorizing_user_id',
'project_id', 'role_ids', 'consumer_id',
'expires_at']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
access_secret = sql.Column(sql.String(64), nullable=False)
authorizing_user_id = sql.Column(sql.String(64), nullable=False,
index=True)
project_id = sql.Column(sql.String(64), nullable=False)
role_ids = sql.Column(sql.Text(), nullable=False)
consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'),
nullable=False)
expires_at = sql.Column(sql.String(64), nullable=True)
@classmethod
def from_dict(cls, user_dict):
return cls(**user_dict)
def to_dict(self):
return dict(self.items())
class OAuth1(object):
def _get_consumer(self, session, consumer_id):
consumer_ref = session.query(Consumer).get(consumer_id)
if consumer_ref is None:
raise exception.NotFound(_('Consumer not found'))
return consumer_ref
def get_consumer_with_secret(self, consumer_id):
session = sql.get_session()
consumer_ref = self._get_consumer(session, consumer_id)
return consumer_ref.to_dict()
def get_consumer(self, consumer_id):
return core.filter_consumer(
self.get_consumer_with_secret(consumer_id))
def create_consumer(self, consumer):
consumer['secret'] = uuid.uuid4().hex
if not consumer.get('description'):
consumer['description'] = None
session = sql.get_session()
with session.begin():
consumer_ref = Consumer.from_dict(consumer)
session.add(consumer_ref)
return consumer_ref.to_dict()
def _delete_consumer(self, session, consumer_id):
consumer_ref = self._get_consumer(session, consumer_id)
session.delete(consumer_ref)
def _delete_request_tokens(self, session, consumer_id):
q = session.query(RequestToken)
req_tokens = q.filter_by(consumer_id=consumer_id)
req_tokens_list = set([x.id for x in req_tokens])
for token_id in req_tokens_list:
token_ref = self._get_request_token(session, token_id)
session.delete(token_ref)
def _delete_access_tokens(self, session, consumer_id):
q = session.query(AccessToken)
acc_tokens = q.filter_by(consumer_id=consumer_id)
acc_tokens_list = set([x.id for x in acc_tokens])
for token_id in acc_tokens_list:
token_ref = self._get_access_token(session, token_id)
session.delete(token_ref)
def delete_consumer(self, consumer_id):
session = sql.get_session()
with session.begin():
self._delete_request_tokens(session, consumer_id)
self._delete_access_tokens(session, consumer_id)
self._delete_consumer(session, consumer_id)
def list_consumers(self):
session = sql.get_session()
cons = session.query(Consumer)
return [core.filter_consumer(x.to_dict()) for x in cons]
def update_consumer(self, consumer_id, consumer):
session = sql.get_session()
with session.begin():
consumer_ref = self._get_consumer(session, consumer_id)
old_consumer_dict = consumer_ref.to_dict()
old_consumer_dict.update(consumer)
new_consumer = Consumer.from_dict(old_consumer_dict)
consumer_ref.description = new_consumer.description
consumer_ref.extra = new_consumer.extra
return core.filter_consumer(consumer_ref.to_dict())
def create_request_token(self, consumer_id, project_id, token_duration,
request_token_id=None, request_token_secret=None):
if request_token_id is None:
request_token_id = uuid.uuid4().hex
if request_token_secret is None:
request_token_secret = uuid.uuid4().hex
expiry_date = None
if token_duration:
now = timeutils.utcnow()
future = now + datetime.timedelta(seconds=token_duration)
expiry_date = utils.isotime(future, subsecond=True)
ref = {}
ref['id'] = request_token_id
ref['request_secret'] = request_token_secret
ref['verifier'] = None
ref['authorizing_user_id'] = None
ref['requested_project_id'] = project_id
ref['role_ids'] = None
ref['consumer_id'] = consumer_id
ref['expires_at'] = expiry_date
session = sql.get_session()
with session.begin():
token_ref = RequestToken.from_dict(ref)
session.add(token_ref)
return token_ref.to_dict()
def _get_request_token(self, session, request_token_id):
token_ref = session.query(RequestToken).get(request_token_id)
if token_ref is None:
raise exception.NotFound(_('Request token not found'))
return token_ref
def get_request_token(self, request_token_id):
session = sql.get_session()
token_ref = self._get_request_token(session, request_token_id)
return token_ref.to_dict()
def authorize_request_token(self, request_token_id, user_id,
role_ids):
session = sql.get_session()
with session.begin():
token_ref = self._get_request_token(session, request_token_id)
token_dict = token_ref.to_dict()
token_dict['authorizing_user_id'] = user_id
token_dict['verifier'] = ''.join(random.sample(core.VERIFIER_CHARS,
8))
token_dict['role_ids'] = jsonutils.dumps(role_ids)
new_token = RequestToken.from_dict(token_dict)
for attr in RequestToken.attributes:
if (attr == 'authorizing_user_id' or attr == 'verifier'
or attr == 'role_ids'):
setattr(token_ref, attr, getattr(new_token, attr))
return token_ref.to_dict()
def create_access_token(self, request_token_id, token_duration,
access_token_id=None, access_token_secret=None):
if access_token_id is None:
access_token_id = uuid.uuid4().hex
if access_token_secret is None:
access_token_secret = uuid.uuid4().hex
session = sql.get_session()
with session.begin():
req_token_ref = self._get_request_token(session, request_token_id)
token_dict = req_token_ref.to_dict()
expiry_date = None
if token_duration:
now = timeutils.utcnow()
future = now + datetime.timedelta(seconds=token_duration)
expiry_date = utils.isotime(future, subsecond=True)
# add Access Token
ref = {}
ref['id'] = access_token_id
ref['access_secret'] = access_token_secret
ref['authorizing_user_id'] = token_dict['authorizing_user_id']
ref['project_id'] = token_dict['requested_project_id']
ref['role_ids'] = token_dict['role_ids']
ref['consumer_id'] = token_dict['consumer_id']
ref['expires_at'] = expiry_date
token_ref = AccessToken.from_dict(ref)
session.add(token_ref)
# remove request token, it's been used
session.delete(req_token_ref)
return token_ref.to_dict()
def _get_access_token(self, session, access_token_id):
token_ref = session.query(AccessToken).get(access_token_id)
if token_ref is None:
raise exception.NotFound(_('Access token not found'))
return token_ref
def get_access_token(self, access_token_id):
session = sql.get_session()
token_ref = self._get_access_token(session, access_token_id)
return token_ref.to_dict()
def list_access_tokens(self, user_id):
session = sql.get_session()
q = session.query(AccessToken)
user_auths = q.filter_by(authorizing_user_id=user_id)
return [core.filter_token(x.to_dict()) for x in user_auths]
def delete_access_token(self, user_id, access_token_id):
session = sql.get_session()
with session.begin():
token_ref = self._get_access_token(session, access_token_id)
token_dict = token_ref.to_dict()
if token_dict['authorizing_user_id'] != user_id:
raise exception.Unauthorized(_('User IDs do not match'))
session.delete(token_ref)

View File

@ -12,143 +12,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools from oslo_log import log
from oslo_log import versionutils
from keystone.common import json_home
from keystone.common import wsgi from keystone.common import wsgi
from keystone.contrib.oauth1 import controllers from keystone.i18n import _
build_resource_relation = functools.partial( LOG = log.getLogger(__name__)
json_home.build_v3_extension_resource_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
build_parameter_relation = functools.partial(
json_home.build_v3_extension_parameter_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
ACCESS_TOKEN_ID_PARAMETER_RELATION = build_parameter_relation(
parameter_name='access_token_id')
class OAuth1Extension(wsgi.V3ExtensionRouter): class OAuth1Extension(wsgi.Middleware):
"""API Endpoints for the OAuth1 extension.
The goal of this extension is to allow third-party service providers def __init__(self, *args, **kwargs):
to acquire tokens with a limited subset of a user's roles for acting super(OAuth1Extension, self).__init__(*args, **kwargs)
on behalf of that user. This is done using an oauth-similar flow and msg = _("Remove oauth1_extension from the paste pipeline, the "
api. "oauth1 extension is now always available. Update the "
"[pipeline:api_v3] section in keystone-paste.ini accordingly, "
The API looks like:: "as it will be removed in the O release.")
versionutils.report_deprecated_feature(LOG, msg)
# Basic admin-only consumer crud
POST /OS-OAUTH1/consumers
GET /OS-OAUTH1/consumers
PATCH /OS-OAUTH1/consumers/{consumer_id}
GET /OS-OAUTH1/consumers/{consumer_id}
DELETE /OS-OAUTH1/consumers/{consumer_id}
# User access token crud
GET /users/{user_id}/OS-OAUTH1/access_tokens
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles
GET /users/{user_id}/OS-OAUTH1/access_tokens
/{access_token_id}/roles/{role_id}
DELETE /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
# OAuth interfaces
POST /OS-OAUTH1/request_token # create a request token
PUT /OS-OAUTH1/authorize # authorize a request token
POST /OS-OAUTH1/access_token # create an access token
"""
def add_routes(self, mapper):
consumer_controller = controllers.ConsumerCrudV3()
access_token_controller = controllers.AccessTokenCrudV3()
access_token_roles_controller = controllers.AccessTokenRolesV3()
oauth_controller = controllers.OAuthControllerV3()
# basic admin-only consumer crud
self._add_resource(
mapper, consumer_controller,
path='/OS-OAUTH1/consumers',
get_action='list_consumers',
post_action='create_consumer',
rel=build_resource_relation(resource_name='consumers'))
self._add_resource(
mapper, consumer_controller,
path='/OS-OAUTH1/consumers/{consumer_id}',
get_action='get_consumer',
patch_action='update_consumer',
delete_action='delete_consumer',
rel=build_resource_relation(resource_name='consumer'),
path_vars={
'consumer_id':
build_parameter_relation(parameter_name='consumer_id'),
})
# user access token crud
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens',
get_action='list_access_tokens',
rel=build_resource_relation(resource_name='user_access_tokens'),
path_vars={
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
get_action='get_access_token',
delete_action='delete_access_token',
rel=build_resource_relation(resource_name='user_access_token'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles',
get_action='list_access_token_roles',
rel=build_resource_relation(
resource_name='user_access_token_roles'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles/{role_id}',
get_action='get_access_token_role',
rel=build_resource_relation(
resource_name='user_access_token_role'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'role_id': json_home.Parameters.ROLE_ID,
'user_id': json_home.Parameters.USER_ID,
})
# oauth flow calls
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/request_token',
post_action='create_request_token',
rel=build_resource_relation(resource_name='request_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/access_token',
post_action='create_access_token',
rel=build_resource_relation(resource_name='access_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/authorize/{request_token_id}',
path_vars={
'request_token_id':
build_parameter_relation(parameter_name='request_token_id')
},
put_action='authorize_request_token',
rel=build_resource_relation(
resource_name='authorize_request_token'))

View File

@ -0,0 +1,16 @@
# 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.
from keystone.oauth1.core import * # noqa
from keystone.oauth1 import routers # noqa

View File

View File

@ -0,0 +1,272 @@
# 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.
import datetime
import random as _random
import uuid
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from keystone.common import sql
from keystone.common import utils
from keystone import exception
from keystone.i18n import _
from keystone.oauth1 import core
random = _random.SystemRandom()
class Consumer(sql.ModelBase, sql.DictBase):
__tablename__ = 'consumer'
attributes = ['id', 'description', 'secret']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
description = sql.Column(sql.String(64), nullable=True)
secret = sql.Column(sql.String(64), nullable=False)
extra = sql.Column(sql.JsonBlob(), nullable=False)
class RequestToken(sql.ModelBase, sql.DictBase):
__tablename__ = 'request_token'
attributes = ['id', 'request_secret',
'verifier', 'authorizing_user_id', 'requested_project_id',
'role_ids', 'consumer_id', 'expires_at']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
request_secret = sql.Column(sql.String(64), nullable=False)
verifier = sql.Column(sql.String(64), nullable=True)
authorizing_user_id = sql.Column(sql.String(64), nullable=True)
requested_project_id = sql.Column(sql.String(64), nullable=False)
role_ids = sql.Column(sql.Text(), nullable=True)
consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'),
nullable=False, index=True)
expires_at = sql.Column(sql.String(64), nullable=True)
@classmethod
def from_dict(cls, user_dict):
return cls(**user_dict)
def to_dict(self):
return dict(self.items())
class AccessToken(sql.ModelBase, sql.DictBase):
__tablename__ = 'access_token'
attributes = ['id', 'access_secret', 'authorizing_user_id',
'project_id', 'role_ids', 'consumer_id',
'expires_at']
id = sql.Column(sql.String(64), primary_key=True, nullable=False)
access_secret = sql.Column(sql.String(64), nullable=False)
authorizing_user_id = sql.Column(sql.String(64), nullable=False,
index=True)
project_id = sql.Column(sql.String(64), nullable=False)
role_ids = sql.Column(sql.Text(), nullable=False)
consumer_id = sql.Column(sql.String(64), sql.ForeignKey('consumer.id'),
nullable=False)
expires_at = sql.Column(sql.String(64), nullable=True)
@classmethod
def from_dict(cls, user_dict):
return cls(**user_dict)
def to_dict(self):
return dict(self.items())
class OAuth1(object):
def _get_consumer(self, session, consumer_id):
consumer_ref = session.query(Consumer).get(consumer_id)
if consumer_ref is None:
raise exception.NotFound(_('Consumer not found'))
return consumer_ref
def get_consumer_with_secret(self, consumer_id):
session = sql.get_session()
consumer_ref = self._get_consumer(session, consumer_id)
return consumer_ref.to_dict()
def get_consumer(self, consumer_id):
return core.filter_consumer(
self.get_consumer_with_secret(consumer_id))
def create_consumer(self, consumer):
consumer['secret'] = uuid.uuid4().hex
if not consumer.get('description'):
consumer['description'] = None
session = sql.get_session()
with session.begin():
consumer_ref = Consumer.from_dict(consumer)
session.add(consumer_ref)
return consumer_ref.to_dict()
def _delete_consumer(self, session, consumer_id):
consumer_ref = self._get_consumer(session, consumer_id)
session.delete(consumer_ref)
def _delete_request_tokens(self, session, consumer_id):
q = session.query(RequestToken)
req_tokens = q.filter_by(consumer_id=consumer_id)
req_tokens_list = set([x.id for x in req_tokens])
for token_id in req_tokens_list:
token_ref = self._get_request_token(session, token_id)
session.delete(token_ref)
def _delete_access_tokens(self, session, consumer_id):
q = session.query(AccessToken)
acc_tokens = q.filter_by(consumer_id=consumer_id)
acc_tokens_list = set([x.id for x in acc_tokens])
for token_id in acc_tokens_list:
token_ref = self._get_access_token(session, token_id)
session.delete(token_ref)
def delete_consumer(self, consumer_id):
session = sql.get_session()
with session.begin():
self._delete_request_tokens(session, consumer_id)
self._delete_access_tokens(session, consumer_id)
self._delete_consumer(session, consumer_id)
def list_consumers(self):
session = sql.get_session()
cons = session.query(Consumer)
return [core.filter_consumer(x.to_dict()) for x in cons]
def update_consumer(self, consumer_id, consumer):
session = sql.get_session()
with session.begin():
consumer_ref = self._get_consumer(session, consumer_id)
old_consumer_dict = consumer_ref.to_dict()
old_consumer_dict.update(consumer)
new_consumer = Consumer.from_dict(old_consumer_dict)
consumer_ref.description = new_consumer.description
consumer_ref.extra = new_consumer.extra
return core.filter_consumer(consumer_ref.to_dict())
def create_request_token(self, consumer_id, project_id, token_duration,
request_token_id=None, request_token_secret=None):
if request_token_id is None:
request_token_id = uuid.uuid4().hex
if request_token_secret is None:
request_token_secret = uuid.uuid4().hex
expiry_date = None
if token_duration:
now = timeutils.utcnow()
future = now + datetime.timedelta(seconds=token_duration)
expiry_date = utils.isotime(future, subsecond=True)
ref = {}
ref['id'] = request_token_id
ref['request_secret'] = request_token_secret
ref['verifier'] = None
ref['authorizing_user_id'] = None
ref['requested_project_id'] = project_id
ref['role_ids'] = None
ref['consumer_id'] = consumer_id
ref['expires_at'] = expiry_date
session = sql.get_session()
with session.begin():
token_ref = RequestToken.from_dict(ref)
session.add(token_ref)
return token_ref.to_dict()
def _get_request_token(self, session, request_token_id):
token_ref = session.query(RequestToken).get(request_token_id)
if token_ref is None:
raise exception.NotFound(_('Request token not found'))
return token_ref
def get_request_token(self, request_token_id):
session = sql.get_session()
token_ref = self._get_request_token(session, request_token_id)
return token_ref.to_dict()
def authorize_request_token(self, request_token_id, user_id,
role_ids):
session = sql.get_session()
with session.begin():
token_ref = self._get_request_token(session, request_token_id)
token_dict = token_ref.to_dict()
token_dict['authorizing_user_id'] = user_id
token_dict['verifier'] = ''.join(random.sample(core.VERIFIER_CHARS,
8))
token_dict['role_ids'] = jsonutils.dumps(role_ids)
new_token = RequestToken.from_dict(token_dict)
for attr in RequestToken.attributes:
if (attr == 'authorizing_user_id' or attr == 'verifier'
or attr == 'role_ids'):
setattr(token_ref, attr, getattr(new_token, attr))
return token_ref.to_dict()
def create_access_token(self, request_token_id, token_duration,
access_token_id=None, access_token_secret=None):
if access_token_id is None:
access_token_id = uuid.uuid4().hex
if access_token_secret is None:
access_token_secret = uuid.uuid4().hex
session = sql.get_session()
with session.begin():
req_token_ref = self._get_request_token(session, request_token_id)
token_dict = req_token_ref.to_dict()
expiry_date = None
if token_duration:
now = timeutils.utcnow()
future = now + datetime.timedelta(seconds=token_duration)
expiry_date = utils.isotime(future, subsecond=True)
# add Access Token
ref = {}
ref['id'] = access_token_id
ref['access_secret'] = access_token_secret
ref['authorizing_user_id'] = token_dict['authorizing_user_id']
ref['project_id'] = token_dict['requested_project_id']
ref['role_ids'] = token_dict['role_ids']
ref['consumer_id'] = token_dict['consumer_id']
ref['expires_at'] = expiry_date
token_ref = AccessToken.from_dict(ref)
session.add(token_ref)
# remove request token, it's been used
session.delete(req_token_ref)
return token_ref.to_dict()
def _get_access_token(self, session, access_token_id):
token_ref = session.query(AccessToken).get(access_token_id)
if token_ref is None:
raise exception.NotFound(_('Access token not found'))
return token_ref
def get_access_token(self, access_token_id):
session = sql.get_session()
token_ref = self._get_access_token(session, access_token_id)
return token_ref.to_dict()
def list_access_tokens(self, user_id):
session = sql.get_session()
q = session.query(AccessToken)
user_auths = q.filter_by(authorizing_user_id=user_id)
return [core.filter_token(x.to_dict()) for x in user_auths]
def delete_access_token(self, user_id, access_token_id):
session = sql.get_session()
with session.begin():
token_ref = self._get_access_token(session, access_token_id)
token_dict = token_ref.to_dict()
if token_dict['authorizing_user_id'] != user_id:
raise exception.Unauthorized(_('User IDs do not match'))
session.delete(token_ref)

View File

@ -22,11 +22,11 @@ from keystone.common import controller
from keystone.common import dependency from keystone.common import dependency
from keystone.common import utils from keystone.common import utils
from keystone.common import wsgi from keystone.common import wsgi
from keystone.contrib.oauth1 import core as oauth1
from keystone.contrib.oauth1 import validator
from keystone import exception from keystone import exception
from keystone.i18n import _ from keystone.i18n import _
from keystone import notifications from keystone import notifications
from keystone.oauth1 import core as oauth1
from keystone.oauth1 import validator
CONF = cfg.CONF CONF = cfg.CONF

154
keystone/oauth1/routers.py Normal file
View File

@ -0,0 +1,154 @@
# 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.
import functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.oauth1 import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
build_parameter_relation = functools.partial(
json_home.build_v3_extension_parameter_relation,
extension_name='OS-OAUTH1', extension_version='1.0')
ACCESS_TOKEN_ID_PARAMETER_RELATION = build_parameter_relation(
parameter_name='access_token_id')
class Routers(wsgi.RoutersBase):
"""API Endpoints for the OAuth1 extension.
The goal of this extension is to allow third-party service providers
to acquire tokens with a limited subset of a user's roles for acting
on behalf of that user. This is done using an oauth-similar flow and
api.
The API looks like::
# Basic admin-only consumer crud
POST /OS-OAUTH1/consumers
GET /OS-OAUTH1/consumers
PATCH /OS-OAUTH1/consumers/{consumer_id}
GET /OS-OAUTH1/consumers/{consumer_id}
DELETE /OS-OAUTH1/consumers/{consumer_id}
# User access token crud
GET /users/{user_id}/OS-OAUTH1/access_tokens
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles
GET /users/{user_id}/OS-OAUTH1/access_tokens
/{access_token_id}/roles/{role_id}
DELETE /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}
# OAuth interfaces
POST /OS-OAUTH1/request_token # create a request token
PUT /OS-OAUTH1/authorize # authorize a request token
POST /OS-OAUTH1/access_token # create an access token
"""
def append_v3_routers(self, mapper, routers):
consumer_controller = controllers.ConsumerCrudV3()
access_token_controller = controllers.AccessTokenCrudV3()
access_token_roles_controller = controllers.AccessTokenRolesV3()
oauth_controller = controllers.OAuthControllerV3()
# basic admin-only consumer crud
self._add_resource(
mapper, consumer_controller,
path='/OS-OAUTH1/consumers',
get_action='list_consumers',
post_action='create_consumer',
rel=build_resource_relation(resource_name='consumers'))
self._add_resource(
mapper, consumer_controller,
path='/OS-OAUTH1/consumers/{consumer_id}',
get_action='get_consumer',
patch_action='update_consumer',
delete_action='delete_consumer',
rel=build_resource_relation(resource_name='consumer'),
path_vars={
'consumer_id':
build_parameter_relation(parameter_name='consumer_id'),
})
# user access token crud
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens',
get_action='list_access_tokens',
rel=build_resource_relation(resource_name='user_access_tokens'),
path_vars={
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}',
get_action='get_access_token',
delete_action='delete_access_token',
rel=build_resource_relation(resource_name='user_access_token'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles',
get_action='list_access_token_roles',
rel=build_resource_relation(
resource_name='user_access_token_roles'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'user_id': json_home.Parameters.USER_ID,
})
self._add_resource(
mapper, access_token_roles_controller,
path='/users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/'
'roles/{role_id}',
get_action='get_access_token_role',
rel=build_resource_relation(
resource_name='user_access_token_role'),
path_vars={
'access_token_id': ACCESS_TOKEN_ID_PARAMETER_RELATION,
'role_id': json_home.Parameters.ROLE_ID,
'user_id': json_home.Parameters.USER_ID,
})
# oauth flow calls
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/request_token',
post_action='create_request_token',
rel=build_resource_relation(resource_name='request_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/access_token',
post_action='create_access_token',
rel=build_resource_relation(resource_name='access_tokens'))
self._add_resource(
mapper, oauth_controller,
path='/OS-OAUTH1/authorize/{request_token_id}',
path_vars={
'request_token_id':
build_parameter_relation(parameter_name='request_token_id')
},
put_action='authorize_request_token',
rel=build_resource_relation(
resource_name='authorize_request_token'))

View File

@ -18,8 +18,8 @@ from oslo_log import log
import six import six
from keystone.common import dependency from keystone.common import dependency
from keystone.contrib.oauth1 import core as oauth1
from keystone import exception from keystone import exception
from keystone.oauth1 import core as oauth1
METHOD_NAME = 'oauth_validator' METHOD_NAME = 'oauth_validator'

View File

@ -15,12 +15,12 @@ from keystone import auth
from keystone import catalog from keystone import catalog
from keystone.common import cache from keystone.common import cache
from keystone.contrib import endpoint_filter from keystone.contrib import endpoint_filter
from keystone.contrib import oauth1
from keystone.contrib import revoke from keystone.contrib import revoke
from keystone import credential from keystone import credential
from keystone import endpoint_policy from keystone import endpoint_policy
from keystone import federation from keystone import federation
from keystone import identity from keystone import identity
from keystone import oauth1
from keystone import policy from keystone import policy
from keystone import resource from keystone import resource
from keystone import token from keystone import token

View File

@ -15,16 +15,19 @@
import copy import copy
import uuid import uuid
import mock
from oslo_config import cfg from oslo_config import cfg
from oslo_log import versionutils
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from pycadf import cadftaxonomy from pycadf import cadftaxonomy
from six.moves import http_client from six.moves import http_client
from six.moves import urllib from six.moves import urllib
from keystone.contrib import oauth1 from keystone.contrib.oauth1 import routers
from keystone.contrib.oauth1 import controllers
from keystone.contrib.oauth1 import core
from keystone import exception from keystone import exception
from keystone import oauth1
from keystone.oauth1 import controllers
from keystone.oauth1 import core
from keystone.tests import unit from keystone.tests import unit
from keystone.tests.unit.common import test_notifications from keystone.tests.unit.common import test_notifications
from keystone.tests.unit.ksfixtures import temporaryfile from keystone.tests.unit.ksfixtures import temporaryfile
@ -34,10 +37,17 @@ from keystone.tests.unit import test_v3
CONF = cfg.CONF CONF = cfg.CONF
class OAuth1Tests(test_v3.RestfulTestCase): class OAuth1ContribTests(test_v3.RestfulTestCase):
EXTENSION_NAME = 'oauth1' @mock.patch.object(versionutils, 'report_deprecated_feature')
EXTENSION_TO_ADD = 'oauth1_extension' def test_exception_happens(self, mock_deprecator):
routers.OAuth1Extension(mock.ANY)
mock_deprecator.assert_called_once_with(mock.ANY, mock.ANY)
args, _kwargs = mock_deprecator.call_args
self.assertIn("Remove oauth1_extension from", args[1])
class OAuth1Tests(test_v3.RestfulTestCase):
CONSUMER_URL = '/OS-OAUTH1/consumers' CONSUMER_URL = '/OS-OAUTH1/consumers'

View File

@ -29,6 +29,7 @@ from keystone import endpoint_policy
from keystone import federation from keystone import federation
from keystone.i18n import _LW from keystone.i18n import _LW
from keystone import identity from keystone import identity
from keystone import oauth1
from keystone import policy from keystone import policy
from keystone import resource from keystone import resource
from keystone import token from keystone import token
@ -131,7 +132,8 @@ def v3_app_factory(global_conf, **local_conf):
identity, identity,
policy, policy,
resource, resource,
federation] federation,
oauth1]
if CONF.trust.enabled: if CONF.trust.enabled:
router_modules.append(trust) router_modules.append(trust)

View File

@ -165,7 +165,7 @@ keystone.federation =
sql = keystone.federation.backends.sql:Federation sql = keystone.federation.backends.sql:Federation
keystone.oauth1 = keystone.oauth1 =
sql = keystone.contrib.oauth1.backends.sql:OAuth1 sql = keystone.oauth1.backends.sql:OAuth1
keystone.revoke = keystone.revoke =
kvs = keystone.contrib.revoke.backends.kvs:Revoke kvs = keystone.contrib.revoke.backends.kvs:Revoke