Skip to content

Commit 2d7002c

Browse files
authored
Merge pull request #36 from IABTechLab/aaq-UID2-3242-dsp-check-token-lifetime-v2-handling
[UID2-3242] Handle lifetime check for v2 tokens differently
2 parents 48e0e1b + ac62ec8 commit 2d7002c

File tree

7 files changed

+171
-138
lines changed

7 files changed

+171
-138
lines changed

tests/test_bidstream_client.py

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@ def encode_keys(keys):
2626
return key_json
2727

2828

29-
def key_bidstream_response_json(keys, identity_scope=IdentityScope.UID2):
29+
def key_bidstream_response_with_lifetime_json(keys, identity_scope, max_bidstream_lifetime_seconds):
30+
if identity_scope is None:
31+
identity_scope = IdentityScope.UID2
32+
if max_bidstream_lifetime_seconds is None:
33+
max_bidstream_lifetime_seconds = dt.timedelta(days=3).total_seconds()
3034
encoded_keys = encode_keys(keys)
3135
json_obj = {
3236
"body": {
33-
"max_bidstream_lifetime_seconds": dt.timedelta(days=3).total_seconds(),
37+
"max_bidstream_lifetime_seconds": max_bidstream_lifetime_seconds,
3438
"identity_scope": identity_scope.name,
3539
"allow_clock_skew_seconds": 1800, # 30 mins
3640
"keys": encoded_keys,
@@ -52,14 +56,22 @@ def key_bidstream_response_json(keys, identity_scope=IdentityScope.UID2):
5256
return json.dumps(json_obj)
5357

5458

59+
def key_bidstream_response_json(keys, identity_scope=IdentityScope.UID2, max_bidstream_lifetime_seconds=None):
60+
return key_bidstream_response_with_lifetime_json(keys, identity_scope, max_bidstream_lifetime_seconds)
61+
62+
5563
def key_bidstream_response_json_default_keys(identity_scope=IdentityScope.UID2):
56-
return key_bidstream_response_json([master_key, site_key], identity_scope)
64+
return key_bidstream_response_with_lifetime_json([master_key, site_key], identity_scope, None)
5765

5866

5967
class TestBidStreamClient(unittest.TestCase):
6068
_CONST_BASE_URL = 'base_url'
6169
_CONST_API_KEY = 'api_key'
6270

71+
def refresh(self, refresh_json):
72+
refresh_response = self._client._refresh_json(refresh_json)
73+
self.assertTrue(refresh_response.success)
74+
6375
def assert_success(self, decryption_response, token_version, scope):
6476
self.assertTrue(decryption_response.success)
6577
self.assertEqual(decryption_response.uid, example_uid)
@@ -88,21 +100,20 @@ def _decrypt_and_assert_success(self, token, token_version, scope):
88100
def setUp(self):
89101
self._client = BidstreamClient(self._CONST_BASE_URL, self._CONST_API_KEY, client_secret)
90102

91-
def test_smoke_test(self): # SmokeTest
103+
def test_smoke_test_for_bidstream(self): # SmokeTestForBidstream
92104
for expected_scope, expected_version in test_cases_all_scopes_all_versions:
93105
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
94-
token = generate_uid_token(expected_scope, expected_version)
95-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
96-
expected_scope))
97-
self.assertTrue(refresh_response.success)
106+
token = generate_uid_token(expected_scope, expected_version,
107+
identity_established_at=now - dt.timedelta(days=120),
108+
generated_at=YESTERDAY,
109+
expires_at=IN_2_DAYS)
110+
self.refresh(key_bidstream_response_json_default_keys(expected_scope))
98111
self._decrypt_and_assert_success(token, expected_version, expected_scope)
99112

100113
def test_phone_uids(self): # PhoneTest
101114
for expected_scope, expected_version in test_cases_all_scopes_v3_v4_versions:
102115
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
103-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
104-
expected_scope))
105-
self.assertTrue(refresh_response.success)
116+
self.refresh(key_bidstream_response_json_default_keys(expected_scope))
106117
token = generate_uid_token(expected_scope, expected_version, phone_uid)
107118
self.assertEqual(IdentityType.Phone, get_identity_type(token))
108119
result = self._client.decrypt_token_into_raw_uid(token, None)
@@ -112,25 +123,40 @@ def test_phone_uids(self): # PhoneTest
112123
self.assertEqual(result.identity_scope, expected_scope)
113124
self.assertEqual(result.advertising_token_version, expected_version)
114125

115-
def test_token_lifetime_too_long_for_bidstream(self): # TokenLifetimeTooLongForBidstream
116-
expires_in_sec = IN_3_DAYS + dt.timedelta(minutes=1)
126+
def test_token_lifetime_too_long_for_bidstream_but_remaining_lifetime_allowed(self): # TokenLifetimeTooLongForBidstreamButRemainingLifetimeAllowed
127+
generated = YESTERDAY
128+
expires_in_sec = generated + dt.timedelta(days=3) + dt.timedelta(minutes=1)
129+
max_bidstream_lifetime_seconds = dt.timedelta(days=3).total_seconds()
117130
for expected_scope, expected_version in test_cases_all_scopes_all_versions:
118131
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
119-
token = generate_uid_token(expected_scope, expected_version, expires_at=expires_in_sec)
120-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
121-
expected_scope))
122-
self.assertTrue(refresh_response.success)
132+
token = generate_uid_token(expected_scope, expected_version, generated_at=generated,
133+
expires_at=expires_in_sec)
134+
self.refresh(key_bidstream_response_json([master_key, site_key], expected_scope,
135+
max_bidstream_lifetime_seconds))
136+
result = self._client.decrypt_token_into_raw_uid(token, None)
137+
if expected_version == AdvertisingTokenVersion.ADVERTISING_TOKEN_V2:
138+
self.assert_success(result, expected_version, expected_scope)
139+
else:
140+
self.assert_fails(result, expected_version, expected_scope)
141+
142+
def test_token_remaining_lifetime_too_long_for_bidstream(self): # TokenRemainingLifetimeTooLongForBidstream
143+
generated = now
144+
expires_in_sec = generated + dt.timedelta(days=3) + dt.timedelta(minutes=1)
145+
for expected_scope, expected_version in test_cases_all_scopes_v3_v4_versions:
146+
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
147+
token = generate_uid_token(expected_scope, expected_version, generated_at=generated,
148+
expires_at=expires_in_sec)
149+
self.refresh(key_bidstream_response_json_default_keys(expected_scope))
123150
result = self._client.decrypt_token_into_raw_uid(token, None)
124151
self.assert_fails(result, expected_version, expected_scope)
125152

126153
def test_token_generated_in_the_future_to_simulate_clock_skew(self): # TokenGeneratedInTheFutureToSimulateClockSkew
154+
# Note V2 does not have a "token generated" field, therefore v2 tokens can't have a future "token generated" date and are excluded from this test.
127155
created_at_future = dt.datetime.now(tz=timezone.utc) + dt.timedelta(minutes=31) #max allowed clock skew is 30m
128-
for expected_scope, expected_version in test_cases_all_scopes_all_versions:
156+
for expected_scope, expected_version in test_cases_all_scopes_v3_v4_versions:
129157
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
130-
token = generate_uid_token(expected_scope, expected_version, created_at=created_at_future)
131-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
132-
expected_scope))
133-
self.assertTrue(refresh_response.success)
158+
token = generate_uid_token(expected_scope, expected_version, generated_at=created_at_future)
159+
self.refresh(key_bidstream_response_json_default_keys(expected_scope))
134160
result = self._client.decrypt_token_into_raw_uid(token, None)
135161
self.assert_fails(result, expected_version, expected_scope)
136162
self.assertEqual(result.status, DecryptionStatus.INVALID_TOKEN_LIFETIME)
@@ -140,17 +166,14 @@ def test_token_generated_in_the_future_within_allowed_clock_skew(self): # Token
140166
for expected_scope, expected_version in test_cases_all_scopes_all_versions:
141167
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
142168
token = generate_uid_token(expected_scope, expected_version, expires_at=created_at_future)
143-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
144-
expected_scope))
145-
self.assertTrue(refresh_response.success)
169+
self.refresh(key_bidstream_response_json_default_keys(expected_scope))
146170
self._decrypt_and_assert_success(token, expected_version, expected_scope)
147171

148172
def test_legacy_response_from_old_operator(self):
149173
test_cases = [AdvertisingTokenVersion.ADVERTISING_TOKEN_V2,
150174
AdvertisingTokenVersion.ADVERTISING_TOKEN_V3,
151175
AdvertisingTokenVersion.ADVERTISING_TOKEN_V4]
152-
refresh_response = self._client._refresh_json(key_set_to_json_for_sharing([master_key, site_key]))
153-
self.assertTrue(refresh_response.success)
176+
self.refresh(key_set_to_json_for_sharing([master_key, site_key]))
154177
for token_version in test_cases:
155178
with self.subTest(token_version=token_version):
156179
token = generate_uid_token(IdentityScope.UID2, token_version)
@@ -164,7 +187,7 @@ def test_token_generated_in_the_future_legacy_client(self): # TokenGeneratedInT
164187
with self.subTest(expected_scope=expected_scope, expected_version=expected_version):
165188
legacy_client.refresh_json(key_bidstream_response_json_default_keys(
166189
expected_scope))
167-
token = generate_uid_token(expected_scope, expected_version, created_at=created_at_future)
190+
token = generate_uid_token(expected_scope, expected_version, generated_at=created_at_future)
168191
result = legacy_client.decrypt(token)
169192
self.assert_success(result, expected_version, expected_scope)
170193

@@ -190,9 +213,7 @@ def test_identity_scope_and_types(self): # IdentityScopeAndType_TestCases
190213
for uid, identity_scope, identity_type in test_cases:
191214
with self.subTest(identity_scope=identity_scope, identity_type=identity_type):
192215
token = generate_uid_token(identity_scope, AdvertisingTokenVersion.ADVERTISING_TOKEN_V4)
193-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys(
194-
identity_scope))
195-
self.assertTrue(refresh_response.success)
216+
self.refresh(key_bidstream_response_json_default_keys(identity_scope))
196217
self._decrypt_and_assert_success(token, AdvertisingTokenVersion.ADVERTISING_TOKEN_V4, identity_scope)
197218

198219
def test_empty_keys(self): # EmptyKeyContainer
@@ -207,9 +228,7 @@ def test_master_key_expired(self): #ExpiredKeyContainer
207228
site_key_expired = EncryptionKey(site_key_id, site_id, created=now, activates=now - dt.timedelta(hours=2),
208229
expires=now - dt.timedelta(hours=1), secret=site_secret, keyset_id=99999)
209230

210-
refresh_response = self._client._refresh_json(key_bidstream_response_json(
211-
[master_key_expired, site_key_expired]))
212-
self.assertTrue(refresh_response.success)
231+
self.refresh(key_bidstream_response_json([master_key_expired, site_key_expired]))
213232

214233
result = self._client.decrypt_token_into_raw_uid(example_uid, None)
215234
self.assertFalse(result.success)
@@ -219,18 +238,15 @@ def test_not_authorized_for_master_key(self): #NotAuthorizedForMasterKey
219238

220239
another_master_key = EncryptionKey(master_key_id + site_key_id + 1, -1, created=now, activates=now, expires=now + dt.timedelta(hours=1), secret=master_secret)
221240
another_site_key = EncryptionKey(master_key_id + site_key_id + 2, site_id, created=now, activates=now, expires=now + dt.timedelta(hours=1), secret=site_secret)
222-
refresh_response = self._client._refresh_json(key_bidstream_response_json(
223-
[another_master_key, another_site_key]))
224-
self.assertTrue(refresh_response.success)
241+
self.refresh(key_bidstream_response_json([another_master_key, another_site_key]))
225242
token = generate_uid_token(IdentityScope.UID2, AdvertisingTokenVersion.ADVERTISING_TOKEN_V4)
226243

227244
result = self._client.decrypt_token_into_raw_uid(token, None)
228245
self.assertFalse(result.success)
229246
self.assertEqual(result.status, DecryptionStatus.NOT_AUTHORIZED_FOR_MASTER_KEY)
230247

231248
def test_invalid_payload(self): #InvalidPayload
232-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys())
233-
self.assertTrue(refresh_response.success)
249+
self.refresh(key_bidstream_response_json_default_keys())
234250
token = generate_uid_token(IdentityScope.UID2, AdvertisingTokenVersion.ADVERTISING_TOKEN_V4)
235251
payload = Uid2Base64UrlCoder.decode(token)
236252
bad_token = base64.urlsafe_b64encode(payload[:0])
@@ -240,13 +256,12 @@ def test_invalid_payload(self): #InvalidPayload
240256
self.assertEqual(result.status, DecryptionStatus.INVALID_PAYLOAD)
241257

242258
def test_token_expiry_custom_decryption_time(self): #TokenExpiryAndCustomNow
243-
refresh_response = self._client._refresh_json(key_bidstream_response_json_default_keys())
244-
self.assertTrue(refresh_response.success)
259+
self.refresh(key_bidstream_response_json_default_keys())
245260

246261
expires_at = now - dt.timedelta(days=60)
247262
created_at = expires_at - dt.timedelta(minutes=1)
248263
token = generate_uid_token(IdentityScope.UID2, AdvertisingTokenVersion.ADVERTISING_TOKEN_V4,
249-
created_at=created_at, expires_at=expires_at)
264+
generated_at=created_at, expires_at=expires_at)
250265
result = self._client._decrypt_token_into_raw_uid(token, None, expires_at + dt.timedelta(seconds=1))
251266
self.assertFalse(result.success)
252267
self.assertEqual(result.status, DecryptionStatus.EXPIRED_TOKEN)

tests/test_encryption.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ def validate_advertising_token(self, advertising_token_string, identity_scope, i
7070
self.assertEqual(-1, advertising_token_string.find("/"))
7171

7272
def generate_uid2_token_v4(self, uid, master_key, site_id, site_key, params = Params(), identity_type = IdentityType.Email, identity_scope = IdentityScope.UID2):
73+
if not isinstance(params.token_expiry, dt.datetime):
74+
params.token_expiry = dt.datetime.now(tz=timezone.utc) + params.token_expiry
7375
advertising_token = UID2TokenGenerator.generate_uid2_token_v4(uid, master_key, site_id, site_key, params)
7476
self.validate_advertising_token(advertising_token, identity_scope, identity_type)
7577
return advertising_token
@@ -155,7 +157,7 @@ def test_decrypt_token_v4_no_site_key(self):
155157
result = decrypt(token, keys)
156158

157159
def test_decrypt_token_v4_invalid_version(self):
158-
params = Params(dt.timedelta(hours=1))
160+
params = Params(dt.datetime.now(tz=timezone.utc) + dt.timedelta(hours=1))
159161
token = UID2TokenGenerator.generate_uid2_token_with_debug_info(_example_id, _master_key, _site_id, _site_key, params, 1)
160162

161163
keys = EncryptionKeysCollection([_master_key, _site_key])
@@ -174,7 +176,8 @@ def test_decrypt_token_v4_expired(self):
174176
result = decrypt(token, keys)
175177

176178
def _generate_v2_token(self, expires_in_seconds):
177-
return UID2TokenGenerator.generate_uid2_token_v2(_example_id, _master_key, _site_id, _site_key, Params(expires_in_seconds))
179+
return UID2TokenGenerator.generate_uid2_token_v2(_example_id, _master_key, _site_id, _site_key,
180+
Params(dt.datetime.now(tz=timezone.utc) + expires_in_seconds))
178181

179182
def _generate_v4_token(self, expires_in_seconds):
180183
return self.generate_uid2_token_v4(_example_id, _master_key, _site_id, _site_key, Params(expires_in_seconds))
@@ -297,7 +300,7 @@ def test_decrypt_token_v3_no_site_key(self):
297300
result = decrypt(token, keys)
298301

299302
def test_decrypt_token_v3_invalid_version(self):
300-
params = Params(dt.timedelta(hours=1))
303+
params = Params(dt.datetime.now(tz=timezone.utc) + dt.timedelta(hours=1))
301304
token = UID2TokenGenerator.generate_uid2_token_with_debug_info(_example_id, _master_key, _site_id, _site_key, params, 1)
302305

303306
keys = EncryptionKeysCollection([_master_key, _site_key])
@@ -307,7 +310,7 @@ def test_decrypt_token_v3_invalid_version(self):
307310

308311

309312
def test_decrypt_token_v3_expired(self):
310-
params = Params(dt.timedelta(seconds=-1))
313+
params = Params(dt.datetime.now(tz=timezone.utc) + dt.timedelta(seconds=-1))
311314
token = UID2TokenGenerator.generate_uid2_token_v3(_example_id, _master_key, _site_id, _site_key, params)
312315

313316
keys = EncryptionKeysCollection([_master_key, _site_key])
@@ -331,7 +334,7 @@ def test_decrypt_token_v3_custom_now(self):
331334

332335

333336
def test_decrypt_token_v3_invalid_payload(self):
334-
params = Params(dt.timedelta(seconds=-1))
337+
params = Params(dt.datetime.now(tz=timezone.utc) + dt.timedelta(seconds=-1))
335338
token = UID2TokenGenerator.generate_uid2_token_v3(_example_id, _master_key, _site_id, _site_key, params)
336339

337340
keys = EncryptionKeysCollection([_master_key, _site_key])
@@ -418,7 +421,7 @@ def test_smoke_token_v3(self):
418421
token_expiry = now + dt.timedelta(days=30) if keys.get_token_expiry_seconds() is None \
419422
else now + dt.timedelta(seconds=int(keys.get_token_expiry_seconds()))
420423
result = UID2TokenGenerator.generate_uid2_token_v3(uid2, _master_key, _site_id, _site_key,
421-
Params(expiry=token_expiry, token_generated_at=now))
424+
Params(expiry=token_expiry, token_generated=now))
422425
final = decrypt(result, keys, now=now)
423426

424427
self.assertEqual(uid2, final.uid)

0 commit comments

Comments
 (0)