Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VerificationAPI #13

Merged
merged 49 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8646f81
feat(VerificationAPI): WiP
650elx Dec 6, 2023
31b75d9
feat(verification): WiP
650elx Dec 11, 2023
a9ee46c
feat(Verification): WiP
650elx Dec 19, 2023
5440396
feat(VerificationAPI): domain facade updated
650elx Dec 20, 2023
4ddccb6
feat(Verification): adapt exception structure
650elx Dec 20, 2023
f9fd637
feat(Verification): WiP
650elx Dec 20, 2023
1aa3683
feat(Signature): WiP
650elx Dec 20, 2023
b465ea4
feat(Verification): GET requests WiP
650elx Dec 21, 2023
53c9c07
feat(Verification): GET methods WiP
650elx Dec 21, 2023
d05360f
feat(Verification): GET methods finished
650elx Dec 21, 2023
e72af0e
feat(Verification): WiP
650elx Dec 22, 2023
0b38e27
feat(VerificationAPI): remaining API endpoints, models and tests added
650elx Jan 2, 2024
3789224
feat(Verification): validation exception added
650elx Jan 2, 2024
38cc672
refactor(Client): Client classes adapted
650elx Jan 2, 2024
0bfb679
feat(Verification): async tests added
650elx Jan 2, 2024
2f79350
feat(Verification): token refresh tests adopted
650elx Jan 2, 2024
fa5937a
Merge branch 'main' into DEVEXP-205-186-verification-API
650elx Jan 2, 2024
b074fc0
fix(pep8)
650elx Jan 2, 2024
7b5a205
CI:(Verification): map GH secrets
650elx Jan 3, 2024
67be035
ci(Verification): map GH secret
650elx Jan 3, 2024
5077a00
test(Verification): async client fixutre fixed
650elx Jan 3, 2024
f881811
fix(Endpoint): remove redundat method
650elx Jan 3, 2024
90fdaa7
refactor(Verification): ReportVerification model extracted
650elx Jan 5, 2024
fe2c438
Support for Python 3.8 removed, 3.12 added
650elx Jan 9, 2024
0aa0435
Revert "Support for Python 3.8 removed, 3.12 added"
650elx Jan 9, 2024
6e06e40
fix(Verification): unified method names added
650elx Jan 12, 2024
3bd60ba
refactor(Verification): main facade
650elx Jan 15, 2024
0ecbd2d
test(Verification): tests adapted
650elx Jan 15, 2024
143e4f1
fix(Verification): VerificationMethod enum added
650elx Jan 18, 2024
4c2df89
test(Verification): signature tests improved
650elx Jan 18, 2024
3317bf0
fix(Signature): naming fixed
650elx Jan 18, 2024
d52e838
fix(Singature): verification timestamp added to the constructor
650elx Jan 18, 2024
3c2a9cc
feat(Sinature): content type added to the init
650elx Jan 18, 2024
2de175f
fix(Verification): better naming
650elx Jan 18, 2024
a1fb0ca
fix(Verification): enum for method added
650elx Jan 18, 2024
86174a8
test(Verification): redunant lines removed
650elx Jan 18, 2024
edc92d4
feat(CI): Verification API specific env vars added
650elx Jan 19, 2024
6bbb1c3
Merge branch 'main' into DEVEXP-205-186-verification-API
650elx Jan 19, 2024
bd32d50
test(Logging): caplog usage fixed
650elx Jan 19, 2024
2a80584
test(Logging): simpfly caplog usage
650elx Jan 19, 2024
79da667
test(Logging): filter for only relevant log records
650elx Jan 19, 2024
3b1c765
feat(Verfication): DTO improved
650elx Jan 22, 2024
0ac9c0a
fix(Verification): simplyfy model usage
650elx Jan 22, 2024
97d53f2
feat(Verification): Start verification splitted into smaller DTOs
650elx Jan 22, 2024
f444e84
fix(Verification): identity type changed
650elx Jan 22, 2024
53691e1
fix(Verification): flashcall naming unified
650elx Jan 26, 2024
cc6a49e
fix(Verification): redundant transformation removed
650elx Jan 26, 2024
13ce08f
test(Verification): more specific DTOs assertions
650elx Jan 26, 2024
2d7a61e
fix(style): remove unused import
650elx Jan 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sinch/core/clients/sinch_client_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
class ClientBase(ABC):
"""
Sinch abstract base class for concrete Sinch Client implementations.
By default this SDK provides two implementations - sync and async.
By default, this SDK provides two implementations - sync and async.
Feel free to utilize any of them for you custom implementation.
"""
def __init__(
Expand Down
7 changes: 6 additions & 1 deletion sinch/core/clients/sinch_client_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@ def __init__(
logger=None,
logger_name: str = None,
disable_https=False,
connection_timeout=10
connection_timeout=10,
verification_key: str = None,
verification_secret: str = None
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
):
self.key_id = key_id
self.key_secret = key_secret
self.project_id = project_id
self.verification_key = verification_key
self.verification_secret = verification_secret
self.connection_timeout = connection_timeout
self.auth_origin = "auth.sinch.com"
self.numbers_origin = "numbers.api.sinch.com"
self.verification_origin = "verification.api.sinch.com"
self._conversation_region = "eu"
self._conversation_domain = ".conversation.api.sinch.com"
self._sms_region = "us"
Expand Down
10 changes: 8 additions & 2 deletions sinch/core/clients/sinch_client_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from sinch.domains.numbers import Numbers
from sinch.domains.conversation import Conversation
from sinch.domains.sms import SMS
from sinch.domains.verification import Verification


class Client(ClientBase):
Expand All @@ -20,7 +21,9 @@ def __init__(
key_secret,
project_id,
logger_name=None,
logger=None
logger=None,
verification_key: str = None,
verification_secret: str = None
):
super().__init__(
key_id=key_id,
Expand All @@ -36,9 +39,12 @@ def __init__(
logger_name=logger_name,
logger=logger,
transport=HTTPTransportRequests(self),
token_manager=TokenManager(self)
token_manager=TokenManager(self),
verification_key=verification_key,
verification_secret=verification_secret
)
self.authentication = Authentication(self)
self.numbers = Numbers(self)
self.conversation = Conversation(self)
self.sms = SMS(self)
self.verification = Verification(self)
1 change: 1 addition & 0 deletions sinch/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ class HTTPMethods(Enum):
class HTTPAuthentication(Enum):
BASIC = "BASIC"
OAUTH = "OAUTH"
SIGNED = "SIGNED"
2 changes: 2 additions & 0 deletions sinch/core/ports/http_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def authenticate(self, endpoint, request_data):
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
elif endpoint.HTTP_AUTHENTICATION == HTTPAuthentication.SIGNED.value:
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
pass

return request_data

Expand Down
45 changes: 45 additions & 0 deletions sinch/core/signature.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import hashlib
import hmac
import base64
from datetime import datetime, timezone
import json


class Signature:
def __init__(self, sinch, http_method, request_data, request_uri):
self.sinch = sinch
self.http_method = http_method
self.content_type = 'application/json; charset=UTF-8'
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
self.request_data = request_data
self.signature_time = datetime.now(timezone.utc).isoformat()
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
self.request_uri = request_uri
self.authorization_signature = None

def get_http_headers_with_signature(self):
return {
"Content-Type": self.content_type,
"Authorization": (
f"Application {self.sinch.configuration.verification_key}:{self.authorization_signature}"
),
"x-timestamp": self.signature_time
}

def calculate(self):
b64_encoded_application_secret = base64.b64decode(self.sinch.configuration.verification_secret)
encoded_verification_request = json.dumps(self.request_data).encode()
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
md5_verification_request = hashlib.md5(encoded_verification_request)
encoded_md5_to_base64_verification_request = base64.b64encode(md5_verification_request.digest())

request_timestamp = "x-timestamp:" + self.signature_time

string_to_sign = (
self.http_method + '\n'
+ encoded_md5_to_base64_verification_request.decode() + '\n'
+ self.content_type + '\n'
+ request_timestamp + '\n'
+ self.request_uri
)

self.authorization_signature = base64.b64encode(
hmac.new(b64_encoded_application_secret, string_to_sign.encode(), hashlib.sha256).digest()
).decode()
46 changes: 46 additions & 0 deletions sinch/domains/verification/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from sinch.domains.verification.endpoints.start_verification import StartVerificationEndpoint
from sinch.domains.verification.models.responses import StartVerificationResponse
from sinch.domains.verification.models.requests import StartVerificationRequest


class Verification:
"""
Documentation for the Verification API: https://developers.sinch.com/docs/verification
"""
def __init__(self, sinch):
self._sinch = sinch

def start(
self,
identity: dict,
method: str,
reference: str = None,
custom: str = None,
flash_call_options: object = None
) -> StartVerificationResponse:
return self._sinch.configuration.transport.request(
StartVerificationEndpoint(
request_data=StartVerificationRequest(
identity=identity,
method=method,
reference=reference,
custom=custom,
flash_call_options=flash_call_options
)
)
)

def get_by_id(self):
pass

def get_by_identity(self):
pass

def get_by_reference(self):
pass

def report_using_id(self):
pass

def report_using_identity(self):
pass
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
25 changes: 25 additions & 0 deletions sinch/domains/verification/endpoints/start_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from sinch.core.models.http_response import HTTPResponse
from sinch.domains.verification.endpoints.verification_endpoint import VerificationEndpoint
from sinch.core.enums import HTTPAuthentication, HTTPMethods
# from sinch.domains.conversation.models.contact.responses import CreateConversationContactResponse
# from sinch.domains.conversation.models.contact.requests import CreateConversationContactRequest


class StartVerificationEndpoint(VerificationEndpoint):
ENDPOINT_URL = "{origin}/verification/v1/verifications"
HTTP_METHOD = HTTPMethods.POST.value
HTTP_AUTHENTICATION = HTTPAuthentication.SIGNED.value

def __init__(self, request_data):
self.request_data = request_data

def build_url(self, sinch):
return self.ENDPOINT_URL.format(
origin=sinch.configuration.verification_origin,
)

def request_body(self):
return self.request_data.as_json()

def handle_response(self, response: HTTPResponse):
pass
13 changes: 13 additions & 0 deletions sinch/domains/verification/endpoints/verification_endpoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sinch.core.models.http_response import HTTPResponse
from sinch.core.endpoint import HTTPEndpoint
from sinch.domains.verification.exceptions import VerificationException


class VerificationEndpoint(HTTPEndpoint):
def handle_response(self, response: HTTPResponse):
if response.status_code >= 400:
raise VerificationException(
message=response.body["error"].get("message"),
response=response,
is_from_server=True
)
5 changes: 5 additions & 0 deletions sinch/domains/verification/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from sinch.core.exceptions import SinchException


class VerificationException(SinchException):
pass
Empty file.
11 changes: 11 additions & 0 deletions sinch/domains/verification/models/requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from dataclasses import dataclass
from sinch.core.models.base_model import SinchRequestBaseModel


@dataclass
class StartVerificationRequest(SinchRequestBaseModel):
identity: dict
method: str
asein-sinch marked this conversation as resolved.
Show resolved Hide resolved
reference: str
custom: str
flash_call_options: object
13 changes: 13 additions & 0 deletions sinch/domains/verification/models/responses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from dataclasses import dataclass
from typing import List

from sinch.core.models.base_model import SinchBaseModel
from sinch.domains.numbers.models import Number


@dataclass
class StartVerificationResponse(SinchBaseModel):
asein-sinch marked this conversation as resolved.
Show resolved Hide resolved
id: str
method: str
sms: dict
_links: list
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ def origin_phone_number():
return os.getenv("ORIGIN_PHONE_NUMBER")


@pytest.fixture
def verification_key():
return os.getenv("VERIFICATION_KEY")


@pytest.fixture
def verification_secret():
return os.getenv("VERIFICATION_SECRET")


@pytest.fixture
def app_id():
return os.getenv("APP_ID")
Expand Down
21 changes: 21 additions & 0 deletions tests/e2e/verification/test_start_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from sinch.domains.verification.models.responses import StartVerificationResponse


def test_start_verification(
sinch_client_sync,
phone_number,
verification_key,
verification_secret
):
sinch_client_sync.configuration.verification_key = verification_key
sinch_client_sync.configuration.verification_secret = verification_secret

verification_response = sinch_client_sync.verification.start(
method="sms",
identity={
"type": "number",
"endpoint": phone_number
}
)

assert isinstance(verification_response, StartVerificationResponse)
12 changes: 12 additions & 0 deletions tests/integration/test_request_signing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from sinch.core.signature import Signature


def test_verification_signature(sinch_client_sync):
signature = Signature(
sinch_client_sync,
http_method="POST",
request_data={},
request_uri="/verification/v1/verifications"
)
signature.calculate()
assert signature.authorization_signature
JPPortier marked this conversation as resolved.
Show resolved Hide resolved
Loading