From 564069fb39f46bb95d702c9b1418cd6ba8ac8c37 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Mon, 5 Aug 2024 13:40:02 +0200 Subject: [PATCH 1/7] Improve metadata handling --- .../Edit in Excel/src/EditInExcel.Codeunit.al | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al index c77237efb8..4e4fbd9abb 100644 --- a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al @@ -5,6 +5,7 @@ namespace System.Integration.Excel; +using System; using System.Integration; /// @@ -14,6 +15,42 @@ codeunit 1481 "Edit in Excel" { Access = Public; + procedure CreateMetadataWebRequest() + var + EnvironmentInfo: Codeunit "Environment Information"; + Token: Text; + begin + Token := 'herpderp'; + + + end; + + procedure IsMetadataGeneratedForWebService(): Boolean + var + HttpClient: HttpClient; + HttpResponseMessage: HttpResponseMessage; + MetadataUrl: Text; + EntitySetXml: Text; + Document: DotNet XmlDocument; + NodeList: DotNet XmlNodeList; + NameSpaceManager: DotNet XmlNamespaceManager; + EntitySetName: Text; + begin + MetadataUrl := 'https://metadatastoragexyz.blob.core.windows.net/metadata/metadata.xml?sp=r&st=2024-08-02T09:38:22Z&se=2024-08-02T17:38:22Z&spr=https&sv=2022-11-02&sr=b&sig=eP4pvx1m1OiiGVhYVD4haMIYvSgOAWIUysk3mD7h8Ds%3D'; + EntitySetName := 'Company'; + if HttpClient.Get(MetadataUrl, HttpResponseMessage) then + if HttpResponseMessage.IsSuccessStatusCode then begin + HttpResponseMessage.Content().ReadAs(EntitySetXml); + Document := Document.XmlDocument(); + Document.LoadXml(EntitySetXml); + NameSpaceManager := NameSpaceManager.XmlNamespaceManager(Document.NameTable()); + NameSpaceManager.AddNamespace('edm', 'http://docs.oasis-open.org/odata/ns/edm'); + NodeList := Document.SelectNodes('//edm:EntitySet[@Name="' + EntitySetName + '"]', NameSpaceManager); + exit(NodeList.Count() > 0); + end; + exit(false); + end; + #if not CLEAN22 /// /// Creates web service for the specified page, and uses the web service to prepare and download an Excel file for the Edit in Excel functionality. From 4067ebcaf5107b4e8ac1a5fa7025dded700541f0 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Mon, 12 Aug 2024 10:56:38 +0200 Subject: [PATCH 2/7] ... --- .../Edit in Excel/src/EditInExcel.Codeunit.al | 37 ----- .../src/EditinExcelImpl.Codeunit.al | 141 ++++++++++++++++++ 2 files changed, 141 insertions(+), 37 deletions(-) diff --git a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al index 4e4fbd9abb..c77237efb8 100644 --- a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al @@ -5,7 +5,6 @@ namespace System.Integration.Excel; -using System; using System.Integration; /// @@ -15,42 +14,6 @@ codeunit 1481 "Edit in Excel" { Access = Public; - procedure CreateMetadataWebRequest() - var - EnvironmentInfo: Codeunit "Environment Information"; - Token: Text; - begin - Token := 'herpderp'; - - - end; - - procedure IsMetadataGeneratedForWebService(): Boolean - var - HttpClient: HttpClient; - HttpResponseMessage: HttpResponseMessage; - MetadataUrl: Text; - EntitySetXml: Text; - Document: DotNet XmlDocument; - NodeList: DotNet XmlNodeList; - NameSpaceManager: DotNet XmlNamespaceManager; - EntitySetName: Text; - begin - MetadataUrl := 'https://metadatastoragexyz.blob.core.windows.net/metadata/metadata.xml?sp=r&st=2024-08-02T09:38:22Z&se=2024-08-02T17:38:22Z&spr=https&sv=2022-11-02&sr=b&sig=eP4pvx1m1OiiGVhYVD4haMIYvSgOAWIUysk3mD7h8Ds%3D'; - EntitySetName := 'Company'; - if HttpClient.Get(MetadataUrl, HttpResponseMessage) then - if HttpResponseMessage.IsSuccessStatusCode then begin - HttpResponseMessage.Content().ReadAs(EntitySetXml); - Document := Document.XmlDocument(); - Document.LoadXml(EntitySetXml); - NameSpaceManager := NameSpaceManager.XmlNamespaceManager(Document.NameTable()); - NameSpaceManager.AddNamespace('edm', 'http://docs.oasis-open.org/odata/ns/edm'); - NodeList := Document.SelectNodes('//edm:EntitySet[@Name="' + EntitySetName + '"]', NameSpaceManager); - exit(NodeList.Count() > 0); - end; - exit(false); - end; - #if not CLEAN22 /// /// Creates web service for the specified page, and uses the web service to prepare and download an Excel file for the Edit in Excel functionality. diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index c5879405c2..2fa3ae04f0 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -12,6 +12,8 @@ using System.Environment; using System.Azure.Identity; #endif using System.Reflection; +using System.Security.Authentication; +using System.Azure.KeyVault; codeunit 1482 "Edit in Excel Impl." { @@ -48,6 +50,16 @@ codeunit 1482 "Edit in Excel Impl." ExcelFileNameTxt: Text; XmlByteEncodingTok: Label '_x00%1_%2', Locked = true; XmlByteEncoding2Tok: Label '%1_x00%2_%3', Locked = true; + // Retrieving Metadata related: + AcquiredCESTokenLbl: Label 'AcquireTokensWithCertificate call was successful.', Locked = true; + AuthorityLbl: Label 'https://login.microsoftonline.com/microsoft.onmicrosoft.com', Locked = true; + BearerLbl: Label 'Bearer %1', Locked = true, Comment = '%1 - Bearer token'; + ClientCertificateAKVSecretNameLbl: Label 'bctocesappcertificatename', Locked = true; + MissingClientIdOrCertificateTelemetryTxt: Label 'The client id or certificate have not been initialized.', Locked = true; + MissingScopeTelemetryTxt: Label 'The scope have not been initialized.', Locked = true; + ScopeAKVSecretNameLbl: Label 'bctocesappscope', Locked = true; + ClientIdAKVSecretNameLbl: Label 'bctocesappid', Locked = true; + CategoryTok: Label 'Customer Experience Survey', Locked = true; procedure EditPageInExcel(PageCaption: Text[240]; PageId: Integer; EditinExcelFilters: Codeunit "Edit in Excel Filters"; FileName: Text) var @@ -348,6 +360,10 @@ codeunit 1482 "Edit in Excel Impl." TenantWebService.ExcludeNonEditableFlowFields := true; TenantWebService.Published := true; TenantWebService.Insert(true); + + if not IsMetadataGeneratedForWebService(ServiceName) then + Message('Service was not generated'); + exit(ServiceName); end; @@ -889,4 +905,129 @@ codeunit 1482 "Edit in Excel Impl." ConcatenatedErrors := DelStr(ConcatenatedErrors, StrLen(ConcatenatedErrors) - 1); exit(ConcatenatedErrors); end; + + procedure CreateMetadataWebRequest(MetadataUrl: Text): HttpRequestMessage + var + AccessToken: SecretText; + HttpClient: HttpClient; + HttpRequestMessage: HttpRequestMessage; + HttpHeaders: HttpHeaders; + ErrorMessage: Text; + begin + if EnvironmentInformation.IsSaaS() then begin + AccessToken := AcquireToken(ErrorMessage); + if not AccessToken.IsEmpty() then begin + HttpRequestMessage.Method('GET'); + HttpRequestMessage.SetRequestUri(MetadataUrl); + HttpHeaders := HttpClient.DefaultRequestHeaders(); + HttpHeaders.Add('Accept', 'application/json'); + HttpHeaders.Add('Authorization', SecretStrSubstNo(BearerLbl, AccessToken)); + exit(HttpRequestMessage); + end; + end; + end; + + procedure IsMetadataGeneratedForWebService(EntitySetName: Text): Boolean + var + HttpClient: HttpClient; + HttpResponseMessage: HttpResponseMessage; + HttpRequestMessage: HttpRequestMessage; + MetadataUrl: Text; + EntitySetXml: Text; + Document: DotNet XmlDocument; + NodeList: DotNet XmlNodeList; + NameSpaceManager: DotNet XmlNamespaceManager; + begin + MetadataUrl := 'https://api.businesscentral.dynamics-tie.com/v2.0/Production/ODataV4/$metadata'; + HttpRequestMessage := CreateMetadataWebRequest(MetadataUrl); + if not HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin + if HttpResponseMessage.IsSuccessStatusCode then begin + HttpResponseMessage.Content().ReadAs(EntitySetXml); + Document := Document.XmlDocument(); + Document.LoadXml(EntitySetXml); + NameSpaceManager := NameSpaceManager.XmlNamespaceManager(Document.NameTable()); + NameSpaceManager.AddNamespace('edm', 'http://docs.oasis-open.org/odata/ns/edm'); + NodeList := Document.SelectNodes('//edm:EntitySet[@Name="' + EntitySetName + '"]', NameSpaceManager); + exit(NodeList.Count() > 0); + end; + Message('It looks like the HTTP request was sent but it didnt have a successful status code'); + exit(false); + end; + Message('The HttpClient could not even sent the request!'); + exit(false); + end; + + local procedure AcquireToken(var ErrorMessage: Text): SecretText + var + OAuth2: Codeunit OAuth2; + Scopes: List of [Text]; + ClientId: Text; + ClientCertificate: SecretText; + AccessToken: SecretText; + IdToken: Text; + begin + ClientId := GetClientId(); + ClientCertificate := GetClientCertificate(); + Scopes.Add(GetScope()); + + if (ClientId <> '') and (not ClientCertificate.IsEmpty()) then + if OAuth2.AcquireTokensWithCertificate(ClientId, ClientCertificate, GetRedirectURL(), AuthorityLbl, Scopes, AccessToken, IdToken) then begin + Session.LogMessage('0000J9B', AcquiredCESTokenLbl, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit(AccessToken); + end; + + ErrorMessage := GetLastErrorText(); + end; + + [NonDebuggable] + local procedure GetClientCertificate(): SecretText + var + AzureKeyVault: Codeunit "Azure Key Vault"; + Certificate: SecretText; + CertificateName: Text; + begin + if not AzureKeyVault.GetAzureKeyVaultSecret(ClientCertificateAKVSecretNameLbl, CertificateName) then begin + Session.LogMessage('0000J9E', MissingClientIdOrCertificateTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + exit(Certificate); + end; + + if not AzureKeyVault.GetAzureKeyVaultCertificate(CertificateName, Certificate) then + Session.LogMessage('0000J9F', MissingClientIdOrCertificateTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok); + + exit(Certificate); + end; + + [NonDebuggable] + local procedure GetRedirectURL(): Text + var + OAuth2: Codeunit OAuth2; + RedirectURL: Text; + begin + OAuth2.GetDefaultRedirectURL(RedirectURL); + exit(RedirectURL) + end; + + [NonDebuggable] + local procedure GetScope(): Text + var + AzureKeyVault: Codeunit "Azure Key Vault"; + Scope: Text; + begin + if not AzureKeyVault.GetAzureKeyVaultSecret(ScopeAKVSecretNameLbl, Scope) then + Session.LogMessage('0000JRJ', MissingScopeTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok) + else + exit(Scope); + end; + + [NonDebuggable] + local procedure GetClientId(): Text + var + AzureKeyVault: Codeunit "Azure Key Vault"; + ClientId: Text; + begin + if not AzureKeyVault.GetAzureKeyVaultSecret(ClientIdAKVSecretNameLbl, ClientId) then + Session.LogMessage('0000J9D', MissingClientIdOrCertificateTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok) + else + exit(ClientId); + end; } \ No newline at end of file From 7ca2187ad4f3fc7f9fc7006698062680315849c8 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Mon, 12 Aug 2024 15:06:23 +0200 Subject: [PATCH 3/7] ... --- .../App/Edit in Excel/src/EditInExcel.Codeunit.al | 7 +++++++ .../App/Edit in Excel/src/EditinExcelImpl.Codeunit.al | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al index c77237efb8..a405c9a4bf 100644 --- a/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditInExcel.Codeunit.al @@ -14,6 +14,13 @@ codeunit 1481 "Edit in Excel" { Access = Public; + procedure IsMetadataGeneratedForWebService(WebServiceName: Text): Boolean + var + EditInExcelImpl: Codeunit "Edit in Excel Impl."; + begin + exit(EditInExcelImpl.IsMetadataGeneratedForWebService(WebServiceName)) + end; + #if not CLEAN22 /// /// Creates web service for the specified page, and uses the web service to prepare and download an Excel file for the Edit in Excel functionality. diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index 2fa3ae04f0..5a8d25fe32 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -940,7 +940,7 @@ codeunit 1482 "Edit in Excel Impl." begin MetadataUrl := 'https://api.businesscentral.dynamics-tie.com/v2.0/Production/ODataV4/$metadata'; HttpRequestMessage := CreateMetadataWebRequest(MetadataUrl); - if not HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin + if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin if HttpResponseMessage.IsSuccessStatusCode then begin HttpResponseMessage.Content().ReadAs(EntitySetXml); Document := Document.XmlDocument(); From bc47024a126770bf6130580e0494d7e65259534c Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Mon, 12 Aug 2024 23:20:42 +0200 Subject: [PATCH 4/7] Change scope --- .../App/Edit in Excel/src/EditinExcelImpl.Codeunit.al | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index 5a8d25fe32..602c69029d 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -1010,13 +1010,8 @@ codeunit 1482 "Edit in Excel Impl." [NonDebuggable] local procedure GetScope(): Text var - AzureKeyVault: Codeunit "Azure Key Vault"; - Scope: Text; begin - if not AzureKeyVault.GetAzureKeyVaultSecret(ScopeAKVSecretNameLbl, Scope) then - Session.LogMessage('0000JRJ', MissingScopeTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CategoryTok) - else - exit(Scope); + exit('https://api.businesscentral.dynamics.com/.default'); end; [NonDebuggable] From 58e3a0b3676c1408b1bc8a2eb372701782467f63 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Tue, 13 Aug 2024 09:36:26 +0200 Subject: [PATCH 5/7] remove unused variables --- .../App/Edit in Excel/src/EditinExcelImpl.Codeunit.al | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index 602c69029d..410ede5c00 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -56,8 +56,6 @@ codeunit 1482 "Edit in Excel Impl." BearerLbl: Label 'Bearer %1', Locked = true, Comment = '%1 - Bearer token'; ClientCertificateAKVSecretNameLbl: Label 'bctocesappcertificatename', Locked = true; MissingClientIdOrCertificateTelemetryTxt: Label 'The client id or certificate have not been initialized.', Locked = true; - MissingScopeTelemetryTxt: Label 'The scope have not been initialized.', Locked = true; - ScopeAKVSecretNameLbl: Label 'bctocesappscope', Locked = true; ClientIdAKVSecretNameLbl: Label 'bctocesappid', Locked = true; CategoryTok: Label 'Customer Experience Survey', Locked = true; From 15186a1b437da89eef68df2f6a3637dca80176a7 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Wed, 21 Aug 2024 15:00:51 +0200 Subject: [PATCH 6/7] Remove CLEAN preprocessor around variable we need going forward --- .../App/Edit in Excel/src/EditinExcelImpl.Codeunit.al | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index 410ede5c00..289ea071ab 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -29,9 +29,7 @@ codeunit 1482 "Edit in Excel Impl." #endif var -#if not CLEAN22 EnvironmentInformation: Codeunit "Environment Information"; -#endif EditinExcel: Codeunit "Edit in Excel"; #if not CLEAN22 TenantWebserviceDoesNotExistTxt: Label 'Tenant web service does not exist.', Locked = true; From 3b0677b39167db12d5cab4a63456996d2e661b66 Mon Sep 17 00:00:00 2001 From: Matti Nielsen Date: Wed, 21 Aug 2024 19:46:20 +0200 Subject: [PATCH 7/7] Try to test it out in FastProd instead --- .../App/Edit in Excel/src/EditinExcelImpl.Codeunit.al | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al index 289ea071ab..c2bca16f37 100644 --- a/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al +++ b/src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al @@ -934,7 +934,7 @@ codeunit 1482 "Edit in Excel Impl." NodeList: DotNet XmlNodeList; NameSpaceManager: DotNet XmlNamespaceManager; begin - MetadataUrl := 'https://api.businesscentral.dynamics-tie.com/v2.0/Production/ODataV4/$metadata'; + MetadataUrl := 'https://api.businesscentral.dynamics.com/v2.0/Production/ODataV4/$metadata'; HttpRequestMessage := CreateMetadataWebRequest(MetadataUrl); if HttpClient.Send(HttpRequestMessage, HttpResponseMessage) then begin if HttpResponseMessage.IsSuccessStatusCode then begin