Skip to content

Commit 4eb314f

Browse files
committed
token decryption using token from excel spreadsheet
1 parent 52b9889 commit 4eb314f

File tree

1 file changed

+210
-12
lines changed

1 file changed

+210
-12
lines changed
Lines changed: 210 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,235 @@
11
import sys
2+
import os
3+
import pandas as pd
24

35
from uid2_client import BidstreamClient
6+
from uid2_client.decryption_status import DecryptionStatus
7+
from uid2_client.encryption import EncryptionError
48

59

610
# this sample client decrypts an advertising token into a raw UID2
711
# to demonstrate decryption for DSPs
12+
# Can process a single token or read UID2s from an Excel file
813

914
def _usage():
10-
print('Usage: python3 sample_bidstream_client.py <base_url> <auth_key> <secret_key> <domain_name> <ad_token>', file=sys.stderr)
15+
print('Usage: python3 sample_bidstream_client.py <base_url> <auth_key> <secret_key> <domain_name> <ad_token_or_excel_file>', file=sys.stderr)
16+
print(' If the 5th argument is an .xlsx file, it will read UID2s from the "UID" column in the "GAM" sheet', file=sys.stderr)
1117
sys.exit(1)
1218

1319

20+
def get_error_summary(exception):
21+
"""Extract a concise root cause message from an exception."""
22+
# For EncryptionError with "invalid payload", show the underlying exception
23+
if isinstance(exception, EncryptionError):
24+
error_msg = str(exception)
25+
# If it's an "invalid payload" error, always show the underlying cause
26+
if error_msg == 'invalid payload' and exception.__cause__:
27+
root_cause = str(exception.__cause__)
28+
# Extract the first line and clean it up
29+
root_line = root_cause.split('\n')[0].strip()
30+
# Remove common exception prefixes if present
31+
if root_line.startswith(exception.__cause__.__class__.__name__ + ':'):
32+
root_line = root_line.split(':', 1)[1].strip()
33+
# Combine with "invalid payload" context
34+
return f"invalid payload: {root_line}"
35+
# For other EncryptionError messages, use as-is
36+
return error_msg.split('\n')[0].strip()
37+
38+
# For other exceptions, check for chained exceptions (root cause)
39+
if exception.__cause__:
40+
root_cause = str(exception.__cause__)
41+
# Extract the first line and clean it up
42+
root_line = root_cause.split('\n')[0].strip()
43+
# Remove common exception prefixes if present
44+
if root_line.startswith(exception.__cause__.__class__.__name__ + ':'):
45+
root_line = root_line.split(':', 1)[1].strip()
46+
return root_line
47+
48+
# Otherwise, extract the exception message
49+
error_msg = str(exception)
50+
# Remove exception class name prefix if present (e.g., "ValueError: message")
51+
if ':' in error_msg and error_msg.split(':')[0].strip() == exception.__class__.__name__:
52+
error_msg = error_msg.split(':', 1)[1].strip()
53+
54+
return error_msg.split('\n')[0].strip()
55+
56+
57+
def decrypt_token(client, ad_token, domain_name, index=None):
58+
"""Decrypt a single token and return the result with error handling."""
59+
token_suffix = ad_token[-6:] if len(ad_token) >= 6 else ad_token
60+
try:
61+
decrypt_result = client.decrypt_token_into_raw_uid(ad_token, domain_name)
62+
63+
result = {
64+
'index': index,
65+
'token': ad_token[:50] + '...' if len(ad_token) > 50 else ad_token,
66+
'token_suffix': token_suffix,
67+
'status': decrypt_result.status,
68+
'uid': decrypt_result.uid,
69+
'established': decrypt_result.established,
70+
'site_id': decrypt_result.site_id,
71+
'identity_type': decrypt_result.identity_type,
72+
'advertising_token_version': decrypt_result.advertising_token_version,
73+
'is_client_side_generated': decrypt_result.is_client_side_generated,
74+
'error': None,
75+
}
76+
return result
77+
except EncryptionError as e:
78+
# Handle encryption errors - extract root cause
79+
return {
80+
'index': index,
81+
'token': ad_token[:50] + '...' if len(ad_token) > 50 else ad_token,
82+
'token_suffix': token_suffix,
83+
'status': None,
84+
'uid': None,
85+
'established': None,
86+
'site_id': None,
87+
'identity_type': None,
88+
'advertising_token_version': None,
89+
'is_client_side_generated': None,
90+
'error': get_error_summary(e),
91+
}
92+
except Exception as e:
93+
# Handle any other unexpected errors - extract root cause
94+
return {
95+
'index': index,
96+
'token': ad_token[:50] + '...' if len(ad_token) > 50 else ad_token,
97+
'token_suffix': token_suffix,
98+
'status': None,
99+
'uid': None,
100+
'established': None,
101+
'site_id': None,
102+
'identity_type': None,
103+
'advertising_token_version': None,
104+
'is_client_side_generated': None,
105+
'error': get_error_summary(e),
106+
}
107+
108+
109+
def print_result(result):
110+
"""Print decryption result in a formatted way."""
111+
token_suffix = result.get('token_suffix', '')
112+
if result['index'] is not None:
113+
print(f"\n{'='*60}")
114+
if token_suffix:
115+
print(f"Token #{result['index'] + 1} (last 6 chars: {token_suffix}): {result['token']}")
116+
else:
117+
print(f"Token #{result['index'] + 1}: {result['token']}")
118+
print(f"{'='*60}")
119+
else:
120+
print(f"\n{'='*60}")
121+
if token_suffix:
122+
print(f"Token (last 6 chars: {token_suffix}): {result['token']}")
123+
else:
124+
print(f"Token: {result['token']}")
125+
print(f"{'='*60}")
126+
127+
# Check if there was an error or if status indicates failure
128+
if result['error'] is not None:
129+
print(f"ERROR: {result['error']}")
130+
elif result['status'] is None:
131+
print(f"ERROR: Unknown error occurred")
132+
elif result['status'] != DecryptionStatus.SUCCESS:
133+
print(f"ERROR: {result['status'].value}")
134+
else:
135+
print(f"Status = {result['status'].name} ({result['status'].value})")
136+
print(f"UID = {result['uid']}")
137+
print(f"Established = {result['established']}")
138+
print(f"Site ID = {result['site_id']}")
139+
print(f"Identity Type = {result['identity_type']}")
140+
print(f"Advertising Token Version = {result['advertising_token_version']}")
141+
print(f"Is Client Side Generated = {result['is_client_side_generated']}")
142+
143+
14144
if len(sys.argv) < 6:
15145
_usage()
16146

17147
base_url = sys.argv[1]
18148
auth_key = sys.argv[2]
19149
secret_key = sys.argv[3]
20150
domain_name = sys.argv[4]
21-
ad_token = sys.argv[5]
151+
input_arg = sys.argv[5]
22152

153+
# Initialize client
23154
client = BidstreamClient(base_url, auth_key, secret_key)
24155
refresh_response = client.refresh()
25156
if not refresh_response.success:
26-
print('Failed to refresh keys due to =', refresh_response.reason)
157+
print('Failed to refresh keys due to =', refresh_response.reason, file=sys.stderr)
27158
sys.exit(1)
28159

29-
decrypt_result = client.decrypt_token_into_raw_uid(ad_token, domain_name)
30-
31-
print('Status =', decrypt_result.status)
32-
print('UID =', decrypt_result.uid)
33-
print('Established =', decrypt_result.established)
34-
print('Site ID =', decrypt_result.site_id)
35-
print('Identity Type =', decrypt_result.identity_type)
36-
print('Advertising Token Version =', decrypt_result.advertising_token_version)
37-
print('Is Client Side Generated =', decrypt_result.is_client_side_generated)
160+
# Check if input is an Excel file
161+
if input_arg.endswith('.xlsx') and os.path.exists(input_arg):
162+
# Read UID2s from Excel file
163+
print(f"Reading UID2s from Excel file: {input_arg}", file=sys.stderr)
164+
try:
165+
df = pd.read_excel(input_arg, sheet_name='GAM')
166+
167+
# Get the UID2 column (it's called 'UID' in the file)
168+
if 'UID' not in df.columns:
169+
print(f"Error: 'UID' column not found in GAM sheet. Available columns: {df.columns.tolist()}", file=sys.stderr)
170+
sys.exit(1)
171+
172+
# Filter out invalid entries (like "Bad Envelope")
173+
uid2_tokens = df['UID'].dropna().astype(str)
174+
uid2_tokens = uid2_tokens[uid2_tokens != 'Bad Envelope']
175+
uid2_tokens = uid2_tokens[uid2_tokens.str.strip() != ''].tolist()
176+
177+
print(f"Found {len(uid2_tokens)} valid UID2 tokens to decrypt", file=sys.stderr)
178+
179+
# Decrypt each token sequentially
180+
results = []
181+
for idx, token in enumerate(uid2_tokens):
182+
# Print the last 6 characters of the token first
183+
token_suffix = token[-6:] if len(token) >= 6 else token
184+
print(f"\nProcessing token {idx + 1}/{len(uid2_tokens)} (last 6 chars: {token_suffix})...", file=sys.stderr)
185+
try:
186+
result = decrypt_token(client, token, domain_name, index=idx)
187+
results.append(result)
188+
189+
# Print one-line error summary if failed, otherwise full result
190+
if result['error'] is not None:
191+
print(f"Token #{idx + 1} ({token_suffix}) FAILED: {result['error']}")
192+
elif result['status'] is not None and result['status'] != DecryptionStatus.SUCCESS:
193+
print(f"Token #{idx + 1} ({token_suffix}) FAILED: {result['status'].value}")
194+
else:
195+
print_result(result)
196+
except Exception as e:
197+
# Catch any unexpected errors during processing
198+
token_suffix = token[-6:] if len(token) >= 6 else token
199+
error_summary = get_error_summary(e)
200+
error_result = {
201+
'index': idx,
202+
'token': token[:50] + '...' if len(token) > 50 else token,
203+
'token_suffix': token_suffix,
204+
'status': None,
205+
'uid': None,
206+
'established': None,
207+
'site_id': None,
208+
'identity_type': None,
209+
'advertising_token_version': None,
210+
'is_client_side_generated': None,
211+
'error': error_summary,
212+
}
213+
results.append(error_result)
214+
print(f"Token #{idx + 1} ({token_suffix}) FAILED: {error_summary}")
215+
216+
# Print summary
217+
print(f"\n{'='*60}")
218+
print(f"SUMMARY")
219+
print(f"{'='*60}")
220+
print(f"Total tokens processed: {len(results)}")
221+
successful = sum(1 for r in results if r.get('status') == DecryptionStatus.SUCCESS)
222+
print(f"Successful decryptions: {successful}")
223+
print(f"Failed decryptions: {len(results) - successful}")
224+
225+
except Exception as e:
226+
print(f"Error reading Excel file: {e}", file=sys.stderr)
227+
sys.exit(1)
228+
else:
229+
# Process single token
230+
try:
231+
result = decrypt_token(client, input_arg, domain_name, index=None)
232+
print_result(result)
233+
except Exception as e:
234+
print(f"ERROR: {str(e)}", file=sys.stderr)
235+
sys.exit(1)

0 commit comments

Comments
 (0)