============
Client Usage
============

To use barbicanclient, you must first create an instance of the
:class:`barbicanclient.client.Client` class.

The client uses Keystone Sessions for both authentication and for handling HTTP
requests.  You can provide authentication credentials to the client by
creating a Keystone Session with the appropriate auth plugin and then passing
that session to the new Client.

See :doc:`authentication` for more details.

Example:

  .. code-block:: python

    from barbicanclient import client

    barbican = client.Client(...)

The client object has different attributes that can be used to interact with
the Barbican service.  Each attribute represents an entity in the Barbican
service:  Secrets, Orders and Containers.

Secrets
=======

Secrets represent keys, credentials, and other sensitive data that is stored by
the Barbican service.  To store or retrieve a secret in the Barbican service
you should use the different methods of the
:class:`barbicanclient.secrets.SecretManager` class that is exposed as the
`secrets` attribute of the Client.

Example:

  .. code-block:: python

    # Store a random text password in Barbican

    from barbicanclient import client
    import random
    import string

    def random_password(length):
        sys_random = random.SystemRandom()
        return ''.join(
            sys_random.choice(string.ascii_letters + string.digits) for _ in range(length)
        )

    barbican = client.Client(...)

    my_secret = barbican.secrets.create()
    my_secret.name = 'Random plain text password'
    my_secret.payload = random_password(24)

    my_secret_ref = my_secret.store()

The secret reference returned by
:meth:`barbicanclient.secrets.SecretManager.store` can later be used to
retrieve the secret data from barbican.

Example:

  .. code-block:: python

    # Retrieve Secret from secret reference

    retrieved_secret = barbican.secrets.get(my_secret_ref)
    my_password = retrieved_secret.payload

Secret Content Types
--------------------

The Barbican service defines a Secret Content Type.  The client will choose the
correct Content Type based on the type of the data that is set on the
`Secret.payload` property.  The following table summarizes the mapping of
Python types to Barbican Secret Content Types:

+---------------+--------------------------+
| Python 3 Type | Barbican Content Type    |
+===============+==========================+
| bytes         | application/octet-stream |
+---------------+--------------------------+
| str           | text/plain               |
+---------------+--------------------------+

.. WARNING::
   Previous versions of python-barbicanclient allowed the user to set the
   `payload_content_type` and `payload_content_encoding` properties for any
   secret.  This can lead to unexpected behavior such as changing a unicode
   string back to a byte string in Python 2, and dropping the base64 encoding
   of a binary secret as in Launchpad Bug #1419166.
   Because of this, manually setting the `payload_content_type` and the
   `payload_content_encoding` has been deprecated.

Orders
======

Orders are used to request secret material to be created by the Barbican
service.  Submitting an order will result in a Secret being created on your
behalf.  The Secret can then be used like any Secret you may have uploaded
yourself.  Orders should be created using the factory methods in the
:class:`barbicanclient.orders.OrderManager` instance in the `orders` attribute
of the `Client`.

Example:

  .. code-block:: python

    # Submit an order to generate a random encryption key

    from barbicanclient import client

    barbican = client.Client(...)

    my_order = barbican.orders.create_key()
    my_order.algorithm = 'AES'
    my_order.mode = 'CBC'
    my_order.bit_length = 256

    my_order_ref = my_order.submit()

The order reference returned by :meth:`barbicanclient.orders.Order.submit` can
later be used to retrieve the order from Barbican.

Example:

  .. code-block:: python

    # Retrieve Order from order reference

    retrieved_order = barbican.orders.get(my_order_ref)

Once your order has been processed by Barbican, the order status will be set to
`'ACTIVE'`.  An active order will contain the reference to the requested
secret (or container).

Example:

  .. code-block:: python

    # Retrieve Encryption Key generated by the above KeyOrder

    generated_secret = barbican.secrets.get(retrieved_order.secret_ref)
    key = generated_secret.payload

Currently the client can submit :class:`barbicanclient.orders.KeyOrder` orders
for Keys suitable for symmetric encryption, and
:class:`barbicanclient.orders.AsymmetricOrder` for Asymmetric keys such as RSA
keys.

Containers
==========

Containers can be either arbitrary groupings of `Secrets` or a strict grouping
of Secrets, such as the Public and Private keys of an RSA keypair.

Containers should be managed using the
:class:`barbicanclient.containers.ContainerManager` instance in the
`containers` attribute of the `Client`

Example:

  .. code-block:: python

    # Add the Secrets created above to a container

    my_container = barbican.containers.create()

    my_container.add('Retrieved Secret', retrieved_secret)
    my_container.add('Generated Secret', generated_secret)

    my_container_ref = my_container.store()

The container reference returned by
:meth:`barbicanclient.containers.Container.store` can later be used to
retrieve the container from Barbican.

Example:

  .. code-block:: python

    # Retrieve container from Barbican

    retrieved_container = barbican.containers.get(my_container_ref)

Secret Consumers
================

Secret consumers are represented by three attributes: service, resource type and resource id.
Callers can register secret consumers to indicate that they are using a secret.
For instance, in the example  below, the caller creates a secret consumer to indicate that
the created secret is being used to encrypt a specific Glance image.

Secret consumers should be managed using the
:class:`barbicanclient.secrets.SecretManager` instance in the
`secrets` attribute of the `Client` and by the corresponding
`register_consumer`, `remove_consumer` and `list_consumers` methods.

Example:

  .. code-block:: python

    # Creating a secret and adding a consumer to it

    secret = barbican.secrets.create(name='image encryption key',
                                     payload='encryption_key')
    secret.store()

    barbican.secrets.register_consumer(
        secret.secret_ref,
        service="image",
        resource_type="image",
        resource_id="123e4567-e89b-12d3-a456-426614174002"
    )

    # Listing all the secret's consumers

    consumers = barbican.secrets.list_consumers(secret.secret_ref)
    for consumer in consumers:
        print(f"Service: {consumer['service']}\t"
              f"Resource Type: {consumer['resource_type']}\t"
              f"Resource id: {consumer['resource_id']}")

    # Removing the previously created consumer

    barbican.secrets.remove_consumer(
        secret.secret_ref,
        service="image",
        resource_type="image",
        resource_id="123e4567-e89b-12d3-a456-426614174002"
    )

ACLs
====

Access Control List (ACL) feature in Barbican provides user level access
control for secrets and containers. By default Barbican manages access to its
resources (secrets, containers) on a per project level and authorization is
granted based on the roles a user has in that project.

ACLs should be managed using the :class:`barbicanclient.acls.ACLManager`
instance in the `acls` attribute of the `Client`.

Example:

  .. code-block:: python

    # Submits ACLs on an existing Secret with URI as 'secret_ref'

    # create ACL entity object with needed settings
    acl_entity = barbican.acls.create(entity_ref=secret_ref, users=[u1, u2],
                                      project_access=False)

    acl_ref = acl_entity.submit()  # submits ACL setting to server at this point.

The secret or container URI can be used to read all of its ACL setting.
Returned value is instance of either :class:`barbicanclient.acls.SecretACL` or
:class:`barbicanclient.acls.ContainerACL`. Refer to respective class for its
available APIs.

Example:

  .. code-block:: python

    # Get ACL entity for a Secret
    # Returned entity will be either SecretACL or ContainerACL.
    # This entity has ACL settings per operation type (e.g. 'read')

    secret_acl = barbican.acls.get(secret_ref)

    # To retrieve (load) ACL using existing ACL entity e.g. container_acl
    container_acl.load_acls_data()

ACLs setting can also be retrieved directly from secret or container entity.
Its data is lazy loaded i.e. related ACL settings are not read till `acls`
attribute is accessed on secret or container entity.

Example:

  .. code-block:: python

    # Get secret entity for a given ref
    secret = barbican.secrets.get(secret_ref)

    # To get project access flag or users for 'read' operation
    project_access_flag = secret.acls.read.project_access
    read_acl_users = secret.acls.read.users


    # Get container entity for a given ref
    container = barbican.containers.get(container_ref)

    # To get project access flag or users for 'read' operation
    project_access_flag = container.acls.read.project_access
    read_acl_users = container.acls.read.users


If need to add users to existing 'read' ACL settings on a secret or container,
above mentioned get and submit methods can be used.

Example:

  .. code-block:: python

    # Every Barbican secret and container has default ACL setting which
    # reflects default project access behavior.

    # ACL settings is modified via submit operation on ACL entity.

    # provide users to be added as list.
    add_users = ['user1', 'user2', 'users3']

    # Case 1 - Add users to 'read' operation ACL setting
    # --------------------------------------------------

    # Get ACL entity from server
    acl_entity = barbican.acls.get(entity_ref=secret_ref)

    # add new users to existing users for 'read' operation
    acl_entity.read.users.extend(add_users)
    # OR
    # acl_entity.get('read').users.extend(add_users)

    acl_ref = acl_entity.submit() # here submits ACL changes to server.

    # Case 2 - Add same users to ACL settings for each operation type
    # ---------------------------------------------------------------

    # Get ACL entity from server
    acl_entity = barbican.acls.get(entity_ref=secret_ref)

    # Go through each operation ACL setting and add users to existing list
    for op_acl in acl_entity.operation_acls
        op_acl.users.extend(add_users)

    acl_ref = acl_entity.submit() # here submits ACL changes to server.

If need to remove some users from existing ACL settings on a secret or
container, similar approach can be used as mentioned above for `add` example.

Example:

  .. code-block:: python

    # provide users to be removed as list.
    remove_users = ['user1', 'user2', 'users3']

    # Case 1 - Remove users from 'read' operation ACL setting
    # -------------------------------------------------------

    # Get ACL entity from server
    acl_entity = barbican.acls.get(entity_ref=container_ref)

    existing_users = acl_entity.read.users
    # OR
    # existing users = acl_entity.get('read').users

    # remove matching users from existing users list
    updated_users = set(existing_users).difference(remove_users)

    # set back updated users to operation specific acl setting
    acl_entity.read.users = updated_users
    # OR
    # acl_entity.get('read').users = updated_users

    acl_ref = acl_entity.submit() # here submits ACL changes to server.

    # Case 2 - Remove same users from ACL settings for each operation type
    # --------------------------------------------------------------------

    # Get ACL from server
    acl_entity = barbican.acls.get(secret_ref)

    # Go through each operation ACL setting and remove users from existing list
    for op_acl in acl_entity.operation_acls
        existing_users = op_acl.users

        # remove matching users from existing users list
        updated_users = set(existing_users).difference(remove_users)

        # set back updated users to operation specific acl setting
        op_acl.users = updated_users

    acl_ref = acl_entity.submit() # here submits ACL changes to server.


If need to unset or delete ACL settings on a secret or container,
:meth:`barbicanclient.acls.SecretACL.remove` or
:meth:`barbicanclient.acls.ContainerACL.remove` can be used.

Example:

  .. code-block:: python

    # create ACL entity object with secret or container ref
    blank_acl_entity = barbican.acls.create(entity_ref=secret_ref)

    # removes all ACL settings for the secret on server
    blank_acl_entity.remove()

    # To remove 'read' operation specific ACL setting
    acl_entity = barbican.acls.get(entity_ref=secret_ref)
    acl_entity.read.remove()
    # OR
    # acl_entity.get('read').remove()
