Skip to content

Commit

Permalink
Cstg (#22)
Browse files Browse the repository at this point in the history
* Added DecryptionResponse.IsClientSideGenerated for decrypting tokens generated from Client Side Token Generation way. They are usually filled but can be null if the token decryption fails and we cannot determine the property.
* Added DecryptionStatus.UserOptedOut when a Client Side Token Generated token is decrypted and it indicates the user actually has opted out previously so no valid raw UID2 can be used after this decrypt call.
* Created the method interface "DecryptionResponse Decrypt(string token, string expectedDomainName)" for domain name check when decrypting UID2 token, to verify if the expectedDomainName param belongs to the UID2 creator's allowed domain name list (which needs to be registered with UID2 admins). If they don't match then it's a domain name check failure. 
* new DecryptionStatus.DomainNameCheckFailed status to indicate if domain name check failed
* Refactored encryption tests to use a builder for advertiser token
* Make it a 5.3.0 version

---------

Co-authored-by: Sunny Wu <[email protected]>

---------

Co-authored-by: Aleksandrs Ulme <[email protected]>
Co-authored-by: mcollins-ttd <[email protected]>
  • Loading branch information
3 people authored Oct 10, 2023
1 parent 12ca430 commit c7f19e8
Show file tree
Hide file tree
Showing 25 changed files with 1,210 additions and 527 deletions.
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true

[*]
indent_style = space
max_line_length=200
2 changes: 1 addition & 1 deletion UID2.Client.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package>
<metadata>
<id>UID2.Client</id>
<version>5.2.0</version>
<version>5.3.0</version>
<title>UID2 Client C# SDK</title>
<authors>UID2 team</authors>
<owners>UID2 team</owners>
Expand Down
14 changes: 9 additions & 5 deletions src/SampleApp/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Text;
using System.Threading;
using UID2.Client;

Expand All @@ -11,6 +10,7 @@ class Program
static string _authKey;
static string _secretKey;
static string _advertisingToken;
static string _domain;

static void StartExample(string name)
{
Expand All @@ -32,11 +32,12 @@ static void ExampleBasicRefresh()
return;
}

var result = client.Decrypt(_advertisingToken);
var result = client.Decrypt(_advertisingToken, _domain);
Console.WriteLine($"DecryptedSuccess={result.Success} Status={result.Status}");
Console.WriteLine($"UID={result.Uid}");
Console.WriteLine($"EstablishedAt={result.Established}");
Console.WriteLine($"SiteId={result.SiteId}");
Console.WriteLine($"IsClientSideGenerated={result.IsClientSideGenerated}");
}

static void ExampleAutoRefresh()
Expand All @@ -59,7 +60,7 @@ static void ExampleAutoRefresh()

for (int i = 0; i < 5; ++i)
{
var result = client.Decrypt(_advertisingToken);
var result = client.Decrypt(_advertisingToken, _domain);
Console.WriteLine($"DecryptSuccess={result.Success} Status={result.Status} UID={result.Uid}");
Console.Out.Flush();
Thread.Sleep(TimeSpan.FromSeconds(5));
Expand Down Expand Up @@ -106,15 +107,18 @@ static int Main(string[] args)
{
if (args.Length < 4)
{
Console.Error.WriteLine("Usage: test-client <base-url> <auth-key> <secret-key> <ad-token>");
Console.Error.WriteLine("Usage: test-client <base-url> <auth-key> <secret-key> <ad-token> [<domain-name>]");
return 1;
}

_baseUrl = args[0];
_authKey = args[1];
_secretKey = args[2];
_advertisingToken = args[3];

if (args.Length >= 5)
{
_domain = args[4];
}

ExampleBasicRefresh();
ExampleAutoRefresh();
Expand Down
40 changes: 17 additions & 23 deletions src/UID2.Client/DecryptionResponse.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;

namespace UID2.Client
{
public struct DecryptionResponse
public readonly struct DecryptionResponse
{
private readonly DecryptionStatus _status;
private readonly string _uid;
private readonly DateTime? _established;
private readonly int? _siteId;
private readonly int? _siteKeySiteId;

public DecryptionResponse(DecryptionStatus status, string uid, DateTime? established, int? siteId, int? siteKeySiteId)
public DecryptionResponse(DecryptionStatus status, string uid, DateTime? established, int? siteId, int? siteKeySiteId, bool? isClientSideGenerated = false)
{
_status = status;
_uid = uid;
_established = established;
_siteId = siteId;
_siteKeySiteId = siteKeySiteId;
Status = status;
Uid = uid;
Established = established;
SiteId = siteId;
SiteKeySiteId = siteKeySiteId;
IsClientSideGenerated = isClientSideGenerated;
}

public static DecryptionResponse MakeError(DecryptionStatus status)
{
return new DecryptionResponse(status, null, null, null, null);
return new DecryptionResponse(status, null, null, null, null, null);
}

public bool Success => _status == DecryptionStatus.Success;
public DecryptionStatus Status => _status;
public string Uid => _uid;
public DateTime? Established => _established;
public int? SiteId => _siteId;
public int? SiteKeySiteId => _siteKeySiteId;
public bool Success => Status == DecryptionStatus.Success;
public DecryptionStatus Status { get; }
public string Uid { get; }
public DateTime? Established { get; }
public int? SiteId { get; }
public int? SiteKeySiteId { get; }
public bool? IsClientSideGenerated { get; }
}
}
}
5 changes: 5 additions & 0 deletions src/UID2.Client/DecryptionStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@ public enum DecryptionStatus
VersionNotSupported,
InvalidPayloadType,
InvalidIdentityScope,
/// <summary>
/// DSPs are still expected to check their records for user opt out, even when this status is not returned
/// </summary>
UserOptedOut,
DomainNameCheckFailed
}
}
3 changes: 2 additions & 1 deletion src/UID2.Client/IUID2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ public interface IUID2Client
/// <returns>Response showing if decryption is successful and the resulting UID if successful.
/// Or it could return error codes/string indicating what went wrong
/// </returns>
[Obsolete("Please use Decrypt(string token) instead.")]
[Obsolete("Please use Decrypt(string token) instead.")]
DecryptionResponse Decrypt(string token, DateTime utcNow);
DecryptionResponse Decrypt(string token);
DecryptionResponse Decrypt(string token, string expectedDomainName);

EncryptionDataResponse Encrypt(string rawUid);
[Obsolete("Please use Encrypt(string rawUid) instead.")]
Expand Down
18 changes: 17 additions & 1 deletion src/UID2.Client/KeyContainer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UID2.Client.Utils;

namespace UID2.Client
Expand All @@ -12,6 +13,9 @@ internal class KeyContainer
private readonly Dictionary<int, List<Key>> _keysBySite = new Dictionary<int, List<Key>>(); //for legacy /key/latest

private readonly Dictionary<int, List<Key>> _keysByKeyset = new Dictionary<int, List<Key>>();

private readonly Dictionary<int, Site> _siteIdToSite = new Dictionary<int, Site>();

private readonly int _callerSiteId;
private readonly int _masterKeysetId;
private readonly int _defaultKeysetId;
Expand Down Expand Up @@ -45,7 +49,7 @@ internal KeyContainer(List<Key> keys)
}
}

internal KeyContainer(int callerSiteId, int masterKeysetId, int defaultKeysetId, long tokenExpirySeconds, List<Key> keys)
internal KeyContainer(int callerSiteId, int masterKeysetId, int defaultKeysetId, long tokenExpirySeconds, List<Key> keys, IEnumerable<Site> sites)
{ //key/sharing
_callerSiteId = callerSiteId;
_masterKeysetId = masterKeysetId;
Expand Down Expand Up @@ -77,6 +81,8 @@ internal KeyContainer(int callerSiteId, int masterKeysetId, int defaultKeysetId,
{
kv.Value.Sort((Key a, Key b) => a.Activates.CompareTo(b.Activates));
}

this._siteIdToSite = sites.ToDictionary(site => site.Id, site => site);
}

public bool IsValid(DateTime asOf)
Expand Down Expand Up @@ -104,6 +110,16 @@ public bool TryGetMasterKey(DateTime now, out Key key)
return TryGetKeysetActiveKey(_masterKeysetId, now, out key);
}

public bool IsDomainNameAllowedForSite(int siteId, string domainName)
{
if (domainName == null)
{
return false;
}

return this._siteIdToSite.TryGetValue(siteId, out var site) && site.AllowDomainName(domainName);
}

private bool TryGetKeysetActiveKey(int keysetId, DateTime now, out Key key)
{
if (!_keysByKeyset.TryGetValue(keysetId, out var keyset) || keyset.Count == 0)
Expand Down
25 changes: 24 additions & 1 deletion src/UID2.Client/KeyParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,32 @@ internal static KeyContainer Parse(JObject json)
Convert.FromBase64String(item.Value<string>("secret"))
)).ToList();

return new KeyContainer(callerSiteId, masterKeysetId, defaultKeysetId, tokenExpirySeconds, keys);
var sites = Enumerable.Empty<Site>();
if (TryGetSitesJson(body, out var sitesJson))
{
sites = sitesJson.Select(SiteFromJson).ToList();
}

return new KeyContainer(callerSiteId, masterKeysetId, defaultKeysetId, tokenExpirySeconds, keys, sites);
}
}

private static bool TryGetSitesJson(JObject obj, out JArray value)
{
if (obj.TryGetValue("site_data", StringComparison.OrdinalIgnoreCase, out var sites) && sites.Type == JTokenType.Array)
{
value = (JArray)sites;
return true;
}

value = default;
return false;
}

private static Site SiteFromJson(JToken item)
{
var domainNames = (JArray)item["domain_names"];
return new Site(item.Value<int>("id"), domainNames.Select(x => (string)x));
}
}
}
22 changes: 22 additions & 0 deletions src/UID2.Client/PrivacyBits.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Collections;

namespace UID2.Client
{
internal class PrivacyBits
{
// Bit 0 is legacy and is no longer in use
private const int BitClientSideGenerated = 1;
private const int BitOptedOut = 2;

private readonly BitArray _bits;

public PrivacyBits(int bitsAsInt)
{
_bits = new BitArray(new [] {bitsAsInt});
}

public bool IsClientSideGenerated => _bits.Get(BitClientSideGenerated);

public bool IsOptedOut => _bits.Get(BitOptedOut);
}
}
20 changes: 20 additions & 0 deletions src/UID2.Client/Site.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;

namespace UID2.Client
{
internal class Site
{
private readonly HashSet<string> _domainNames;

public Site(int id, IEnumerable<string> domainNames)
{
Id = id;
_domainNames = new HashSet<string>(domainNames, StringComparer.OrdinalIgnoreCase);
}

public int Id { get; }

public bool AllowDomainName(string domainName) => _domainNames.Contains(domainName);
}
}
4 changes: 1 addition & 3 deletions src/UID2.Client/UID2Base64UrlCoder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Tokens;

namespace UID2.Client
{
Expand Down
18 changes: 14 additions & 4 deletions src/UID2.Client/UID2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,20 @@ public UID2Client(string endpoint, string authKey, string secretKey, IdentitySco

public DecryptionResponse Decrypt(string token)
{
return Decrypt(token, DateTime.UtcNow);
return Decrypt(token, DateTime.UtcNow, expectedDomainName: null);
}

public DecryptionResponse Decrypt(string token, DateTime now)
public DecryptionResponse Decrypt(string token, DateTime utcNow)
{
return Decrypt(token, utcNow, expectedDomainName: null);
}

public DecryptionResponse Decrypt(string token, string expectedDomainName)
{
return Decrypt(token, DateTime.UtcNow, expectedDomainName);
}

public DecryptionResponse Decrypt(string token, DateTime now, string expectedDomainName)
{
var container = Volatile.Read(ref _container);
if (container == null)
Expand All @@ -52,7 +62,7 @@ public DecryptionResponse Decrypt(string token, DateTime now)

try
{
return UID2Encryption.Decrypt(token, container, now, _identityScope);
return UID2Encryption.Decrypt(token, container, now, expectedDomainName, _identityScope);
}
catch (Exception)
{
Expand Down Expand Up @@ -124,7 +134,7 @@ public async Task<RefreshResponse> RefreshAsync(CancellationToken token)

private string GetAssemblyNameAndVersion()
{
var version = "5.2.0";
var version = "5.3.0";
return "uid-client-net-" + version;
}

Expand Down
Loading

0 comments on commit c7f19e8

Please sign in to comment.