Skip to content
Merged
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
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "uid2_client"
version = "2.4.6"
version = "3.0.0a1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this how the version should be bumped, or is it done as part of the pipeline?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its done by the pipeline. It will bump up the major, minor or patch number corresponding to the Github action that's run. To publish an alpha package it will have to done manually

authors = [
{ name = "UID2 team", email = "unifiedid-admin@thetradedesk.com" }
]
Expand All @@ -18,9 +18,9 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
requires-python = ">=3.6"
requires-python = ">=3.8"
dependencies = [
"setuptools",
"requests",
"pycryptodome",
"bitarray"
]
Expand Down
9 changes: 5 additions & 4 deletions tests/test_identity_map_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import unittest
from urllib.error import URLError, HTTPError

import requests

from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone

Expand Down Expand Up @@ -134,19 +135,19 @@ def test_identity_map_bad_url(self):
identity_map_input = IdentityMapInput.from_emails(
["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"])
client = IdentityMapClient("https://operator-bad-url.uidapi.com", os.getenv("UID2_API_KEY"), os.getenv("UID2_SECRET_KEY"))
self.assertRaises(URLError, client.generate_identity_map, identity_map_input)
self.assertRaises(requests.exceptions.ConnectionError, client.generate_identity_map, identity_map_input)

def test_identity_map_bad_api_key(self):
identity_map_input = IdentityMapInput.from_emails(
["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"])
client = IdentityMapClient(os.getenv("UID2_BASE_URL"), "bad-api-key", os.getenv("UID2_SECRET_KEY"))
self.assertRaises(HTTPError, client.generate_identity_map,identity_map_input)
self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map,identity_map_input)

def test_identity_map_bad_secret(self):
identity_map_input = IdentityMapInput.from_emails(
["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"])
client = IdentityMapClient(os.getenv("UID2_BASE_URL"), os.getenv("UID2_API_KEY"), "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=")
self.assertRaises(HTTPError, client.generate_identity_map,
self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map,
identity_map_input)

def assert_mapped(self, response, dii):
Expand Down
12 changes: 6 additions & 6 deletions tests/test_publisher_client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import os
import unittest

import requests

from uid2_client import Uid2PublisherClient
from uid2_client import TokenGenerateInput
from uid2_client import TokenGenerateResponse
from uid2_client.identity_tokens import IdentityTokens
from urllib.request import HTTPError


class PublisherEuidIntegrationTests(unittest.TestCase):
Expand Down Expand Up @@ -175,7 +175,7 @@ def test_integration_bad_requests(self):

expired_respose = "{\"advertising_token\":\"AgAAAAN6QZRCFTau+sfOlMMUY2ftElFMq2TCrcu1EAaD9WmEfoT2BWm2ZKz1tumbT00tWLffRDQ/9POXfA0O/Ljszn7FLtG5EzTBM3HYs4f5irkqeEvu38DhVCxUEpI+gZZZkynRap1oYx6AmC/ip3rk+7pmqa3r3saDs1mPRSSTm+Nh6A==\",\"user_token\":\"AgAAAAL6aleYI4BubI5ZXMBshqmMEfCkbCJF4fLeg1sdI0BTLzj9sXsSISjkG0lMC743diC2NVy3ElkbO1lLysd+Lm6alkqevPrcuWDisQ1939YdoH6LqpwBH3FNSE4/xa3Q+94=\",\"refresh_token\":\"AAAAAARomrP3NjjH+8mt5djfTHbmRZXjOMnAN8WpjJoe30AhUCvYksO/xoDSj77GzWv4M99DhnPl2cVco8CZFTcE10nauXI4Barr890ILnH0IIacOei5Zjwh6DycFkoXkAAuHY1zjmxb7niGLfSP2RctWkZdRVGWQv/UW/grw6+paU9bnKEWPzVvLwwdW2NgjDKu+szE6A+b5hkY+I3voKoaz8/kLDmX8ddJGLy/YOh/LIveBspSAvEg+v89OuUCwAqm8L3Rt8PxDzDnt0U4Na+AUawvvfsIhmsn/zMpRRks6GHhIAB/EQUHID8TedU8Hv1WFRsiraG9Dfn1Kc5/uYnDJhEagWc+7RgTGT+U5GqI6+afrAl5091eBLbmvXnXn9ts\",\"identity_expires\":1668059799628,\"refresh_expires\":1668142599628,\"refresh_from\":1668056202628,\"refresh_response_key\":\"P941vVeuyjaDRVnFQ8DPd0AZnW4bPeiJPXER2K9QXcU=\"}"
current_identity = IdentityTokens.from_json_string(expired_respose)
with self.assertRaises(HTTPError):
with self.assertRaises(requests.exceptions.HTTPError):
self.publisher_client.refresh_token(current_identity)

with self.assertRaises(TypeError):
Expand All @@ -185,15 +185,15 @@ def test_integration_bad_requests(self):
self.publisher_client.refresh_token(None)

bad_url_client = Uid2PublisherClient("https://www.something.com", self.UID2_API_KEY, self.UID2_SECRET_KEY)
with self.assertRaises(HTTPError):
with self.assertRaises(requests.exceptions.HTTPError):
bad_url_client.generate_token(TokenGenerateInput.from_email("test@example.com"))

bad_secret_client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, "badSecretKeypB64Y3fV2dAed8t/mupw3sjN5jNRFzg=")
with self.assertRaises(HTTPError):
with self.assertRaises(requests.exceptions.HTTPError):
bad_secret_client.generate_token(TokenGenerateInput.from_email("test@example.com"))

bad_api_client = Uid2PublisherClient(self.UID2_BASE_URL, "not-real-key", self.UID2_SECRET_KEY)
with self.assertRaises(HTTPError):
with self.assertRaises(requests.exceptions.HTTPError):
bad_secret_client.generate_token(TokenGenerateInput.from_email("test@example.com"))


Expand Down
46 changes: 23 additions & 23 deletions tests/test_refresh_keys_util.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import json
import unittest
from unittest.mock import patch

import responses

from uid2_client import refresh_keys_util
from test_utils import *
from uid2_client.encryption import _encrypt_gcm, _decrypt_gcm


class TestRefreshKeysUtil(unittest.TestCase):
class MockPostResponse:
def __init__(self, return_value):
self.return_value = return_value

def read(self):
return base64.b64encode(self.return_value)

def _make_post_response(self, request_data, response_payload):
d = base64.b64decode(request_data)[1:]
d = _decrypt_gcm(d, client_secret_bytes)
Expand All @@ -25,11 +19,11 @@ def _make_post_response(self, request_data, response_payload):
payload += response_payload
envelope = _encrypt_gcm(payload, None, client_secret_bytes)

return self.MockPostResponse(envelope)
return 200, {}, base64.b64encode(envelope)

def _get_post_refresh_keys_response(self, base_url, path, headers, data):
def _get_post_refresh_keys_response(self, request):
response_payload = key_set_to_json_for_sharing([master_key, site_key]).encode()
return self._make_post_response(data, response_payload)
return self._make_post_response(request.body, response_payload)

def _validate_master_and_site_key(self, keys):
self.assertEqual(len(keys.values()), 2)
Expand All @@ -55,23 +49,29 @@ def _validate_master_and_site_key(self, keys):
self.assertEqual(master_secret, master.secret)
self.assertEqual(1, master.keyset_id)

@patch('uid2_client.refresh_keys_util.post')
def test_refresh_sharing_keys(self, mock_post):
mock_post.side_effect = self._get_post_refresh_keys_response
refresh_response = refresh_keys_util.refresh_sharing_keys("base_url", "auth_key", base64.b64decode(client_secret))
@responses.activate
def test_refresh_sharing_keys(self):
responses.add_callback(
responses.POST,
"https://base_url/v2/key/sharing",
callback=self._get_post_refresh_keys_response,
)

refresh_response = refresh_keys_util.refresh_sharing_keys("https://base_url", "auth_key", base64.b64decode(client_secret))
self.assertTrue(refresh_response.success)
self._validate_master_and_site_key(refresh_response.keys)
mock_post.assert_called_once()
self.assertEqual(mock_post.call_args[0], ('base_url', '/v2/key/sharing'))

@patch('uid2_client.refresh_keys_util.post')
def test_refresh_bidstream_keys(self, mock_post):
mock_post.side_effect = self._get_post_refresh_keys_response
refresh_response = refresh_keys_util.refresh_bidstream_keys("base_url", "auth_key", base64.b64decode(client_secret))
@responses.activate
def test_refresh_bidstream_keys(self):
responses.add_callback(
responses.POST,
"https://base_url/v2/key/bidstream",
callback=self._get_post_refresh_keys_response,
)

refresh_response = refresh_keys_util.refresh_bidstream_keys("https://base_url", "auth_key", base64.b64decode(client_secret))
self.assertTrue(refresh_response.success)
self._validate_master_and_site_key(refresh_response.keys)
mock_post.assert_called_once()
self.assertEqual(mock_post.call_args[0], ('base_url', '/v2/key/bidstream'))

def test_parse_keys_json_identity(self):
response_body_str = key_set_to_json_for_sharing([master_key, site_key])
Expand Down
3 changes: 2 additions & 1 deletion uid2_client/identity_map_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ def generate_identity_map(self, identity_map_input):
req, nonce = make_v2_request(self._client_secret, dt.datetime.now(tz=timezone.utc),
identity_map_input.get_identity_map_input_as_json_string().encode())
resp = post(self._base_url, '/v2/identity/map', headers=auth_headers(self._api_key), data=req)
resp_body = parse_v2_response(self._client_secret, resp.read(), nonce)
resp.raise_for_status()
resp_body = parse_v2_response(self._client_secret, resp.text, nonce)
return IdentityMapResponse(resp_body, identity_map_input)
6 changes: 4 additions & 2 deletions uid2_client/publisher_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ def generate_token(self, token_generate_input):
req, nonce = make_v2_request(self._secret_key, dt.datetime.now(tz=timezone.utc),
token_generate_input.get_as_json_string().encode())
resp = post(self._base_url, '/v2/token/generate', headers=auth_headers(self._auth_key), data=req)
resp_body = parse_v2_response(self._secret_key, resp.read(), nonce)
resp.raise_for_status()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In future we should probably wrap base_url, auth_key, secret_key in their own class to get better reuse / single responsibility.
That class could have a method that replaces lines 50-54 with a single line (as those lines appear often):

resp_body = v2_request_helper.request('/v2/token/generate', token_generate_input.get_as_json_string())

resp_body = parse_v2_response(self._secret_key, resp.text, nonce)
return TokenGenerateResponse(resp_body)

def refresh_token(self, current_identity):
resp = post(self._base_url, '/v2/token/refresh', headers=auth_headers(self._auth_key),
data=current_identity.get_refresh_token().encode())
resp_bytes = base64_to_byte_array(resp.read())
resp.raise_for_status()
resp_bytes = base64_to_byte_array(resp.text)
decrypted = _decrypt_gcm(resp_bytes, base64_to_byte_array(current_identity.get_refresh_response_key()))
return TokenRefreshResponse(decrypted.decode(), dt.datetime.now(tz=timezone.utc))
3 changes: 2 additions & 1 deletion uid2_client/refresh_keys_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def _fetch_keys(base_url, path, auth_key, secret_key):
try:
req, nonce = make_v2_request(secret_key, dt.datetime.now(tz=timezone.utc))
resp = post(base_url, path, headers=auth_headers(auth_key), data=req)
resp_body = json.loads(parse_v2_response(secret_key, resp.read(), nonce)).get('body')
resp.raise_for_status()
resp_body = json.loads(parse_v2_response(secret_key, resp.text, nonce)).get('body')
keys = _parse_keys_json(resp_body)
return RefreshResponse.make_success(keys)
except Exception as exc:
Expand Down
14 changes: 6 additions & 8 deletions uid2_client/request_response_util.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import base64
from importlib.metadata import version
import os
from urllib import request

import pkg_resources
import requests

from uid2_client.encryption import _encrypt_gcm, _decrypt_gcm

Expand All @@ -13,12 +12,12 @@ def _make_url(base_url, path):

def auth_headers(auth_key):
try:
version = pkg_resources.get_distribution("uid2_client").version
client_version = version("uid2_client")
except Exception:
version = "non-packaged-mode"
client_version = "non-packaged-mode"

return {'Authorization': 'Bearer ' + auth_key,
"X-UID2-Client-Version": "uid2-client-python-" + version}
"X-UID2-Client-Version": "uid2-client-python-" + client_version}


def make_v2_request(secret_key, now, data=None):
Expand All @@ -42,5 +41,4 @@ def parse_v2_response(secret_key, encrypted, nonce):


def post(base_url, path, headers, data):
req = request.Request(_make_url(base_url, path), headers=headers, method='POST', data=data)
return request.urlopen(req)
return requests.post(_make_url(base_url, path), data=data, headers=headers)