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

[Edit-In Excel] Improve metadata handling #1721

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// <summary>
/// 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using System.Azure.Identity;
#endif
using System.Reflection;
using System.Security.Authentication;
using System.Azure.KeyVault;

Check failure on line 16 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0791 The namespace 'KeyVault' is unknown.

Check failure on line 16 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0791 The namespace 'KeyVault' is unknown.

codeunit 1482 "Edit in Excel Impl."
{
Expand All @@ -27,9 +29,7 @@
#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;
Expand All @@ -48,6 +48,14 @@
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;
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
Expand Down Expand Up @@ -348,6 +356,10 @@
TenantWebService.ExcludeNonEditableFlowFields := true;
TenantWebService.Published := true;
TenantWebService.Insert(true);

if not IsMetadataGeneratedForWebService(ServiceName) then
Message('Service was not generated');

exit(ServiceName);
end;

Expand Down Expand Up @@ -889,4 +901,124 @@
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.com/v2.0/Production/ODataV4/$metadata';
HttpRequestMessage := CreateMetadataWebRequest(MetadataUrl);
if 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;

Check failure on line 958 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0185 Codeunit 'OAuth2' is missing

Check failure on line 958 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0185 Codeunit 'OAuth2' is missing
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";

Check failure on line 981 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0185 Codeunit 'Azure Key Vault' is missing

Check failure on line 981 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0185 Codeunit 'Azure Key Vault' is missing
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;

Check failure on line 999 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0185 Codeunit 'OAuth2' is missing

Check failure on line 999 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0185 Codeunit 'OAuth2' is missing
RedirectURL: Text;
begin
OAuth2.GetDefaultRedirectURL(RedirectURL);
exit(RedirectURL)
end;

[NonDebuggable]
local procedure GetScope(): Text
var
begin
exit('https://api.businesscentral.dynamics.com/.default');
end;

[NonDebuggable]
local procedure GetClientId(): Text
var
AzureKeyVault: Codeunit "Azure Key Vault";

Check failure on line 1016 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Clean) / System Application Modules (Clean)

AL0185 Codeunit 'Azure Key Vault' is missing

Check failure on line 1016 in src/System Application/App/Edit in Excel/src/EditinExcelImpl.Codeunit.al

View workflow job for this annotation

GitHub Actions / Build System Application Modules (Default) / System Application Modules (Default)

AL0185 Codeunit 'Azure Key Vault' is missing
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;
}
Loading