Skip to content

Commit

Permalink
Merge pull request #39 from Covata/delta-client-identity
Browse files Browse the repository at this point in the history
Delta client identity (#28)
  • Loading branch information
numbat committed Feb 21, 2017
2 parents 6b87e3d + 7205664 commit a62e846
Show file tree
Hide file tree
Showing 11 changed files with 323 additions and 38 deletions.
3 changes: 0 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@
API Client
==========

The Delta API Client is an abstraction over the Delta API for execution of
requests and responses.

.. currentmodule:: covata.delta

.. autoclass:: ApiClient
Expand Down
21 changes: 21 additions & 0 deletions docs/client.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.. Copyright 2017 Covata Limited or its affiliates
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.
Client
======

.. currentmodule:: covata.delta

.. autoclass:: Client
:members:
21 changes: 21 additions & 0 deletions docs/identity.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.. Copyright 2017 Covata Limited or its affiliates
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.
Identity
========

.. currentmodule:: covata.delta

.. autoclass:: Identity
:members:
2 changes: 2 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ across networks, and organisations.

self
quick_start
client
identity
api
crypto
keystore
Expand Down
7 changes: 4 additions & 3 deletions src/main/python/covata/delta/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@

from __future__ import absolute_import

from .client import Client, Identity
from .apiclient import ApiClient
from .keystore import DeltaKeyStore
from .keystore import FileSystemKeyStore
from .keystore import DeltaKeyStore, FileSystemKeyStore
from .utils import LogMixin

__all__ = ["ApiClient", "FileSystemKeyStore", "LogMixin", "DeltaKeyStore"]
__all__ = ["Client", "Identity", "ApiClient", "FileSystemKeyStore", "LogMixin",
"DeltaKeyStore"]
31 changes: 15 additions & 16 deletions src/main/python/covata/delta/apiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@

import requests

from covata.delta import utils
from covata.delta import crypto
from covata.delta import signer
from . import signer
from . import utils


class ApiClient(utils.LogMixin):
"""
The Delta API Client is an abstraction over the Delta API for execution of
requests and responses.
"""

DELTA_URL = 'https://delta.covata.io/v1' # type: str
RESOURCE_IDENTITIES = '/identities' # type: str
Expand All @@ -41,27 +44,27 @@ def __init__(self, key_store):
def key_store(self):
return self.__key_store

def register_identity(self, external_id=None, metadata=None):
def register_identity(self, public_encryption_key, public_signing_key,
external_id=None, metadata=None):
"""
Creates a new identity in Delta with the provided metadata
and external id.
:param str public_encryption_key: the public encryption key to
associate with the identity
:param str public_signing_key: the public signing key to associate
with the identity
:param external_id: the external id to associate with the identity
:param metadata: the metadata to associate with the identity
:return: the id of the newly created identity
:type external_id: str | None
:type metadata: dict[str, str] | None
:return: the id of the newly created identity
:rtype: str
"""
private_signing_key = crypto.generate_private_key()
private_encryption_key = crypto.generate_private_key()

public_signing_key = private_signing_key.public_key()
public_encryption_key = private_encryption_key.public_key()

body = dict(
signingPublicKey=crypto.serialize_public_key(public_signing_key),
cryptoPublicKey=crypto.serialize_public_key(public_encryption_key),
signingPublicKey=public_signing_key,
cryptoPublicKey=public_encryption_key,
externalId=external_id,
metadata=metadata)

Expand All @@ -71,10 +74,6 @@ def register_identity(self, external_id=None, metadata=None):
response.raise_for_status()
identity_id = response.json()['identityId']

self.key_store.store_keys(
identity_id=identity_id,
private_signing_key=private_signing_key,
private_encryption_key=private_encryption_key)
return identity_id

def get_identity(self, requestor_id, identity_id):
Expand Down
152 changes: 152 additions & 0 deletions src/main/python/covata/delta/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
# Copyright 2017 Covata Limited or its affiliates
#
# 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 __future__ import absolute_import

from . import crypto
from . import utils


class Client(utils.LogMixin):
"""
The main entry point for the Delta SDK.
An instance of this class will provide an interface to work and interact
with the Delta API. The core domain objects (Identity, Secret and
Event) are returned from method calls to this class, and themselves provide
fluent interface that can be used to continue interactive with the Delta
API. Consumers of this SDK can therefore choose whether they wish to
construct all the calls from base values (i.e. id strings such as
identity_id, secret_id, etc) or via the fluent interfaces (or a mixture of
both).
"""

def __init__(self, config):
"""
Creates a new DeltaClient instance from the provided configuration.
:param config: the configuration for the client
:type config: dict[str, any]
"""
self.__key_store = config["key_store"]
self.__api_client = config["api_client"]

@property
def key_store(self):
return self.__key_store

@property
def api_client(self):
return self.__api_client

def create_identity(self, external_id=None, metadata=None):
"""
Creates a new identity in Delta.
:param external_id: the external id to associate with the identity
:param metadata: the metadata to associate with the identity
:type external_id: str | None
:type metadata: dict[str, str] | None
:return: the identity
:rtype: :class:`~.Identity`
"""
private_signing_key = crypto.generate_private_key()
private_encryption_key = crypto.generate_private_key()

public_signing_key = crypto.serialize_public_key(
private_signing_key.public_key())
public_encryption_key = crypto.serialize_public_key(
private_encryption_key.public_key())

identity_id = self.api_client.register_identity(public_encryption_key,
public_signing_key,
external_id, metadata)

self.key_store.store_keys(identity_id=identity_id,
private_signing_key=private_signing_key,
private_encryption_key=private_encryption_key)

return Identity(self, identity_id, public_encryption_key,
external_id, metadata)

def get_identity(self, identity_id, identity_to_retrieve=None):
"""
Gets the identity matching the given identity id.
:param identity_id: the authenticating identity id
:type identity_to_retrieve: str | None
:return: the identity
:rtype: :class:`~.Identity`
"""
response = self.api_client.get_identity(identity_id,
identity_id
if identity_to_retrieve is None
else identity_to_retrieve)
return Identity(self,
response.get("id"),
response.get("cryptoPublicKey"),
response.get("externalId"),
response.get("metadata"))


class Identity:
"""
An instance of this class encapsulates an identity in Covata Delta. An
identity can be a user, application, device or any other identifiable
entity that can create secrets and/or be target recipient of a secret.
An has two sets of asymmetric keys, for encryption and for signing of
requests. Identities may also have optional, public, searchable metadata
and a reference to an identifier in an external system.
"""

def __init__(self, parent, identity_id, public_encryption_key,
external_id, metadata):
"""
Creates a new identity in Delta with the provided metadata
and external id.
:param parent: the Delta client that constructed this instance
:param str public_encryption_key: the public signing key of the identity
:param external_id: the external id of the identity
:param metadata: the metadata belonging to the identity
:type parent: :class:`~.Client`
:type external_id: str | None
:type metadata: dict[str, str] | None
"""
self.__parent = parent
self.__identity_id = identity_id
self.__public_encryption_key = public_encryption_key
self.__external_id = external_id
self.__metadata = metadata

@property
def parent(self):
return self.__parent

@property
def identity_id(self):
return self.__identity_id

@property
def public_encryption_key(self):
return self.__public_encryption_key

@property
def external_id(self):
return self.__external_id

@property
def metadata(self):
return self.__metadata
6 changes: 2 additions & 4 deletions src/main/python/covata/delta/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@
from binascii import hexlify

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

__all__ = ["generate_private_key", "serialize_public_key",
Expand Down
7 changes: 4 additions & 3 deletions src/main/python/covata/delta/signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

from covata.delta.crypto import calculate_sha256hex
from . import crypto

__all__ = ["get_updated_headers"]

Expand Down Expand Up @@ -57,7 +57,8 @@ def __init__(self, *args, **kwargs):
self.__string_to_sign = "\n".join([
SIGNING_ALGORITHM,
self.cvt_date,
calculate_sha256hex(self.__canonical_request).decode('utf-8')])
crypto. calculate_sha256hex(self.__canonical_request).decode(
'utf-8')])

@property
def canonical_request(self):
Expand Down Expand Up @@ -147,7 +148,7 @@ def __get_hashed_payload(payload):
json.loads(payload.decode('utf-8')),
separators=(',', ':'),
sort_keys=True)
return calculate_sha256hex(sorted_payload).decode('utf-8')
return crypto.calculate_sha256hex(sorted_payload).decode('utf-8')


def __encode_uri(resource_path):
Expand Down
18 changes: 9 additions & 9 deletions src/test/python/test_apiclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import responses

from covata.delta import ApiClient
from covata.delta import crypto


@pytest.fixture(scope="function")
Expand All @@ -35,8 +36,7 @@ def api_client(key_store):


@responses.activate
def test_register_identity(mocker, api_client, key_store, private_key,
key2bytes):
def test_register_identity(mocker, api_client, private_key):
public_key = private_key.public_key()
expected_id = "identity_id"
responses.add(responses.POST,
Expand All @@ -47,21 +47,21 @@ def test_register_identity(mocker, api_client, key_store, private_key,
mocker.patch('covata.delta.crypto.generate_private_key',
return_value=private_key)

identity_id = api_client.register_identity("1", {})
crypto_key = key_store.get_private_encryption_key(identity_id)
signing_key = key_store.get_private_signing_key(identity_id)
public_signing_key = crypto.serialize_public_key(public_key)
public_encryption_key = crypto.serialize_public_key(public_key)

identity_id = api_client.register_identity(public_encryption_key,
public_signing_key, "1", {})

assert len(responses.calls) == 1
assert identity_id == expected_id
assert key2bytes(crypto_key) == key2bytes(private_key)
assert key2bytes(signing_key) == key2bytes(private_key)

request_body = json.loads(responses.calls[0].request.body.decode("utf-8"))
expected_request_body = dict(
externalId="1",
metadata=dict(),
cryptoPublicKey=b64encode(key2bytes(public_key)).decode("utf-8"),
signingPublicKey=b64encode(key2bytes(public_key)).decode("utf-8"),
cryptoPublicKey=public_encryption_key,
signingPublicKey=public_signing_key
)

assert request_body == expected_request_body
Expand Down

0 comments on commit a62e846

Please sign in to comment.