From 7c2e04b47df72f86d3040d47bc7fcf0b00a7e5a6 Mon Sep 17 00:00:00 2001 From: Matt Carroll Date: Fri, 2 Aug 2024 13:30:45 +1000 Subject: [PATCH] ON-42397 # Removed userToken from signed urls to submit forms --- CHANGELOG.md | 9 +++++ OneBlink.SDK.Tests/FormsClientTests.cs | 3 -- OneBlink.SDK/FormsClient.cs | 39 ++++---------------- OneBlink.SDK/Token.cs | 30 ++++++++++----- OneBlink.SDK/models/FormUrl.cs | 51 ++++++++++++++++++-------- 5 files changed, 74 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4962881..8a04a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ - Minimum Role Permission to Client docs +### Removed + +- **[BREAKING]** `EncryptUserToken` and `DecryptUserToken` methods from the `FormsClient` class +- **[BREAKING]** `secret` property from `FormUrlOptions` class + +### Changed + +- `GenerateFormUrl` method in the `FormsClient` class to include the `username` property into the JWT payload as the `sub` claim + ## [6.2.2] - 2024-07-10 ### Changed diff --git a/OneBlink.SDK.Tests/FormsClientTests.cs b/OneBlink.SDK.Tests/FormsClientTests.cs index e990e88..2ccc2c9 100644 --- a/OneBlink.SDK.Tests/FormsClientTests.cs +++ b/OneBlink.SDK.Tests/FormsClientTests.cs @@ -335,15 +335,12 @@ public async void can_generate_form_url() new FormUrlOptions( formId: 475, username: "zac@oneblink.io", - secret: "secret", preFillData: preFill, externalId: "myExternalId" - ) ); Assert.Contains("?access_key=", result.formUrl); Assert.Contains("&externalId=myExternalId", result.formUrl); - Assert.Contains("&userToken=", result.formUrl); Assert.Contains("&preFillFormDataId=", result.formUrl); Assert.NotNull(result.expiry); diff --git a/OneBlink.SDK/FormsClient.cs b/OneBlink.SDK/FormsClient.cs index ddabf3e..27a9e10 100644 --- a/OneBlink.SDK/FormsClient.cs +++ b/OneBlink.SDK/FormsClient.cs @@ -152,25 +152,6 @@ public async Task Delete(long id, bool overrideLock = false) await this.oneBlinkApiClient.DeleteRequest(url); } - public static string EncryptUserToken(string username, string secret) - { - if (String.IsNullOrEmpty(username) || String.IsNullOrEmpty(secret)) - { - throw new Exception("Must pass a valid username and secret"); - } - AesUserToken aesUserToken = new AesUserToken(secret); - return aesUserToken.encrypt(username); - } - public static string DecryptUserToken(string userToken, string secret) - { - if (String.IsNullOrEmpty(userToken) || String.IsNullOrEmpty(secret)) - { - throw new Exception("Must pass a valid userToken and secret"); - } - AesUserToken aesUserToken = new AesUserToken(secret); - return aesUserToken.decrypt(userToken); - } - public async Task GenerateFormUrl(FormUrlOptions parameters) { if (parameters == null) @@ -225,24 +206,22 @@ public async Task GenerateFormUrl(FormUrlOptions parameters) }; } - string userToken = null; - if (parameters.username != null) - { - AesUserToken aesUserToken = new AesUserToken(parameters.secret); - userToken = aesUserToken.encrypt(parameters.username); - } - // Default expiry for token is 8 hours int jwtExpiry = parameters.expiryInSeconds ?? 28800; - string token = Token.GenerateJSONWebToken(accessKey: oneBlinkApiClient.accessKey, oneBlinkApiClient.secretKey, jwtExpiry, developerKeyAccess); + string token = Token.GenerateJSONWebToken( + this.oneBlinkApiClient.accessKey, + this.oneBlinkApiClient.secretKey, + jwtExpiry, + developerKeyAccess, + parameters.username + ); - string formUrl = _generateFormUrl( + string formUrl = this._generateFormUrl( formId: parameters.formId, formsApp: formsApp, token: token, externalId: parameters.externalId, preFillFormDataId: preFillFormDataId, - userToken: userToken, previousFormSubmissionApprovalId: parameters.previousFormSubmissionApprovalId ); string expiry = DateTime.UtcNow.AddSeconds(jwtExpiry).ToString("o"); @@ -422,14 +401,12 @@ private string _generateFormUrl( string token, string externalId, Guid? preFillFormDataId, - string userToken, long? previousFormSubmissionApprovalId) { var query = HttpUtility.ParseQueryString(string.Empty); OneBlinkHttpClient.AddItemToQuery(query, "access_key", token); OneBlinkHttpClient.AddItemToQuery(query, nameof(externalId), externalId); OneBlinkHttpClient.AddItemToQuery(query, nameof(preFillFormDataId), preFillFormDataId); - OneBlinkHttpClient.AddItemToQuery(query, nameof(userToken), userToken); OneBlinkHttpClient.AddItemToQuery(query, nameof(previousFormSubmissionApprovalId), previousFormSubmissionApprovalId); string url = $"https://{formsApp.hostname}/forms/{formId}?{query.ToString()}"; diff --git a/OneBlink.SDK/Token.cs b/OneBlink.SDK/Token.cs index 985dbd5..bb642b4 100644 --- a/OneBlink.SDK/Token.cs +++ b/OneBlink.SDK/Token.cs @@ -7,26 +7,38 @@ using System.Threading.Tasks; using Newtonsoft.Json.Linq; using System.Security.Cryptography; +using System.Collections.Generic; +using System.Security.Claims; namespace OneBlink.SDK { internal class Token { - internal static string GenerateJSONWebToken(string accessKey, string secretKey, int expiryInSeconds, DeveloperKeyAccess developerKeyAccess = null) + internal static string GenerateJSONWebToken(string accessKey, string secretKey, int expiryInSeconds, DeveloperKeyAccess developerKeyAccess = null, string username = null) { - var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); - var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + SymmetricSecurityKey securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)); + SigningCredentials credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); + JwtHeader jwtHeader = new JwtHeader(credentials); - JwtSecurityToken token = new JwtSecurityToken(accessKey, - accessKey, - null, + JwtPayload jwtPayload = new JwtPayload( + issuer: accessKey, + audience: null, + claims: null, + notBefore: null, expires: DateTime.Now.AddSeconds(expiryInSeconds), - signingCredentials: credentials); - + issuedAt: DateTime.Now + ); + if (!string.IsNullOrEmpty(username)) + { + jwtPayload.Add("sub", username); + } if (developerKeyAccess != null) { - token.Payload.Add("oneblink:access", developerKeyAccess); + jwtPayload.Add("oneblink:access", developerKeyAccess); } + + JwtSecurityToken token = new JwtSecurityToken(jwtHeader, jwtPayload); + return new JwtSecurityTokenHandler().WriteToken(token); } diff --git a/OneBlink.SDK/models/FormUrl.cs b/OneBlink.SDK/models/FormUrl.cs index 941afe7..a043399 100644 --- a/OneBlink.SDK/models/FormUrl.cs +++ b/OneBlink.SDK/models/FormUrl.cs @@ -4,14 +4,34 @@ namespace OneBlink.SDK.Model { public class FormUrlOptions { - public long formId { get; } - public long? formsAppId { get; set; } - public int? expiryInSeconds { get; } - public string externalId { get; } - public dynamic preFillData { get; } - public string username { get; } - public string secret { get; } - public long? previousFormSubmissionApprovalId {get;} + public long formId + { + get; + } + public long? formsAppId + { + get; set; + } + public int? expiryInSeconds + { + get; + } + public string externalId + { + get; + } + public dynamic preFillData + { + get; + } + public string username + { + get; + } + public long? previousFormSubmissionApprovalId + { + get; + } public FormUrlOptions( long formId, @@ -20,20 +40,15 @@ public FormUrlOptions( string externalId = null, dynamic preFillData = null, string username = null, - string secret = null, long? previousFormSubmissionApprovalId = null ) { - if (!string.IsNullOrEmpty(username) && string.IsNullOrEmpty(secret)) { - throw new Exception("Must supply \"secret\" as a string if \"username\" is used"); - } this.formId = formId; this.formsAppId = formsAppId; this.expiryInSeconds = expiryInSeconds; this.externalId = externalId; this.preFillData = preFillData; this.username = username; - this.secret = secret; this.previousFormSubmissionApprovalId = previousFormSubmissionApprovalId; } @@ -41,8 +56,14 @@ public FormUrlOptions( public class FormUrlResult { - public string formUrl { get; set; } - public string expiry { get; set; } + public string formUrl + { + get; set; + } + public string expiry + { + get; set; + } } }