summaryrefslogtreecommitdiff
path: root/barbicanclient/v1/acls.py
diff options
context:
space:
mode:
Diffstat (limited to 'barbicanclient/v1/acls.py')
-rw-r--r--barbicanclient/v1/acls.py473
1 files changed, 473 insertions, 0 deletions
diff --git a/barbicanclient/v1/acls.py b/barbicanclient/v1/acls.py
new file mode 100644
index 0000000..9292d12
--- /dev/null
+++ b/barbicanclient/v1/acls.py
@@ -0,0 +1,473 @@
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 logging
+
+from oslo_utils.timeutils import parse_isotime
+
+from barbicanclient import base
+from barbicanclient import formatter
+
+
+LOG = logging.getLogger(__name__)
+
+DEFAULT_OPERATION_TYPE = 'read'
+
+VALID_ACL_OPERATIONS = ['read', 'write', 'delete', 'list']
+
+
+class ACLFormatter(formatter.EntityFormatter):
+
+ columns = ("Operation Type",
+ "Project Access",
+ "Users",
+ "Created",
+ "Updated",
+ )
+
+ def _get_formatted_data(self):
+ created = self.created.isoformat() if self.created else None
+ updated = self.updated.isoformat() if self.updated else None
+ data = (self.operation_type,
+ self.project_access,
+ self.users,
+ created,
+ updated,
+ self.acl_ref,
+ )
+ return data
+
+
+class _PerOperationACL(ACLFormatter):
+
+ def __init__(self, parent_acl, entity_ref=None, users=None,
+ project_access=None, operation_type=None,
+ created=None, updated=None):
+ """Per Operation ACL data instance for secret or container.
+
+ This class not to be instantiated outside of this module.
+
+ :param parent_acl: acl entity to this per operation data belongs to
+ :param str entity_ref: Full HATEOAS reference to a secret or container
+ :param users: List of Keystone userid(s) to be used for ACL.
+ :type users: List or None
+ :param bool project_access: Flag indicating project access behavior
+ :param str operation_type: Type indicating which class of Barbican
+ operations this ACL is defined for e.g. 'read' operations
+ :param str created: Time string indicating ACL create timestamp. This
+ is populated only when populating data from api response. Not
+ needed in client input.
+ :param str updated: Time string indicating ACL last update timestamp.
+ This is populated only when populating data from api response. Not
+ needed in client input.
+ """
+ self._parent_acl = parent_acl
+ self._entity_ref = entity_ref
+ self._users = users if users else list()
+ self._project_access = project_access
+ self._operation_type = operation_type
+ self._created = parse_isotime(created) if created else None
+ self._updated = parse_isotime(updated) if updated else None
+
+ @property
+ def acl_ref(self):
+ return ACL.get_acl_ref_from_entity_ref(self.entity_ref)
+
+ @property
+ def entity_ref(self):
+ return self._entity_ref
+
+ @property
+ def project_access(self):
+ """Flag indicating project access behavior is enabled or not"""
+ return self._project_access
+
+ @property
+ def users(self):
+ """List of users for this ACL setting"""
+ return self._users
+
+ @property
+ def operation_type(self):
+ """Type indicating class of Barbican operations for this ACL"""
+ return self._operation_type
+
+ @property
+ def created(self):
+ return self._created
+
+ @property
+ def updated(self):
+ return self._updated
+
+ @operation_type.setter
+ def operation_type(self, value):
+ self._operation_type = value
+
+ @project_access.setter
+ def project_access(self, value):
+ self._project_access = value
+
+ @users.setter
+ def users(self, value):
+ self._users = value
+
+ def remove(self):
+ """Remove operation specific setting defined for a secret or container
+
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ """
+ LOG.debug('Removing {0} operation specific ACL for href: {1}'
+ .format(self.operation_type, self.acl_ref))
+
+ self._parent_acl.load_acls_data()
+ acl_entity = self._parent_acl
+
+ # Find matching operation specific acl entry and remove from list
+ per_op_acl = acl_entity.get(self.operation_type)
+ if per_op_acl:
+ acl_entity.operation_acls.remove(per_op_acl)
+
+ # after above operation specific acl removal, check if there are
+ # any remaining acls. If yes, then submit updates to server.
+ # If not, then remove/delete acls from server.
+ if acl_entity.operation_acls:
+ acl_entity.submit()
+ else:
+ acl_entity.remove()
+
+ def _validate_users_type(self):
+ if self.users and not (type(self.users) is list or
+ type(self.users) is set):
+ raise ValueError('Users value is expected to be provided'
+ ' as list/set.')
+
+
+class ACL(object):
+
+ _resource_name = 'acl'
+
+ def __init__(self, api, entity_ref, users=None, project_access=None,
+ operation_type=DEFAULT_OPERATION_TYPE, created=None,
+ updated=None):
+ """Base ACL entity instance for secret or container.
+
+ Provide ACL data arguments to set ACL setting for given operation_type.
+
+ To add ACL setting for other operation types, use `add_operation_acl`
+ method.
+
+ :param api: client instance reference
+ :param str entity_ref: Full HATEOAS reference to a secret or container
+ :param users: List of Keystone userid(s) to be used for ACL.
+ :type users: str List or None
+ :param bool project_access: Flag indicating project access behavior
+ :param str operation_type: Type indicating which class of Barbican
+ operations this ACL is defined for e.g. 'read' operations
+ :param str created: Time string indicating ACL create timestamp. This
+ is populated only when populating data from api response. Not
+ needed in client input.
+ :param str updated: Time string indicating ACL last update timestamp.
+ This is populated only when populating data from api response. Not
+ needed in client input.
+ """
+
+ self._api = api
+ self._entity_ref = entity_ref
+ self._operation_acls = []
+
+ # create per operation ACL data entity only when client has set users
+ # or project_access flag.
+ if users is not None or project_access is not None:
+ acl = _PerOperationACL(parent_acl=self, entity_ref=entity_ref,
+ users=users, project_access=project_access,
+ operation_type=operation_type,
+ created=created, updated=updated)
+ self._operation_acls.append(acl)
+
+ @property
+ def entity_ref(self):
+ """Entity URI reference."""
+ return self._entity_ref
+
+ @property
+ def operation_acls(self):
+ """List of operation specific ACL settings."""
+ return self._operation_acls
+
+ @property
+ def acl_ref(self):
+ return ACL.get_acl_ref_from_entity_ref(self.entity_ref)
+
+ def add_operation_acl(self, users=None, project_access=None,
+ operation_type=None, created=None,
+ updated=None,):
+ """Add ACL settings to entity for specific operation type.
+
+ If matching operation_type ACL already exists, then it replaces it with
+ new PerOperationACL object using provided inputs. Otherwise it appends
+ new PerOperationACL object to existing per operation ACL list.
+
+ This just adds to local entity and have not yet applied these changes
+ to server.
+
+ :param users: List of Keystone userid(s) to be used in ACL.
+ :type users: List or None
+ :param bool project_access: Flag indicating project access behavior
+ :param str operation_type: Type indicating which class of Barbican
+ operations this ACL is defined for e.g. 'read' operations
+ :param str created: Time string indicating ACL create timestamp. This
+ is populated only when populating data from api response. Not
+ needed in client input.
+ :param str updated: Time string indicating ACL last update timestamp.
+ This is populated only when populating data from api response. Not
+ needed in client input.
+ """
+ new_acl = _PerOperationACL(parent_acl=self, entity_ref=self.entity_ref,
+ users=users, project_access=project_access,
+ operation_type=operation_type,
+ created=created, updated=updated)
+
+ for i, acl in enumerate(self._operation_acls):
+ if acl.operation_type == operation_type:
+ # replace with new ACL setting
+ self._operation_acls[i] = new_acl
+ break
+ else:
+ self._operation_acls.append(new_acl)
+
+ def _get_operation_acl(self, operation_type):
+ return next((acl for acl in self._operation_acls
+ if acl.operation_type == operation_type), None)
+
+ def get(self, operation_type):
+ """Get operation specific ACL instance.
+
+ :param str operation_type: Type indicating which operation's ACL
+ setting is needed.
+ """
+ return self._get_operation_acl(operation_type)
+
+ def __getattr__(self, name):
+ if name in VALID_ACL_OPERATIONS:
+ return self._get_operation_acl(name)
+ else:
+ raise AttributeError(name)
+
+ def submit(self):
+ """Submits ACLs for a secret or a container defined in server
+
+ In existing ACL case, this overwrites the existing ACL setting with
+ provided inputs. If input users are None or empty list, this will
+ remove existing ACL users if there. If input project_access flag is
+ None, then default project access behavior is enabled.
+
+ :returns: str acl_ref: Full HATEOAS reference to a secret or container
+ ACL.
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ :raises barbicanclient.exceptions.HTTPServerError: 5xx Responses
+ """
+ LOG.debug('Submitting complete {0} ACL for href: {1}'
+ .format(self.acl_type, self.entity_ref))
+ if not self.operation_acls:
+ raise ValueError('ACL data for {0} is not provided.'.
+ format(self._acl_type))
+
+ self.validate_input_ref()
+
+ acl_dict = {}
+
+ for per_op_acl in self.operation_acls:
+ per_op_acl._validate_users_type()
+ op_type = per_op_acl.operation_type
+ acl_data = {}
+ if per_op_acl.project_access is not None:
+ acl_data['project-access'] = per_op_acl.project_access
+ if per_op_acl.users is not None:
+ acl_data['users'] = per_op_acl.users
+ acl_dict[op_type] = acl_data
+
+ response = self._api.put(self.acl_ref, json=acl_dict)
+
+ return response.json().get('acl_ref')
+
+ def remove(self):
+ """Remove Barbican ACLs setting defined for a secret or container
+
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ """
+ self.validate_input_ref()
+ LOG.debug('Removing ACL for {0} for href: {1}'
+ .format(self.acl_type, self.entity_ref))
+ self._api.delete(self.acl_ref)
+
+ def load_acls_data(self):
+ """Loads ACL entity from Barbican server using its acl_ref
+
+ Clears the existing list of per operation ACL settings if there.
+ Populates current ACL entity with ACL settings received from Barbican
+ server.
+
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ :raises barbicanclient.exceptions.HTTPServerError: 5xx Responses
+ """
+
+ response = self._api.get(self.acl_ref)
+
+ del self.operation_acls[:] # clearing list for all of its references
+ for op_type in response:
+ acl_dict = response.get(op_type)
+ proj_access = acl_dict.get('project-access')
+ users = acl_dict.get('users')
+ created = acl_dict.get('created')
+ updated = acl_dict.get('updated')
+ self.add_operation_acl(operation_type=op_type,
+ project_access=proj_access,
+ users=users, created=created,
+ updated=updated)
+
+ def validate_input_ref(self):
+ res_title = self._acl_type.title()
+ if not self.entity_ref:
+ raise ValueError('{0} href is required.'.format(res_title))
+ if self._parent_entity_path in self.entity_ref:
+ if '/acl' in self.entity_ref:
+ raise ValueError('{0} ACL URI provided. Expecting {0} URI.'
+ .format(res_title))
+ ref_type = self._acl_type
+ else:
+ raise ValueError('{0} URI is not specified.'.format(res_title))
+
+ base.validate_ref(self.entity_ref, ref_type)
+ return ref_type
+
+ @staticmethod
+ def get_acl_ref_from_entity_ref(entity_ref):
+ # Utility for converting entity ref to acl ref
+ if entity_ref:
+ entity_ref = entity_ref.rstrip('/')
+ return '{0}/{1}'.format(entity_ref, ACL._resource_name)
+
+ @staticmethod
+ def identify_ref_type(entity_ref):
+ # Utility for identifying ACL type from given entity URI.
+ if not entity_ref:
+ raise ValueError('Secret or container href is required.')
+ if '/secrets' in entity_ref:
+ ref_type = 'secret'
+ elif '/containers' in entity_ref:
+ ref_type = 'container'
+ else:
+ raise ValueError('Secret or container URI is not specified.')
+
+ return ref_type
+
+
+class SecretACL(ACL):
+ """ACL entity for a secret"""
+
+ columns = ACLFormatter.columns + ("Secret ACL Ref",)
+ _acl_type = 'secret'
+ _parent_entity_path = '/secrets'
+
+ @property
+ def acl_type(self):
+ return self._acl_type
+
+
+class ContainerACL(ACL):
+ """ACL entity for a container"""
+
+ columns = ACLFormatter.columns + ("Container ACL Ref",)
+ _acl_type = 'container'
+ _parent_entity_path = '/containers'
+
+ @property
+ def acl_type(self):
+ return self._acl_type
+
+
+class ACLManager(base.BaseEntityManager):
+ """Entity Manager for Secret or Container ACL entities"""
+
+ acl_class_map = {
+ 'secret': SecretACL,
+ 'container': ContainerACL
+ }
+
+ def __init__(self, api):
+ super(ACLManager, self).__init__(api, ACL._resource_name)
+
+ def create(self, entity_ref=None, users=None, project_access=None,
+ operation_type=DEFAULT_OPERATION_TYPE):
+ """Factory method for creating `ACL` entity.
+
+ `ACL` object returned by this method have not yet been
+ stored in Barbican.
+
+ Input entity_ref is used to determine whether
+ ACL object type needs to be :class:`barbicanclient.acls.SecretACL`
+ or :class:`barbicanclient.acls.ContainerACL`.
+
+ :param str entity_ref: Full HATEOAS reference to a secret or container
+ :param users: List of Keystone userid(s) to be used in ACL.
+ :type users: List or None
+ :param bool project_access: Flag indicating project access behavior
+ :param str operation_type: Type indicating which class of Barbican
+ operations this ACL is defined for e.g. 'read' operations
+ :returns: ACL object instance
+ :rtype: :class:`barbicanclient.acls.SecretACL` or
+ :class:`barbicanclient.acls.ContainerACL`
+ """
+ entity_type = ACL.identify_ref_type(entity_ref)
+
+ entity_class = ACLManager.acl_class_map.get(entity_type)
+ # entity_class cannot be None as entity_ref is already validated above
+ return entity_class(api=self._api, entity_ref=entity_ref, users=users,
+ project_access=project_access,
+ operation_type=operation_type)
+
+ def get(self, entity_ref):
+ """Retrieve existing ACLs for a secret or container found in Barbican
+
+ :param str entity_ref: Full HATEOAS reference to a secret or container.
+ :returns: ACL entity object instance
+ :rtype: :class:`barbicanclient.acls.SecretACL` or
+ :class:`barbicanclient.acls.ContainerACL`
+ :raises barbicanclient.exceptions.HTTPAuthError: 401 Responses
+ :raises barbicanclient.exceptions.HTTPClientError: 4xx Responses
+ """
+ entity = self._validate_acl_ref(entity_ref)
+ LOG.debug('Getting ACL for {0} href: {1}'
+ .format(entity.acl_type, entity.acl_ref))
+ entity.load_acls_data()
+ return entity
+
+ def _validate_acl_ref(self, entity_ref):
+ if entity_ref is None:
+ raise ValueError('Expected secret or container URI is not '
+ 'specified.')
+
+ entity_ref = entity_ref.rstrip('/')
+ entity_type = ACL.identify_ref_type(entity_ref)
+
+ entity_class = ACLManager.acl_class_map.get(entity_type)
+ acl_entity = entity_class(api=self._api, entity_ref=entity_ref)
+ acl_entity.validate_input_ref()
+ return acl_entity