Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Microsoft.EServices.EDocumentConnector.Avalara;

using System.Security.Authentication;
using System.Utilities;
codeunit 6374 "Authenticator"
{
Access = Internal;
Expand Down Expand Up @@ -129,6 +130,42 @@ codeunit 6374 "Authenticator"
exit(IsolatedStorage.Contains(TokenKey, TokenDataScope));
end;

internal procedure GetAuthURL(): Text
var
ConnectionSetup: Record "Connection Setup";
URI: Codeunit Uri;
begin
if ConnectionSetup.Get() then
exit(URI.ValidateIntegrationURL(ConnectionSetup."Authentication URL", AuthURLTxt));
end;

internal procedure GetSandboxAuthURL(): Text
var
ConnectionSetup: Record "Connection Setup";
URI: Codeunit Uri;
begin
if ConnectionSetup.Get() then
exit(URI.ValidateIntegrationURL(ConnectionSetup."Sandbox Authentication URL", SandboxAuthURLTxt));
end;

internal procedure GetAPIURL(): Text
var
ConnectionSetup: Record "Connection Setup";
URI: Codeunit Uri;
begin
if ConnectionSetup.Get() then
exit(URI.ValidateIntegrationURL(ConnectionSetup."API URL", APIURLTxt));
end;

internal procedure GetSandboxAPIURL(): Text
var
ConnectionSetup: Record "Connection Setup";
URI: Codeunit Uri;
begin
if ConnectionSetup.Get() then
exit(URI.ValidateIntegrationURL(ConnectionSetup."Sandbox API URL", SandboxAPIURLTxt));
end;

var
AuthURLTxt: Label 'https://identity.avalara.com', Locked = true;
APIURLTxt: Label 'https://api.avalara.com', Locked = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,15 @@ codeunit 6376 Requests
procedure GetBaseUrl(): Text
var
ConnectionSetup: Record "Connection Setup";
Authenticator: Codeunit "Authenticator";
begin
ConnectionSetup.Get();

case ConnectionSetup."Avalara Send Mode" of
"Avalara Send Mode"::Production:
exit(ConnectionSetup."API URL");
exit(Authenticator.GetAPIURL());
"Avalara Send Mode"::Test:
exit(ConnectionSetup."Sandbox API URL");
exit(Authenticator.GetSandboxAPIURL());
else
Error('Unsupported %1 in %2', ConnectionSetup.FieldCaption("Avalara Send Mode"), ConnectionSetup.TableCaption);
end;
Expand All @@ -251,14 +252,15 @@ codeunit 6376 Requests
local procedure GetAuthUrl(): Text
var
ConnectionSetup: Record "Connection Setup";
Authenticator: Codeunit "Authenticator";
begin
ConnectionSetup.Get();

case ConnectionSetup."Avalara Send Mode" of
"Avalara Send Mode"::Production:
exit(ConnectionSetup."Authentication URL");
exit(Authenticator.GetAuthURL());
"Avalara Send Mode"::Test:
exit(ConnectionSetup."Sandbox Authentication URL");
exit(Authenticator.GetSandboxAuthURL());
else
Error('Unsupported %1 in %2', ConnectionSetup.FieldCaption("Avalara Send Mode"), ConnectionSetup.TableCaption);
end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,25 +174,20 @@ codeunit 30199 "Shpfy Authentication Mgt."
end;

internal procedure AssertValidShopUrl(ShopUrl: Text)
begin
if not IsValidShopUrl(ShopUrl) then
Error(InvalidShopUrlErr);
end;

local procedure IsValidShopUrl(ShopUrl: Text): Boolean
var
Regex: Codeunit Regex;
URI: Codeunit Uri;
PatternLbl: Label '^(https)\:\/\/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com[\/]*$', Locked = true;
begin
exit(Regex.IsMatch(ShopUrl, PatternLbl))
if not URI.IsValidURIPattern(ShopUrl, PatternLbl) then
Error(InvalidShopUrlErr);
end;

procedure IsValidHostName(Hostname: Text): Boolean
var
Regex: Codeunit Regex;
URI: Codeunit Uri;
PatternLbl: Label '^[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com$', Locked = true;
begin
exit(Regex.IsMatch(Hostname, PatternLbl))
exit(URI.IsValidURIPattern(Hostname, PatternLbl));
end;

procedure CorrectShopUrl(var ShopUrl: Text[250])
Expand Down
8 changes: 7 additions & 1 deletion src/System Application/App/URI/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"name": "DotNet Aliases",
"publisher": "Microsoft",
"version": "28.0.0.0"
},
{
"id": "b185fd4a-677b-48d3-a701-768de7563df0",
"name": "Regex",
"publisher": "Microsoft",
"version": "28.0.0.0"
}
],
"screenshots": [],
Expand All @@ -28,4 +34,4 @@
"platform": "28.0.0.0",
"target": "OnPrem",
"contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/"
}
}
43 changes: 43 additions & 0 deletions src/System Application/App/URI/src/Uri.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,49 @@ codeunit 3060 Uri
exit(Uri.IsBaseOf(LocalUri));
end;

/// <summary>
/// Validates integration URI host from setup table field is the same as the hardcoded integration URI host, returns the hardcoded integration URI if validation fails.
/// </summary>
/// <param name="FieldValueURL">The integration URL from the setup table field.</param>
/// <param name="HardcodedIntegrationURL">The hardcoded integration URL to validate.</param>
/// <returns>The integration URL from the setup table if it has the same host; otherwise, the provided integration URL.</returns>
procedure ValidateIntegrationURL(FieldValueURL: Text; HardcodedIntegrationURL: Text): Text
begin
if AreURIsHaveSameHost(FieldValueURL, HardcodedIntegrationURL) then
exit(FieldValueURL)
else
exit(HardcodedIntegrationURL);
end;

/// <summary>
/// Verifies whether two URIs have the same host (e.g., both subdomain.example.com\test1 and subdomain.example.com\test2 have the same host subdomain.example.com).
/// </summary>
/// <param name="UriString1">The first URI string.</param>
/// <param name="UriString2">The second URI string.</param>
/// <returns>True if both URIs have the same host; otherwise, false.</returns>
procedure AreURIsHaveSameHost(UriString1: Text; UriString2: Text): Boolean
var
Uri1, Uri2 : DotNet Uri;
begin
Uri1 := Uri1.Uri(UriString1.ToLower());
Uri2 := Uri2.Uri(UriString2.ToLower());

exit(Uri1.Host = Uri2.Host);
end;

/// <summary>
/// Verifies whether the URI is in a valid pattern.
/// </summary>
/// <param name="UriString">The URI string to validate.</param>
/// <param name="Pattern">The regular expression pattern to match against.</param>
/// <returns>True if the URI string is in a valid pattern; otherwise, false.</returns>
procedure IsValidURIPattern(UriString: Text; Pattern: Text): Boolean
var
RegEx: Codeunit Regex;
begin
exit(Regex.IsMatch(UriString, Pattern));
end;

/// <summary>
/// Gets the underlying .Net Uri variable.
/// </summary>
Expand Down
60 changes: 60 additions & 0 deletions src/System Application/Test/URI/src/UriTest.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,66 @@ codeunit 135070 "Uri Test"
TestUriWellformed('http:\\\host/path/file', UriKind::Absolute, false);
end;

[Test]
[Scope('OnPrem')]
procedure AreURIsHaveSameHostTest()
var
Uri: Codeunit Uri;
begin
// [Given] Two URIs with the same host
// [Then] AreURIsHaveSameHost should return true
LibraryAssert.IsTrue(Uri.AreURIsHaveSameHost('http://microsoft.com/path1', 'http://microsoft.com/path2'), 'URIs with same host should return true');
LibraryAssert.IsTrue(Uri.AreURIsHaveSameHost('https://contoso.com/test', 'https://contoso.com/other'), 'URIs with same scheme should return true');
LibraryAssert.IsTrue(Uri.AreURIsHaveSameHost('http://subdomain.example.com/test1', 'http://subdomain.example.com/test2'), 'URIs with same subdomain host should return true');

// [Given] URIs with different hosts
// [Then] AreURIsHaveSameHost should return false
LibraryAssert.IsFalse(Uri.AreURIsHaveSameHost('http://microsoft.com/path1', 'http://contoso.com/path2'), 'URIs with different hosts should return false');
LibraryAssert.IsFalse(Uri.AreURIsHaveSameHost('https://example.com/test', 'https://different.com/test'), 'URIs with different hosts should return false');
LibraryAssert.IsFalse(Uri.AreURIsHaveSameHost('http://subdomain1.example.com/test1', 'http://subdomain2.example.com/test2'), 'URIs with different subdomain host should return false');

// [Given] URIs with case differences in host
// [Then] AreURIsHaveSameHost should return true (case insensitive)
LibraryAssert.IsTrue(Uri.AreURIsHaveSameHost('http://Microsoft.com/path1', 'http://MICROSOFT.COM/path2'), 'URIs with case differences in host should return true');
end;

[Test]
[Scope('OnPrem')]
procedure IsValidURIPatternTest()
var
Uri: Codeunit Uri;
begin
// [Given] URIs and patterns that should match
// [Then] IsValidURIPattern should return true
LibraryAssert.IsTrue(Uri.IsValidURIPattern('http://microsoft.com', '^https?://.*'), 'HTTP URI should match HTTP(S) pattern');
LibraryAssert.IsTrue(Uri.IsValidURIPattern('https://microsoft.com', '^https?://.*'), 'HTTPS URI should match HTTP(S) pattern');
LibraryAssert.IsTrue(Uri.IsValidURIPattern('ftp://files.example.com', '^ftp://.*'), 'FTP URI should match FTP pattern');
LibraryAssert.IsTrue(Uri.IsValidURIPattern('https://api.contoso.com/v1/users', '.*api\..*'), 'API URI should match API pattern');
LibraryAssert.IsTrue(Uri.IsValidURIPattern('http://subdomain.example.com', '.*\.example\.com$'), 'Subdomain URI should match domain pattern');
LibraryAssert.IsTrue(Uri.IsValidURIPattern('https://shop1.myshopify.com/admin/dashboard', '^(https)\:\/\/[a-zA-Z0-9][a-zA-Z0-9\-]*\.myshopify\.com[\/].*$'), 'URL should match provided pattern');

// [Given] URIs and patterns that should not match
// [Then] IsValidURIPattern should return false
LibraryAssert.IsFalse(Uri.IsValidURIPattern('http://microsoft.com', '^https://.*'), 'HTTP URI should not match HTTPS-only pattern');
LibraryAssert.IsFalse(Uri.IsValidURIPattern('ftp://files.example.com', '^https?://.*'), 'FTP URI should not match HTTP(S) pattern');
LibraryAssert.IsFalse(Uri.IsValidURIPattern('https://contoso.com', '.*microsoft\.com$'), 'Contoso URI should not match Microsoft domain pattern');
LibraryAssert.IsFalse(Uri.IsValidURIPattern('invalid-uri', '^https?://.*'), 'Invalid URI should not match valid URI pattern');
end;

[Test]
[Scope('OnPrem')]
procedure ValidateIntegrationURLTest()
var
Uri: Codeunit Uri;
begin
// [Given] Integration URLs with the same host and scheme stored in the setup table
LibraryAssert.AreEqual(Uri.ValidateIntegrationURL('https://valid-integration.com/api', 'https://valid-integration.com/api2'), 'https://valid-integration.com/api', 'Integration URLs should have the same host and scheme');
LibraryAssert.AreEqual(Uri.ValidateIntegrationURL('https://valid-integration.com/API1', 'https://valid-integration.com/API2'), 'https://valid-integration.com/API1', 'Integration URLs should have the same host and scheme');

// [Given] Invalid Integration URLs with different hosts or schemes
LibraryAssert.AreNotEqual(Uri.ValidateIntegrationURL('https://subdomain1.example.com/api', 'https://subdomain2.example.com/api'), 'https://subdomain1.example.com/api', 'Invalid integration URL should return false');
end;

local procedure TestUriWellformed(UriString: Text; UriKind: Enum UriKind; ExpectedResult: Boolean)
var
LocalUri: Codeunit Uri;
Expand Down
Loading