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
changes/98/234598/18
Steve Martinelli 7 years ago committed by Dave Chen
parent 9f3abc6983
commit 78e256273a
  1. 5
      etc/keystone-paste.ini
  2. 4
      keystone/auth/plugins/oauth1.py
  3. 15
      keystone/contrib/oauth1/__init__.py
  4. 262
      keystone/contrib/oauth1/backends/sql.py
  5. 145
      keystone/contrib/oauth1/routers.py
  6. 16
      keystone/oauth1/__init__.py
  7. 0
      keystone/oauth1/backends/__init__.py
  8. 272
      keystone/oauth1/backends/sql.py
  9. 4
      keystone/oauth1/controllers.py
  10. 0
      keystone/oauth1/core.py
  11. 154
      keystone/oauth1/routers.py
  12. 2
      keystone/oauth1/validator.py
  13. 2
      keystone/server/backends.py
  14. 22
      keystone/tests/unit/test_v3_oauth1.py
  15. 4
      keystone/version/service.py
  16. 2
      setup.cfg

@ -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.oauth1.backends import sql
from keystone.common import sql
from keystone.common import utils
from keystone.contrib.oauth1 import core
from keystone import exception
from keystone.i18n import _
_OLD = "keystone.contrib.oauth1.backends.sql.OAuth1"
_NEW = "sql"
random = _random.SystemRandom()
class OAuth1(sql.OAuth1):
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)
@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')
LOG = log.getLogger(__name__)
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.Middleware):
class OAuth1Extension(wsgi.V3ExtensionRouter):
"""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 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):
@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])
EXTENSION_NAME = 'oauth1'
EXTENSION_TO_ADD = 'oauth1_extension'
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)

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

Loading…
Cancel
Save