Skip to content

Commit 6ecfbe4

Browse files
committed
clean up code slightly
1 parent 8221c49 commit 6ecfbe4

File tree

8 files changed

+608
-0
lines changed

8 files changed

+608
-0
lines changed

tests/test_publisher_client.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import os
2+
import unittest
3+
4+
from uid2_client import Uid2PublisherClient
5+
from uid2_client import TokenGenerateInput
6+
from uid2_client import TokenGenerateResponse
7+
from uid2_client.identity_tokens import IdentityTokens
8+
from urllib.request import HTTPError
9+
10+
11+
class PublisherEuidIntegrationTests(unittest.TestCase):
12+
13+
EUID_SECRET_KEY = None
14+
EUID_API_KEY = None
15+
EUID_BASE_URL = None
16+
17+
publisher_client = None
18+
19+
@classmethod
20+
def setUpClass(cls):
21+
cls.EUID_BASE_URL = os.getenv("EUID_BASE_URL")
22+
cls.EUID_API_KEY = os.getenv("EUID_API_KEY")
23+
cls.EUID_SECRET_KEY = os.getenv("EUID_SECRET_KEY")
24+
25+
print(cls.EUID_BASE_URL, cls.EUID_API_KEY, cls.EUID_SECRET_KEY)
26+
27+
if cls.EUID_BASE_URL and cls.EUID_API_KEY and cls.EUID_SECRET_KEY:
28+
cls.publisher_client = Uid2PublisherClient(cls.EUID_BASE_URL, cls.EUID_API_KEY, cls.EUID_SECRET_KEY)
29+
30+
def test_integration_tc_string(self):
31+
tc_string = "CPhJRpMPhJRpMABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA"
32+
33+
token_generate_response = self.publisher_client.generate_token(
34+
TokenGenerateInput.from_email("[email protected]").with_transparency_and_consent_string(tc_string))
35+
self.assertFalse(token_generate_response.is_optout())
36+
37+
identity = token_generate_response.get_identity()
38+
self.assertIsNotNone(identity)
39+
self.assertFalse(identity.is_due_for_refresh())
40+
self.assertIsNotNone(identity.get_advertising_token())
41+
self.assertIsNotNone(identity.get_refresh_token())
42+
self.assertIsNotNone(identity.get_json_string())
43+
self.assertTrue(identity.is_refreshable())
44+
45+
def test_integration_tc_string_with_insufficient_consent(self):
46+
tc_string = "CPehXK9PehXK9ABAMBFRACBoADAAAEJAAIYgAKwAQAKgArABAAqAAA"
47+
with self.assertRaises(ValueError):
48+
self.publisher_client.generate_token(TokenGenerateInput.from_email("[email protected]").with_transparency_and_consent_string(tc_string))
49+
50+
51+
def test_integration_optout_generate_token(self):
52+
publisher_client = Uid2PublisherClient(self.EUID_BASE_URL, self.EUID_API_KEY, self.EUID_SECRET_KEY)
53+
tc_string = "CPhJRpMPhJRpMABAMBFRACBoALAAAEJAAIYgAKwAQAKgArABAAqAAA"
54+
input = TokenGenerateInput.from_email("[email protected]").do_not_generate_tokens_for_opted_out().with_transparency_and_consent_string(tc_string)
55+
token_generate_response = publisher_client.generate_token(input)
56+
self.assertTrue(token_generate_response.is_optout())
57+
self.assertFalse(token_generate_response.is_success())
58+
self.assertIsNone(token_generate_response.get_identity())
59+
60+
class PublisherUid2IntegrationTests(unittest.TestCase):
61+
62+
UID2_SECRET_KEY = None
63+
UID2_API_KEY = None
64+
UID2_BASE_URL = None
65+
66+
publisher_client = None
67+
68+
@classmethod
69+
def setUpClass(cls):
70+
cls.UID2_BASE_URL = os.getenv("UID2_BASE_URL")
71+
cls.UID2_API_KEY = os.getenv("UID2_API_KEY")
72+
cls.UID2_SECRET_KEY = os.getenv("UID2_SECRET_KEY")
73+
74+
print(cls.UID2_BASE_URL, cls.UID2_API_KEY, cls.UID2_SECRET_KEY)
75+
76+
if cls.UID2_BASE_URL and cls.UID2_API_KEY and cls.UID2_SECRET_KEY:
77+
cls.publisher_client = Uid2PublisherClient(cls.UID2_BASE_URL, cls.UID2_API_KEY, cls.UID2_SECRET_KEY)
78+
79+
# Test methods
80+
def test_integration_generate_and_refresh(self):
81+
82+
token_generate_response = self.publisher_client.generate_token(
83+
TokenGenerateInput.from_email("[email protected]"))
84+
85+
self.assertFalse(token_generate_response.is_optout())
86+
87+
identity = token_generate_response.get_identity()
88+
self.assertIsNotNone(identity)
89+
self.assertFalse(identity.is_due_for_refresh())
90+
self.assertIsNotNone(identity.get_advertising_token())
91+
self.assertIsNotNone(identity.get_refresh_token())
92+
self.assertIsNotNone(identity.get_json_string())
93+
self.assertTrue(identity.is_refreshable())
94+
95+
token_refresh_response = self.publisher_client.refresh_token(identity)
96+
self.assertTrue(token_refresh_response.is_success())
97+
self.assertFalse(token_refresh_response.is_optout())
98+
self.assertIsNotNone(token_refresh_response.get_identity_json_string())
99+
100+
refreshed_identity = token_refresh_response.get_identity()
101+
self.assertIsNotNone(refreshed_identity)
102+
self.assertFalse(refreshed_identity.is_due_for_refresh())
103+
self.assertIsNotNone(refreshed_identity.get_advertising_token())
104+
self.assertIsNotNone(refreshed_identity.get_refresh_token())
105+
self.assertIsNotNone(refreshed_identity.get_json_string())
106+
self.assertTrue(identity.is_refreshable())
107+
108+
def test_integration_optout(self):
109+
110+
token_generate_response = self.publisher_client.generate_token(TokenGenerateInput.from_email("[email protected]"))
111+
112+
self.assertFalse(token_generate_response.is_optout())
113+
114+
identity = token_generate_response.get_identity()
115+
self.assertIsNotNone(identity)
116+
self.assertFalse(identity.is_due_for_refresh())
117+
self.assertIsNotNone(identity.get_advertising_token())
118+
self.assertIsNotNone(identity.get_refresh_token())
119+
self.assertIsNotNone(identity.get_json_string())
120+
self.assertTrue(identity.is_refreshable())
121+
122+
token_refresh_response = self.publisher_client.refresh_token(identity)
123+
self.assertFalse(token_refresh_response.is_success())
124+
self.assertTrue(token_refresh_response.is_optout())
125+
self.assertIsNone(token_refresh_response.get_identity_json_string())
126+
self.assertIsNone(token_refresh_response.get_identity())
127+
128+
def test_integration_phone(self):
129+
130+
token_generate_response = self.publisher_client.generate_token(
131+
TokenGenerateInput.from_phone("+12345678901"))
132+
133+
self.assertFalse(token_generate_response.is_optout())
134+
identity = token_generate_response.get_identity()
135+
self.assertIsNotNone(identity)
136+
self.assertFalse(identity.is_due_for_refresh())
137+
self.assertIsNotNone(identity.get_advertising_token())
138+
self.assertIsNotNone(identity.get_refresh_token())
139+
self.assertIsNotNone(identity.get_json_string())
140+
self.assertTrue(identity.is_refreshable())
141+
142+
token_refresh_response = self.publisher_client.refresh_token(identity)
143+
self.assertTrue(token_refresh_response.is_success())
144+
self.assertFalse(token_refresh_response.is_optout())
145+
self.assertIsNotNone(token_refresh_response.get_identity_json_string())
146+
147+
refreshed_identity = token_refresh_response.get_identity()
148+
self.assertIsNotNone(refreshed_identity)
149+
self.assertFalse(refreshed_identity.is_due_for_refresh())
150+
self.assertIsNotNone(refreshed_identity.get_advertising_token())
151+
self.assertIsNotNone(refreshed_identity.get_refresh_token())
152+
self.assertIsNotNone(refreshed_identity.get_json_string())
153+
self.assertTrue(identity.is_refreshable())
154+
155+
def test_integration_bad_requests(self):
156+
157+
with self.assertRaises(ValueError):
158+
self.publisher_client.generate_token(TokenGenerateInput.from_email("this is not an email address"))
159+
160+
with self.assertRaises(ValueError):
161+
self.publisher_client.generate_token(TokenGenerateInput.from_phone("this is not a phone number"))
162+
163+
unnormalized_phone_number = " +123 44 55-66-77"
164+
with self.assertRaises(ValueError):
165+
self.publisher_client.generate_token(TokenGenerateInput.from_phone(unnormalized_phone_number))
166+
167+
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=\"}"
168+
current_identity = IdentityTokens.from_json_string(expired_respose)
169+
with self.assertRaises(HTTPError):
170+
self.publisher_client.refresh_token(current_identity)
171+
172+
with self.assertRaises(TypeError):
173+
self.publisher_client.generate_token(TokenGenerateInput.from_email(None))
174+
175+
with self.assertRaises(AttributeError):
176+
self.publisher_client.refresh_token(None)
177+
178+
bad_url_client = Uid2PublisherClient("https://www.something.com", self.UID2_API_KEY, self.UID2_SECRET_KEY)
179+
with self.assertRaises(HTTPError):
180+
bad_url_client.generate_token(TokenGenerateInput.from_email("[email protected]"))
181+
182+
bad_secret_client = Uid2PublisherClient(self.UID2_BASE_URL, self.UID2_API_KEY, "badSecretKeypB64Y3fV2dAed8t/mupw3sjN5jNRFzg=")
183+
with self.assertRaises(HTTPError):
184+
bad_secret_client.generate_token(TokenGenerateInput.from_email("[email protected]"))
185+
186+
bad_api_client = Uid2PublisherClient(self.UID2_BASE_URL, "not-real-key", self.UID2_SECRET_KEY)
187+
with self.assertRaises(HTTPError):
188+
bad_secret_client.generate_token(TokenGenerateInput.from_email("[email protected]"))
189+
190+
191+
if __name__ == '__main__':
192+
unittest.main()

uid2_client/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@
1414
from .keys import *
1515
from .euid_client_factory import *
1616
from .uid2_client_factory import *
17+
from .token_generate_input import *
18+
from .token_generate_response import *
19+
from .publisher_client import *
1720

1821

uid2_client/identity_tokens.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import datetime
2+
import json
3+
4+
5+
class IdentityTokens:
6+
def __init__(self, advertising_token, refresh_token, refresh_response_key, identity_expires,
7+
refresh_expires, refresh_from, json_string):
8+
self._advertising_token = advertising_token
9+
self._refresh_token = refresh_token
10+
self._refresh_response_key = refresh_response_key
11+
self._identity_expires = identity_expires
12+
self._refresh_expires = refresh_expires
13+
self._refresh_from = refresh_from
14+
self._json_string = json_string
15+
16+
@staticmethod
17+
def from_json_string(json_string):
18+
assert json_string is not None, "jsonString must not be null"
19+
20+
try:
21+
return IdentityTokens.from_json(json.loads(json_string))
22+
except json.JSONDecodeError:
23+
print("Invalid json string")
24+
return None
25+
except KeyError:
26+
print("Missing field in json string")
27+
return None
28+
29+
@staticmethod
30+
def from_json(json_obj):
31+
return IdentityTokens(
32+
json_obj.get("advertising_token"),
33+
json_obj.get("refresh_token"),
34+
json_obj.get("refresh_response_key"),
35+
json_obj.get("identity_expires"),
36+
json_obj.get("refresh_expires"),
37+
json_obj.get("refresh_from"),
38+
json.dumps(json_obj)
39+
)
40+
41+
def is_due_for_refresh(self):
42+
return self.is_due_for_refresh_impl(datetime.datetime.now())
43+
44+
def get_advertising_token(self):
45+
return self._advertising_token
46+
47+
def get_refresh_token(self):
48+
return self._refresh_token
49+
50+
def get_json_string(self):
51+
return self._json_string
52+
53+
def is_refreshable(self):
54+
return self.is_refreshable_impl(datetime.datetime.now())
55+
56+
def is_refreshable_impl(self, timestamp):
57+
refresh_expires = self._refresh_expires
58+
if refresh_expires is None or timestamp.timestamp() > refresh_expires:
59+
return False
60+
return self._refresh_token is not None
61+
62+
def is_due_for_refresh_impl(self, timestamp):
63+
return timestamp.timestamp() > self._refresh_from or self.has_identity_expired(timestamp)
64+
65+
def has_identity_expired(self, timestamp):
66+
return timestamp.timestamp() > self._identity_expires
67+
68+
def get_refresh_response_key(self):
69+
return self._refresh_response_key
70+
71+
def get_identity_expires(self):
72+
return self._identity_expires
73+
74+
def get_refresh_expires(self):
75+
return self._refresh_expires
76+
77+
def get_refresh_from(self):
78+
return self._refresh_from

uid2_client/input_util.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import hashlib
2+
import base64
3+
4+
5+
def is_phone_number_normalized(phone_number):
6+
min_phone_number_digits = 10
7+
max_phone_number_digits = 15
8+
9+
if phone_number is None or len(phone_number) < min_phone_number_digits:
10+
return False
11+
12+
if phone_number[0] != '+':
13+
return False
14+
15+
total_digits = sum(char.isdigit() for char in phone_number[1:])
16+
17+
return min_phone_number_digits <= total_digits <= max_phone_number_digits
18+
19+
20+
def normalize_email_string(email):
21+
pre_sb = []
22+
pre_sb_specialized = []
23+
sb = []
24+
ws_buffer = []
25+
26+
class EmailParsingState:
27+
Starting = 1
28+
Pre = 2
29+
SubDomain = 3
30+
31+
parsing_state = EmailParsingState.Starting
32+
33+
in_extension = False
34+
35+
for i in range(len(email)):
36+
c_given = email[i]
37+
if 'A' <= c_given <= 'Z':
38+
c = chr(ord(c_given) + 32)
39+
else:
40+
c = c_given
41+
42+
if parsing_state == EmailParsingState.Starting:
43+
if c == ' ':
44+
continue
45+
else:
46+
parsing_state = EmailParsingState.Pre
47+
48+
if parsing_state == EmailParsingState.Pre:
49+
if c == '@':
50+
parsing_state = EmailParsingState.SubDomain
51+
elif c == '.':
52+
pre_sb.append(c)
53+
elif c == '+':
54+
pre_sb.append(c)
55+
in_extension = True
56+
else:
57+
pre_sb.append(c)
58+
if not in_extension:
59+
pre_sb_specialized.append(c)
60+
elif parsing_state == EmailParsingState.SubDomain:
61+
if c == '@':
62+
return None
63+
elif c == ' ':
64+
ws_buffer.append(c)
65+
continue
66+
if len(ws_buffer) > 0:
67+
sb.extend(ws_buffer)
68+
ws_buffer = []
69+
sb.append(c)
70+
71+
if len(sb) == 0:
72+
return None
73+
domain_part = ''.join(sb)
74+
75+
gmail_domain = "gmail.com"
76+
if gmail_domain == domain_part:
77+
address_part_to_use = pre_sb_specialized
78+
else:
79+
address_part_to_use = pre_sb
80+
81+
if len(address_part_to_use) == 0:
82+
return None
83+
84+
return ''.join(address_part_to_use + ['@'] + [domain_part])
85+
86+
87+
def is_ascii_digit(d):
88+
return '0' <= d <= '9'
89+
90+
91+
def base64_to_byte_array(str):
92+
return base64.b64decode(str)
93+
94+
95+
def byte_array_to_base64(b):
96+
return base64.b64encode(b).decode()
97+
98+
99+
def get_base64_encoded_hash(input):
100+
return byte_array_to_base64(get_sha256_bytes(input))
101+
102+
103+
def get_sha256_bytes(input):
104+
return hashlib.sha256(input.encode()).digest()

0 commit comments

Comments
 (0)