From 1663962fb9994481a44bd4d6b6665ba593c3f262 Mon Sep 17 00:00:00 2001 From: Felipe Biaggi Carvalho Date: Tue, 28 Dec 2021 10:51:57 -0300 Subject: [PATCH 1/3] =?UTF-8?q?Suporte=20a=20nova=20authentica=C3=A7=C3=A3?= =?UTF-8?q?o=20SL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- cpqdtrd/api.py | 139 ++++++++++++++++++++++++++++++++++++++++------ cpqdtrd/client.py | 25 +++++++-- setup.py | 2 +- 4 files changed, 143 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index b447940..715d625 100755 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Testado com Python 3.7. Para dependências, ver _requirements.txt_. Para instalação automática do SDK e dependências via `pip`, execute a linha abaixo: ```shell -$ pip install git+https://github.com/CPqD/trd-sdk-python.git@v1.0.1 +$ pip install git+https://github.com/CPqD/trd-sdk-python.git@v1.1.0 ``` #### Servidor WSGI para _callbacks_ via Webhooks diff --git a/cpqdtrd/api.py b/cpqdtrd/api.py index 0363338..dbb618a 100644 --- a/cpqdtrd/api.py +++ b/cpqdtrd/api.py @@ -29,17 +29,38 @@ def __init__( password: str = "", retry: int = 60, retry_period: float = 2, + sl_host=None, + sl_port=None, + sl_token=None, + sl_username=None, + sl_password=None, ): + self._log = logging.getLogger("cpqdtrd.api") + i = 0 ok = False self._url = url + self._sl_host = sl_host + self._sl_port = sl_port + self._sl_username = sl_username + self._sl_password = sl_password + + if sl_token: + self._sl_token = sl_token + self._token_expiration = None + else: + self._sl_token, self._token_expiration = self.create_token() + + if self._sl_token: + self._headers = {"Authorization": "Bearer " + self._sl_token} + else: + self._headers = {} if username and password: self._auth = requests.auth.HTTPBasicAuth(username, password) else: self._auth = None - self._log = logging.getLogger("cpqdtrd.api") while not ok: try: for r in self.query(): @@ -54,7 +75,15 @@ def __init__( msg = "API call retries exceeded" raise self.TimeoutException(msg) - def create(self, file_path: str, tag: str = None, config: List[str] = None, callbacks_url: List = []): + def create( + self, + file_path: str, + tag: str = None, + config: List[str] = None, + callbacks_url: List = [], + ): + self.check_token_expiration() + upload_request = "{}/job/create".format(self._url) if tag: upload_request += "?tag={}".format(tag) @@ -64,34 +93,70 @@ def create(self, file_path: str, tag: str = None, config: List[str] = None, call data["config"] = config if len(callbacks_url) > 0: - data["callback_urls"] = ','.join(callbacks_url) + data["callback_urls"] = ",".join(callbacks_url) with open(file_path, "rb") as f: upload_file = [("upload_file", f)] return requests.post( - upload_request, data=data, files=upload_file, auth=self._auth + upload_request, + data=data, + files=upload_file, + auth=self._auth, + headers=self._headers, ) def list_jobs(self, page: int = 1, limit: int = 100, tag: str = None): + self.check_token_expiration() + params = {"page": page, "limit": limit} if tag: params["tag"] = tag - return requests.get("{}/job".format(self._url), params=params, auth=self._auth) + return requests.get( + "{}/job".format(self._url), + params=params, + auth=self._auth, + headers=self._headers, + ) def status(self, job_id: str): - return requests.get("{}/job/status/{}".format(self._url, job_id), auth=self._auth) + self.check_token_expiration() + return requests.get( + "{}/job/status/{}".format(self._url, job_id), + auth=self._auth, + headers=self._headers, + ) def result(self, job_id: str): - return requests.get("{}/job/result/{}".format(self._url, job_id), auth=self._auth) + self.check_token_expiration() + return requests.get( + "{}/job/result/{}".format(self._url, job_id), + auth=self._auth, + headers=self._headers, + ) def stop(self, job_id: str): - return requests.post("{}/job/stop/{}".format(self._url, job_id), auth=self._auth) + self.check_token_expiration() + return requests.post( + "{}/job/stop/{}".format(self._url, job_id), + auth=self._auth, + headers=self._headers, + ) def retry(self, job_id: str): - return requests.post("{}/job/retry/{}".format(self._url, job_id), auth=self._auth) + self.check_token_expiration() + return requests.post( + "{}/job/retry/{}".format(self._url, job_id), + auth=self._auth, + headers=self._headers, + ) def delete(self, job_id: str): - return requests.delete("{}/job/{}".format(self._url, job_id), auth=self._auth) + self.check_token_expiration() + return requests.delete( + "{}/job/{}".format(self._url, job_id), + auth=self._auth, + headers=self._headers, + ) def query( self, @@ -105,6 +170,7 @@ def query( start_date: datetime = None, end_date: datetime = None, ): + self.check_token_expiration() request = "{}/query/job".format(self._url) params = {} @@ -127,13 +193,22 @@ def query( if end_date: params["end_date"] = end_date.isoformat() - with closing(requests.get(request, params=params, stream=True, auth=self._auth)) as r: + with closing( + requests.get( + request, + params=params, + stream=True, + auth=self._auth, + headers=self._headers, + ) + ) as r: for line in r.iter_lines(): yield line def webhook_whoami(self): + self.check_token_expiration() whoami_request = "{}/webhook/whoami".format(self._url) - return requests.get(whoami_request, auth=self._auth) + return requests.get(whoami_request, auth=self._auth, headers=self._headers) def webhook_validate( self, @@ -144,21 +219,51 @@ def webhook_validate( token: str = "", crt: str = "", ): + self.check_token_expiration() test_request = "{}/webhook/validate".format(self._url) webhook_url = host if port is not None: webhook_url += ":{}".format(port) - payload = { - "url": webhook_url - } + payload = {"url": webhook_url} if timeout: payload["timeout"] = int(timeout) if retries: payload["retries"] = int(retries) if crt is not None or token is not None: r = requests.post( - test_request, params=payload, auth=self._auth, json={"crt": crt, "token": token} + test_request, + params=payload, + auth=self._auth, + json={"crt": crt, "token": token}, + headers=self._headers, ) else: - r = requests.get(test_request, params=payload, auth=self._auth) + r = requests.get( + test_request, params=payload, auth=self._auth, headers=self._headers + ) return r + + def create_token(self): + if None not in ( + self._sl_host, + self._sl_port, + self._sl_username, + self._sl_password, + ): + request = requests.post( + url="http://{}:{}/auth/token".format(self._sl_host, self._sl_port), + auth=(self._sl_username, self._sl_password), + timeout=10, + ) + if request.status_code == 200: + access_token = request.json()["access_token"] + token_expiration = int(request.json()["expires_in"]) + int(time.time()) + return access_token, token_expiration + request.raise_for_status() + return None, None + + def check_token_expiration(self): + if self._token_expiration: + if time.time() >= self._token_expiration: + self._sl_token, self._token_expiration = self.create_token() + self._headers = {"Authorization": "Bearer " + self._sl_token} diff --git a/cpqdtrd/client.py b/cpqdtrd/client.py index d66a8f6..5aae33c 100644 --- a/cpqdtrd/client.py +++ b/cpqdtrd/client.py @@ -34,13 +34,27 @@ def __init__( password=None, cert_path=None, key_path=None, + sl_host=None, + sl_port=None, + sl_token=None, + sl_username=None, + sl_password=None, **flask_kwargs ): + self._log = logging.getLogger(self.__class__.__name__) + self._result_events = dd(dict) # Event variable dict self._flask_kwargs = flask_kwargs self.api = TranscriptionApi( - api_url, username=username, password=password + url=api_url, + username=username, + password=password, + sl_host=sl_host, + sl_port=sl_port, + sl_token=sl_token, + sl_username=sl_username, + sl_password=sl_password, ) if webhook_host is not None: @@ -64,8 +78,6 @@ def __init__( self._cert_path = cert_path self._key_path = key_path - self._log = logging.getLogger(self.__class__.__name__) - # User callbacks self._callbacks = {} @@ -147,7 +159,6 @@ def root_callback(job_id): ) ) - def __del__(self): self.stop() @@ -159,7 +170,7 @@ def stop(self): def register_callback(self, callback, name=None): """Register a callback with optional name.""" - #print("register_callback:", callback, name) + # print("register_callback:", callback, name) if name is None: name = "_callback_{}".format(len(self._callbacks)) elif name[:9] == "_callback": @@ -222,7 +233,9 @@ def unregister_all(self): del self._callbacks[name] self._reset_start() - def transcribe(self, path, tag=None, config=None, timeout="auto", delete_after=True): + def transcribe( + self, path, tag=None, config=None, timeout="auto", delete_after=True + ): """ Transcribe an audio file. diff --git a/setup.py b/setup.py index 9347155..43fa261 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ setup( name="cpqdtrd", - version="1.0.1", + version="1.1.0", description="CPqD Dialog Transcription Client", install_requires=install_requires, author="Akira Miasato", From 6b9e2e70422f7c82730ea8b21001f132d0467468 Mon Sep 17 00:00:00 2001 From: Felipe Biaggi Carvalho Date: Tue, 28 Dec 2021 11:17:06 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Atualiza=C3=A7=C3=A3o=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 715d625..f416e83 100755 --- a/README.md +++ b/README.md @@ -153,6 +153,28 @@ for id in c.results: ) ``` +## Autenticação JWT +O SDK passa a fornecer autenticação utilizando tokens de autenticação em +formato JWT. Os tokens são gerados automaticamente com a inicialização da classe +de transcrição, além disso em cada requisição são feitas validações do tempo de +vida do token. + +```python +from cpqdtrd import TranscriptionClient + +client = TranscriptionClient( + api_url="https://speech.cpqd.com.br/trd/v3", + webhook_port=443, # Outbound, precisa de redirecionamento para a WAN + webhook_host="100.100.100.100", # IP externo ou DNS + webhook_listener='0.0.0.0', + webhook_protocol="https", + sl_username="", + sl_password="", + sl_host="", + sl_port="" + ) +``` + ## Segurança O SDK também serve de exemplo para uma implementação aderente aos requisitos From b1efc0fd20ad42a8d60c6ff2f61331f758695f8d Mon Sep 17 00:00:00 2001 From: Felipe Biaggi Carvalho Date: Tue, 28 Dec 2021 14:26:34 -0300 Subject: [PATCH 3/3] =?UTF-8?q?Adi=C3=A7=C3=A3o=20de=20protocolo=20de=20co?= =?UTF-8?q?nfigura=C3=A7=C3=A3o=20para=20SL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cpqdtrd/api.py | 5 ++++- cpqdtrd/client.py | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cpqdtrd/api.py b/cpqdtrd/api.py index dbb618a..5b19be5 100644 --- a/cpqdtrd/api.py +++ b/cpqdtrd/api.py @@ -31,6 +31,7 @@ def __init__( retry_period: float = 2, sl_host=None, sl_port=None, + sl_protocol="https", sl_token=None, sl_username=None, sl_password=None, @@ -42,6 +43,7 @@ def __init__( self._url = url self._sl_host = sl_host self._sl_port = sl_port + self._sl_protocol = sl_protocol self._sl_username = sl_username self._sl_password = sl_password @@ -249,9 +251,10 @@ def create_token(self): self._sl_port, self._sl_username, self._sl_password, + self._sl_protocol ): request = requests.post( - url="http://{}:{}/auth/token".format(self._sl_host, self._sl_port), + url="{}://{}:{}/auth/token".format(self._sl_protocol, self._sl_host, self._sl_port), auth=(self._sl_username, self._sl_password), timeout=10, ) diff --git a/cpqdtrd/client.py b/cpqdtrd/client.py index 5aae33c..62734c5 100644 --- a/cpqdtrd/client.py +++ b/cpqdtrd/client.py @@ -36,6 +36,7 @@ def __init__( key_path=None, sl_host=None, sl_port=None, + sl_protocol="https", sl_token=None, sl_username=None, sl_password=None, @@ -52,6 +53,7 @@ def __init__( password=password, sl_host=sl_host, sl_port=sl_port, + sl_protocol=sl_protocol, sl_token=sl_token, sl_username=sl_username, sl_password=sl_password,