Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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="<Usuário da licença>",
sl_password="<Senha da licença>",
sl_host="<Servidor de Autenticação>",
sl_port="<Porta de Autenticação>"
)
```

## Segurança

O SDK também serve de exemplo para uma implementação aderente aos requisitos
Expand Down
142 changes: 125 additions & 17 deletions cpqdtrd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,40 @@ def __init__(
password: str = "",
retry: int = 60,
retry_period: float = 2,
sl_host=None,
sl_port=None,
sl_protocol="https",
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_protocol = sl_protocol
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():
Expand All @@ -54,7 +77,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)
Expand All @@ -64,34 +95,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,
Expand All @@ -105,6 +172,7 @@ def query(
start_date: datetime = None,
end_date: datetime = None,
):
self.check_token_expiration()
request = "{}/query/job".format(self._url)

params = {}
Expand All @@ -127,13 +195,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,
Expand All @@ -144,21 +221,52 @@ 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,
self._sl_protocol
):
request = requests.post(
url="{}://{}:{}/auth/token".format(self._sl_protocol, 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}
66 changes: 41 additions & 25 deletions cpqdtrd/cert.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,56 @@
# -*- coding: utf-8 -*-
from OpenSSL import crypto
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
import ipaddress
from datetime import datetime, timedelta, timezone
import re


def create_self_signed_cert(hostname, cert_path, pkey_path):
"""Generate a certificate and private key, and returns the public key as str."""
# create a key pair
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 2048)

# create a self-signed cert
cert = crypto.X509()
cert.get_subject().C = "BR"
cert.get_subject().ST = "Sao Paulo"
cert.get_subject().L = "Campinas"
cert.get_subject().O = hostname
cert.get_subject().OU = hostname
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
cert.set_issuer(cert.get_subject())
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = datetime.now(timezone.utc)
subject = issuer = x509.Name(
[
x509.NameAttribute(NameOID.COUNTRY_NAME, "BR"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Sao Paulo"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Campinas"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, hostname),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, hostname),
]
)

if re.compile(r"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}").match(hostname):
alt_name = "IP:" + hostname
subject_alt_name = x509.IPAddress(ipaddress.ip_address(hostname))
else:
alt_name = "DNS:" + hostname
cert.add_extensions(
[crypto.X509Extension(b"subjectAltName", False, alt_name.encode())]
)
subject_alt_name = x509.DNSName(hostname)

cert.set_pubkey(k)
cert.sign(k, "sha512")
cert = (
x509.CertificateBuilder()
.subject_name(subject)
.issuer_name(issuer)
.public_key(key.public_key())
.serial_number(1000)
.not_valid_before(now)
.not_valid_after(now + timedelta(days=3650))
.add_extension(
x509.SubjectAlternativeName([subject_alt_name]),
critical=False,
)
.sign(private_key=key, algorithm=hashes.SHA512())
)

with open(cert_path, "wb") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
f.write(cert.public_bytes(serialization.Encoding.PEM))
with open(pkey_path, "wb") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
f.write(
key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
)

return
Loading