diff --git a/barbican/model/migration/alembic_migrations/versions/13d127569afa_create_secret_metadata_table.py b/barbican/model/migration/alembic_migrations/versions/13d127569afa_create_secret_metadata_table.py new file mode 100644 index 000000000..9c0e17e01 --- /dev/null +++ b/barbican/model/migration/alembic_migrations/versions/13d127569afa_create_secret_metadata_table.py @@ -0,0 +1,51 @@ +# Copyright (c) 2014 The Johns Hopkins University/Applied Physics Laboratory +# 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. + +"""create_secret_metadata_table + +Revision ID: 13d127569afa +Revises: 1a0c2cdafb38 +Create Date: 2014-04-24 13:15:41.858266 + +""" + +# revision identifiers, used by Alembic. +revision = '13d127569afa' +down_revision = '1a0c2cdafb38' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + meta = sa.MetaData() + meta.reflect(bind=rep._ENGINE, only=['secret_metadata']) + if 'secret_metadata' not in meta.tables.keys(): + op.create_table('secret_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('secret_id', sa.String(length=36), nullable=False), + sa.Column('key', sa.String(length=255), nullable=False), + sa.Column('value', sa.String(length=255), nullable=False), + sa.ForeignKeyConstraint(['secret_id'], ['secrets.id'],), + sa.PrimaryKeyConstraint('id'), + ) + +def downgrade(): + op.drop_table('secret_metadata') diff --git a/barbican/model/models.py b/barbican/model/models.py index d49c39aa7..3b79842e9 100644 --- a/barbican/model/models.py +++ b/barbican/model/models.py @@ -21,6 +21,7 @@ from sqlalchemy.ext import compiler from sqlalchemy.ext import declarative from sqlalchemy import orm +from barbican.common import exception from barbican.common import utils from barbican.openstack.common import timeutils @@ -216,6 +217,7 @@ class Secret(BASE, ModelBase): # metadata is retrieved. # See barbican.api.resources.py::SecretsResource.on_get() encrypted_data = orm.relationship("EncryptedDatum", lazy='joined') + secret_metadata = orm.relationship("SecretMetadatum", lazy='joined') def __init__(self, parsed_request=None): """Creates secret from a dict.""" @@ -232,6 +234,9 @@ class Secret(BASE, ModelBase): def _do_delete_children(self, session): """Sub-class hook: delete children relationships.""" + for datum in self.secret_metadata: + datum.delete(session) + for datum in self.encrypted_data: datum.delete(session) @@ -240,16 +245,58 @@ class Secret(BASE, ModelBase): def _do_extra_dict_fields(self): """Sub-class hook method: return dict of fields.""" - return {'secret_id': self.id, - 'name': self.name or self.id, - 'expiration': self.expiration, - 'algorithm': self.algorithm, - 'bit_length': self.bit_length, - 'mode': self.mode} + fields = {'secret_id': self.id, + 'name': self.name or self.id, + 'expiration': self.expiration, + 'algorithm': self.algorithm, + 'bit_length': self.bit_length, + 'mode': self.mode} + #TODO(kaitlin.farr) will change implementation to avoid overwriting + # existing fields in a future patch + for m in self.secret_metadata: + metadata_field = m._do_extra_dict_fields() + fields.update({metadata_field['key']: metadata_field['value']}) + return fields + + +class SecretMetadatum(BASE, ModelBase): + """Represents the metadatum for a single key-value pair for a Secret""" + + __tablename__ = "secret_metadata" + + secret_id = sa.Column(sa.String(36), sa.ForeignKey('secrets.id'), + nullable=False) + key = sa.Column(sa.String(255), nullable=False) + value = sa.Column(sa.String(255), nullable=False) + + def __init__(self, secret, key, value): + super(SecretMetadatum, self).__init__() + + msg = ("Must supply non-None {0} argument for SecretMetadatum entry.") + + if secret is None: + raise exception.MissingArgumentError(msg.format("secret")) + else: + self.secret_id = secret.id + + if key is None: + raise exception.MissingArgumentError(msg.format("key")) + else: + self.key = key + + if value is None: + raise exception.MissingArgumentError(msg.format("value")) + else: + 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, ModelBase): - """Represents a the encrypted data for a Secret.""" + """Represents the encrypted data for a Secret.""" __tablename__ = 'encrypted_data'