Skip to content
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

Syw UID2-3679 cpp sdk base64 v4 token #26

Merged
merged 14 commits into from
Aug 21, 2024
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ sudo ln -s $(brew --prefix llvm@14)/bin/clang-format /usr/local/bin/clang-format
sudo ln -s $(brew --prefix llvm@14)/bin/clang-tidy /usr/local/bin/clang-tidy-14
```

## Build and Test in CLion Using Docker on Mac OS
Run
```
docker build -t clion/ubuntu/cpp-env:1.0 -f tools/Dockerfile.cpp-env-ubuntu .
```
And [setup a Docker Toolchain in CLion](https://www.jetbrains.com/help/clion/clion-toolchains-in-docker.html)

And you would be able to develop and test within CLion easily.

## Build, Test, Install

To build, run unit tests, and install under the default prefix (`/usr/local`):
Expand Down
14 changes: 10 additions & 4 deletions lib/uid2encryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ DecryptionResult DecryptToken(const std::string& token, const KeyContainer& keys
return DecryptionResult::MakeError(DecryptionStatus::INVALID_PAYLOAD);
}

const std::string headerStr = token.substr(0, 4);
const bool isBase64UrlEncoding = std::any_of(headerStr.begin(), headerStr.end(), [](char c) { return c == '-' || c == '_'; });
// check the whole ad token string instead of the headerStr to make sure
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - headerStr should be moved to where it's first used (above line 54)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

const bool isBase64UrlEncoding = std::any_of(token.begin(), token.end(), [](char c) { return c == '-' || c == '_'; });
try {
std::vector<std::uint8_t> encryptedId;
std::vector<std::uint8_t> headerBytes;
const std::string headerStr = token.substr(0, 4);

if (isBase64UrlEncoding) {
uid2::UID2Base64UrlCoder::Decode(headerStr, headerBytes);
Expand All @@ -69,8 +70,13 @@ DecryptionResult DecryptToken(const std::string& token, const KeyContainer& keys
return DecryptTokenV3(encryptedId, keys, now, identityScope, checkValidity);
}
if (headerBytes[1] == static_cast<std::uint8_t>(AdvertisingTokenVersion::V4)) {
// same as V3 but use Base64URL encoding
uid2::UID2Base64UrlCoder::Decode(token, encryptedId);
if (isBase64UrlEncoding) {
// same as V3 but use Base64URL encoding
uid2::UID2Base64UrlCoder::Decode(token, encryptedId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different from the Java SDK which converts the token to to base64 and then calls decryptV3.
Is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not intentional but i just chose the easiest way - don't necessarily see the need to clone the exact same logic as long as we have unit tests to cover it?

} else {
// handling the rare situation where participant changed the encoding from Base64URL to Base64
macaron::Base64::Decode(token, encryptedId);
}
return DecryptTokenV3(encryptedId, keys, now, identityScope, checkValidity);
}
return DecryptionResult::MakeError(DecryptionStatus::INVALID_PAYLOAD);
Expand Down
45 changes: 37 additions & 8 deletions test/encryption_tests_v4.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <gtest/gtest.h>

#include <algorithm>
#include <sstream>

using namespace uid2;
Expand Down Expand Up @@ -116,15 +117,46 @@ std::string GenerateUid2TokenV4AndValidate(
return advertisingToken;
}

void DecryptAndAssertSuccess(UID2Client& client, const std::string& advertisingTokenString, Timestamp timestamp = Timestamp::Now())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does const UID2Client& work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tried but no

{
const auto res = client.Decrypt(advertisingTokenString, timestamp);
EXPECT_TRUE(res.IsSuccess());
EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus());
EXPECT_EQ(EXAMPLE_UID, res.GetUid());
}

TEST(EncryptionTestsV4, CanDecryptV4TokenEncodedAsBase64)
{
UID2Client client("ep", "ak", CLIENT_SECRET, IdentityScope::UID2);
client.RefreshJson(KeySetToJson({MASTER_KEY, SITE_KEY}));
std::string advertisingToken;

// for testing purposes, the token must have some Base64URL encoding characters
do {
advertisingToken = GenerateUid2TokenV4AndValidate(EXAMPLE_UID, MASTER_KEY, SITE_ID, SITE_KEY, EncryptTokenParams());
} while (!std::any_of(advertisingToken.begin(), advertisingToken.end(), [](char c) { return c == '-' || c == '_'; }));

const bool isBase64UrlEncoding = std::any_of(advertisingToken.begin(), advertisingToken.end(), [](char c) { return c == '-' || c == '_'; });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need this anymore because of the while condition

EXPECT_TRUE(isBase64UrlEncoding);

std::vector<std::uint8_t> adTokenBytes;
uid2::UID2Base64UrlCoder::Decode(advertisingToken, adTokenBytes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is any of the below code duplicated from the other tests?
Worth making a function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done some refactoring to have DecryptAndAssertSuccess method like other SDK


// explicitly encode into Base64 (non-URL friendly) encoding again
const auto base64NonURLAdTokenV4 = macaron::Base64::Encode(adTokenBytes);
const bool isBase64NonUrlEncoding =
std::any_of(base64NonURLAdTokenV4.begin(), base64NonURLAdTokenV4.end(), [](char c) { return c == '=' || c == '+' || c == '/'; });
EXPECT_TRUE(isBase64NonUrlEncoding);

DecryptAndAssertSuccess(client, base64NonURLAdTokenV4);
}

TEST(EncryptionTestsV4, SmokeTest)
{
UID2Client client("ep", "ak", CLIENT_SECRET, IdentityScope::UID2);
client.RefreshJson(KeySetToJson({MASTER_KEY, SITE_KEY}));
const auto advertisingToken = GenerateUid2TokenV4AndValidate(EXAMPLE_UID, MASTER_KEY, SITE_ID, SITE_KEY, EncryptTokenParams());
const auto res = client.Decrypt(advertisingToken, Timestamp::Now());
EXPECT_TRUE(res.IsSuccess());
EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus());
EXPECT_EQ(EXAMPLE_UID, res.GetUid());
DecryptAndAssertSuccess(client, advertisingToken);
}

TEST(EncryptionTestsV4, EmptyKeyContainer)
Expand Down Expand Up @@ -188,10 +220,7 @@ TEST(EncryptionTestsV4, TokenExpiryAndCustomNow)
EXPECT_FALSE(res.IsSuccess());
EXPECT_EQ(DecryptionStatus::EXPIRED_TOKEN, res.GetStatus());

res = client.Decrypt(advertisingToken, expiry.AddSeconds(-1));
EXPECT_TRUE(res.IsSuccess());
EXPECT_EQ(DecryptionStatus::SUCCESS, res.GetStatus());
EXPECT_EQ(EXAMPLE_UID, res.GetUid());
DecryptAndAssertSuccess(client, advertisingToken, expiry.AddSeconds(-1));
}

TEST(EncryptDataTestsV4, SiteIdFromToken)
Expand Down
28 changes: 28 additions & 0 deletions tools/Dockerfile.cpp-env-ubuntu
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Based on https://github.com/JetBrains/clion-remote/blob/master/Dockerfile.cpp-env-ubuntu
# Build and run:
# docker build -t clion/ubuntu/cpp-env:1.0 -f Dockerfile.cpp-env-ubuntu .

FROM ubuntu:22.04

RUN DEBIAN_FRONTEND="noninteractive" apt-get update && apt-get -y install tzdata

RUN apt-get update \
&& apt-get install -y build-essential \
gcc \
g++ \
gdb \
clang \
make \
ninja-build \
cmake \
autoconf \
automake \
libtool \
valgrind \
locales-all \
dos2unix \
rsync \
tar \
libssl-dev \
libgtest-dev \
&& apt-get clean
Loading