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

[E-Document Connector] Logiq E-Document Connector #27562

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
@@ -0,0 +1,19 @@
namespace Microsoft.EServices.EDocumentConnector.Logiq;

enum 6380 "Logiq API Engine"
Copy link
Contributor

Choose a reason for hiding this comment

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

Dont prefix with Logiq

{
Extensible = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Mark it as internal.


value(0; " ")
{
Caption = '', Locked = true;
}
value(1; Engine1)
{
Caption = 'Engine 1';
}
value(2; Engine3)
{
Caption = 'Engine 3';
}
}
182 changes: 182 additions & 0 deletions Apps/W1/EDocumentsConnector/app/src/Logiq/LogiqAuth.Codeunit.al
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
namespace Microsoft.EServices.EDocumentConnector.Logiq;

codeunit 6380 "Logiq Auth"
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move the code to EDocumentConnectors

Create app for the connector.
Create app for the test automation.
Feel free to look at Avalara as the example.

In the mean time we will start looking at the code.

{
internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText; TokenDataScope: DataScope)
begin
if IsNullGuid(ValueKey) then
ValueKey := CreateGuid();

IsolatedStorage.Set(ValueKey, Value, TokenDataScope);
end;

internal procedure SetIsolatedStorageValue(var ValueKey: Guid; Value: SecretText)
begin
SetIsolatedStorageValue(ValueKey, Value, DataScope::Company);
end;

internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText; TokenDataScope: DataScope)
begin
if IsNullGuid(ValueKey) then
exit;
IsolatedStorage.Get(ValueKey, TokenDataScope, Value);
end;

internal procedure GetIsolatedStorageValue(var ValueKey: Guid; var Value: SecretText)
begin
GetIsolatedStorageValue(ValueKey, Value, DataScope::Company);
end;

[NonDebuggable]
internal procedure GetTokens()
var
LogiqConnectionSetup: Record "Logiq Connection Setup";
LogiqConnectionUserSetup: Record "Logiq Connection User Setup";
Client: HttpClient;
Headers: HttpHeaders;
Content: HttpContent;
RequestMessage: HttpRequestMessage;
ResponseMessage: HttpResponseMessage;
AccessToken, RefreshToken : SecretText;
AccessTokExpires, RefreshTokExpires : DateTime;
AuthenticationFailedErr: Label 'Logiq authentication failed. Please check the user credentials.';
Copy link
Contributor

Choose a reason for hiding this comment

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

Labels needs to be on global var.

begin
CheckSetup(LogiqConnectionSetup);
CheckUserCredentials(LogiqConnectionUserSetup);

RequestMessage.Method('POST');
RequestMessage.SetRequestUri(LogiqConnectionSetup."Authentication URL");

BuildTokenRequestBody(Content);

Content.GetHeaders(Headers);
Headers.Clear();
Headers.Add('Content-Type', 'application/x-www-form-urlencoded');

RequestMessage.Content(Content);

Client.Send(RequestMessage, ResponseMessage);

if not ResponseMessage.IsSuccessStatusCode() then begin
if GuiAllowed then
Message(AuthenticationFailedErr);
exit;
end;

ParseTokens(ResponseMessage, AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires);

SaveTokens(AccessToken, RefreshToken, AccessTokExpires, RefreshTokExpires);
end;

local procedure BuildTokenRequestBody(var Content: HttpContent)
var
LogiqConnectionSetup: Record "Logiq Connection Setup";
LogiqConnectionUserSetup: Record "Logiq Connection User Setup";
BodyText: SecretText;
CredentialsBodyTok: Label 'grant_type=password&scope=openid&client_id=%1&client_secret=%2&username=%3&password=%4', Locked = true;
RefreshTokenBodyTok: Label 'grant_type=refresh_token&client_id=%1&client_secret=%2&refresh_token=%3', Locked = true;
begin
LogiqConnectionSetup.Get();
LogiqConnectionUserSetup.Get(UserId());

if (not IsNullGuid(LogiqConnectionUserSetup."Refresh Token")) and (LogiqConnectionUserSetup."Refresh Token Expiration" > (CurrentDateTime + 60 * 1000)) then
BodyText := SecretText.SecretStrSubstNo(RefreshTokenBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.GetRefreshToken())
else
BodyText := SecretText.SecretStrSubstNo(CredentialsBodyTok, LogiqConnectionSetup."Client ID", LogiqConnectionSetup.GetClientSecret(), LogiqConnectionUserSetup.Username, LogiqConnectionUserSetup.GetPassword());

Content.WriteFrom(BodyText);
end;

internal procedure CheckUserCredentials(var LogiqConnectionUserSetup: Record "Logiq Connection User Setup")
var
NoSetupErr: Label 'No user setup found. Please fill the user setup in the Logiq Connection User Setup page.';
Copy link
Contributor

Choose a reason for hiding this comment

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

Labels to global var

MissingCredentialsErr: Label 'User credentials are missing. Please enter username and password in the Logiq Connection User Setup page.';
begin
if not LogiqConnectionUserSetup.Get(UserId()) then
Error(NoSetupErr);

if (LogiqConnectionUserSetup.Username = '') or (IsNullGuid(LogiqConnectionUserSetup."Password")) then
Error(MissingCredentialsErr);
end;

internal procedure CheckUserSetup(var LogiqConnectionUserSetup: Record "Logiq Connection User Setup")
var
MissingAPIEngineErr: Label 'API Engine is missing. Please select the API Engine in the Logiq Connection User Setup page.';
MissingEndpointsErr: Label 'Endpoints are missing. Please fill the Document Transfer Endpoint and Document Status Endpoint in the Logiq Connection User Setup page.';
begin
CheckUserCredentials(LogiqConnectionUserSetup);

if (LogiqConnectionUserSetup."API Engine" = LogiqConnectionUserSetup."API Engine"::" ") then
Error(MissingAPIEngineErr);

if (LogiqConnectionUserSetup."Document Transfer Endpoint" = '') or (LogiqConnectionUserSetup."Document Status Endpoint" = '') then
Error(MissingEndpointsErr);
end;

internal procedure CheckSetup(var LogiqConnectionSetup: Record "Logiq Connection Setup")
var
NoSetupErr: Label 'No setup found. Please fill the setup in the Logiq Connection Setup page.';
MissingClientInfoErr: Label 'Client ID or Client Secret is missing. Please fill the Client ID and Client Secret in the Logiq Connection Setup page.';
MissingAuthUrlErr: Label 'Authentication URL is missing. Please fill the Authentication URL in the Logiq Connection Setup page.';
MissingBaseUrlErr: Label 'Base URL is missing. Please fill the API Base URL in the Logiq Connection Setup page.';
begin
if not LogiqConnectionSetup.Get() then
Error(NoSetupErr);

if (LogiqConnectionSetup."Client ID" = '') or (IsNullGuid(LogiqConnectionSetup."Client Secret")) then
Error(MissingClientInfoErr);

if LogiqConnectionSetup."Authentication URL" = '' then
Error(MissingAuthUrlErr);

if LogiqConnectionSetup."Base URL" = '' then
Error(MissingBaseUrlErr);
end;

[NonDebuggable]
local procedure ParseTokens(ResponseMessage: HttpResponseMessage; var AccessToken: SecretText; var RefreshToken: SecretText; var AccessTokExpires: DateTime; var RefreshTokExpires: DateTime)
var
ContentJson: JsonObject;
JsonTok: JsonToken;
ResponseTxt: Text;
begin
ResponseMessage.Content.ReadAs(ResponseTxt);
ContentJson.ReadFrom(ResponseTxt);
if ContentJson.Get('access_token', JsonTok) then
AccessToken := JsonTok.AsValue().AsText();
if ContentJson.Get('refresh_token', JsonTok) then
RefreshToken := JsonTok.AsValue().AsText();
if ContentJson.Get('expires_in', JsonTok) then
AccessTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000;
if ContentJson.Get('refresh_expires_in', JsonTok) then
RefreshTokExpires := CurrentDateTime + JsonTok.AsValue().AsInteger() * 1000;
end;

local procedure SaveTokens(AccessToken: SecretText; RefreshToken: SecretText; AccessTokExpires: DateTime; RefreshTokExpires: DateTime)
var
LogiqConnectionUserSetup: Record "Logiq Connection User Setup";
begin
LogiqConnectionUserSetup.Get(UserId());
SetIsolatedStorageValue(LogiqConnectionUserSetup."Access Token", AccessToken, DataScope::User);
SetIsolatedStorageValue(LogiqConnectionUserSetup."Refresh Token", RefreshToken, DataScope::User);
LogiqConnectionUserSetup."Access Token Expiration" := AccessTokExpires;
LogiqConnectionUserSetup."Refresh Token Expiration" := RefreshTokExpires;
LogiqConnectionUserSetup.Modify(false);
end;

internal procedure HasToken(ValueKey: Guid; DataScope: DataScope): Boolean
begin
exit(IsolatedStorage.Contains(ValueKey, DataScope));
end;

internal procedure CheckUpdateTokens()
var
LogiqConnectionUserSetup: Record "Logiq Connection User Setup";
NoSetupErr: Label 'No user setup found. Please fill the user setup in the Logiq Connection User Setup page.';
begin
if not LogiqConnectionUserSetup.Get(UserId()) then
Error(NoSetupErr);
if IsNullGuid(LogiqConnectionUserSetup."Access Token") or (LogiqConnectionUserSetup."Access Token Expiration" < (CurrentDateTime + 5 * 60 * 1000)) then
GetTokens();
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace Microsoft.EServices.EDocumentConnector.Logiq;

page 6380 "Logiq Connection Setup"
{
Caption = 'E-Document External Connection Setup';
Copy link
Contributor

Choose a reason for hiding this comment

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

Different name. Use connector name

PageType = Card;
ApplicationArea = Basic, Suite;
UsageCategory = None;
SourceTable = "Logiq Connection Setup";

layout
{
area(Content)
{
group(General)
{
Caption = 'General';

field(ClientID; Rec."Client ID")
{
ShowMandatory = true;
}
field(ClientSecret; ClientSecret)
{
Caption = 'Client Secret';
ToolTip = 'Specifies the client secret token.';
ExtendedDatatype = Masked;
ShowMandatory = true;

trigger OnValidate()
begin
LogiqAuth.SetIsolatedStorageValue(Rec."Client Secret", ClientSecret);
end;
}
field("Authentication URL"; Rec."Authentication URL")
{
}
field("Base URL"; Rec."Base URL")
{
}
field("File List Endpoint"; Rec."File List Endpoint")
{
}
}
}
}

actions
{
area(Processing)
{
action(Connect)
{
ApplicationArea = All;
Caption = 'User Setup';
Image = Setup;
ToolTip = 'Open page for User Setup.';

trigger OnAction()
var
LogiqConnectionUserSetup: Record "Logiq Connection User Setup";
LoqiqConnectionUserSetupPage: Page "Logiq Connection User Setup";
begin
LogiqConnectionUserSetup.FindUserSetup(CopyStr(UserId(), 1, 50));
LoqiqConnectionUserSetupPage.SetRecord(LogiqConnectionUserSetup);
LoqiqConnectionUserSetupPage.Run();
end;
}
}
area(Promoted)
{
actionref(Connect_Promoted; Connect)
{
}
}
}

var
LogiqAuth: Codeunit "Logiq Auth";
[NonDebuggable]
ClientSecret: Text;

trigger OnOpenPage()
begin
if not Rec.Get() then begin
Rec.Init();
Rec.Insert(true);
end;

if LogiqAuth.HasToken(Rec."Client Secret", DataScope::Company) then
ClientSecret := '*';
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
namespace Microsoft.EServices.EDocumentConnector.Logiq;

table 6380 "Logiq Connection Setup"
{
Caption = 'Logiq Connection Setup';
DataClassification = CustomerContent;
fields
{
field(1; PK; Code[10])
Copy link
Contributor

Choose a reason for hiding this comment

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

20 is standard.

{
DataClassification = CustomerContent;
}
field(21; "Authentication URL"; Text[100])
{
Caption = 'Authorization URL';
DataClassification = CustomerContent;
ToolTip = 'Specifies the Authorization URL.';
}
field(22; "Base URL"; Text[100])
{
Caption = 'Base URL';
DataClassification = CustomerContent;
ToolTip = 'Specifies the Base URL.';
}
field(25; "File List Endpoint"; Text[100])
{
Caption = 'File List Endpoint';
DataClassification = CustomerContent;
ToolTip = 'Specifies the Endpoint to list available files.';
}
field(31; "Client ID"; Text[100])
{
Caption = 'Client ID';
ToolTip = 'Specifies the client ID token.';
DataClassification = EndUserIdentifiableInformation;
}
field(32; "Client Secret"; Guid)
{
Caption = 'Client Secret';
DataClassification = EndUserIdentifiableInformation;
}
}

keys
{
key(PK; PK)
{
Clustered = true;
}
}

var
LogiqAuth: Codeunit "Logiq Auth";

trigger OnInsert()
var
AuthenticationUrlTok: Label 'https://pilot-sso.logiq.no/auth/realms/connect-api/protocol/openid-connect/token', Locked = true;
FileListTok: Label '1.0/listfiles', Locked = true;
BaseUrlTok: Label 'https://pilot-api.logiq.no/edi/connect/', Locked = true;
begin
Rec."Authentication URL" := AuthenticationUrlTok;
Rec."File List Endpoint" := FileListTok;
Rec."Base URL" := BaseUrlTok;
end;

internal procedure GetClientSecret(): SecretText
var
ClientSecret: SecretText;
begin
LogiqAuth.GetIsolatedStorageValue(Rec."Client Secret", ClientSecret);
exit(ClientSecret);
end;
}
Loading
Loading