Skip to content

Commit

Permalink
feat: add invitation features
Browse files Browse the repository at this point in the history
  • Loading branch information
cowan-macady committed Mar 14, 2023
1 parent dc1ef77 commit 306b26a
Show file tree
Hide file tree
Showing 9 changed files with 576 additions and 4 deletions.
42 changes: 41 additions & 1 deletion indykite_sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,10 +1284,50 @@ positional arguments:
python3 api.py start_forgotten_password DIGITAL_TWIN_ID TENANT_ID
```
80. Create email invitation (invite a user)
```shell
python3 api.py create_email_invitation TENANT_ID EMAIL
```
```shell
positional arguments:
digital_twin_id String
tenant_id String
email String
```
81. Check invitation state : check if invitation invalid, in future, pending, accepted, expired, cancelled
```shell
python3 api.py check_invitation_state REFERENCE_ID
```
```shell
positional arguments: one of:
reference_id id used to create the invitation
invitation_token token used in the invitation email
```
82. Resend an invitation
```shell
python3 api.py resend_invitation REFERENCE_ID
```
```shell
positional arguments:
reference_id id used to create the invitation
```
83. Cancel an invitation
```shell
python3 api.py cancel_invitation REFERENCE_ID
```
```shell
positional arguments:
reference_id id used to create the invitation
```
----------------
Expand Down
83 changes: 83 additions & 0 deletions indykite_sdk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import json
from datetime import datetime
from uuid import UUID
import uuid
from google.protobuf.json_format import MessageToJson
import os

Expand Down Expand Up @@ -493,6 +494,32 @@ def main():
start_forgotten_password.add_argument("digital_twin_id", help="gid ID of the digital twin with forgotten password")
start_forgotten_password.add_argument("tenant_id", help="gid ID of the tenant")

# create_email_invitation
create_email_invitation = subparsers.add_parser("create_email_invitation")
create_email_invitation.add_argument("tenant_id", help="gid ID of the tenant")
create_email_invitation.add_argument("email", help="invitee's email")

# create_email_invitation_with_date
create_email_invitation_with_date = subparsers.add_parser("create_email_invitation_with_date")
create_email_invitation_with_date.add_argument("tenant_id", help="gid ID of the tenant")
create_email_invitation_with_date.add_argument("email", help="invitee's email")

# create_mobile_invitation
create_mobile_invitation = subparsers.add_parser("create_mobile_invitation")
create_mobile_invitation.add_argument("tenant_id", help="gid ID of the tenant")
create_mobile_invitation.add_argument("mobile", help="invitee's mobile")

# check_invitation_state
check_invitation_state = subparsers.add_parser("check_invitation_state")
check_invitation_state.add_argument("reference_id", help="external ID of the invitation reference")

# resend_invitation
resend_invitation = subparsers.add_parser("resend_invitation")
resend_invitation.add_argument("reference_id", help="external ID of the invitation reference")

# cancel_invitation
cancel_invitation = subparsers.add_parser("cancel_invitation")
cancel_invitation.add_argument("reference_id", help="external ID of the invitation reference")

args = parser.parse_args()
local = args.local
Expand Down Expand Up @@ -1741,6 +1768,62 @@ def main():
else:
print("Invalid forgotten password response")

elif command == "create_email_invitation":
reference_id = str(uuid.uuid4())
# any unique external reference id
print(reference_id)
tenant_id = args.tenant_id
email = args.email
invitation_response = client.create_email_invitation(tenant_id, reference_id, email, invite_at_time=None,
expire_time=None, message_attributes=None)
if invitation_response is not None:
print(invitation_response)
else:
print("Invalid invitation response")

elif command == "create_email_invitation_with_date":
reference_id = str(uuid.uuid4())
print(reference_id)
tenant_id = args.tenant_id
email = args.email
t = datetime.now().timestamp()
invite_at_time_in_seconds = int(t) + 3600
expire_time_in_seconds = invite_at_time_in_seconds + 172800 # now + 2 days example
message_attributes = {"attr1": "value1"}
invitation_response = client.create_email_invitation(tenant_id, reference_id, email,
invite_at_time_in_seconds,
expire_time_in_seconds,
message_attributes)
if invitation_response is not None:
print(invitation_response)
else:
print("Invalid invitation response")

elif command == "check_invitation_state":
reference_id = args.reference_id
# check with either reference_id or invitation_token
invitation_response = client.check_invitation_state(reference_id, None)
if invitation_response is not None:
print_response(invitation_response)
else:
print("Invalid invitation response")

elif command == "resend_invitation":
reference_id = args.reference_id
invitation_response = client.resend_invitation(reference_id)
if invitation_response is not None:
print(invitation_response)
else:
print("Invalid invitation response")

elif command == "cancel_invitation":
reference_id = args.reference_id
invitation_response = client.cancel_invitation(reference_id)
if invitation_response is not None:
print(invitation_response)
else:
print("Invalid invitation response")


def print_verify_info(digital_twin_info): # pragma: no cover
print("Digital twin info")
Expand Down
1 change: 1 addition & 0 deletions indykite_sdk/identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,4 @@ def __init__(self, local=False):
from .import_digital_twins import import_digital_twins
from .consent import create_consent, list_consents, revoke_consent
from .forgotten_password import start_forgotten_password_flow
from .invitation import create_email_invitation, create_mobile_invitation, check_invitation_state, resend_invitation, cancel_invitation
3 changes: 1 addition & 2 deletions indykite_sdk/identity/get_digital_twin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ def get_digital_twin_by_token(self, token, fields):
if len(token) < 32:
raise Exception("Token must be 32 chars or more.")
except Exception as exception:
print(exception)
return None
return logger.logger_error(exception)

try:
response = self.stub.GetDigitalTwin(
Expand Down
114 changes: 114 additions & 0 deletions indykite_sdk/identity/invitation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from indykite_sdk.indykite.identity.v1beta2 import identity_management_api_pb2 as pb2
import sys
from indykite_sdk.utils.logger import handle_excepthook, logger_error
from google.protobuf.timestamp_pb2 import Timestamp
from indykite_sdk.indykite.objects.v1beta1 import struct_pb2 as struct
from indykite_sdk.utils.message_to_value import arg_to_value
from indykite_sdk.model.invitation import Invitation


def create_email_invitation(self, tenant_id, reference_id, email, invite_at_time, expire_time, message_attributes):
sys.excepthook = handle_excepthook
try:
invite_at_time_seconds = None
if invite_at_time:
invite_at_time_seconds = Timestamp(seconds=invite_at_time)
expire_time_seconds = None
if expire_time:
expire_time_seconds = Timestamp(seconds=expire_time)
fields = {}
if message_attributes:
for key, value in message_attributes.items():
fields[key] = arg_to_value(value)

request = pb2.CreateInvitationRequest(
tenant_id=str(tenant_id),
reference_id=str(reference_id),
email=str(email),
message_attributes=struct.MapValue(fields=fields),
invite_at_time=invite_at_time_seconds,
expire_time=expire_time_seconds
)
response = self.stub.CreateInvitation(request)
if not response:
return None
return response

except Exception as exception:
return logger_error(exception)


def create_mobile_invitation(self, tenant_id, reference_id, mobile, invite_at_time, expire_time, message_attributes):
# mobile not implemented yet
sys.excepthook = handle_excepthook
try:
invite_at_time_seconds = None
if invite_at_time:
invite_at_time_seconds = Timestamp(seconds=invite_at_time)
expire_time_seconds = None
if expire_time:
expire_time_seconds = Timestamp(seconds=expire_time)
fields = {}
if message_attributes:
for key, value in message_attributes.items():
fields[key] = arg_to_value(value)

request = pb2.CreateInvitationRequest(
tenant_id=str(tenant_id),
reference_id=str(reference_id),
mobile=str(mobile),
message_attributes=struct.MapValue(fields=fields),
invite_at_time=invite_at_time_seconds,
expire_time=expire_time_seconds
)
response = self.stub.CreateInvitation(request)
if not response:
return None
return response

except Exception as exception:
return logger_error(exception)


def check_invitation_state(self, reference_id, invitation_token):
sys.excepthook = handle_excepthook
try:
if reference_id and invitation_token:
return logger_error("You can not specify both the reference ID and the invitation token")
elif not reference_id and not invitation_token:
return logger_error("You have not specified any identifier: neither reference ID nor invitation token")
response = self.stub.CheckInvitationState(
pb2.CheckInvitationStateRequest(
reference_id=reference_id,
invitation_token=invitation_token
)
)
return Invitation.deserialize(response.invitation)
except Exception as exception:
return logger_error(exception)


def resend_invitation(self, reference_id):
sys.excepthook = handle_excepthook
try:
response = self.stub.ResendInvitation(
pb2.ResendInvitationRequest(
reference_id=reference_id
)
)
return response
except Exception as exception:
return logger_error(exception)


def cancel_invitation(self, reference_id):
sys.excepthook = handle_excepthook
try:
response = self.stub.CancelInvitation(
pb2.CancelInvitationRequest(
reference_id=reference_id
)
)
return response
except Exception as exception:
return logger_error(exception)
45 changes: 45 additions & 0 deletions indykite_sdk/model/invitation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from indykite_sdk.indykite.identity.v1beta2.model_pb2 import Invitation, DigitalTwin
from indykite_sdk.utils import timestamp_to_date
from google.protobuf.json_format import MessageToDict


class Invitation:
@classmethod
def deserialize(cls, message):
if message is None:
return None
invitation = Invitation(
tenant_id=str(message.tenant_id),
reference_id = str(message.reference_id),
state = message.state)
if message.HasField('invite_at_time'):
invitation.invite_at_time = timestamp_to_date(message.invite_at_time)
if message.HasField('expire_time'):
invitation.expire_time = timestamp_to_date(message.expire_time)
if message.HasField('message_attributes'):
invitation.message_attributes = MessageToDict(message.message_attributes)
if message.HasField('accepted_by'):
invitation.accepted_by = DigitalTwin(id=message.accepted_by.id,
tenant_id=message.accepted_by.tenant_id,
kind=message.accepted_by.kind,
state=message.accepted_by.state,
tags=message.accepted_by.tags)

if message.HasField('email'):
invitation.email = str(message.email)
if message.HasField('mobile'):
invitation.mobile = str(message.mobile)

return invitation

def __init__(self, tenant_id, reference_id, state):

self.tenant_id = tenant_id
self.reference_id = reference_id
self.state = state
self.invite_at_time = None
self.expire_time = None
self.message_attributes = None
self.accepted_by = None
self.email = None
self.mobile = None
26 changes: 26 additions & 0 deletions indykite_sdk/utils/message_to_value.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
'''Converts gRPC object to a value'''
from indykite_sdk.indykite.identity.v1beta2.model_pb2 import PostalAddress as PostalAddressPb
from indykite_sdk.model.postal_address import PostalAddress
from indykite_sdk.indykite.objects.v1beta1 import struct_pb2 as struct
from datetime import datetime, date
from google.protobuf.timestamp_pb2 import Timestamp


def object_to_value(grpc_object):
'''Converts gRPC object to a value'''
Expand Down Expand Up @@ -52,3 +56,25 @@ def object_to_value(grpc_object):
return mapped

return None


def arg_to_value(value):
if not value:
return struct.Value(null_value=value)

if isinstance(value, int):
return struct.Value(integer_value=value)

if isinstance(value, bool):
return struct.Value(bool_value=value)

if isinstance(value, float):
return struct.Value(double_value=value)

if isinstance(value, str):
return struct.Value(string_value=value)

if isinstance(value, bytes):
return struct.Value(bytes_value=value)

return None
2 changes: 1 addition & 1 deletion tests/test_get_digital_twin.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def test_get_digital_twin_by_token_short_token(capsys):
response = client.get_digital_twin_by_token(token, [])
captured = capsys.readouterr()

assert captured.out == "Token must be 32 chars or more.\n"
assert "Token must be 32 chars or more" in captured.err


def test_get_digital_twin_by_token_expired_token(capsys):
Expand Down

0 comments on commit 306b26a

Please sign in to comment.