Merge pull request 'Updated .NET PADP reference implementation - added ability to explicitly provide userAccessToken to requests; add V3 OTP endpoints (while also keeping V2 endpoints)' (#25) from feature/PADP-reference-POC into develop

Reviewed-on: #25
Reviewed-by: Bas Boterman <b.boterman@htm.nl>
This commit is contained in:
Max Martens 2025-12-01 09:44:13 +00:00
commit ed40c1d546
21 changed files with 1464 additions and 0 deletions

View File

@ -0,0 +1,614 @@
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using Microsoft.AspNetCore.Http.HttpResults;
class ApimHelper
{
private readonly string _apimBaseUri;
private readonly string _b2bApiUri;
private readonly string _padp1201Uri;
private readonly string _padp1202Uri;
private readonly string _padp1204Uri;
private readonly string _padp1205Uri;
private readonly string _padp1206V2Uri;
private readonly string _padp1207V2Uri;
private readonly string _padp1206V3Uri;
private readonly string _padp1207V3Uri;
private readonly string _padp1210Uri;
private readonly string _padp1211Uri;
private readonly string _b2bApiKey;
private readonly string _padApiKey;
private readonly string _clientId;
private readonly string _clientSecret;
private CryptoHelper cryptoHelper;
private readonly HttpClient _httpClient;
private Dictionary<string, UserAuthInfo> userAuthInfoMap = new Dictionary<string, UserAuthInfo>();
public ApimHelper(UserProperties userProperties)
{
_apimBaseUri = userProperties.Uris.ApimBaseUri;
_b2bApiUri = userProperties.Uris.B2bApiUri;
_padp1201Uri = userProperties.Uris.Padp1201Uri;
_padp1202Uri = userProperties.Uris.Padp1202Uri;
_padp1204Uri = userProperties.Uris.Padp1204Uri;
_padp1205Uri = userProperties.Uris.Padp1205Uri;
_padp1206V2Uri = userProperties.Uris.Padp1206V2Uri;
_padp1207V2Uri = userProperties.Uris.Padp1207V2Uri;
_padp1206V3Uri = userProperties.Uris.Padp1206V3Uri;
_padp1207V3Uri = userProperties.Uris.Padp1207V3Uri;
_padp1210Uri = userProperties.Uris.Padp1210Uri;
_padp1211Uri = userProperties.Uris.Padp1211Uri;
_b2bApiKey = userProperties.Credentials.B2bApiKey;
_padApiKey = userProperties.Credentials.PadApiKey;
_clientId = userProperties.Credentials.ClientId;
_clientSecret = userProperties.Credentials.ClientSecret;
cryptoHelper = new CryptoHelper(userProperties);
HttpClientHandler handler = new HttpClientHandler();
string clientCertPath = Environment.CurrentDirectory + userProperties.Credentials.ClientCertFile;
Console.WriteLine("Loading client cert from: {0}", clientCertPath);
handler.ClientCertificates.Add(new X509Certificate2(clientCertPath, userProperties.Credentials.ClientCertPassword));
_httpClient = new HttpClient(handler);
_httpClient.BaseAddress = new Uri(_apimBaseUri);
}
public async Task<Results<Ok<B2bAccessToken>, JsonHttpResult<ErrorResponse>>> GetB2bAccessToken()
{
var request = new HttpRequestMessage(HttpMethod.Post, _b2bApiUri);
request.Headers.Add("APIKey", _b2bApiKey);
request.Headers.Add("Accept", "application/json");
request.Content = new StringContent($"grant_type=client_credentials&client_id={_clientId}&client_secret={_clientSecret}", Encoding.UTF8, "application/x-www-form-urlencoded");
Console.WriteLine("Sending request to {0}...", _b2bApiUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK) {
return TypedResults.Ok(await response.Content.ReadFromJsonAsync<B2bAccessToken>());
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
private string GetB2bAccessTokenAsString()
{
var b2bAccessToken = GetB2bAccessToken();
return ((Ok<B2bAccessToken>)b2bAccessToken.Result.Result).Value.accessToken;
}
public IResult GetImageFromBase64(string base64Image)
{
byte[] imageBytes = Convert.FromBase64String(base64Image);
return TypedResults.File(imageBytes, "image/jpeg");
}
public async Task<Results<Created, JsonHttpResult<ErrorResponse>>> CreatePersonalData(string xTat, string email, string? name, string? birthDate, IFormFile? photo)
{
UserAuthInfo userAuthInfo;
if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.encryptedEphemeralKey != null) {
userAuthInfo = authInfo;
} else {
var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate and validate OTP first using API 1210!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
var b2bAccessToken = await GetB2bAccessToken();
var requestUri = _padp1201Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + ((Ok<B2bAccessToken>)b2bAccessToken.Result).Value.accessToken);
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
var createPersonalDataRequest = new CreatePersonalDataRequest();
createPersonalDataRequest.metadata.email = email;
createPersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias;
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey);
if (name != null) {
createPersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name);
}
if (birthDate != null) {
createPersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);;
}
if (photo != null) {
using (MemoryStream memoryStream = new MemoryStream())
{
photo.CopyTo(memoryStream);
byte[] photoBytes = memoryStream.ToArray();
createPersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes);
}
}
request.Content = JsonContent.Create(createPersonalDataRequest);
Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Created) {
return TypedResults.Created();
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Created, JsonHttpResult<ErrorResponse>>> CreatePersonalDataWithEmailVerification(string xTat, string email, string? userAccessToken, string? name, string? birthDate, IFormFile? photo)
{
UserAuthInfo userAuthInfo;
if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo)) {
if (authInfo.encryptedEphemeralKey == null) {
var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
} else if (authInfo.UserAccessToken == null && userAccessToken == null) {
var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
userAuthInfo = authInfo;
} else {
var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
var requestUri = _padp1201Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
if (userAccessToken != null) {
Console.WriteLine("Using provided User Access Token {0}...", userAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAccessToken);
} else {
Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken);
}
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
var createPersonalDataRequest = new CreatePersonalDataRequest();
createPersonalDataRequest.metadata.email = email;
createPersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias;
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey);
if (name != null) {
createPersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name);
}
if (birthDate != null) {
createPersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);;
}
if (photo != null) {
using (MemoryStream memoryStream = new MemoryStream())
{
photo.CopyTo(memoryStream);
byte[] photoBytes = memoryStream.ToArray();
createPersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes);
}
}
request.Content = JsonContent.Create(createPersonalDataRequest);
Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Created) {
return TypedResults.Created();
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<PersonalData>, JsonHttpResult<ErrorResponse>>> GetPersonalData(string xTat, string? userAccessToken)
{
UserAuthInfo? userAuthInfo = null;
if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.UserAccessToken != null)
{
userAuthInfo = authInfo;
} else if (userAccessToken == null) {
var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
var requestUri = _padp1202Uri.Replace("{xtat}", xTat) + "?pemRsaPublicKey=" + cryptoHelper.GetPublicKeyRsa();
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
if (userAccessToken != null)
{
Console.WriteLine("Using provided User Access Token {0}...", userAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAccessToken);
} else {
Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken);
}
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
return TypedResults.Ok(await response.Content.ReadFromJsonAsync<PersonalData>());
}
else
{
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<DecryptedPersonalData>, JsonHttpResult<ErrorResponse>>> GetDecryptedPersonalData(string xTat, string? userAccessToken)
{
var personalData = await GetPersonalData(xTat, userAccessToken);
if (personalData.Result.GetType() == typeof(JsonHttpResult<ErrorResponse>)) {
return (JsonHttpResult<ErrorResponse>)personalData.Result;
} else {
var decryptedPersonalData = await DecryptPersonalData(((Ok<PersonalData>)personalData.Result).Value);
return TypedResults.Ok(decryptedPersonalData);
}
}
public async Task<Results<Ok<DeletePersonalDataResponse>, JsonHttpResult<ErrorResponse>>> DeletePersonalData(string xTat, string? userAccessToken)
{
UserAuthInfo? userAuthInfo = null;
if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo) && authInfo.UserAccessToken != null)
{
userAuthInfo = authInfo;
} else if (userAccessToken == null) {
var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
var requestUri = _padp1204Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Delete, requestUri);
if (userAccessToken != null) {
Console.WriteLine("Using provided User Access Token {0}...", userAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAccessToken);
} else {
Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken);
}
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK) {
return TypedResults.Ok(await response.Content.ReadFromJsonAsync<DeletePersonalDataResponse>());
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok, JsonHttpResult<ErrorResponse>>> UpdatePersonalData(string xTat, string? userAccessToken, bool skipUpdateCounter, string? name, string? birthDate, IFormFile? photo)
{
UserAuthInfo userAuthInfo;
if (userAuthInfoMap.TryGetValue(xTat, out UserAuthInfo? authInfo)) {
if (authInfo.encryptedEphemeralKey == null) {
var errorResponse = new ErrorResponse("No ephemeral key found for xTAT " + xTat + " - generate an ephemeral key first, using API 1210!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
} else if (authInfo.UserAccessToken == null && userAccessToken == null) {
var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
userAuthInfo = authInfo;
} else {
var errorResponse = new ErrorResponse("No User Access Token found for xTAT " + xTat + " - provide one in the request, or generate and validate OTP using API 1206 and 1207!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
var requestUri = _padp1205Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Put, requestUri);
if (userAccessToken != null) {
Console.WriteLine("Using provided User Access Token {0}...", userAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAccessToken);
} else {
Console.WriteLine("Using stored User Access Token {0}...", userAuthInfo.UserAccessToken);
request.Headers.Add("Authorization", "Bearer " + userAuthInfo.UserAccessToken);
}
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
var updatePersonalDataRequest = new UpdatePersonalDataRequest();
updatePersonalDataRequest.metadata.skipUpdateCounter = skipUpdateCounter;
updatePersonalDataRequest.metadata.ephemeralKeyAlias = userAuthInfo.ephemeralKeyAlias;
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(userAuthInfo.encryptedEphemeralKey);
if (name != null) {
updatePersonalDataRequest.data.personalAccountData.name = cryptoHelper.EncryptText(decryptedEphemeralKey, name);
}
if (birthDate != null) {
updatePersonalDataRequest.data.personalAccountData.birthdate = cryptoHelper.EncryptText(decryptedEphemeralKey, birthDate);;
}
if (photo != null) {
using (MemoryStream memoryStream = new MemoryStream())
{
photo.CopyTo(memoryStream);
byte[] photoBytes = memoryStream.ToArray();
updatePersonalDataRequest.data.personalAccountData.photo = cryptoHelper.EncryptPhoto(decryptedEphemeralKey, photoBytes);
}
}
request.Content = JsonContent.Create(updatePersonalDataRequest);
Console.WriteLine("Sending request to {0} with request body {1}...", requestUri, request.Content.ReadAsStringAsync().Result);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK) {
return TypedResults.Ok();
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<JsonHttpResult<OtpResponse>, JsonHttpResult<ErrorResponse>>> GenerateOtpV2(string xTat)
{
var requestUri = _padp1206V2Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
request.Content = new StringContent("{\"channel\": \"EMAIL\"}", Encoding.UTF8, "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Accepted) {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<OtpResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<UserAccessToken>, JsonHttpResult<ErrorResponse>>> ValidateOtpV2(string xTat, string otp)
{
var requestUri = _padp1207V2Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
request.Content = new StringContent("{\"otp\": \"" + otp + "\"}", Encoding.UTF8, "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK) {
var content = await response.Content.ReadFromJsonAsync<UserAccessToken>();
Console.WriteLine("Successfully retrieved User Access Token for xTAT {0} - storing for use in following calls...", xTat);
if (!userAuthInfoMap.ContainsKey(xTat)) {
userAuthInfoMap.Add(xTat, new UserAuthInfo());
}
UserAuthInfo userAuthInfo = userAuthInfoMap[xTat];
userAuthInfo.UserAccessToken = content.accessToken;
return TypedResults.Ok(content);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<JsonHttpResult<OtpResponse>, JsonHttpResult<ErrorResponse>>> GenerateOtpV3(string? email, string? xTat)
{
var requestUri = _padp1206V3Uri;
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
var generateOtpRequest = new GenerateOtpRequestV3();
generateOtpRequest.channel = "EMAIL";
if (email != null) {
generateOtpRequest.source = "EMAIL";
generateOtpRequest.recipient = email;
} else if (xTat != null) {
generateOtpRequest.source = "XTAT";
generateOtpRequest.recipient = xTat;
} else if (email != null && xTat != null){
var errorResponse = new ErrorResponse("Both e-mail and xTAT filled, only one should be filled in the request!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
request.Content = JsonContent.Create(generateOtpRequest);
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Accepted) {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<OtpResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<UserAccessToken>, JsonHttpResult<ErrorResponse>>> ValidateOtpV3(string? email, string? xTat, string otp)
{
var requestUri = _padp1207V3Uri;
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
var validateOtpRequest = new ValidateOtpRequestV3();
validateOtpRequest.otp = otp;
if (email != null)
{
validateOtpRequest.source = "EMAIL";
validateOtpRequest.recipient = email;
}
else if (xTat != null)
{
validateOtpRequest.source = "XTAT";
validateOtpRequest.recipient = xTat;
}
else if (email != null && xTat != null)
{
var errorResponse = new ErrorResponse("Both e-mail and xTAT filled, only one should be filled in the request!", 400);
return TypedResults.Json(errorResponse, JsonSerializerOptions.Default, "application/json", 400);
}
request.Content = JsonContent.Create(validateOtpRequest);
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK) {
var content = await response.Content.ReadFromJsonAsync<UserAccessToken>();
if (xTat != null) {
Console.WriteLine("Successfully retrieved User Access Token for xTAT {0} - storing for use in following calls...", xTat);
if (!userAuthInfoMap.ContainsKey(xTat))
{
userAuthInfoMap.Add(xTat, new UserAuthInfo());
}
UserAuthInfo userAuthInfo = userAuthInfoMap[xTat];
userAuthInfo.UserAccessToken = content.accessToken;
}
else {
Console.WriteLine("Successfully retrieved User Access Token for e-mail {0} - we don't know the xTAT (yet), so we don't cannot the user access token for later use...");
}
return TypedResults.Ok(content);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public async Task<Results<Ok<AdministrativeData>, JsonHttpResult<ErrorResponse>>> GetAdministrativeData(string xTat)
{
var requestUri = _padp1211Uri.Replace("{xtat}", xTat);
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
return TypedResults.Ok(await response.Content.ReadFromJsonAsync<AdministrativeData>());
}
else
{
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
public string EncryptDecryptPoc(string textToEncrypt, string encryptedEphemeralKey)
{
Console.WriteLine("Encrypting, then decrypting text {0} using encrypted ephemeral key {1}...", textToEncrypt, encryptedEphemeralKey);
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey);
var encryptedText = cryptoHelper.EncryptText(decryptedEphemeralKey, textToEncrypt);
Console.WriteLine($"Encrypted text: {encryptedText}");
Console.WriteLine("Decrypting text {0}...", encryptedText);
var decryptedText = cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, encryptedText);
Console.WriteLine($"Decrypted text: {decryptedText.Result}");
return "Encrypted text: " + encryptedText + "\nDecrypted text: " + decryptedText.Result;
}
public string EncryptPoc(string textToEncrypt, string encryptedEphemeralKey)
{
Console.WriteLine("Encrypting text {0} using encrypted ephemeral key {1}...", textToEncrypt, encryptedEphemeralKey);
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey);
var encryptedText = cryptoHelper.EncryptText(decryptedEphemeralKey, textToEncrypt);
Console.WriteLine($"Encrypted text: {encryptedText}");
return "Encrypted text: " + encryptedText;
}
public string DecryptPoc(string textToDecrypt, string encryptedEphemeralKey)
{
Console.WriteLine("Decrypting text {0} using ephemeral key {1}...", textToDecrypt, encryptedEphemeralKey);
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(encryptedEphemeralKey);
var decryptedText = cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, textToDecrypt);
Console.WriteLine($"Decrypted text: {decryptedText.Result}");
return "Decrypted text: " + decryptedText.Result;
}
public async Task<Results<Ok<EphemeralKey>, JsonHttpResult<ErrorResponse>>> CreateEphemeralKey(string xTat)
{
var requestUri = _padp1210Uri + "?pemRsaPublicKey=" + cryptoHelper.GetPublicKeyRsa();
var request = new HttpRequestMessage(HttpMethod.Post, requestUri);
request.Headers.Add("Authorization", "Bearer " + GetB2bAccessTokenAsString());
request.Headers.Add("APIKey", _padApiKey);
request.Headers.Add("requestId", Guid.NewGuid().ToString());
request.Headers.Add("Accept", "application/json");
Console.WriteLine("Sending request to {0}...", requestUri);
var response = await _httpClient.SendAsync(request);
if (response.StatusCode == System.Net.HttpStatusCode.Created) {
var content = await response.Content.ReadFromJsonAsync<EphemeralKey>();
Console.WriteLine("Successfully created Ephemeral Key for xTAT {0} - storing for use in following calls...", xTat);
if (!userAuthInfoMap.ContainsKey(xTat)) {
userAuthInfoMap.Add(xTat, new UserAuthInfo());
}
UserAuthInfo userAuthInfo = userAuthInfoMap[xTat];
userAuthInfo.ephemeralKeyAlias = content.ephemeralKeyAlias;
userAuthInfo.encryptedEphemeralKey = content.encryptedEphemeralKey;
return TypedResults.Ok(content);
} else {
return TypedResults.Json(await response.Content.ReadFromJsonAsync<ErrorResponse>(), JsonSerializerOptions.Default, "application/json", (int)response.StatusCode);
}
}
private async Task<DecryptedPersonalData> DecryptPersonalData(PersonalData personalData)
{
var decryptedEphemeralKey = cryptoHelper.DecryptEphemeralKey(personalData.metadata.encryptedEphemeralKey);
Console.WriteLine("Decrypted encryptedEphemeralKey: {0}", Convert.ToBase64String(decryptedEphemeralKey));
var decryptedData = new DecryptedPersonalData.DecryptedData();
if (personalData.data.name != null) {
decryptedData.decryptedName = await cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, personalData.data.name);
Console.WriteLine($"Decrypted name: {decryptedData.decryptedName}");
}
if (personalData.data.birthdate != null) {
decryptedData.decryptedBirthdate = await cryptoHelper.DecryptTextAsync(decryptedEphemeralKey, personalData.data.birthdate);
Console.WriteLine($"Decrypted birthDate: {decryptedData.decryptedBirthdate}");
}
if (personalData.data.photo != null) {
decryptedData.decryptedPhoto = await cryptoHelper.DecryptPhotoAsync(decryptedEphemeralKey, personalData.data.photo);
Console.WriteLine($"Decrypted photo: {decryptedData.decryptedPhoto}");
}
return new DecryptedPersonalData(decryptedData, personalData);
}
}

View File

@ -0,0 +1,124 @@
using System.Security.Cryptography;
using System.Text;
public class CryptoHelper
{
private RSA rsa;
private string _publicKeyRsa;
public CryptoHelper(UserProperties userProperties)
{
string publicKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PublicKeyFile;
string privateKeyPath = Environment.CurrentDirectory + userProperties.Rsa.PrivateKeyFile;
Console.WriteLine("Loading public key from: {0}", publicKeyPath);
Console.WriteLine("Loading private key from: {0}", privateKeyPath);
rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText(publicKeyPath));
rsa.ImportFromPem(File.ReadAllText(privateKeyPath));
_publicKeyRsa = Convert.ToBase64String(rsa.ExportSubjectPublicKeyInfo());
}
public string GetPublicKeyRsa() => _publicKeyRsa;
public byte[] DecryptEphemeralKey(string encryptedEphemeralKey)
{
return rsa.Decrypt(Convert.FromBase64String(encryptedEphemeralKey), RSAEncryptionPadding.OaepSHA512);
}
public async Task<string> DecryptTextAsync(byte[] decryptedEphemeralKey, string encryptedText)
{
var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedText);
return Encoding.UTF8.GetString(decryptedBytes);
}
public async Task<string> DecryptPhotoAsync(byte[] decryptedEphemeralKey, string encryptedPhoto)
{
var decryptedBytes = await DecryptContentAsync(decryptedEphemeralKey, encryptedPhoto);
return Convert.ToBase64String(decryptedBytes);
}
public string EncryptText(byte[] decryptedEphemeralKey, string textValue)
{
return EncryptContent(decryptedEphemeralKey, Encoding.UTF8.GetBytes(textValue));
}
public string EncryptPhoto(byte[] decryptedEphemeralKey, byte[] photoBytes)
{
return EncryptContent(decryptedEphemeralKey, photoBytes);
}
private async Task<byte[]> DecryptContentAsync(byte[] decryptedEphemeralKey, string encryptedData)
{
byte[] encryptedDataByteArray = Convert.FromBase64String(encryptedData);
using MemoryStream memoryStream = new(encryptedDataByteArray);
using Aes aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
byte[] iv = new byte[aes.IV.Length];
int numBytesToRead = aes.IV.Length;
int numBytesRead = 0;
while (numBytesToRead > 0)
{
int n = memoryStream.Read(iv, numBytesRead, numBytesToRead);
if (n == 0)
{
break;
}
numBytesRead += n;
numBytesToRead -= n;
}
byte[] key = decryptedEphemeralKey;
await using CryptoStream cryptoStream = new(memoryStream,
aes.CreateDecryptor(key, iv),
CryptoStreamMode.Read);
using MemoryStream ms = new();
await cryptoStream.CopyToAsync(ms);
var bytes = ms.ToArray();
return bytes;
}
private string EncryptContent(byte[] decryptedEphemeralKey, byte[] content)
{
byte[] encrypted;
byte[] iv;
using (var aesAlg = Aes.Create())
{
aesAlg.Key = decryptedEphemeralKey;
aesAlg.GenerateIV();
iv = aesAlg.IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
// Create an encryptor to perform the stream transform.
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new())
{
using (CryptoStream csEncrypt = new(msEncrypt, encryptor, CryptoStreamMode.Write))
{
//Write all data to the stream.
csEncrypt.Write(content, 0, content.Length);
}
encrypted = msEncrypt.ToArray();
}
}
var combinedIvCt = new byte[iv.Length + encrypted.Length];
Array.Copy(iv, 0, combinedIvCt, 0, iv.Length);
Array.Copy(encrypted, 0, combinedIvCt, iv.Length, encrypted.Length);
var encryptedData = Convert.ToBase64String(combinedIvCt);
return encryptedData;
}
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.14" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="8.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,250 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(setupAction =>
{
setupAction.EnableAnnotations();
setupAction.SchemaFilter<SwaggerSchemaExampleFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var group = app.MapGroup("/").DisableAntiforgery().WithTags("PADP Reference API");
var pocGroup = app.MapGroup("/poc").DisableAntiforgery().WithTags("Encrypt/decrypt POC");
UserProperties userProperties = app.Configuration.GetSection("UserProperties").Get<UserProperties>();
ApimHelper apimHelper = new ApimHelper(userProperties);
group.MapGet("/idp/b2b-access-token", () =>
{
return apimHelper.GetB2bAccessToken();
})
.Produces<B2bAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GetB2bAccessToken")
.WithSummary("API 1020 - Get B2B Access Token")
.WithDescription("Returns a client access token, needed for most other PADP APIs.")
.WithOpenApi();
group.MapGet("/get-image-from-base64", ([FromHeader]string base64String) =>
{
return apimHelper.GetImageFromBase64(base64String);
})
.Produces<FileContentHttpResult>(200, "image/jpeg")
.WithName("GetImageFromBase64")
.WithSummary("Get rendered image from Base64 encoded String")
.WithOpenApi();
group.MapPost("/personal-data/{xtat}", (string xTat, [SwaggerParameter("Email address to be used for OTP challenges")] string email, [SwaggerParameter("Should be at least two words (first name and last name)")] string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")] string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")] IFormFile? photo) =>
{
Console.WriteLine("Creating personal data for xTAT: " + xTat);
return apimHelper.CreatePersonalData(xTat, email, name, birthDate, photo);
})
.Produces(201)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("CreatePersonalData")
.WithSummary("API 1201 - Create Personal Data - First create an ephemeral key using API 1210!")
.WithDescription("First create an ephemeral key using API 1210!")
.WithOpenApi();
group.MapPost("/personal-data/{xtat}/with-email-verification", (string xTat, [SwaggerParameter("Email address to be used for OTP challenges")]string email, [SwaggerParameter("This user access token will be used for e-mail verification if provided - otherwise, the internally stored one (based on previous V3 1206+1207 calls) will be used")]string? userAccessToken, [SwaggerParameter("Should be at least two words (first name and last name)")]string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")]string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")]IFormFile? photo) =>
{
Console.WriteLine("Creating personal data with e-mail verification for xTAT: " + xTat);
return apimHelper.CreatePersonalDataWithEmailVerification(xTat, email, userAccessToken,name, birthDate, photo);
})
.Produces(201)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("CreatePersonalDataWithEmailVerification")
.WithSummary("API 1201 - Create Personal Data with e-mail verification - First create an ephemeral key using API 1210!")
.WithDescription("Performs extra e-mail verification using OTP flow; using internally stored (after 1206+1207 call) or explicitly provided user access token. First create an ephemeral key using API 1210!")
.WithOpenApi();
group.MapGet("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) =>
{
Console.WriteLine("Retrieving personal data for xTAT: " + xTat);
return apimHelper.GetPersonalData(xTat, userAccessToken);
})
.Produces<PersonalData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GetPersonalData")
.WithSummary("API 1202 - Get Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithOpenApi();
group.MapGet("/personal-data/{xtat}/decrypted", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) =>
{
Console.WriteLine("Retrieving decrypted personal data for xTAT: " + xTat);
return apimHelper.GetDecryptedPersonalData(xTat, userAccessToken);
})
.Produces<DecryptedPersonalData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GetDecryptedPersonalData")
.WithSummary("API 1202 - Get Personal Data AND decrypt response - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithOpenApi();
group.MapDelete("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken) =>
{
Console.WriteLine("Deleting personal data for xTAT: " + xTat);
return apimHelper.DeletePersonalData(xTat, userAccessToken);
})
.Produces<DeletePersonalDataResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("DeletePersonalData")
.WithSummary("API 1204 - Delete Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithDescription("First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithOpenApi();
group.MapPut("/personal-data/{xtat}", (string xTat, [SwaggerParameter("If provided, this user access token will be used over the internally stored one (that was restrieved via earlier 1206+1207 calls)")]string? userAccessToken, bool skipUpdateCounter, [SwaggerParameter("Should be at least two words (first name and last name)")]string? name, [SwaggerParameter("Should be a date between 1900-01-01 and now, in the format YYYY-MM-DD")]string? birthDate, [SwaggerSchema("Should be a JPG image, of max. 512KB and resolution between 520x520 and 720x720")]IFormFile? photo) =>
{
Console.WriteLine("Replacing personal data for xTAT: " + xTat);
return apimHelper.UpdatePersonalData(xTat, userAccessToken, skipUpdateCounter, name, birthDate, photo);
})
.WithName("UpdatePersonalData")
.WithSummary("API 1205 - Update Personal Data - First perform an OTP challenge using API 1206 and 1207 or explicitly provide user access token!")
.WithDescription("Performs a complete replacement; empty request parameters will result in the corresponding PADP attribute being deleted.")
.WithOpenApi();
group.MapGet("/v2/personal-data/{xtat}/generate-otp", (string xTat) =>
{
Console.WriteLine("Generating OTP for xTAT: " + xTat);
return apimHelper.GenerateOtpV2(xTat);
})
.Produces<OtpResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GenerateOtpV2")
.WithSummary("API 1206 V2 - Generate OTP")
.WithOpenApi();
group.MapGet("/v2/personal-data/{xtat}/validate-otp", (string xTat, string otp) =>
{
Console.WriteLine("Validating OTP {0} for xTAT: {1}", otp, xTat);
return apimHelper.ValidateOtpV2(xTat, otp);
})
.Produces<UserAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("ValidateOtpV2")
.WithSummary("API 1207 V2 - Validate OTP")
.WithOpenApi();
group.MapGet("/v3/personal-data/generate-otp", (string? email, string? xTat) =>
{
if (email != null){
Console.WriteLine("Generating OTP for e-mail: " + email);
} else if (xTat != null){
Console.WriteLine("Generating OTP for xTAT: " + xTat);
}
return apimHelper.GenerateOtpV3(email, xTat);
})
.Produces<OtpResponse>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GenerateOtpV3")
.WithSummary("API 1206 V3 - Generate OTP")
.WithDescription("If e-mail is provided, the OTP is generated for the e-mail address. If xTAT is provided, the OTP is generated for the xTAT.")
.WithOpenApi();
group.MapGet("/v3/personal-data/validate-otp", (string? email, string? xTat, string otp) =>
{
if (email != null){
Console.WriteLine("Validating OTP {0} for e-mail: {1}", otp, email);
} else if (xTat != null){
Console.WriteLine("Validating OTP {0} for xTAT: {1}", otp, xTat);
}
return apimHelper.ValidateOtpV3(email, xTat, otp);
})
.Produces<UserAccessToken>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("ValidateOtpV3")
.WithSummary("API 1207 V3 - Validate OTP")
.WithDescription("If e-mail is provided, the OTP is validated for the e-mail address. If xTAT is provided, the OTP is validated for the xTAT.")
.WithOpenApi();
group.MapGet("/personal-data/{xtat}/encrypted-update-init", (string xTat) =>
{
return apimHelper.CreateEphemeralKey(xTat);
})
.Produces<EphemeralKey>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("CreateEphemeralKey")
.WithSummary("API 1210 - Create Ephemeral Key")
.WithOpenApi();
group.MapGet("/personal-data/{xtat}/administrative-data", (string xTat) =>
{
Console.WriteLine("Retrieving administrative data for xTAT: {0}", xTat);
return apimHelper.GetAdministrativeData(xTat);
})
.Produces<AdministrativeData>(200)
.Produces<ErrorResponse>(400)
.Produces<ErrorResponse>(404)
.Produces<ErrorResponse>(500)
.WithName("GetAdministrativeData")
.WithSummary("API 1211 - Get Administrative Data")
.WithOpenApi();
pocGroup.MapGet("/encrypt-decrypt-poc", ([FromHeader]string textToEncrypt, string encryptedEphemeralKey) =>
{
Console.WriteLine("Text to encrypt: {0}", textToEncrypt);
return apimHelper.EncryptDecryptPoc(textToEncrypt, encryptedEphemeralKey);
})
.WithName("EncryptDecryptPoc")
.WithSummary("Encrypt/Decrypt POC")
.WithOpenApi();
pocGroup.MapGet("/encrypt-poc", ([FromHeader]string textToEncrypt, string encryptedEphemeralKey) =>
{
Console.WriteLine("Text to encrypt: {0}", textToEncrypt);
return apimHelper.EncryptPoc(textToEncrypt, encryptedEphemeralKey);
})
.WithName("EncryptPoc")
.WithSummary("Encrypt POC")
.WithOpenApi();
pocGroup.MapGet("/decrypt-poc", ([FromHeader]string textToDecrypt, string encryptedEphemeralKey) =>
{
Console.WriteLine("Text to decrypt: {0}", textToDecrypt);
return apimHelper.DecryptPoc(textToDecrypt, encryptedEphemeralKey);
})
.WithName("DecryptPoc")
.WithSummary("Decrypt POC")
.WithOpenApi();
app.Run();

View File

@ -0,0 +1,38 @@
// Rename this file to appsettings.json and provide correct credentials and locations for the cryptographic files
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"UserProperties": {
"uris": {
"apimBaseUri": "https://api-ovpay-acc.translink.nl",
"b2bApiUri": "/b2b-client-authentication/v1/token",
"padp1201Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1202Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1204Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1205Uri": "/pad-management/v2/personal-data/{xtat}",
"padp1206V2Uri": "/pad-management/v2/personal-data/{xtat}/generate-otp",
"padp1207V2Uri": "/pad-management/v2/personal-data/{xtat}/validate-otp",
"padp1206V3Uri": "/pad-management/v3/personal-data/generate-otp",
"padp1207V3Uri": "/pad-management/v3/personal-data/validate-otp",
"padp1210Uri": "/pad-management/v2/personal-data/encrypted-update-init",
"padp1211Uri": "/pad-management/v2/personal-data/{xtat}/administrative-data"
},
"rsa": {
"publicKeyFile": "\\Properties\\{public key in PEM format}.pem",
"privateKeyFile": "\\Properties\\{private key in PEM format}.pem"
},
"credentials": {
"clientCertFile": "\\Properties\\{APIM client certificate}.pfx",
"clientCertPassword": "{pfxPassword}",
"clientId": "HTM_Retailer",
"clientSecret": "{clientSecret}",
"b2bApiKey": "{b2bApiKey}",
"padApiKey": "{padApiKey}"
}
}
}

View File

@ -0,0 +1,44 @@
using System.Text.Json.Serialization;
public class AdministrativeData
{
public AdministrativeData(AdministrativeDataElement name, AdministrativeDataElement photo, AdministrativeDataElement birthdate)
{
this.name = name;
this.photo = photo;
this.birthdate = birthdate;
}
public AdministrativeDataElement name { get; set; }
public AdministrativeDataElement photo { get; set; }
public AdministrativeDataElement birthdate { get; set; }
public class AdministrativeDataElement
{
public AdministrativeDataElement(bool inaccuracyFlag, string inaccuracyFlagReason, int inaccuracyFlagCounter, int changeCounter, int maxUpdatesVerificationCount, DateTime lastChangeDate, bool isValidated)
{
this.inaccuracyFlag = inaccuracyFlag;
this.inaccuracyFlagReason = inaccuracyFlagReason;
this.inaccuracyFlagCounter = inaccuracyFlagCounter;
this.changeCounter = changeCounter;
this.maxUpdatesVerificationCount = maxUpdatesVerificationCount;
this.lastChangeDate = lastChangeDate;
this.isValidated = isValidated;
}
[JsonPropertyName("inaccuracyFlag")]
public bool inaccuracyFlag { get; set; }
[JsonPropertyName("inaccuracyFlagReason")]
public string inaccuracyFlagReason { get; set; }
[JsonPropertyName("inaccuracyFlagCounter")]
public int inaccuracyFlagCounter { get; set; }
[JsonPropertyName("changeCounter")]
public int changeCounter { get; set; }
[JsonPropertyName("maxUpdatesVerificationCount")]
public int maxUpdatesVerificationCount { get; set; }
[JsonPropertyName("lastChangeDate")]
public DateTime lastChangeDate { get; set; }
[JsonPropertyName("isValidated")]
public bool isValidated { get; set; }
}
}

View File

@ -0,0 +1,33 @@
using System.Text.Json.Serialization;
public class B2bAccessToken
{
public B2bAccessToken(string accessToken, int expiresIn, int refreshExpiresIn, string refreshToken, string tokenType, int notBeforePolicy, string scope, string beId)
{
this.accessToken = accessToken;
this.expiresIn = expiresIn;
this.refreshExpiresIn = refreshExpiresIn;
this.refreshToken = refreshToken;
this.tokenType = tokenType;
this.notBeforePolicy = notBeforePolicy;
this.scope = scope;
this.beId = beId;
}
[JsonPropertyName("access_token")]
public string accessToken { get; set; }
[JsonPropertyName("expires_in")]
public int expiresIn { get; set; }
[JsonPropertyName("refresh_expires_in")]
public int refreshExpiresIn { get; set; }
[JsonPropertyName("refresh_token")]
public string refreshToken { get; set; }
[JsonPropertyName("token_type")]
public string tokenType { get; set; }
[JsonPropertyName("not-before-policy")]
public int notBeforePolicy { get; set; }
[JsonPropertyName("scope")]
public string scope { get; set; }
[JsonPropertyName("BE_ID")]
public string beId { get; set; }
}

View File

@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
public class CreatePersonalDataRequest
{
[JsonPropertyName("metadata")]
public Metadata metadata { get; set; } = new Metadata();
[JsonPropertyName("data")]
public Data data { get; set; } = new Data();
public class Metadata
{
public string? email { get; set; }
public string? ephemeralKeyAlias { get; set; }
}
public class Data
{
public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData();
}
public class PersonalAccountData
{
public string? name { get; set; }
public string? birthdate { get; set; }
public string? photo { get; set; }
}
}

View File

@ -0,0 +1,23 @@
using System.Text.Json.Serialization;
using Swashbuckle.AspNetCore.Annotations;
public class DecryptedPersonalData
{
public DecryptedPersonalData(DecryptedData decryptedData, PersonalData encryptedData)
{
this.decryptedData = decryptedData;
this.encryptedData = encryptedData;
}
public DecryptedData decryptedData { get; set; }
public PersonalData encryptedData { get; set; }
public class DecryptedData
{
[JsonPropertyName("decryptedName")]
public string? decryptedName { get; set; }
[JsonPropertyName("decryptedBirthdate")]
public string? decryptedBirthdate { get; set; }
[SwaggerSchema(Format = "byte", Description = "Base64 encoded photo")]
public string? decryptedPhoto { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
public class DeletePersonalDataResponse
{
public DeletePersonalDataResponse(string[] deletedAttributes)
{
this.deletedAttributes = deletedAttributes;
}
[JsonPropertyName("deletedAttributes")]
public string[] deletedAttributes { get; set; }
}

View File

@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
public class EphemeralKey
{
public EphemeralKey(string ephemeralKeyAlias, string encryptedEphemeralKey)
{
this.ephemeralKeyAlias = ephemeralKeyAlias;
this.encryptedEphemeralKey = encryptedEphemeralKey;
}
[JsonPropertyName("ephemeralKeyAlias")]
public string ephemeralKeyAlias { get; set; }
[JsonPropertyName("encryptedEphemeralKey")]
public string encryptedEphemeralKey { get; set; }
}

View File

@ -0,0 +1,38 @@
using System.Text.Json.Serialization;
public class ErrorResponse
{
[JsonConstructor]
public ErrorResponse(Error[] errors, string exceptionClassName, string exceptionStackTrace)
{
this.errors = errors;
this.exceptionClassName = exceptionClassName;
this.exceptionStackTrace = exceptionStackTrace;
}
public ErrorResponse(string errorMessage, int statusCode)
{
this.errors = new Error[] { new Error(statusCode.ToString(), new string[] { }, errorMessage) };
this.exceptionClassName = null;
this.exceptionStackTrace = null;
}
public Error[] errors { get; set; }
public string? exceptionClassName { get; set; }
public string? exceptionStackTrace { get; set; }
public class Error
{
public Error(string code, string[] data, string message)
{
this.code = code;
this.data = data;
this.message = message;
}
public string code { get; set; }
public string[] data { get; set; }
public string message { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
public class GenerateOtpRequestV3
{
[JsonPropertyName("source")]
public string source { get; set; }
[JsonPropertyName("recipient")]
public string recipient { get; set; }
[JsonPropertyName("channel")]
public string channel { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
public class OtpResponse
{
public OtpResponse(string maskedEmailAddress)
{
this.maskedEmailAddress = maskedEmailAddress;
}
[JsonPropertyName("maskedEmailAddress")]
public string maskedEmailAddress { get; set; }
}

View File

@ -0,0 +1,42 @@
using System.Reflection.Metadata.Ecma335;
using System.Text.Json.Serialization;
public class PersonalData
{
public PersonalData(Metadata metadata, Data data)
{
this.metadata = metadata;
this.data = data;
}
public Metadata metadata { get; set; }
public Data data { get; set; }
public class Metadata
{
public Metadata(string encryptedEphemeralKey)
{
this.encryptedEphemeralKey = encryptedEphemeralKey;
}
[JsonPropertyName("encryptedEphemeralKey")]
public string encryptedEphemeralKey { get; set; }
}
public class Data
{
public Data(string name, string birthdate, string photo)
{
this.name = name;
this.birthdate = birthdate;
this.photo = photo;
}
[JsonPropertyName("name")]
public string name { get; set; }
[JsonPropertyName("birthdate")]
public string birthdate { get; set; }
[JsonPropertyName("photo")]
public string photo { get; set; }
}
}

View File

@ -0,0 +1,42 @@
using System.Reflection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Struct |
AttributeTargets.Parameter |
AttributeTargets.Property |
AttributeTargets.Enum,
AllowMultiple = false)]
public class SwaggerSchemaExampleAttribute : Attribute
{
public SwaggerSchemaExampleAttribute(string example)
{
Example = example;
}
public string Example { get; set; }
}
public class SwaggerSchemaExampleFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.MemberInfo != null)
{
var schemaAttribute = context.MemberInfo.GetCustomAttributes<SwaggerSchemaExampleAttribute>()
.FirstOrDefault();
if (schemaAttribute != null)
ApplySchemaAttribute(schema, schemaAttribute);
}
}
private void ApplySchemaAttribute(OpenApiSchema schema, SwaggerSchemaExampleAttribute schemaAttribute)
{
if (schemaAttribute.Example != null)
{
schema.Example = new Microsoft.OpenApi.Any.OpenApiString(schemaAttribute.Example);
}
}
}

View File

@ -0,0 +1,29 @@
using System.Text.Json.Serialization;
public class UpdatePersonalDataRequest
{
[JsonPropertyName("metadata")]
public Metadata metadata { get; set; } = new Metadata();
[JsonPropertyName("data")]
public Data data { get; set; } = new Data();
public class Metadata
{
public bool skipUpdateCounter { get; set; }
public string? ephemeralKeyAlias { get; set; }
}
public class Data
{
public PersonalAccountData personalAccountData { get; set; } = new PersonalAccountData();
}
public class PersonalAccountData
{
public string? name { get; set; }
public string? birthdate { get; set; }
public string? photo { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
public class UserAccessToken
{
public UserAccessToken(string accessToken)
{
this.accessToken = accessToken;
}
[JsonPropertyName("accessToken")]
public string accessToken { get; set; }
}

View File

@ -0,0 +1,7 @@
public class UserAuthInfo
{
public string? UserAccessToken { get; set; }
public string? ephemeralKeyAlias { get; set; }
public string? encryptedEphemeralKey { get; set; }
}

View File

@ -0,0 +1,58 @@
public class UserProperties
{
public UserProperties(UrisSettings uris, RsaSettings rsa, CredentialsSettings credentials)
{
(Uris, Rsa, Credentials) = (uris, rsa, credentials);
}
public UrisSettings Uris { get; set; }
public RsaSettings Rsa { get; set; }
public CredentialsSettings Credentials { get; set; }
public class UrisSettings
{
public UrisSettings(string apimBaseUri, string b2bApiUri, string padp1201Uri, string padp1202Uri, string padp1204Uri, string padp1205Uri, string padp1206V2Uri, string padp1207V2Uri, string padp1206V3Uri, string padp1207V3Uri, string padp1210Uri, string padp1211Uri)
{
(ApimBaseUri, B2bApiUri, Padp1201Uri, Padp1202Uri, Padp1204Uri, Padp1205Uri, Padp1206V2Uri, Padp1207V2Uri, Padp1206V3Uri, Padp1207V3Uri, Padp1210Uri, Padp1211Uri) = (apimBaseUri, b2bApiUri, padp1201Uri, padp1202Uri, padp1204Uri, padp1205Uri, padp1206V2Uri, padp1207V2Uri, padp1206V3Uri, padp1207V3Uri, padp1210Uri, padp1211Uri);
}
public string ApimBaseUri { get; set; }
public string B2bApiUri { get; set; }
public string Padp1201Uri { get; set; }
public string Padp1202Uri { get; set; }
public string Padp1204Uri { get; set; }
public string Padp1205Uri { get; set; }
public string Padp1206V2Uri { get; set; }
public string Padp1207V2Uri { get; set; }
public string Padp1206V3Uri { get; set; }
public string Padp1207V3Uri { get; set; }
public string Padp1210Uri { get; set; }
public string Padp1211Uri { get; set; }
}
public class RsaSettings
{
public RsaSettings(string publicKeyFile, string privateKeyFile)
{
(PublicKeyFile, PrivateKeyFile) = (publicKeyFile, privateKeyFile);
}
public string PublicKeyFile { get; set; }
public string PrivateKeyFile { get; set; }
}
public class CredentialsSettings
{
public CredentialsSettings(string clientCertFile, string clientCertPassword, string clientId, string clientSecret, string b2bApiKey, string padApiKey)
{
(ClientCertFile, ClientCertPassword, ClientId, ClientSecret, B2bApiKey, PadApiKey) = (clientCertFile, clientCertPassword, clientId, clientSecret, b2bApiKey, padApiKey);
}
public string ClientCertFile { get; set; }
public string ClientCertPassword { get; set; }
public string ClientId { get; set; }
public string ClientSecret { get; set; }
public string B2bApiKey { get; set; }
public string PadApiKey { get; set; }
}
}

View File

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
public class ValidateOtpRequestV3
{
[JsonPropertyName("otp")]
public string otp { get; set; }
[JsonPropertyName("source")]
public string source { get; set; }
[JsonPropertyName("recipient")]
public string recipient { get; set; }
}