Skip to content

UID2-4895 Copy mkc-UID2-3698-v3-session-2 with elapsed time #56

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 4 additions & 6 deletions tests/test_identity_map_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,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 @@ -142,22 +142,20 @@ def test_identity_map_client_bad_url(self):
identity_map_input = IdentityMapInput.from_emails(
["[email protected]", "[email protected]", "[email protected]"])
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(URLError, client.get_identity_buckets, dt.datetime.now())
self.assertRaises(requests.exceptions.ConnectionError, client.generate_identity_map, identity_map_input)

def test_identity_map_client_bad_api_key(self):
identity_map_input = IdentityMapInput.from_emails(
["[email protected]", "[email protected]", "[email protected]"])
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(HTTPError, client.get_identity_buckets, dt.datetime.now())
self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map,identity_map_input)

def test_identity_map_client_bad_secret(self):
identity_map_input = IdentityMapInput.from_emails(
["[email protected]", "[email protected]", "[email protected]"])

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)
self.assertRaises(HTTPError, client.get_identity_buckets,
dt.datetime.now())
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


@unittest.skipIf(
Expand Down Expand Up @@ -187,7 +187,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 @@ -197,15 +197,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("[email protected]"))

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("[email protected]"))

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("[email protected]"))


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
1 change: 1 addition & 0 deletions uid2_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
decrypt_token: decrypt and advertising token to extract advertising ID from it
"""

default_new_session = lambda: None

from .auto_refresh import *
from .client import *
Expand Down
9 changes: 7 additions & 2 deletions uid2_client/identity_map_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import datetime as dt
import json
import time
from datetime import timezone

from .identity_buckets_response import IdentityBucketsResponse
Expand Down Expand Up @@ -37,9 +38,13 @@ def __init__(self, base_url, api_key, client_secret):
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())
start_time = time.time()
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)
return IdentityMapResponse(resp_body, identity_map_input)
resp.raise_for_status()
resp_body = parse_v2_response(self._client_secret, resp.text, nonce)
end_time = time.time()
elapsed_time = end_time - start_time
return IdentityMapResponse(resp_body, identity_map_input, elapsed_time)

def get_identity_buckets(self, since_timestamp):
req, nonce = make_v2_request(self._client_secret, dt.datetime.now(tz=timezone.utc),
Expand Down
15 changes: 14 additions & 1 deletion uid2_client/identity_map_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@


class IdentityMapResponse:
def __init__(self, response, identity_map_input):
def __init__(self, response, identity_map_input, elapsed_time=None):
self._mapped_identities = {}
self._unmapped_identities = {}
self._response = response
self._elapsed_time = None
response_json = json.loads(response)
self._status = response_json["status"]

if elapsed_time is not None:
self._elapsed_time = elapsed_time

if not self.is_success():
raise ValueError("Got unexpected identity map status: " + self._status)

Expand Down Expand Up @@ -44,6 +49,14 @@ def unmapped_identities(self):
@property
def status(self):
return self._status

@property
def elapsed_time(self):
return self._elapsed_time

@property
def response(self):
return self._response


class MappedIdentity:
Expand Down
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()
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
31 changes: 21 additions & 10 deletions uid2_client/request_response_util.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import base64
import importlib.metadata
import os
from urllib import request
import threading
from typing import Optional
import requests

import pkg_resources

from uid2_client.encryption import _encrypt_gcm, _decrypt_gcm
import uid2_client
from uid2_client.encryption import _decrypt_gcm, _encrypt_gcm


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

def auth_headers(auth_key):
try:
version = pkg_resources.get_distribution("uid2_client").version
client_version = importlib.metadata.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 @@ -41,6 +43,15 @@ def parse_v2_response(secret_key, encrypted, nonce):
return payload[16:]


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)
def __default_new_session(threadlocal=threading.local()):
if getattr(threadlocal, 'session', None) is None:
threadlocal.session = requests.Session()

return threadlocal.session


def post(base_url, path, headers, data, session: Optional[requests.Session] = None):
session = (session or uid2_client.default_new_session()
) or __default_new_session()

return session.post(_make_url(base_url, path), data=data, headers=headers, timeout=5)