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:
parent
9f3abc6983
commit
78e256273a
|
@ -30,9 +30,6 @@ use = egg:keystone#ec2_extension
|
|||
[filter:ec2_extension_v3]
|
||||
use = egg:keystone#ec2_extension_v3
|
||||
|
||||
[filter:oauth1_extension]
|
||||
use = egg:keystone#oauth1_extension
|
||||
|
||||
[filter: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]
|
||||
# The last item in this pipeline must be service_v3 or an equivalent
|
||||
# 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]
|
||||
use = egg:keystone#public_version_service
|
||||
|
|
|
@ -18,10 +18,10 @@ from oslo_utils import timeutils
|
|||
from keystone import auth
|
||||
from keystone.common import controller
|
||||
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.i18n import _
|
||||
from keystone.oauth1 import core as oauth
|
||||
from keystone.oauth1 import validator
|
||||
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
|
|
@ -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
|
|
@ -12,261 +12,19 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import random as _random
|
||||
import uuid
|
||||
from oslo_log import versionutils
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
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 _
|
||||
from keystone.oauth1.backends import sql
|
||||
|
||||
|
||||
random = _random.SystemRandom()
|
||||
_OLD = "keystone.contrib.oauth1.backends.sql.OAuth1"
|
||||
_NEW = "sql"
|
||||
|
||||
|
||||
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 OAuth1(sql.OAuth1):
|
||||
|
||||
|
||||
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)
|
||||
@versionutils.deprecated(versionutils.deprecated.MITAKA,
|
||||
in_favor_of=_NEW,
|
||||
what=_OLD)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OAuth1, self).__init__(*args, **kwargs)
|
||||
|
|
|
@ -12,143 +12,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# 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.contrib.oauth1 import controllers
|
||||
from keystone.i18n import _
|
||||
|
||||
|
||||
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')
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class OAuth1Extension(wsgi.V3ExtensionRouter):
|
||||
"""API Endpoints for the OAuth1 extension.
|
||||
class OAuth1Extension(wsgi.Middleware):
|
||||
|
||||
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 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'))
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OAuth1Extension, self).__init__(*args, **kwargs)
|
||||
msg = _("Remove oauth1_extension from the paste pipeline, the "
|
||||
"oauth1 extension is now always available. Update the "
|
||||
"[pipeline:api_v3] section in keystone-paste.ini accordingly, "
|
||||
"as it will be removed in the O release.")
|
||||
versionutils.report_deprecated_feature(LOG, msg)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
|
@ -22,11 +22,11 @@ from keystone.common import controller
|
|||
from keystone.common import dependency
|
||||
from keystone.common import utils
|
||||
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.i18n import _
|
||||
from keystone import notifications
|
||||
from keystone.oauth1 import core as oauth1
|
||||
from keystone.oauth1 import validator
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
|
@ -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'))
|
|
@ -18,8 +18,8 @@ from oslo_log import log
|
|||
import six
|
||||
|
||||
from keystone.common import dependency
|
||||
from keystone.contrib.oauth1 import core as oauth1
|
||||
from keystone import exception
|
||||
from keystone.oauth1 import core as oauth1
|
||||
|
||||
|
||||
METHOD_NAME = 'oauth_validator'
|
|
@ -15,12 +15,12 @@ from keystone import auth
|
|||
from keystone import catalog
|
||||
from keystone.common import cache
|
||||
from keystone.contrib import endpoint_filter
|
||||
from keystone.contrib import oauth1
|
||||
from keystone.contrib import revoke
|
||||
from keystone import credential
|
||||
from keystone import endpoint_policy
|
||||
from keystone import federation
|
||||
from keystone import identity
|
||||
from keystone import oauth1
|
||||
from keystone import policy
|
||||
from keystone import resource
|
||||
from keystone import token
|
||||
|
|
|
@ -15,16 +15,19 @@
|
|||
import copy
|
||||
import uuid
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_log import versionutils
|
||||
from oslo_serialization import jsonutils
|
||||
from pycadf import cadftaxonomy
|
||||
from six.moves import http_client
|
||||
from six.moves import urllib
|
||||
|
||||
from keystone.contrib import oauth1
|
||||
from keystone.contrib.oauth1 import controllers
|
||||
from keystone.contrib.oauth1 import core
|
||||
from keystone.contrib.oauth1 import routers
|
||||
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.unit.common import test_notifications
|
||||
from keystone.tests.unit.ksfixtures import temporaryfile
|
||||
|
@ -34,10 +37,17 @@ from keystone.tests.unit import test_v3
|
|||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class OAuth1Tests(test_v3.RestfulTestCase):
|
||||
class OAuth1ContribTests(test_v3.RestfulTestCase):
|
||||
|
||||
EXTENSION_NAME = 'oauth1'
|
||||
EXTENSION_TO_ADD = 'oauth1_extension'
|
||||
@mock.patch.object(versionutils, 'report_deprecated_feature')
|
||||
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'
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ from keystone import endpoint_policy
|
|||
from keystone import federation
|
||||
from keystone.i18n import _LW
|
||||
from keystone import identity
|
||||
from keystone import oauth1
|
||||
from keystone import policy
|
||||
from keystone import resource
|
||||
from keystone import token
|
||||
|
@ -131,7 +132,8 @@ def v3_app_factory(global_conf, **local_conf):
|
|||
identity,
|
||||
policy,
|
||||
resource,
|
||||
federation]
|
||||
federation,
|
||||
oauth1]
|
||||
|
||||
if CONF.trust.enabled:
|
||||
router_modules.append(trust)
|
||||
|
|
Loading…
Reference in New Issue