Introduce User-Meta table, model, and repo
This patch introduces the user-meta database table, model, and repository. It is the first of several patches which will complete the "User Defined Metadata for Barbican Secrets" Blueprint. Other Patches will include: 1. ) API and Tests(Unit and Functional) 2. ) Documentation 3. ) Client Upgrades Implements: blueprint add-user-metadata Change-Id: I4b6ae9e7090eb66fe8c89e62116d9a8483642a29
This commit is contained in:
parent
83e7caa02c
commit
52b0479fcc
|
@ -0,0 +1,52 @@
|
|||
# Copyright (c) 2015 IBM
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""add-secret-user-metadata
|
||||
|
||||
Revision ID: dce488646127
|
||||
Revises: 39a96e67e990
|
||||
Create Date: 2016-02-09 04:52:03.975486
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'dce488646127'
|
||||
down_revision = '39a96e67e990'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
ctx = op.get_context()
|
||||
con = op.get_bind()
|
||||
table_exists = ctx.dialect.has_table(con.engine, 'secret_user_metadata')
|
||||
if not table_exists:
|
||||
op.create_table(
|
||||
'secret_user_metadata',
|
||||
sa.Column('id', sa.String(length=36), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=False),
|
||||
sa.Column('deleted_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('deleted', sa.Boolean(), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('key', sa.String(length=255), nullable=False),
|
||||
sa.Column('value', sa.String(length=255), nullable=False),
|
||||
sa.Column('secret_id', sa.String(length=36), nullable=False),
|
||||
sa.ForeignKeyConstraint(['secret_id'], ['secrets.id'],),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('secret_id', 'key',
|
||||
name='_secret_key_uc')
|
||||
)
|
|
@ -297,6 +297,12 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
|
|||
backref="secret",
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
secret_user_metadata = orm.relationship(
|
||||
"SecretUserMetadatum",
|
||||
collection_class=col.attribute_mapped_collection('key'),
|
||||
backref="secret",
|
||||
cascade="all, delete-orphan")
|
||||
|
||||
def __init__(self, parsed_request=None):
|
||||
"""Creates secret from a dict."""
|
||||
super(Secret, self).__init__()
|
||||
|
@ -322,6 +328,9 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
|
|||
for k, v in self.secret_store_metadata.items():
|
||||
v.delete(session)
|
||||
|
||||
for k, v in self.secret_user_metadata.items():
|
||||
v.delete(session)
|
||||
|
||||
for datum in self.encrypted_data:
|
||||
datum.delete(session)
|
||||
|
||||
|
@ -346,7 +355,7 @@ class Secret(BASE, SoftDeleteMixIn, ModelBase):
|
|||
'algorithm': self.algorithm,
|
||||
'bit_length': self.bit_length,
|
||||
'mode': self.mode,
|
||||
'creator_id': self.creator_id
|
||||
'creator_id': self.creator_id,
|
||||
}
|
||||
|
||||
|
||||
|
@ -382,6 +391,41 @@ class SecretStoreMetadatum(BASE, SoftDeleteMixIn, ModelBase):
|
|||
}
|
||||
|
||||
|
||||
class SecretUserMetadatum(BASE, SoftDeleteMixIn, ModelBase):
|
||||
"""Represents Secret user metadatum for a single key-value pair."""
|
||||
|
||||
__tablename__ = "secret_user_metadata"
|
||||
|
||||
key = sa.Column(sa.String(255), nullable=False)
|
||||
value = sa.Column(sa.String(255), nullable=False)
|
||||
secret_id = sa.Column(
|
||||
sa.String(36), sa.ForeignKey('secrets.id'), index=True, nullable=False)
|
||||
|
||||
__table_args__ = (sa.UniqueConstraint('secret_id', 'key',
|
||||
name='_secret_key_uc'),)
|
||||
|
||||
def __init__(self, key, value):
|
||||
super(SecretUserMetadatum, self).__init__()
|
||||
|
||||
msg = u._("Must supply non-None {0} argument "
|
||||
"for SecretUserMetadatum entry.")
|
||||
|
||||
if key is None:
|
||||
raise exception.MissingArgumentError(msg.format("key"))
|
||||
self.key = key
|
||||
|
||||
if value is None:
|
||||
raise exception.MissingArgumentError(msg.format("value"))
|
||||
self.value = value
|
||||
|
||||
def _do_extra_dict_fields(self):
|
||||
"""Sub-class hook method: return dict of fields."""
|
||||
return {
|
||||
'key': self.key,
|
||||
'value': self.value
|
||||
}
|
||||
|
||||
|
||||
class EncryptedDatum(BASE, SoftDeleteMixIn, ModelBase):
|
||||
"""Represents the encrypted data for a Secret."""
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ _PROJECT_CA_REPOSITORY = None
|
|||
_PROJECT_QUOTAS_REPOSITORY = None
|
||||
_SECRET_ACL_REPOSITORY = None
|
||||
_SECRET_META_REPOSITORY = None
|
||||
_SECRET_USER_META_REPOSITORY = None
|
||||
_SECRET_REPOSITORY = None
|
||||
_TRANSPORT_KEY_REPOSITORY = None
|
||||
|
||||
|
@ -782,6 +783,58 @@ class SecretStoreMetadatumRepo(BaseRepo):
|
|||
pass
|
||||
|
||||
|
||||
class SecretUserMetadatumRepo(BaseRepo):
|
||||
"""Repository for the SecretUserMetadatum entity
|
||||
|
||||
Stores key/value information on behalf of a Secret.
|
||||
"""
|
||||
|
||||
def save(self, metadata, secret_model):
|
||||
"""Saves the the specified metadata for the secret.
|
||||
|
||||
:raises NotFound if entity does not exist.
|
||||
"""
|
||||
now = timeutils.utcnow()
|
||||
|
||||
for k, v in metadata.items():
|
||||
meta_model = models.SecretUserMetadatum(k, v)
|
||||
meta_model.updated_at = now
|
||||
meta_model.secret = secret_model
|
||||
meta_model.save()
|
||||
|
||||
def get_metadata_for_secret(self, secret_id):
|
||||
"""Returns a dict of SecretUserMetadatum instances."""
|
||||
|
||||
session = get_session()
|
||||
|
||||
try:
|
||||
query = session.query(models.SecretUserMetadatum)
|
||||
query = query.filter_by(deleted=False)
|
||||
|
||||
query = query.filter(
|
||||
models.SecretUserMetadatum.secret_id == secret_id)
|
||||
|
||||
metadata = query.all()
|
||||
|
||||
except sa_orm.exc.NoResultFound:
|
||||
metadata = {}
|
||||
|
||||
return {m.key: m.value for m in metadata}
|
||||
|
||||
def _do_entity_name(self):
|
||||
"""Sub-class hook: return entity name, such as for debugging."""
|
||||
return "SecretUserMetadatum"
|
||||
|
||||
def _do_build_get_query(self, entity_id, external_project_id, session):
|
||||
"""Sub-class hook: build a retrieve query."""
|
||||
query = session.query(models.SecretUserMetadatum)
|
||||
return query.filter_by(id=entity_id)
|
||||
|
||||
def _do_validate(self, values):
|
||||
"""Sub-class hook: validate values."""
|
||||
pass
|
||||
|
||||
|
||||
class KEKDatumRepo(BaseRepo):
|
||||
"""Repository for the KEKDatum entity
|
||||
|
||||
|
@ -2149,6 +2202,13 @@ def get_secret_meta_repository():
|
|||
return _get_repository(_SECRET_META_REPOSITORY, SecretStoreMetadatumRepo)
|
||||
|
||||
|
||||
def get_secret_user_meta_repository():
|
||||
"""Returns a singleton Secret user meta repository instance."""
|
||||
global _SECRET_USER_META_REPOSITORY
|
||||
return _get_repository(_SECRET_USER_META_REPOSITORY,
|
||||
SecretUserMetadatumRepo)
|
||||
|
||||
|
||||
def get_secret_repository():
|
||||
"""Returns a singleton Secret repository instance."""
|
||||
global _SECRET_REPOSITORY
|
||||
|
|
|
@ -64,6 +64,42 @@ class WhenCreatingNewSecret(utils.BaseTestCase):
|
|||
self.assertEqual(self.parsed_secret['secret_type'], secret.secret_type)
|
||||
|
||||
|
||||
class WhenCreatingNewSecretMetadata(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(WhenCreatingNewSecretMetadata, self).setUp()
|
||||
self.key = 'dog'
|
||||
self.value = 'poodle'
|
||||
|
||||
self.metadata = {
|
||||
'key': self.key,
|
||||
'value': self.value
|
||||
}
|
||||
|
||||
def test_new_secret_metadata_is_created_from_dict(self):
|
||||
secret_meta = models.SecretUserMetadatum(self.key, self.value)
|
||||
|
||||
self.assertEqual(self.key, secret_meta.key)
|
||||
self.assertEqual(self.value, secret_meta.value)
|
||||
|
||||
fields = secret_meta.to_dict_fields()
|
||||
self.assertEqual(self.metadata['key'],
|
||||
fields['key'])
|
||||
self.assertEqual(self.metadata['value'],
|
||||
fields['value'])
|
||||
|
||||
def test_should_raise_exception_metadata_with_no_key(self):
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretUserMetadatum,
|
||||
None,
|
||||
self.value)
|
||||
|
||||
def test_should_raise_exception_metadata_with_no_value(self):
|
||||
self.assertRaises(exception.MissingArgumentError,
|
||||
models.SecretUserMetadatum,
|
||||
self.key,
|
||||
None)
|
||||
|
||||
|
||||
class WhenCreatingNewOrder(utils.BaseTestCase):
|
||||
def setUp(self):
|
||||
super(WhenCreatingNewOrder, self).setUp()
|
||||
|
|
|
@ -23,7 +23,7 @@ class SecretModel(BaseModel):
|
|||
secret_ref=None, bit_length=None, mode=None, secret_type=None,
|
||||
payload_content_type=None, payload=None, content_types=None,
|
||||
payload_content_encoding=None, status=None, updated=None,
|
||||
created=None, creator_id=None):
|
||||
created=None, creator_id=None, metadata=None):
|
||||
super(SecretModel, self).__init__()
|
||||
|
||||
self.name = name
|
||||
|
@ -41,3 +41,4 @@ class SecretModel(BaseModel):
|
|||
self.updated = updated
|
||||
self.created = created
|
||||
self.creator_id = creator_id
|
||||
self.metadata = metadata
|
||||
|
|
Loading…
Reference in New Issue