Migrating ec2 credentials to credential.
Merging ec2 credentials into the credentials table to simplify management of ec2 credentials. blueprint migrate-ec2-credentials Change-Id: I8f83c007a44857ca41d7ef23f70cb9718d83ca5d
This commit is contained in:
		@@ -14,6 +14,7 @@
 | 
			
		||||
# License for the specific language governing permissions and limitations
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
import sqlalchemy
 | 
			
		||||
@@ -21,8 +22,9 @@ from sqlalchemy import exc
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from keystone.assignment.backends import sql as assignment_sql
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.contrib.ec2.backends import sql as ec2_sql
 | 
			
		||||
from keystone.credential.backends import sql as ec2_sql
 | 
			
		||||
from keystone.identity.backends import sql as identity_sql
 | 
			
		||||
from keystone.openstack.common import log as logging
 | 
			
		||||
 | 
			
		||||
@@ -67,7 +69,7 @@ class LegacyMigration(object):
 | 
			
		||||
        self.identity_driver.db_sync()
 | 
			
		||||
        self.assignment_driver.db_sync()
 | 
			
		||||
 | 
			
		||||
        self.ec2_driver = ec2_sql.Ec2()
 | 
			
		||||
        self.ec2_driver = ec2_sql.Credential()
 | 
			
		||||
        self._data = {}
 | 
			
		||||
        self._user_map = {}
 | 
			
		||||
        self._project_map = {}
 | 
			
		||||
@@ -178,11 +180,18 @@ class LegacyMigration(object):
 | 
			
		||||
 | 
			
		||||
    def _migrate_ec2(self):
 | 
			
		||||
        for x in self._data['credentials']:
 | 
			
		||||
            new_dict = {'user_id': x['user_id'],
 | 
			
		||||
                        'tenant_id': x['tenant_id'],
 | 
			
		||||
            blob = {
 | 
			
		||||
                'access': x['key'],
 | 
			
		||||
                        'secret': x['secret']}
 | 
			
		||||
                'secret': x['secret']
 | 
			
		||||
            }
 | 
			
		||||
            credential_id = utils.hash_access_key(blob['access'])
 | 
			
		||||
            new_dict = {'user_id': x['user_id'],
 | 
			
		||||
                        'blob': json.dumps(blob),
 | 
			
		||||
                        'project_id': x['tenant_id'],
 | 
			
		||||
                        'id': credential_id,
 | 
			
		||||
                        'type': 'ec2'}
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                self.ec2_driver.create_credential(None, new_dict)
 | 
			
		||||
                self.ec2_driver.create_credential(credential_id, new_dict)
 | 
			
		||||
            except exc.IntegrityError:
 | 
			
		||||
                LOG.exception(_('Cannot migrate EC2 credential: %s') % x)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,111 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 OpenStack LLC
 | 
			
		||||
#
 | 
			
		||||
# 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 sqlalchemy as sql
 | 
			
		||||
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone import exception
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade(migrate_engine):
 | 
			
		||||
    meta = sql.MetaData()
 | 
			
		||||
    meta.bind = migrate_engine
 | 
			
		||||
 | 
			
		||||
    credential_table = sql.Table('credential',
 | 
			
		||||
                                 meta,
 | 
			
		||||
                                 autoload=True)
 | 
			
		||||
 | 
			
		||||
    ec2_cred_table = sql.Table('ec2_credential',
 | 
			
		||||
                               meta,
 | 
			
		||||
                               autoload=True)
 | 
			
		||||
 | 
			
		||||
    session = sql.orm.sessionmaker(bind=migrate_engine)()
 | 
			
		||||
    insert = credential_table.insert()
 | 
			
		||||
    for ec2credential in session.query(ec2_cred_table):
 | 
			
		||||
        cred_exist = check_credential_exists(ec2credential,
 | 
			
		||||
                                             credential_table, session)
 | 
			
		||||
 | 
			
		||||
        if not cred_exist:
 | 
			
		||||
            credential = utils.convert_ec2_to_v3_credential(ec2credential)
 | 
			
		||||
            insert.execute(credential)
 | 
			
		||||
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.close()
 | 
			
		||||
 | 
			
		||||
    ec2_cred_table.drop()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def check_credential_exists(ec2credential, credential_table, session):
 | 
			
		||||
    credential = session.query(credential_table).filter_by(
 | 
			
		||||
        id=utils.hash_access_key(ec2credential.access)).first()
 | 
			
		||||
    if credential is None:
 | 
			
		||||
        return False
 | 
			
		||||
    blob = utils.get_blob_from_credential(credential)
 | 
			
		||||
    # check if credential with same access key but different
 | 
			
		||||
    # secret key already exists in credential table.
 | 
			
		||||
    # If exists raise an exception
 | 
			
		||||
    if blob['secret'] != ec2credential.secret:
 | 
			
		||||
        msg = _('Credential %(access)s already exists with different secret'
 | 
			
		||||
                ' in %(table)s table')
 | 
			
		||||
        message = msg % {'access': ec2credential.access,
 | 
			
		||||
                         'table': credential_table.name}
 | 
			
		||||
        raise exception.Conflict(type='credential', details=message)
 | 
			
		||||
    # check if credential with same access and secret key but
 | 
			
		||||
    # associated with a different project exists. If exists raise
 | 
			
		||||
    # an exception
 | 
			
		||||
    elif credential.project_id is not None and (
 | 
			
		||||
            credential.project_id != ec2credential.tenant_id):
 | 
			
		||||
        msg = _('Credential %(access)s already exists with different project'
 | 
			
		||||
                ' in %(table)s table')
 | 
			
		||||
        message = msg % {'access': ec2credential.access,
 | 
			
		||||
                         'table': credential_table.name}
 | 
			
		||||
        raise exception.Conflict(type='credential', details=message)
 | 
			
		||||
    # if credential with same access and secret key and not associated
 | 
			
		||||
    # with any projects already exists in the credential table, then
 | 
			
		||||
    # return true.
 | 
			
		||||
    else:
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade(migrate_engine):
 | 
			
		||||
    meta = sql.MetaData()
 | 
			
		||||
    meta.bind = migrate_engine
 | 
			
		||||
 | 
			
		||||
    session = sql.orm.sessionmaker(bind=migrate_engine)()
 | 
			
		||||
 | 
			
		||||
    ec2_credential_table = sql.Table(
 | 
			
		||||
        'ec2_credential',
 | 
			
		||||
        meta,
 | 
			
		||||
        sql.Column('access', sql.String(64), primary_key=True),
 | 
			
		||||
        sql.Column('secret', sql.String(64)),
 | 
			
		||||
        sql.Column('user_id', sql.String(64)),
 | 
			
		||||
        sql.Column('tenant_id', sql.String(64)),
 | 
			
		||||
        mysql_engine='InnoDB',
 | 
			
		||||
        mysql_charset='utf8')
 | 
			
		||||
 | 
			
		||||
    ec2_credential_table.create(migrate_engine, checkfirst=True)
 | 
			
		||||
    credential_table = sql.Table('credential',
 | 
			
		||||
                                 meta,
 | 
			
		||||
                                 autoload=True)
 | 
			
		||||
    insert = ec2_credential_table.insert()
 | 
			
		||||
    for credential in session.query(credential_table).filter(
 | 
			
		||||
            sql.and_(credential_table.c.type == 'ec2',
 | 
			
		||||
                     credential_table.c.project_id is not None)).all():
 | 
			
		||||
        ec2_credential = utils.convert_v3_to_ec2_credential(credential)
 | 
			
		||||
        insert.execute(ec2_credential)
 | 
			
		||||
 | 
			
		||||
    session.commit()
 | 
			
		||||
    session.close()
 | 
			
		||||
@@ -16,11 +16,13 @@
 | 
			
		||||
 | 
			
		||||
"""Export data from Nova database and import through Identity Service."""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from keystone import assignment
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.contrib.ec2.backends import sql as ec2_sql
 | 
			
		||||
from keystone.credential.backends import sql as ec2_sql
 | 
			
		||||
from keystone import identity
 | 
			
		||||
from keystone.openstack.common import log as logging
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +44,7 @@ def import_auth(data):
 | 
			
		||||
    _assign_roles(assignment_api, data['role_user_tenant_list'],
 | 
			
		||||
                  role_map, user_map, tenant_map)
 | 
			
		||||
 | 
			
		||||
    ec2_api = ec2_sql.Ec2()
 | 
			
		||||
    ec2_api = ec2_sql.Credential()
 | 
			
		||||
    ec2_creds = data['ec2_credentials']
 | 
			
		||||
    _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map)
 | 
			
		||||
 | 
			
		||||
@@ -127,14 +129,20 @@ def _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map):
 | 
			
		||||
    for ec2_cred in ec2_creds:
 | 
			
		||||
        user_id = user_map[ec2_cred['user_id']]
 | 
			
		||||
        for tenant_id in assignment_api.get_projects_for_user(user_id):
 | 
			
		||||
            cred_dict = {
 | 
			
		||||
            blob = {
 | 
			
		||||
                'access': '%s:%s' % (tenant_id, ec2_cred['access_key']),
 | 
			
		||||
                'secret': ec2_cred['secret_key'],
 | 
			
		||||
            }
 | 
			
		||||
            credential_id = utils.hash_access_key(blob['access'])
 | 
			
		||||
            cred_dict = {
 | 
			
		||||
                'user_id': user_id,
 | 
			
		||||
                'tenant_id': tenant_id,
 | 
			
		||||
                'blob': json.dumps(blob),
 | 
			
		||||
                'project_id': tenant_id,
 | 
			
		||||
                'id': credential_id,
 | 
			
		||||
                'type': 'ec2',
 | 
			
		||||
            }
 | 
			
		||||
            LOG.debug(_(
 | 
			
		||||
                'Creating ec2 cred for user %(user_id)s and tenant '
 | 
			
		||||
                '%(tenant_id)s') % {
 | 
			
		||||
                    'user_id': user_id, 'tenant_id': tenant_id})
 | 
			
		||||
            ec2_api.create_credential(None, cred_dict)
 | 
			
		||||
            ec2_api.create_credential(credential_id, cred_dict)
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,12 @@ def trunc_password(password):
 | 
			
		||||
        raise exception.ValidationError(attribute='string', target='password')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def hash_access_key(access):
 | 
			
		||||
    hash_ = hashlib.sha256()
 | 
			
		||||
    hash_.update(access)
 | 
			
		||||
    return hash_.hexdigest()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def hash_user_password(user):
 | 
			
		||||
    """Hash a user dict's password without modifying the passed-in dict."""
 | 
			
		||||
    try:
 | 
			
		||||
@@ -170,6 +176,38 @@ def check_output(*popenargs, **kwargs):
 | 
			
		||||
    return output
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_blob_from_credential(credential):
 | 
			
		||||
    try:
 | 
			
		||||
        blob = json.loads(credential.blob)
 | 
			
		||||
    except (ValueError, TypeError):
 | 
			
		||||
        raise exception.ValidationError(
 | 
			
		||||
            message=_('Invalid blob in credential'))
 | 
			
		||||
    if not blob or not isinstance(blob, dict):
 | 
			
		||||
        raise exception.ValidationError(attribute='blob',
 | 
			
		||||
                                        target='credential')
 | 
			
		||||
    return blob
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def convert_ec2_to_v3_credential(ec2credential):
 | 
			
		||||
    blob = {'access': ec2credential.access,
 | 
			
		||||
            'secret': ec2credential.secret}
 | 
			
		||||
    return {'id': hash_access_key(ec2credential.access),
 | 
			
		||||
            'user_id': ec2credential.user_id,
 | 
			
		||||
            'project_id': ec2credential.tenant_id,
 | 
			
		||||
            'blob': json.dumps(blob),
 | 
			
		||||
            'type': 'ec2',
 | 
			
		||||
            'extra': json.dumps({})}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def convert_v3_to_ec2_credential(credential):
 | 
			
		||||
    blob = get_blob_from_credential(credential)
 | 
			
		||||
    return {'access': blob.get('access'),
 | 
			
		||||
            'secret': blob.get('secret'),
 | 
			
		||||
            'user_id': credential.user_id,
 | 
			
		||||
            'tenant_id': credential.project_id,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def git(*args):
 | 
			
		||||
    return check_output(['git'] + list(args))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,4 +15,6 @@
 | 
			
		||||
# License for the specific language governing permissions and limitations
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
from keystone.contrib.ec2 import controllers
 | 
			
		||||
from keystone.contrib.ec2.core import *
 | 
			
		||||
from keystone.contrib.ec2.routers import Ec2Extension
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2012 OpenStack LLC
 | 
			
		||||
#
 | 
			
		||||
# 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.common import kvs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ec2(kvs.Base):
 | 
			
		||||
    # Public interface
 | 
			
		||||
    def get_credential(self, credential_id):
 | 
			
		||||
        credential_ref = self.db.get('credential-%s' % credential_id)
 | 
			
		||||
        return credential_ref
 | 
			
		||||
 | 
			
		||||
    def list_credentials(self, user_id):
 | 
			
		||||
        credential_ids = self.db.get('credential_list', [])
 | 
			
		||||
        rv = [self.get_credential(x) for x in credential_ids]
 | 
			
		||||
        return [x for x in rv if x['user_id'] == user_id]
 | 
			
		||||
 | 
			
		||||
    # CRUD
 | 
			
		||||
    def create_credential(self, credential_id, credential):
 | 
			
		||||
        self.db.set('credential-%s' % credential_id, credential)
 | 
			
		||||
        credential_list = set(self.db.get('credential_list', []))
 | 
			
		||||
        credential_list.add(credential_id)
 | 
			
		||||
        self.db.set('credential_list', list(credential_list))
 | 
			
		||||
        return credential
 | 
			
		||||
 | 
			
		||||
    def delete_credential(self, credential_id):
 | 
			
		||||
        # This will ensure credential-%s is here before deleting
 | 
			
		||||
        self.db.get('credential-%s' % credential_id)
 | 
			
		||||
        self.db.delete('credential-%s' % credential_id)
 | 
			
		||||
        credential_list = set(self.db.get('credential_list', []))
 | 
			
		||||
        credential_list.remove(credential_id)
 | 
			
		||||
        self.db.set('credential_list', list(credential_list))
 | 
			
		||||
        return None
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2012 OpenStack LLC
 | 
			
		||||
#
 | 
			
		||||
# 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.common import sql
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ec2Credential(sql.ModelBase, sql.DictBase):
 | 
			
		||||
    __tablename__ = 'ec2_credential'
 | 
			
		||||
    access = sql.Column(sql.String(64), primary_key=True)
 | 
			
		||||
    secret = sql.Column(sql.String(64))
 | 
			
		||||
    user_id = sql.Column(sql.String(64))
 | 
			
		||||
    tenant_id = sql.Column(sql.String(64))
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_dict(cls, user_dict):
 | 
			
		||||
        return cls(**user_dict)
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
        return dict(self.iteritems())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ec2(sql.Base):
 | 
			
		||||
    def get_credential(self, credential_id):
 | 
			
		||||
        session = self.get_session()
 | 
			
		||||
        query = session.query(Ec2Credential)
 | 
			
		||||
        query = query.filter_by(access=credential_id)
 | 
			
		||||
        credential_ref = query.first()
 | 
			
		||||
        if not credential_ref:
 | 
			
		||||
            return
 | 
			
		||||
        return credential_ref.to_dict()
 | 
			
		||||
 | 
			
		||||
    def list_credentials(self, user_id):
 | 
			
		||||
        session = self.get_session()
 | 
			
		||||
        query = session.query(Ec2Credential)
 | 
			
		||||
        credential_refs = query.filter_by(user_id=user_id)
 | 
			
		||||
        return [x.to_dict() for x in credential_refs]
 | 
			
		||||
 | 
			
		||||
    # CRUD
 | 
			
		||||
    def create_credential(self, credential_id, credential):
 | 
			
		||||
        session = self.get_session()
 | 
			
		||||
        with session.begin():
 | 
			
		||||
            credential_ref = Ec2Credential.from_dict(credential)
 | 
			
		||||
            session.add(credential_ref)
 | 
			
		||||
            session.flush()
 | 
			
		||||
        return credential_ref.to_dict()
 | 
			
		||||
 | 
			
		||||
    def delete_credential(self, credential_id):
 | 
			
		||||
        session = self.get_session()
 | 
			
		||||
        query = session.query(Ec2Credential)
 | 
			
		||||
        query = query.filter_by(access=credential_id)
 | 
			
		||||
        credential_ref = query.first()
 | 
			
		||||
        with session.begin():
 | 
			
		||||
            session.delete(credential_ref)
 | 
			
		||||
            session.flush()
 | 
			
		||||
							
								
								
									
										290
									
								
								keystone/contrib/ec2/controllers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								keystone/contrib/ec2/controllers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,290 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 OpenStack LLC
 | 
			
		||||
#
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
"""Main entry point into the EC2 Credentials service.
 | 
			
		||||
 | 
			
		||||
This service allows the creation of access/secret credentials used for
 | 
			
		||||
the ec2 interop layer of OpenStack.
 | 
			
		||||
 | 
			
		||||
A user can create as many access/secret pairs, each of which map to a
 | 
			
		||||
specific project.  This is required because OpenStack supports a user
 | 
			
		||||
belonging to multiple projects, whereas the signatures created on ec2-style
 | 
			
		||||
requests don't allow specification of which project the user wishes to act
 | 
			
		||||
upon.
 | 
			
		||||
 | 
			
		||||
To complete the cycle, we provide a method that OpenStack services can
 | 
			
		||||
use to validate a signature and get a corresponding OpenStack token.  This
 | 
			
		||||
token allows method calls to other services within the context the
 | 
			
		||||
access/secret was created.  As an example, Nova requests Keystone to validate
 | 
			
		||||
the signature of a request, receives a token, and then makes a request to
 | 
			
		||||
Glance to list images needed to perform the requested task.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
 | 
			
		||||
 | 
			
		||||
from keystone.common import controller
 | 
			
		||||
from keystone.common import dependency
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone import exception
 | 
			
		||||
from keystone import token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dependency.requires('catalog_api', 'credential_api', 'token_provider_api')
 | 
			
		||||
class Ec2Controller(controller.V2Controller):
 | 
			
		||||
    def check_signature(self, creds_ref, credentials):
 | 
			
		||||
        signer = ec2_utils.Ec2Signer(creds_ref['secret'])
 | 
			
		||||
        signature = signer.generate(credentials)
 | 
			
		||||
        if utils.auth_str_equal(credentials['signature'], signature):
 | 
			
		||||
            return
 | 
			
		||||
        # NOTE(vish): Some libraries don't use the port when signing
 | 
			
		||||
        #             requests, so try again without port.
 | 
			
		||||
        elif ':' in credentials['signature']:
 | 
			
		||||
            hostname, _port = credentials['host'].split(':')
 | 
			
		||||
            credentials['host'] = hostname
 | 
			
		||||
            signature = signer.generate(credentials)
 | 
			
		||||
            if not utils.auth_str_equal(credentials.signature, signature):
 | 
			
		||||
                raise exception.Unauthorized(message='Invalid EC2 signature.')
 | 
			
		||||
        else:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, context, credentials=None, ec2Credentials=None):
 | 
			
		||||
        """Validate a signed EC2 request and provide a token.
 | 
			
		||||
 | 
			
		||||
        Other services (such as Nova) use this **admin** call to determine
 | 
			
		||||
        if a request they signed received is from a valid user.
 | 
			
		||||
 | 
			
		||||
        If it is a valid signature, an OpenStack token that maps
 | 
			
		||||
        to the user/tenant is returned to the caller, along with
 | 
			
		||||
        all the other details returned from a normal token validation
 | 
			
		||||
        call.
 | 
			
		||||
 | 
			
		||||
        The returned token is useful for making calls to other
 | 
			
		||||
        OpenStack services within the context of the request.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param credentials: dict of ec2 signature
 | 
			
		||||
        :param ec2Credentials: DEPRECATED dict of ec2 signature
 | 
			
		||||
        :returns: token: OpenStack token equivalent to access key along
 | 
			
		||||
                         with the corresponding service catalog and roles
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # FIXME(ja): validate that a service token was used!
 | 
			
		||||
 | 
			
		||||
        # NOTE(termie): backwards compat hack
 | 
			
		||||
        if not credentials and ec2Credentials:
 | 
			
		||||
            credentials = ec2Credentials
 | 
			
		||||
 | 
			
		||||
        if 'access' not in credentials:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 | 
			
		||||
 | 
			
		||||
        creds_ref = self._get_credentials(credentials['access'])
 | 
			
		||||
        self.check_signature(creds_ref, credentials)
 | 
			
		||||
 | 
			
		||||
        # TODO(termie): don't create new tokens every time
 | 
			
		||||
        # TODO(termie): this is copied from TokenController.authenticate
 | 
			
		||||
        token_id = uuid.uuid4().hex
 | 
			
		||||
        tenant_ref = self.identity_api.get_project(creds_ref['tenant_id'])
 | 
			
		||||
        user_ref = self.identity_api.get_user(creds_ref['user_id'])
 | 
			
		||||
        metadata_ref = {}
 | 
			
		||||
        metadata_ref['roles'] = (
 | 
			
		||||
            self.identity_api.get_roles_for_user_and_project(
 | 
			
		||||
                user_ref['id'], tenant_ref['id']))
 | 
			
		||||
 | 
			
		||||
        # Validate that the auth info is valid and nothing is disabled
 | 
			
		||||
        token.validate_auth_info(self, user_ref, tenant_ref)
 | 
			
		||||
 | 
			
		||||
        roles = metadata_ref.get('roles', [])
 | 
			
		||||
        if not roles:
 | 
			
		||||
            raise exception.Unauthorized(message='User not valid for tenant.')
 | 
			
		||||
        roles_ref = [self.identity_api.get_role(role_id)
 | 
			
		||||
                     for role_id in roles]
 | 
			
		||||
 | 
			
		||||
        catalog_ref = self.catalog_api.get_catalog(
 | 
			
		||||
            user_ref['id'], tenant_ref['id'], metadata_ref)
 | 
			
		||||
 | 
			
		||||
        auth_token_data = dict(user=user_ref,
 | 
			
		||||
                               tenant=tenant_ref,
 | 
			
		||||
                               metadata=metadata_ref,
 | 
			
		||||
                               id='placeholder')
 | 
			
		||||
        (token_id, token_data) = self.token_provider_api.issue_v2_token(
 | 
			
		||||
            auth_token_data, roles_ref, catalog_ref)
 | 
			
		||||
        return token_data
 | 
			
		||||
 | 
			
		||||
    def create_credential(self, context, user_id, tenant_id):
 | 
			
		||||
        """Create a secret/access pair for use with ec2 style auth.
 | 
			
		||||
 | 
			
		||||
        Generates a new set of credentials that map the user/tenant
 | 
			
		||||
        pair.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param tenant_id: id of tenant
 | 
			
		||||
        :returns: credential: dict of ec2 credential
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        self._assert_valid_project_id(tenant_id)
 | 
			
		||||
        blob = {'access': uuid.uuid4().hex,
 | 
			
		||||
                'secret': uuid.uuid4().hex}
 | 
			
		||||
        credential_id = utils.hash_access_key(blob['access'])
 | 
			
		||||
        cred_ref = {'user_id': user_id,
 | 
			
		||||
                    'project_id': tenant_id,
 | 
			
		||||
                    'blob': blob,
 | 
			
		||||
                    'id': credential_id,
 | 
			
		||||
                    'type': 'ec2'}
 | 
			
		||||
        self.credential_api.create_credential(credential_id, cred_ref)
 | 
			
		||||
        return {'credential': self._convert_v3_to_ec2_credential(cred_ref)}
 | 
			
		||||
 | 
			
		||||
    def get_credentials(self, context, user_id):
 | 
			
		||||
        """List all credentials for a user.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :returns: credentials: list of ec2 credential dicts
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        credential_refs = self.credential_api.list_credentials(
 | 
			
		||||
            user_id=user_id)
 | 
			
		||||
        return {'credentials':
 | 
			
		||||
                [self._convert_v3_to_ec2_credential(credential)
 | 
			
		||||
                for credential in credential_refs]}
 | 
			
		||||
 | 
			
		||||
    def get_credential(self, context, user_id, credential_id):
 | 
			
		||||
        """Retrieve a user's access/secret pair by the access key.
 | 
			
		||||
 | 
			
		||||
        Grab the full access/secret pair for a given access key.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param credential_id: access key for credentials
 | 
			
		||||
        :returns: credential: dict of ec2 credential
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        return {'credential': self._get_credentials(credential_id)}
 | 
			
		||||
 | 
			
		||||
    def delete_credential(self, context, user_id, credential_id):
 | 
			
		||||
        """Delete a user's access/secret pair.
 | 
			
		||||
 | 
			
		||||
        Used to revoke a user's access/secret pair
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param credential_id: access key for credentials
 | 
			
		||||
        :returns: bool: success
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
            self._assert_owner(user_id, credential_id)
 | 
			
		||||
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        self._get_credentials(credential_id)
 | 
			
		||||
        ec2_credential_id = utils.hash_access_key(credential_id)
 | 
			
		||||
        return self.credential_api.delete_credential(ec2_credential_id)
 | 
			
		||||
 | 
			
		||||
    def _convert_v3_to_ec2_credential(self, credential):
 | 
			
		||||
 | 
			
		||||
        blob = credential['blob']
 | 
			
		||||
        return {'user_id': credential.get('user_id'),
 | 
			
		||||
                'tenant_id': credential.get('project_id'),
 | 
			
		||||
                'access': blob.get('access'),
 | 
			
		||||
                'secret': blob.get('secret')}
 | 
			
		||||
 | 
			
		||||
    def _get_credentials(self, credential_id):
 | 
			
		||||
        """Return credentials from an ID.
 | 
			
		||||
 | 
			
		||||
        :param credential_id: id of credential
 | 
			
		||||
        :raises exception.Unauthorized: when credential id is invalid
 | 
			
		||||
        :returns: credential: dict of ec2 credential.
 | 
			
		||||
        """
 | 
			
		||||
        ec2_credential_id = utils.hash_access_key(credential_id)
 | 
			
		||||
        creds = self.credential_api.get_credential(ec2_credential_id)
 | 
			
		||||
        if not creds:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 access key not found.')
 | 
			
		||||
        return self._convert_v3_to_ec2_credential(creds)
 | 
			
		||||
 | 
			
		||||
    def _assert_identity(self, context, user_id):
 | 
			
		||||
        """Check that the provided token belongs to the user.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :raises exception.Forbidden: when token is invalid
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            token_ref = self.token_api.get_token(context['token_id'])
 | 
			
		||||
        except exception.TokenNotFound as e:
 | 
			
		||||
            raise exception.Unauthorized(e)
 | 
			
		||||
 | 
			
		||||
        if token_ref['user'].get('id') != user_id:
 | 
			
		||||
            raise exception.Forbidden(_('Token belongs to another user'))
 | 
			
		||||
 | 
			
		||||
    def _is_admin(self, context):
 | 
			
		||||
        """Wrap admin assertion error return statement.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :returns: bool: success
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.assert_admin(context)
 | 
			
		||||
            return True
 | 
			
		||||
        except exception.Forbidden:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _assert_owner(self, user_id, credential_id):
 | 
			
		||||
        """Ensure the provided user owns the credential.
 | 
			
		||||
 | 
			
		||||
        :param user_id: expected credential owner
 | 
			
		||||
        :param credential_id: id of credential object
 | 
			
		||||
        :raises exception.Forbidden: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        cred_ref = self.credential_api.get_credential(credential_id)
 | 
			
		||||
        if user_id != cred_ref['user_id']:
 | 
			
		||||
            raise exception.Forbidden(_('Credential belongs to another user'))
 | 
			
		||||
 | 
			
		||||
    def _assert_valid_user_id(self, user_id):
 | 
			
		||||
        """Ensure a valid user id.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: expected credential owner
 | 
			
		||||
        :raises exception.UserNotFound: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        user_ref = self.identity_api.get_user(user_id)
 | 
			
		||||
        if not user_ref:
 | 
			
		||||
            raise exception.UserNotFound(user_id=user_id)
 | 
			
		||||
 | 
			
		||||
    def _assert_valid_project_id(self, project_id):
 | 
			
		||||
        """Ensure a valid project id.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param project_id: expected project
 | 
			
		||||
        :raises exception.ProjectNotFound: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        project_ref = self.identity_api.get_project(project_id)
 | 
			
		||||
        if not project_ref:
 | 
			
		||||
            raise exception.ProjectNotFound(project_id=project_id)
 | 
			
		||||
@@ -14,40 +14,8 @@
 | 
			
		||||
# License for the specific language governing permissions and limitations
 | 
			
		||||
# under the License.
 | 
			
		||||
 | 
			
		||||
"""Main entry point into the EC2 Credentials service.
 | 
			
		||||
 | 
			
		||||
This service allows the creation of access/secret credentials used for
 | 
			
		||||
the ec2 interop layer of OpenStack.
 | 
			
		||||
 | 
			
		||||
A user can create as many access/secret pairs, each of which map to a
 | 
			
		||||
specific tenant.  This is required because OpenStack supports a user
 | 
			
		||||
belonging to multiple tenants, whereas the signatures created on ec2-style
 | 
			
		||||
requests don't allow specification of which tenant the user wishs to act
 | 
			
		||||
upon.
 | 
			
		||||
 | 
			
		||||
To complete the cycle, we provide a method that OpenStack services can
 | 
			
		||||
use to validate a signature and get a corresponding openstack token.  This
 | 
			
		||||
token allows method calls to other services within the context the
 | 
			
		||||
access/secret was created.  As an example, nova requests keystone to validate
 | 
			
		||||
the signature of a request, receives a token, and then makes a request to
 | 
			
		||||
glance to list images needed to perform the requested task.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
 | 
			
		||||
 | 
			
		||||
from keystone.common import controller
 | 
			
		||||
from keystone.common import dependency
 | 
			
		||||
from keystone.common import extension
 | 
			
		||||
from keystone.common import manager
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone.common import wsgi
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone import exception
 | 
			
		||||
from keystone import token
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONF = config.CONF
 | 
			
		||||
 | 
			
		||||
@@ -69,279 +37,3 @@ EXTENSION_DATA = {
 | 
			
		||||
    ]}
 | 
			
		||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
 | 
			
		||||
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dependency.provider('ec2_api')
 | 
			
		||||
class Manager(manager.Manager):
 | 
			
		||||
    """Default pivot point for the EC2 Credentials backend.
 | 
			
		||||
 | 
			
		||||
    See :mod:`keystone.common.manager.Manager` for more details on how this
 | 
			
		||||
    dynamically calls the backend.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(Manager, self).__init__(CONF.ec2.driver)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ec2Extension(wsgi.ExtensionRouter):
 | 
			
		||||
    def add_routes(self, mapper):
 | 
			
		||||
        ec2_controller = Ec2Controller()
 | 
			
		||||
        # validation
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/ec2tokens',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='authenticate',
 | 
			
		||||
            conditions=dict(method=['POST']))
 | 
			
		||||
 | 
			
		||||
        # crud
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='create_credential',
 | 
			
		||||
            conditions=dict(method=['POST']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='get_credentials',
 | 
			
		||||
            conditions=dict(method=['GET']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='get_credential',
 | 
			
		||||
            conditions=dict(method=['GET']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='delete_credential',
 | 
			
		||||
            conditions=dict(method=['DELETE']))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dependency.requires('catalog_api', 'ec2_api', 'token_provider_api')
 | 
			
		||||
class Ec2Controller(controller.V2Controller):
 | 
			
		||||
    def check_signature(self, creds_ref, credentials):
 | 
			
		||||
        signer = ec2_utils.Ec2Signer(creds_ref['secret'])
 | 
			
		||||
        signature = signer.generate(credentials)
 | 
			
		||||
        if utils.auth_str_equal(credentials['signature'], signature):
 | 
			
		||||
            return
 | 
			
		||||
        # NOTE(vish): Some libraries don't use the port when signing
 | 
			
		||||
        #             requests, so try again without port.
 | 
			
		||||
        elif ':' in credentials['signature']:
 | 
			
		||||
            hostname, _port = credentials['host'].split(':')
 | 
			
		||||
            credentials['host'] = hostname
 | 
			
		||||
            signature = signer.generate(credentials)
 | 
			
		||||
            if not utils.auth_str_equal(credentials.signature, signature):
 | 
			
		||||
                raise exception.Unauthorized(message='Invalid EC2 signature.')
 | 
			
		||||
        else:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, context, credentials=None, ec2Credentials=None):
 | 
			
		||||
        """Validate a signed EC2 request and provide a token.
 | 
			
		||||
 | 
			
		||||
        Other services (such as Nova) use this **admin** call to determine
 | 
			
		||||
        if a request they signed received is from a valid user.
 | 
			
		||||
 | 
			
		||||
        If it is a valid signature, an openstack token that maps
 | 
			
		||||
        to the user/tenant is returned to the caller, along with
 | 
			
		||||
        all the other details returned from a normal token validation
 | 
			
		||||
        call.
 | 
			
		||||
 | 
			
		||||
        The returned token is useful for making calls to other
 | 
			
		||||
        OpenStack services within the context of the request.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param credentials: dict of ec2 signature
 | 
			
		||||
        :param ec2Credentials: DEPRECATED dict of ec2 signature
 | 
			
		||||
        :returns: token: openstack token equivalent to access key along
 | 
			
		||||
                         with the corresponding service catalog and roles
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # FIXME(ja): validate that a service token was used!
 | 
			
		||||
 | 
			
		||||
        # NOTE(termie): backwards compat hack
 | 
			
		||||
        if not credentials and ec2Credentials:
 | 
			
		||||
            credentials = ec2Credentials
 | 
			
		||||
 | 
			
		||||
        if 'access' not in credentials:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 signature not supplied.')
 | 
			
		||||
 | 
			
		||||
        creds_ref = self._get_credentials(credentials['access'])
 | 
			
		||||
        self.check_signature(creds_ref, credentials)
 | 
			
		||||
 | 
			
		||||
        # TODO(termie): don't create new tokens every time
 | 
			
		||||
        # TODO(termie): this is copied from TokenController.authenticate
 | 
			
		||||
        token_id = uuid.uuid4().hex
 | 
			
		||||
        tenant_ref = self.identity_api.get_project(creds_ref['tenant_id'])
 | 
			
		||||
        user_ref = self.identity_api.get_user(creds_ref['user_id'])
 | 
			
		||||
        metadata_ref = {}
 | 
			
		||||
        metadata_ref['roles'] = (
 | 
			
		||||
            self.identity_api.get_roles_for_user_and_project(
 | 
			
		||||
                user_ref['id'], tenant_ref['id']))
 | 
			
		||||
 | 
			
		||||
        # Validate that the auth info is valid and nothing is disabled
 | 
			
		||||
        token.validate_auth_info(self, user_ref, tenant_ref)
 | 
			
		||||
 | 
			
		||||
        roles = metadata_ref.get('roles', [])
 | 
			
		||||
        if not roles:
 | 
			
		||||
            raise exception.Unauthorized(message='User not valid for tenant.')
 | 
			
		||||
        roles_ref = [self.identity_api.get_role(role_id)
 | 
			
		||||
                     for role_id in roles]
 | 
			
		||||
 | 
			
		||||
        catalog_ref = self.catalog_api.get_catalog(
 | 
			
		||||
            user_ref['id'], tenant_ref['id'], metadata_ref)
 | 
			
		||||
 | 
			
		||||
        auth_token_data = dict(user=user_ref,
 | 
			
		||||
                               tenant=tenant_ref,
 | 
			
		||||
                               metadata=metadata_ref,
 | 
			
		||||
                               id='placeholder')
 | 
			
		||||
 | 
			
		||||
        (token_id, token_data) = self.token_provider_api.issue_v2_token(
 | 
			
		||||
            auth_token_data, roles_ref, catalog_ref)
 | 
			
		||||
        return token_data
 | 
			
		||||
 | 
			
		||||
    def create_credential(self, context, user_id, tenant_id):
 | 
			
		||||
        """Create a secret/access pair for use with ec2 style auth.
 | 
			
		||||
 | 
			
		||||
        Generates a new set of credentials that map the the user/tenant
 | 
			
		||||
        pair.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param tenant_id: id of tenant
 | 
			
		||||
        :returns: credential: dict of ec2 credential
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        self._assert_valid_project_id(tenant_id)
 | 
			
		||||
 | 
			
		||||
        cred_ref = {'user_id': user_id,
 | 
			
		||||
                    'tenant_id': tenant_id,
 | 
			
		||||
                    'access': uuid.uuid4().hex,
 | 
			
		||||
                    'secret': uuid.uuid4().hex}
 | 
			
		||||
        self.ec2_api.create_credential(cred_ref['access'], cred_ref)
 | 
			
		||||
        return {'credential': cred_ref}
 | 
			
		||||
 | 
			
		||||
    def get_credentials(self, context, user_id):
 | 
			
		||||
        """List all credentials for a user.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :returns: credentials: list of ec2 credential dicts
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        return {'credentials': self.ec2_api.list_credentials(user_id)}
 | 
			
		||||
 | 
			
		||||
    def get_credential(self, context, user_id, credential_id):
 | 
			
		||||
        """Retrieve a user's access/secret pair by the access key.
 | 
			
		||||
 | 
			
		||||
        Grab the full access/secret pair for a given access key.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param credential_id: access key for credentials
 | 
			
		||||
        :returns: credential: dict of ec2 credential
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        creds = self._get_credentials(credential_id)
 | 
			
		||||
        return {'credential': creds}
 | 
			
		||||
 | 
			
		||||
    def delete_credential(self, context, user_id, credential_id):
 | 
			
		||||
        """Delete a user's access/secret pair.
 | 
			
		||||
 | 
			
		||||
        Used to revoke a user's access/secret pair
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :param credential_id: access key for credentials
 | 
			
		||||
        :returns: bool: success
 | 
			
		||||
        """
 | 
			
		||||
        if not self._is_admin(context):
 | 
			
		||||
            self._assert_identity(context, user_id)
 | 
			
		||||
            self._assert_owner(user_id, credential_id)
 | 
			
		||||
 | 
			
		||||
        self._assert_valid_user_id(user_id)
 | 
			
		||||
        self._get_credentials(credential_id)
 | 
			
		||||
        return self.ec2_api.delete_credential(credential_id)
 | 
			
		||||
 | 
			
		||||
    def _get_credentials(self, credential_id):
 | 
			
		||||
        """Return credentials from an ID.
 | 
			
		||||
 | 
			
		||||
        :param credential_id: id of credential
 | 
			
		||||
        :raises exception.Unauthorized: when credential id is invalid
 | 
			
		||||
        :returns: credential: dict of ec2 credential.
 | 
			
		||||
        """
 | 
			
		||||
        creds = self.ec2_api.get_credential(credential_id)
 | 
			
		||||
        if not creds:
 | 
			
		||||
            raise exception.Unauthorized(message='EC2 access key not found.')
 | 
			
		||||
        return creds
 | 
			
		||||
 | 
			
		||||
    def _assert_identity(self, context, user_id):
 | 
			
		||||
        """Check that the provided token belongs to the user.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: id of user
 | 
			
		||||
        :raises exception.Forbidden: when token is invalid
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            token_ref = self.token_api.get_token(context['token_id'])
 | 
			
		||||
        except exception.TokenNotFound as e:
 | 
			
		||||
            raise exception.Unauthorized(e)
 | 
			
		||||
 | 
			
		||||
        if token_ref['user'].get('id') != user_id:
 | 
			
		||||
            raise exception.Forbidden('Token belongs to another user')
 | 
			
		||||
 | 
			
		||||
    def _is_admin(self, context):
 | 
			
		||||
        """Wrap admin assertion error return statement.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :returns: bool: success
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            self.assert_admin(context)
 | 
			
		||||
            return True
 | 
			
		||||
        except exception.Forbidden:
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
    def _assert_owner(self, user_id, credential_id):
 | 
			
		||||
        """Ensure the provided user owns the credential.
 | 
			
		||||
 | 
			
		||||
        :param user_id: expected credential owner
 | 
			
		||||
        :param credential_id: id of credential object
 | 
			
		||||
        :raises exception.Forbidden: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        cred_ref = self.ec2_api.get_credential(credential_id)
 | 
			
		||||
        if not user_id == cred_ref['user_id']:
 | 
			
		||||
            raise exception.Forbidden('Credential belongs to another user')
 | 
			
		||||
 | 
			
		||||
    def _assert_valid_user_id(self, user_id):
 | 
			
		||||
        """Ensure a valid user id.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param user_id: expected credential owner
 | 
			
		||||
        :raises exception.UserNotFound: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        user_ref = self.identity_api.get_user(user_id)
 | 
			
		||||
        if not user_ref:
 | 
			
		||||
            raise exception.UserNotFound(user_id=user_id)
 | 
			
		||||
 | 
			
		||||
    def _assert_valid_project_id(self, tenant_id):
 | 
			
		||||
        """Ensure a valid tenant id.
 | 
			
		||||
 | 
			
		||||
        :param context: standard context
 | 
			
		||||
        :param tenant_id: expected tenant
 | 
			
		||||
        :raises exception.ProjectNotFound: on failure
 | 
			
		||||
 | 
			
		||||
        """
 | 
			
		||||
        tenant_ref = self.identity_api.get_project(tenant_id)
 | 
			
		||||
        if not tenant_ref:
 | 
			
		||||
            raise exception.ProjectNotFound(project_id=tenant_id)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								keystone/contrib/ec2/routers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								keystone/contrib/ec2/routers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2013 OpenStack LLC
 | 
			
		||||
#
 | 
			
		||||
# 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.common import wsgi
 | 
			
		||||
from keystone.contrib.ec2 import controllers
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Ec2Extension(wsgi.ExtensionRouter):
 | 
			
		||||
    def add_routes(self, mapper):
 | 
			
		||||
        ec2_controller = controllers.Ec2Controller()
 | 
			
		||||
        # validation
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/ec2tokens',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='authenticate',
 | 
			
		||||
            conditions=dict(method=['POST']))
 | 
			
		||||
 | 
			
		||||
        # crud
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='create_credential',
 | 
			
		||||
            conditions=dict(method=['POST']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='get_credentials',
 | 
			
		||||
            conditions=dict(method=['GET']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='get_credential',
 | 
			
		||||
            conditions=dict(method=['GET']))
 | 
			
		||||
        mapper.connect(
 | 
			
		||||
            '/users/{user_id}/credentials/OS-EC2/{credential_id}',
 | 
			
		||||
            controller=ec2_controller,
 | 
			
		||||
            action='delete_credential',
 | 
			
		||||
            conditions=dict(method=['DELETE']))
 | 
			
		||||
@@ -31,7 +31,7 @@ from keystone.common import extension
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone.common import wsgi
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.contrib import ec2
 | 
			
		||||
from keystone.contrib.ec2 import controllers
 | 
			
		||||
from keystone import exception
 | 
			
		||||
 | 
			
		||||
CONF = config.CONF
 | 
			
		||||
@@ -64,7 +64,7 @@ class S3Extension(wsgi.ExtensionRouter):
 | 
			
		||||
                       conditions=dict(method=['POST']))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class S3Controller(ec2.Ec2Controller):
 | 
			
		||||
class S3Controller(controllers.Ec2Controller):
 | 
			
		||||
    def check_signature(self, creds_ref, credentials):
 | 
			
		||||
        msg = base64.urlsafe_b64decode(str(credentials['token']))
 | 
			
		||||
        key = str(creds_ref['secret'])
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,12 @@ class Credential(sql.Base, credential.Driver):
 | 
			
		||||
            session.flush()
 | 
			
		||||
        return ref.to_dict()
 | 
			
		||||
 | 
			
		||||
    def list_credentials(self):
 | 
			
		||||
    def list_credentials(self, **filters):
 | 
			
		||||
        session = self.get_session()
 | 
			
		||||
        refs = session.query(CredentialModel).all()
 | 
			
		||||
        query = session.query(CredentialModel)
 | 
			
		||||
        if 'user_id' in filters:
 | 
			
		||||
            query = query.filter_by(user_id=filters.get('user_id'))
 | 
			
		||||
        refs = query.all()
 | 
			
		||||
        return [ref.to_dict() for ref in refs]
 | 
			
		||||
 | 
			
		||||
    def _get_credential(self, session, credential_id):
 | 
			
		||||
 
 | 
			
		||||
@@ -52,8 +52,8 @@ class Driver(object):
 | 
			
		||||
        """
 | 
			
		||||
        raise exception.NotImplemented()
 | 
			
		||||
 | 
			
		||||
    def list_credentials(self):
 | 
			
		||||
        """List all credentials in the system.
 | 
			
		||||
    def list_credentials(self, **filters):
 | 
			
		||||
        """List all credentials in the system applying filters.
 | 
			
		||||
 | 
			
		||||
        :returns: a list of credential_refs or an empty list.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ from keystone import catalog
 | 
			
		||||
from keystone.common import dependency
 | 
			
		||||
from keystone.common import wsgi
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.contrib import ec2
 | 
			
		||||
from keystone.contrib import oauth1
 | 
			
		||||
from keystone import controllers
 | 
			
		||||
from keystone import credential
 | 
			
		||||
@@ -48,7 +47,6 @@ DRIVERS = dict(
 | 
			
		||||
    assignment_api=assignment.Manager(),
 | 
			
		||||
    catalog_api=catalog.Manager(),
 | 
			
		||||
    credentials_api=credential.Manager(),
 | 
			
		||||
    ec2_api=ec2.Manager(),
 | 
			
		||||
    identity_api=_IDENTITY_API,
 | 
			
		||||
    oauth1_api=oauth1.Manager(),
 | 
			
		||||
    policy_api=policy.Manager(),
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@ from keystone.common import sql
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone.common import wsgi
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.contrib import ec2
 | 
			
		||||
from keystone.contrib import oauth1
 | 
			
		||||
from keystone import credential
 | 
			
		||||
from keystone import exception
 | 
			
		||||
@@ -268,7 +267,7 @@ class TestCase(NoModule, unittest.TestCase):
 | 
			
		||||
        # identity driver is available to the assignment manager because the
 | 
			
		||||
        # assignment manager gets the default assignment driver from the
 | 
			
		||||
        # identity driver.
 | 
			
		||||
        for manager in [identity, assignment, catalog, credential, ec2, policy,
 | 
			
		||||
        for manager in [identity, assignment, catalog, credential, policy,
 | 
			
		||||
                        token, token_provider, trust, oauth1]:
 | 
			
		||||
            # manager.__name__ is like keystone.xxx[.yyy],
 | 
			
		||||
            # converted to xxx[_yyy]
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ import uuid
 | 
			
		||||
 | 
			
		||||
from keystone.tests import core as test
 | 
			
		||||
 | 
			
		||||
from keystone.contrib import ec2
 | 
			
		||||
from keystone.contrib import s3
 | 
			
		||||
 | 
			
		||||
from keystone import exception
 | 
			
		||||
@@ -30,7 +29,6 @@ class S3ContribCore(test.TestCase):
 | 
			
		||||
 | 
			
		||||
        self.load_backends()
 | 
			
		||||
 | 
			
		||||
        self.ec2_api = ec2.Manager()
 | 
			
		||||
        self.controller = s3.S3Controller()
 | 
			
		||||
 | 
			
		||||
    def test_good_signature(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -688,91 +688,6 @@ class KeystoneClientTests(object):
 | 
			
		||||
        # TODO(devcamcar): This assert should be more specific.
 | 
			
		||||
        self.assertTrue(len(roles) > 0)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credential_crud(self):
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        creds = client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [])
 | 
			
		||||
 | 
			
		||||
        cred = client.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                                 tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        creds = client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [cred])
 | 
			
		||||
 | 
			
		||||
        got = client.ec2.get(user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
        self.assertEquals(cred, got)
 | 
			
		||||
 | 
			
		||||
        client.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
        creds = client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [])
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_create_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          client.ec2.create,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          client.ec2.create,
 | 
			
		||||
                          user_id=self.user_foo['id'],
 | 
			
		||||
                          tenant_id=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_delete_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          client.ec2.delete,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          access=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_get_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          client.ec2.get,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          access=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_list_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          client.ec2.list,
 | 
			
		||||
                          user_id=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_list_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.list,
 | 
			
		||||
                          user_id=self.user_foo['id'])
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_get_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        foo = self.get_client()
 | 
			
		||||
        cred = foo.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                              tenant_id=self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.get,
 | 
			
		||||
                          user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
        foo.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_delete_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        foo = self.get_client()
 | 
			
		||||
        cred = foo.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                              tenant_id=self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.delete,
 | 
			
		||||
                          user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
        foo.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
    def test_service_crud(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client(admin=True)
 | 
			
		||||
@@ -888,28 +803,6 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests):
 | 
			
		||||
    def get_checkout(self):
 | 
			
		||||
        return KEYSTONECLIENT_REPO, 'master'
 | 
			
		||||
 | 
			
		||||
    def test_ec2_auth(self):
 | 
			
		||||
        client = self.get_client()
 | 
			
		||||
        cred = client.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                                 tenant_id=self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        from keystoneclient.contrib.ec2 import utils as ec2_utils
 | 
			
		||||
        signer = ec2_utils.Ec2Signer(cred.secret)
 | 
			
		||||
        credentials = {'params': {'SignatureVersion': '2'},
 | 
			
		||||
                       'access': cred.access,
 | 
			
		||||
                       'verb': 'GET',
 | 
			
		||||
                       'host': 'localhost',
 | 
			
		||||
                       'path': '/thisisgoingtowork'}
 | 
			
		||||
        signature = signer.generate(credentials)
 | 
			
		||||
        credentials['signature'] = signature
 | 
			
		||||
        url = '%s/ec2tokens' % (client.auth_url)
 | 
			
		||||
        (resp, token) = client.request(url=url,
 | 
			
		||||
                                       method='POST',
 | 
			
		||||
                                       body={'credentials': credentials})
 | 
			
		||||
        # make sure we have a v2 token
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertIn('access', token)
 | 
			
		||||
 | 
			
		||||
    def test_tenant_add_and_remove_user(self):
 | 
			
		||||
        client = self.get_client(admin=True)
 | 
			
		||||
        client.roles.add_user_role(tenant=self.tenant_bar['id'],
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@
 | 
			
		||||
 | 
			
		||||
import uuid
 | 
			
		||||
 | 
			
		||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
 | 
			
		||||
 | 
			
		||||
from keystone.common import sql
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone.tests import core as test
 | 
			
		||||
@@ -37,6 +39,10 @@ class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase, sql.Base):
 | 
			
		||||
        self.engine = self.get_engine()
 | 
			
		||||
        sql.ModelBase.metadata.create_all(bind=self.engine)
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(KcMasterSqlTestCase, self).setUp()
 | 
			
		||||
        self.default_client = self.get_client()
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        sql.ModelBase.metadata.drop_all(bind=self.engine)
 | 
			
		||||
        self.engine.dispose()
 | 
			
		||||
@@ -83,6 +89,146 @@ class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase, sql.Base):
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound, client.endpoints.delete,
 | 
			
		||||
                          id=endpoint.id)
 | 
			
		||||
 | 
			
		||||
    def _send_ec2_auth_request(self, credentials):
 | 
			
		||||
        url = '%s/ec2tokens' % self.default_client.auth_url
 | 
			
		||||
        (resp, token) = self.default_client.request(
 | 
			
		||||
            url=url, method='POST',
 | 
			
		||||
            body={'credentials': credentials})
 | 
			
		||||
        return resp, token
 | 
			
		||||
 | 
			
		||||
    def _generate_default_user_ec2_credentials(self):
 | 
			
		||||
        cred = self. default_client.ec2.create(
 | 
			
		||||
            user_id=self.user_foo['id'],
 | 
			
		||||
            tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        signer = ec2_utils.Ec2Signer(cred.secret)
 | 
			
		||||
        credentials = {'params': {'SignatureVersion': '2'},
 | 
			
		||||
                       'access': cred.access,
 | 
			
		||||
                       'verb': 'GET',
 | 
			
		||||
                       'host': 'localhost',
 | 
			
		||||
                       'path': '/service/cloud'}
 | 
			
		||||
        signature = signer.generate(credentials)
 | 
			
		||||
        return credentials, signature
 | 
			
		||||
 | 
			
		||||
    def test_ec2_auth_success(self):
 | 
			
		||||
        credentials, signature = self._generate_default_user_ec2_credentials()
 | 
			
		||||
        credentials['signature'] = signature
 | 
			
		||||
        resp, token = self._send_ec2_auth_request(credentials)
 | 
			
		||||
        self.assertEqual(resp.status_code, 200)
 | 
			
		||||
        self.assertIn('access', token)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_auth_failure(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        credentials, signature = self._generate_default_user_ec2_credentials()
 | 
			
		||||
        credentials['signature'] = uuid.uuid4().hex
 | 
			
		||||
        self.assertRaises(client_exceptions.Unauthorized,
 | 
			
		||||
                          self._send_ec2_auth_request,
 | 
			
		||||
                          credentials)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credential_crud(self):
 | 
			
		||||
        creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [])
 | 
			
		||||
 | 
			
		||||
        cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                                              tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [cred])
 | 
			
		||||
        got = self.default_client.ec2.get(user_id=self.user_foo['id'],
 | 
			
		||||
                                          access=cred.access)
 | 
			
		||||
        self.assertEquals(cred, got)
 | 
			
		||||
 | 
			
		||||
        self.default_client.ec2.delete(user_id=self.user_foo['id'],
 | 
			
		||||
                                       access=cred.access)
 | 
			
		||||
        creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(creds, [])
 | 
			
		||||
 | 
			
		||||
    def test_ec2_list_credentials(self):
 | 
			
		||||
        cred_1 = self.default_client.ec2.create(
 | 
			
		||||
            user_id=self.user_foo['id'],
 | 
			
		||||
            tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        cred_2 = self.default_client.ec2.create(
 | 
			
		||||
            user_id=self.user_foo['id'],
 | 
			
		||||
            tenant_id=self.tenant_service['id'])
 | 
			
		||||
        cred_3 = self.default_client.ec2.create(
 | 
			
		||||
            user_id=self.user_foo['id'],
 | 
			
		||||
            tenant_id=self.tenant_mtu['id'])
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        cred_4 = two.ec2.create(user_id=self.user_two['id'],
 | 
			
		||||
                                tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
 | 
			
		||||
        self.assertEquals(len(creds), 3)
 | 
			
		||||
        self.assertEquals(sorted([cred_1, cred_2, cred_3],
 | 
			
		||||
                                 key=lambda x: x.access),
 | 
			
		||||
                          sorted(creds, key=lambda x: x.access))
 | 
			
		||||
        self.assertNotIn(cred_4, creds)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_create_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          self.default_client.ec2.create,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          tenant_id=self.tenant_bar['id'])
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          self.default_client.ec2.create,
 | 
			
		||||
                          user_id=self.user_foo['id'],
 | 
			
		||||
                          tenant_id=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_delete_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          self.default_client.ec2.delete,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          access=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_get_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          self.default_client.ec2.get,
 | 
			
		||||
                          user_id=uuid.uuid4().hex,
 | 
			
		||||
                          access=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_list_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        self.assertRaises(client_exceptions.NotFound,
 | 
			
		||||
                          self.default_client.ec2.list,
 | 
			
		||||
                          user_id=uuid.uuid4().hex)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_list_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.list,
 | 
			
		||||
                          user_id=self.user_foo['id'])
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_get_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                                              tenant_id=self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.get,
 | 
			
		||||
                          user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
        self.default_client.ec2.delete(user_id=self.user_foo['id'],
 | 
			
		||||
                                       access=cred.access)
 | 
			
		||||
 | 
			
		||||
    def test_ec2_credentials_delete_user_forbidden(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
 | 
			
		||||
        cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
 | 
			
		||||
                                              tenant_id=self.tenant_bar['id'])
 | 
			
		||||
 | 
			
		||||
        two = self.get_client(self.user_two)
 | 
			
		||||
        self.assertRaises(client_exceptions.Forbidden, two.ec2.delete,
 | 
			
		||||
                          user_id=self.user_foo['id'], access=cred.access)
 | 
			
		||||
 | 
			
		||||
        self.default_client.ec2.delete(user_id=self.user_foo['id'],
 | 
			
		||||
                                       access=cred.access)
 | 
			
		||||
 | 
			
		||||
    def test_endpoint_create_404(self):
 | 
			
		||||
        from keystoneclient import exceptions as client_exceptions
 | 
			
		||||
        client = self.get_client(admin=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@
 | 
			
		||||
import ast
 | 
			
		||||
 | 
			
		||||
from keystone.contrib.admin_crud import core as admin_crud_core
 | 
			
		||||
from keystone.contrib.ec2 import core as ec2_core
 | 
			
		||||
from keystone.contrib.s3 import core as s3_core
 | 
			
		||||
from keystone.contrib.stats import core as stats_core
 | 
			
		||||
from keystone.contrib.user_crud import core as user_crud_core
 | 
			
		||||
@@ -28,7 +27,7 @@ from keystone import service
 | 
			
		||||
class TestSingularPlural(object):
 | 
			
		||||
    def test_keyword_arg_condition_or_methods(self):
 | 
			
		||||
        """Raise if we see a keyword arg called 'condition' or 'methods'."""
 | 
			
		||||
        modules = [admin_crud_core, ec2_core, s3_core, stats_core,
 | 
			
		||||
        modules = [admin_crud_core, s3_core, stats_core,
 | 
			
		||||
                   user_crud_core, identity_core, service]
 | 
			
		||||
        for module in modules:
 | 
			
		||||
            filename = module.__file__
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,10 @@ from keystone.tests import core as test
 | 
			
		||||
 | 
			
		||||
from keystone.common import sql
 | 
			
		||||
from keystone.common.sql import migration
 | 
			
		||||
from keystone.common import utils
 | 
			
		||||
from keystone import config
 | 
			
		||||
from keystone import credential
 | 
			
		||||
from keystone import exception
 | 
			
		||||
 | 
			
		||||
import default_fixtures
 | 
			
		||||
 | 
			
		||||
@@ -69,6 +72,7 @@ class SqlMigrateBase(test.TestCase):
 | 
			
		||||
 | 
			
		||||
        # create and share a single sqlalchemy engine for testing
 | 
			
		||||
        self.engine = self.base.get_engine(allow_global_engine=False)
 | 
			
		||||
        sql.core.set_global_engine(self.engine)
 | 
			
		||||
        self.Session = self.base.get_sessionmaker(engine=self.engine,
 | 
			
		||||
                                                  autocommit=False)
 | 
			
		||||
 | 
			
		||||
@@ -87,6 +91,7 @@ class SqlMigrateBase(test.TestCase):
 | 
			
		||||
                                 autoload=True)
 | 
			
		||||
        self.downgrade(0)
 | 
			
		||||
        table.drop(self.engine, checkfirst=True)
 | 
			
		||||
        sql.core.set_global_engine(None)
 | 
			
		||||
        super(SqlMigrateBase, self).tearDown()
 | 
			
		||||
 | 
			
		||||
    def select_table(self, name):
 | 
			
		||||
@@ -1292,6 +1297,208 @@ class SqlUpgradeTests(SqlMigrateBase):
 | 
			
		||||
        else:
 | 
			
		||||
            self.assertEqual(len(index_data), 0)
 | 
			
		||||
 | 
			
		||||
    def test_migrate_ec2_credential(self):
 | 
			
		||||
        user = {
 | 
			
		||||
            'id': 'foo',
 | 
			
		||||
            'name': 'FOO',
 | 
			
		||||
            'password': 'foo2',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'email': 'foo@bar.com',
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project = {
 | 
			
		||||
            'id': 'bar',
 | 
			
		||||
            'name': 'BAR',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        ec2_credential = {
 | 
			
		||||
            'access': uuid.uuid4().hex,
 | 
			
		||||
            'secret': uuid.uuid4().hex,
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'tenant_id': project['id'],
 | 
			
		||||
        }
 | 
			
		||||
        session = self.Session()
 | 
			
		||||
        self.upgrade(7)
 | 
			
		||||
        self.insert_dict(session, 'ec2_credential', ec2_credential)
 | 
			
		||||
        self.insert_dict(session, 'user', user)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project)
 | 
			
		||||
        self.upgrade(33)
 | 
			
		||||
        self.assertTableDoesNotExist('ec2_credential')
 | 
			
		||||
        cred_table = sqlalchemy.Table('credential',
 | 
			
		||||
                                      self.metadata,
 | 
			
		||||
                                      autoload=True)
 | 
			
		||||
        expected_credential_id = utils.hash_access_key(
 | 
			
		||||
            ec2_credential['access'])
 | 
			
		||||
        cred = session.query(cred_table).filter_by(
 | 
			
		||||
            id=expected_credential_id).one()
 | 
			
		||||
        self.assertEqual(cred.user_id, ec2_credential['user_id'])
 | 
			
		||||
        self.assertEqual(cred.project_id, ec2_credential['tenant_id'])
 | 
			
		||||
        # test list credential using credential manager.
 | 
			
		||||
        credential_api = credential.Manager()
 | 
			
		||||
        self.assertNotEmpty(credential_api.
 | 
			
		||||
                            list_credentials(
 | 
			
		||||
                            user_id=ec2_credential['user_id']))
 | 
			
		||||
        self.downgrade(32)
 | 
			
		||||
        session.commit()
 | 
			
		||||
        self.assertTableExists('ec2_credential')
 | 
			
		||||
        ec2_cred_table = sqlalchemy.Table('ec2_credential',
 | 
			
		||||
                                          self.metadata,
 | 
			
		||||
                                          autoload=True)
 | 
			
		||||
        ec2_cred = session.query(ec2_cred_table).filter_by(
 | 
			
		||||
            access=ec2_credential['access']).one()
 | 
			
		||||
        self.assertEqual(ec2_cred.user_id, ec2_credential['user_id'])
 | 
			
		||||
 | 
			
		||||
    def test_migrate_ec2_credential_with_conflict_project(self):
 | 
			
		||||
        user = {
 | 
			
		||||
            'id': 'foo',
 | 
			
		||||
            'name': 'FOO',
 | 
			
		||||
            'password': 'foo2',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'email': 'foo@bar.com',
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_1 = {
 | 
			
		||||
            'id': 'bar',
 | 
			
		||||
            'name': 'BAR',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_2 = {
 | 
			
		||||
            'id': 'baz',
 | 
			
		||||
            'name': 'BAZ',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        ec2_credential = {
 | 
			
		||||
            'access': uuid.uuid4().hex,
 | 
			
		||||
            'secret': uuid.uuid4().hex,
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'tenant_id': project_1['id'],
 | 
			
		||||
        }
 | 
			
		||||
        blob = {'access': ec2_credential['access'],
 | 
			
		||||
                'secret': ec2_credential['secret']}
 | 
			
		||||
        v3_credential = {
 | 
			
		||||
            'id': utils.hash_access_key(ec2_credential['access']),
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            # set the project id to simulate a conflict
 | 
			
		||||
            'project_id': project_2['id'],
 | 
			
		||||
            'blob': json.dumps(blob),
 | 
			
		||||
            'type': 'ec2',
 | 
			
		||||
            'extra': json.dumps({})
 | 
			
		||||
        }
 | 
			
		||||
        session = self.Session()
 | 
			
		||||
        self.upgrade(7)
 | 
			
		||||
        self.insert_dict(session, 'ec2_credential', ec2_credential)
 | 
			
		||||
        self.insert_dict(session, 'user', user)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_1)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_2)
 | 
			
		||||
        self.upgrade(32)
 | 
			
		||||
        self.insert_dict(session, 'credential', v3_credential)
 | 
			
		||||
        self.assertRaises(exception.Conflict, self.upgrade, 33)
 | 
			
		||||
 | 
			
		||||
    def test_migrate_ec2_credential_with_conflict_secret(self):
 | 
			
		||||
        user = {
 | 
			
		||||
            'id': 'foo',
 | 
			
		||||
            'name': 'FOO',
 | 
			
		||||
            'password': 'foo2',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'email': 'foo@bar.com',
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_1 = {
 | 
			
		||||
            'id': 'bar',
 | 
			
		||||
            'name': 'BAR',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_2 = {
 | 
			
		||||
            'id': 'baz',
 | 
			
		||||
            'name': 'BAZ',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        ec2_credential = {
 | 
			
		||||
            'access': uuid.uuid4().hex,
 | 
			
		||||
            'secret': uuid.uuid4().hex,
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'tenant_id': project_1['id'],
 | 
			
		||||
        }
 | 
			
		||||
        blob = {'access': ec2_credential['access'],
 | 
			
		||||
                'secret': 'different secret'}
 | 
			
		||||
        v3_cred_different_secret = {
 | 
			
		||||
            'id': utils.hash_access_key(ec2_credential['access']),
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'project_id': project_1['id'],
 | 
			
		||||
            'blob': json.dumps(blob),
 | 
			
		||||
            'type': 'ec2',
 | 
			
		||||
            'extra': json.dumps({})
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        session = self.Session()
 | 
			
		||||
        self.upgrade(7)
 | 
			
		||||
        self.insert_dict(session, 'ec2_credential', ec2_credential)
 | 
			
		||||
        self.insert_dict(session, 'user', user)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_1)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_2)
 | 
			
		||||
        self.upgrade(32)
 | 
			
		||||
        self.insert_dict(session, 'credential', v3_cred_different_secret)
 | 
			
		||||
        self.assertRaises(exception.Conflict, self.upgrade, 33)
 | 
			
		||||
 | 
			
		||||
    def test_migrate_ec2_credential_with_invalid_blob(self):
 | 
			
		||||
        user = {
 | 
			
		||||
            'id': 'foo',
 | 
			
		||||
            'name': 'FOO',
 | 
			
		||||
            'password': 'foo2',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'email': 'foo@bar.com',
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_1 = {
 | 
			
		||||
            'id': 'bar',
 | 
			
		||||
            'name': 'BAR',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        project_2 = {
 | 
			
		||||
            'id': 'baz',
 | 
			
		||||
            'name': 'BAZ',
 | 
			
		||||
            'description': 'description',
 | 
			
		||||
            'enabled': True,
 | 
			
		||||
            'extra': json.dumps({'enabled': True})
 | 
			
		||||
        }
 | 
			
		||||
        ec2_credential = {
 | 
			
		||||
            'access': uuid.uuid4().hex,
 | 
			
		||||
            'secret': uuid.uuid4().hex,
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'tenant_id': project_1['id'],
 | 
			
		||||
        }
 | 
			
		||||
        blob = '{"abc":"def"d}'
 | 
			
		||||
        v3_cred_invalid_blob = {
 | 
			
		||||
            'id': utils.hash_access_key(ec2_credential['access']),
 | 
			
		||||
            'user_id': user['id'],
 | 
			
		||||
            'project_id': project_1['id'],
 | 
			
		||||
            'blob': json.dumps(blob),
 | 
			
		||||
            'type': 'ec2',
 | 
			
		||||
            'extra': json.dumps({})
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        session = self.Session()
 | 
			
		||||
        self.upgrade(7)
 | 
			
		||||
        self.insert_dict(session, 'ec2_credential', ec2_credential)
 | 
			
		||||
        self.insert_dict(session, 'user', user)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_1)
 | 
			
		||||
        self.insert_dict(session, 'tenant', project_2)
 | 
			
		||||
        self.upgrade(32)
 | 
			
		||||
        self.insert_dict(session, 'credential', v3_cred_invalid_blob)
 | 
			
		||||
        self.assertRaises(exception.ValidationError, self.upgrade, 33)
 | 
			
		||||
 | 
			
		||||
    def populate_user_table(self, with_pass_enab=False,
 | 
			
		||||
                            with_pass_enab_domain=False):
 | 
			
		||||
        # Populate the appropriate fields in the user
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user