From 1702f14ac06699cf409389359b0b1f3cabe6d0f6 Mon Sep 17 00:00:00 2001
From: Christian Andersen <chriandersen@microsoft.com>
Date: Tue, 12 Nov 2024 12:59:11 +0100
Subject: [PATCH 01/30] Slice 535826: [AI][Public Preview]Copilot Toolkit:
 Automatic access verification for AOAI services to develop and run on
 CAPI/managed AI resources

---
 .../AOAIAuthorization.Codeunit.al             | 49 ++++++++++++++++---
 .../src/Azure OpenAI/AzureOpenAI.Codeunit.al  | 11 ++---
 .../Azure OpenAI/AzureOpenAIImpl.Codeunit.al  |  8 +--
 3 files changed, 50 insertions(+), 18 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 2e5ba3e42a..7059315769 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -48,15 +48,19 @@ codeunit 7767 "AOAI Authorization"
     end;
 
     [NonDebuggable]
-    procedure SetMicrosoftManagedAuthorization(NewEndpoint: Text; NewDeployment: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
+    procedure SetMicrosoftManagedAuthorization(AOAIAccountName: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
+    var
+        IsVerified: Boolean;
     begin
         ClearVariables();
+        IsVerified := VerifyAOAIAccount(AOAIAccountName, NewApiKey.Unwrap());
 
-        ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
-        Endpoint := NewEndpoint;
-        Deployment := NewDeployment;
-        ApiKey := NewApiKey;
-        ManagedResourceDeployment := NewManagedResourceDeployment;
+        if IsVerified then begin
+            ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
+            Endpoint := "endpoint";//NewEndpoint;
+            ApiKey := NewApiKey;
+            ManagedResourceDeployment := NewManagedResourceDeployment;
+        end;
     end;
 
     [NonDebuggable]
@@ -116,4 +120,37 @@ codeunit 7767 "AOAI Authorization"
         Clear(ManagedResourceDeployment);
         Clear(ResourceUtilization);
     end;
+
+    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
+    var
+        HttpClient: HttpClient;
+        HttpRequestMessage: HttpRequestMessage;
+        HttpResponseMessage: HttpResponseMessage;
+        HttpContent: HttpContent;
+        ContentHeaders: HttpHeaders;
+        Url: Text;
+        IsSuccessful: Boolean;
+    begin
+        Url := 'https://' + AOAIAccountName + '.openai.azure.com/openai/models?api-version=2024-06-01';
+
+        HttpContent.GetHeaders(ContentHeaders);
+        if ContentHeaders.Contains('Content-Type') then
+            ContentHeaders.Remove('Content-Type');
+        ContentHeaders.Add('Content-Type', 'application/json');
+        ContentHeaders.Add('api-key', NewApiKey);
+
+        HttpRequestMessage.Method := 'GET';
+        HttpRequestMessage.SetRequestUri(Url);
+        HttpRequestMessage.Content(HttpContent);
+
+        IsSuccessful := HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
+
+        if not IsSuccessful then
+            exit(false);
+
+        if not HttpResponseMessage.IsSuccessStatusCode() then
+            exit(false);
+
+        exit(true);
+    end;
 }
\ No newline at end of file
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al
index 7992af7d3f..d9ee173f1d 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
@@ -91,18 +91,13 @@ codeunit 7771 "Azure OpenAI"
     /// This will send the Azure OpenAI call to the deployment specified in <paramref name="ManagedResourceDeployment"/>, and will use the other parameters to verify that you have access to Azure OpenAI.
     /// </summary>
     /// <param name="ModelType">The model type to set authorization for.</param>
-    /// <param name="Endpoint">The endpoint to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
-    /// <param name="Deployment">The deployment to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
+    /// <param name="AOAIAccountName">Azure OpenAI account name)</param>
     /// <param name="ApiKey">The API key to use  to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
     /// <param name="ManagedResourceDeployment">The managed deployment to use for the model type.</param>
-    /// <remarks> NOTE: This function is currently only available to selected partners.
-    /// Endpoint would look like: https://resource-name.openai.azure.com/
-    /// Deployment would look like: gpt-35-turbo-16k
-    /// </remarks>
     [NonDebuggable]
-    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
+    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; AOAIAccountName: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
     begin
-        AzureOpenAIImpl.SetManagedResourceAuthorization(ModelType, Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+        AzureOpenAIImpl.SetManagedResourceAuthorization(ModelType, AOAIAccountName, ApiKey, ManagedResourceDeployment);
     end;
 
     /// <summary>
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index eb6a83aade..d979ea2895 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -188,15 +188,15 @@ codeunit 7772 "Azure OpenAI Impl"
     end;
 
     [NonDebuggable]
-    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
+    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; AOAIAccountName: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
     begin
         case ModelType of
             Enum::"AOAI Model Type"::"Text Completions":
-                TextCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+                TextCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(AOAIAccountName, ApiKey, ManagedResourceDeployment);
             Enum::"AOAI Model Type"::Embeddings:
-                EmbeddingsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+                EmbeddingsAOAIAuthorization.SetMicrosoftManagedAuthorization(AOAIAccountName, ApiKey, ManagedResourceDeployment);
             Enum::"AOAI Model Type"::"Chat Completions":
-                ChatCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+                ChatCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(AOAIAccountName, ApiKey, ManagedResourceDeployment);
             else
                 Error(InvalidModelTypeErr);
         end;

From 09f9d04c642ec3476eae2780afdb7707f6f2f58c Mon Sep 17 00:00:00 2001
From: Christian Andersen <chriandersen@microsoft.com>
Date: Tue, 12 Nov 2024 15:13:36 +0100
Subject: [PATCH 02/30] Restored SetManagedResourceAuthorization with old
 parameters

---
 .../AOAIAuthorization.Codeunit.al             | 16 +++++++++++++++-
 .../src/Azure OpenAI/AzureOpenAI.Codeunit.al  | 19 +++++++++++++++++++
 .../Azure OpenAI/AzureOpenAIImpl.Codeunit.al  | 15 +++++++++++++++
 3 files changed, 49 insertions(+), 1 deletion(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 7059315769..1d8c71a176 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -47,6 +47,18 @@ codeunit 7767 "AOAI Authorization"
         exit(false);
     end;
 
+    [NonDebuggable]
+    procedure SetMicrosoftManagedAuthorization(NewEndpoint: Text; NewDeployment: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
+    begin
+        ClearVariables();
+
+        ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
+        Endpoint := NewEndpoint;
+        Deployment := NewDeployment;
+        ApiKey := NewApiKey;
+        ManagedResourceDeployment := NewManagedResourceDeployment;
+    end;
+
     [NonDebuggable]
     procedure SetMicrosoftManagedAuthorization(AOAIAccountName: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
     var
@@ -121,6 +133,7 @@ codeunit 7767 "AOAI Authorization"
         Clear(ResourceUtilization);
     end;
 
+    [NonDebuggable]
     local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
     var
         HttpClient: HttpClient;
@@ -130,8 +143,9 @@ codeunit 7767 "AOAI Authorization"
         ContentHeaders: HttpHeaders;
         Url: Text;
         IsSuccessful: Boolean;
+        UrlFormatTxt: Label 'https://%1.openai.azure.com/openai/models?api-version=2024-06-01', Locked = true;
     begin
-        Url := 'https://' + AOAIAccountName + '.openai.azure.com/openai/models?api-version=2024-06-01';
+        Url := StrSubstNo(UrlFormatTxt, AOAIAccountName);
 
         HttpContent.GetHeaders(ContentHeaders);
         if ContentHeaders.Contains('Content-Type') then
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al
index d9ee173f1d..9ce398c680 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
@@ -86,6 +86,25 @@ codeunit 7771 "Azure OpenAI"
         exit(AzureOpenAIImpl.IsInitialized(CopilotCapability, ModelType, CallerModuleInfo));
     end;
 
+    /// <summary>
+    /// Sets the managed Azure OpenAI API authorization to use for a specific model type.
+    /// This will send the Azure OpenAI call to the deployment specified in <paramref name="ManagedResourceDeployment"/>, and will use the other parameters to verify that you have access to Azure OpenAI.
+    /// </summary>
+    /// <param name="ModelType">The model type to set authorization for.</param>
+    /// <param name="Endpoint">The endpoint to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
+    /// <param name="Deployment">The deployment to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
+    /// <param name="ApiKey">The API key to use  to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
+    /// <param name="ManagedResourceDeployment">The managed deployment to use for the model type.</param>
+    /// <remarks> NOTE: This function is currently only available to selected partners.
+    /// Endpoint would look like: https://resource-name.openai.azure.com/
+    /// Deployment would look like: gpt-35-turbo-16k
+    /// </remarks>
+    [NonDebuggable]
+    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
+    begin
+        AzureOpenAIImpl.SetManagedResourceAuthorization(ModelType, Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+    end;
+
     /// <summary>
     /// Sets the managed Azure OpenAI API authorization to use for a specific model type.
     /// This will send the Azure OpenAI call to the deployment specified in <paramref name="ManagedResourceDeployment"/>, and will use the other parameters to verify that you have access to Azure OpenAI.
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index d979ea2895..18552d49dd 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -187,6 +187,21 @@ codeunit 7772 "Azure OpenAI Impl"
         end;
     end;
 
+    [NonDebuggable]
+    procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
+    begin
+        case ModelType of
+            Enum::"AOAI Model Type"::"Text Completions":
+                TextCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+            Enum::"AOAI Model Type"::Embeddings:
+                EmbeddingsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+            Enum::"AOAI Model Type"::"Chat Completions":
+                ChatCompletionsAOAIAuthorization.SetMicrosoftManagedAuthorization(Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
+            else
+                Error(InvalidModelTypeErr);
+        end;
+    end;
+
     [NonDebuggable]
     procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; AOAIAccountName: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
     begin

From 4119ef817d8a4e0f70ff9d0529245a4e05b6712a Mon Sep 17 00:00:00 2001
From: Christian Andersen <chriandersen@microsoft.com>
Date: Tue, 12 Nov 2024 15:37:55 +0100
Subject: [PATCH 03/30] Removed unnessecary variables when verifying Microsoft
 managed resources using new method

---
 .../App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al       | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 1d8c71a176..9586755e99 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -69,8 +69,6 @@ codeunit 7767 "AOAI Authorization"
 
         if IsVerified then begin
             ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
-            Endpoint := "endpoint";//NewEndpoint;
-            ApiKey := NewApiKey;
             ManagedResourceDeployment := NewManagedResourceDeployment;
         end;
     end;

From 22af527a5732d0cb7e248cc4e054b8e52304f23a Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Mon, 16 Dec 2024 16:29:44 +0100
Subject: [PATCH 04/30] Simplified configuration check and made it more
 flexible

---
 .../Azure OpenAI/AOAIAuthorization.Codeunit.al  | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 9586755e99..d77102f3e3 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -25,6 +25,10 @@ codeunit 7767 "AOAI Authorization"
         [NonDebuggable]
         ManagedResourceDeployment: Text;
         ResourceUtilization: Enum "AOAI Resource Utilization";
+        [NonDebuggable]
+        FirstPartyAuthorization: Boolean;
+        SelfManagedAuthorization: Boolean;
+        MicrosoftManagedAuthorization: Boolean;
 
     [NonDebuggable]
     procedure IsConfigured(CallerModule: ModuleInfo): Boolean
@@ -37,11 +41,11 @@ codeunit 7767 "AOAI Authorization"
 
         case ResourceUtilization of
             Enum::"AOAI Resource Utilization"::"First Party":
-                exit((ManagedResourceDeployment <> '') and ALCopilotFunctions.IsPlatformAuthorizationConfigured(CallerModule.Publisher(), CurrentModule.Publisher()));
+                exit(FirstPartyAuthorization and ALCopilotFunctions.IsPlatformAuthorizationConfigured(CallerModule.Publisher(), CurrentModule.Publisher()));
             Enum::"AOAI Resource Utilization"::"Self-Managed":
-                exit((Deployment <> '') and (Endpoint <> '') and (not ApiKey.IsEmpty()));
+                exit(SelfManagedAuthorization);
             Enum::"AOAI Resource Utilization"::"Microsoft Managed":
-                exit((Deployment <> '') and (Endpoint <> '') and (not ApiKey.IsEmpty()) and (ManagedResourceDeployment <> '') and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
+                exit(MicrosoftManagedAuthorization and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
         end;
 
         exit(false);
@@ -57,6 +61,7 @@ codeunit 7767 "AOAI Authorization"
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
         ManagedResourceDeployment := NewManagedResourceDeployment;
+        MicrosoftManagedAuthorization := true;
     end;
 
     [NonDebuggable]
@@ -70,6 +75,7 @@ codeunit 7767 "AOAI Authorization"
         if IsVerified then begin
             ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
             ManagedResourceDeployment := NewManagedResourceDeployment;
+            MicrosoftManagedAuthorization := true;
         end;
     end;
 
@@ -82,6 +88,7 @@ codeunit 7767 "AOAI Authorization"
         Endpoint := NewEndpoint;
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
+        SelfManagedAuthorization := true;
     end;
 
     [NonDebuggable]
@@ -91,6 +98,7 @@ codeunit 7767 "AOAI Authorization"
 
         ResourceUtilization := Enum::"AOAI Resource Utilization"::"First Party";
         ManagedResourceDeployment := NewDeployment;
+        FirstPartyAuthorization := true;
     end;
 
     [NonDebuggable]
@@ -129,6 +137,9 @@ codeunit 7767 "AOAI Authorization"
         Clear(Deployment);
         Clear(ManagedResourceDeployment);
         Clear(ResourceUtilization);
+        Clear(FirstPartyAuthorization);
+        clear(SelfManagedAuthorization);
+        Clear(MicrosoftManagedAuthorization);
     end;
 
     [NonDebuggable]

From 2b18667667a7cbef993dec4e98666f0c261d0000 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 8 Jan 2025 16:51:47 +0100
Subject: [PATCH 05/30] Temporary Database and notification templates added

---
 .../Azure OpenAI/AzureOpenAIImpl.Codeunit.al  | 33 ++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index 18552d49dd..94cc6599d9 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -11,6 +11,7 @@ using System.Environment;
 using System.Globalization;
 using System.Privacy;
 using System.Telemetry;
+using System.Security.AccessControl;
 
 codeunit 7772 "Azure OpenAI Impl"
 {
@@ -755,4 +756,34 @@ codeunit 7772 "Azure OpenAI Impl"
         exit(true);
     end;
 
-}
\ No newline at end of file
+    procedure SendNotification()
+    var
+        Notif: Notification;
+    begin
+        Notif.Message := 'Whatever';
+        Notif.Scope := Notif.Scope::LocalScope;
+        notif.Send();
+        //notif.AddAction(); //for documentation
+    end;
+
+    procedure CreateRecord()
+    var
+        Whatever: Record "Aggregate Permission Set";
+    begin
+        //create
+        Whatever.Name := 'Name';
+        //define primary key (unique module name)
+        Whatever.Insert();
+
+        //add
+        Whatever.Name := 'Name';
+        Whatever.Modify();
+        //update
+        Whatever.Get(); //add primary key
+        Whatever.Name := 'Name';
+        Whatever.Modify();
+        //delete
+        Whatever.Get(); //add primary key
+        Whatever.Delete()
+    end;
+}

From 8a08eb230d64a7c1df183451ed0af7da79b307fb Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 10 Jan 2025 13:54:37 +0100
Subject: [PATCH 06/30] tmp template changes

---
 .../Azure OpenAI/AzureOpenAIImpl.Codeunit.al  | 31 -------------------
 1 file changed, 31 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index 94cc6599d9..5d5020183e 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -755,35 +755,4 @@ codeunit 7772 "Azure OpenAI Impl"
         Session.LogMessage('0000MLE', TelemetryTenantAllowlistedMsg, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
         exit(true);
     end;
-
-    procedure SendNotification()
-    var
-        Notif: Notification;
-    begin
-        Notif.Message := 'Whatever';
-        Notif.Scope := Notif.Scope::LocalScope;
-        notif.Send();
-        //notif.AddAction(); //for documentation
-    end;
-
-    procedure CreateRecord()
-    var
-        Whatever: Record "Aggregate Permission Set";
-    begin
-        //create
-        Whatever.Name := 'Name';
-        //define primary key (unique module name)
-        Whatever.Insert();
-
-        //add
-        Whatever.Name := 'Name';
-        Whatever.Modify();
-        //update
-        Whatever.Get(); //add primary key
-        Whatever.Name := 'Name';
-        Whatever.Modify();
-        //delete
-        Whatever.Get(); //add primary key
-        Whatever.Delete()
-    end;
 }

From d6c54fe501f905e758b654c60692754fbf16eed6 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 10 Jan 2025 17:00:57 +0100
Subject: [PATCH 07/30] Grace period with caching added

---
 .../AOAIAccountVerificationLog.Table.al       | 34 +++++++++++++
 .../AOAIAuthorization.Codeunit.al             | 50 ++++++++++++++++++-
 2 files changed, 83 insertions(+), 1 deletion(-)
 create mode 100644 src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al
new file mode 100644
index 0000000000..c61350a9f2
--- /dev/null
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
@@ -0,0 +1,34 @@
+namespace System.AI;
+
+table 7767 "AOAIAccountVerificationLog"
+{
+    Caption = 'AOAI Account Verification Log';
+    Access = Internal;
+    Extensible = false;
+    InherentEntitlements = RIMD;
+    DataPerCompany = false;
+    ReplicateData = false;
+
+    fields
+    {
+        field(1; AccountName; Text[100])
+        {
+            Caption = 'Account Name';
+            DataClassification = CustomerContent;
+        }
+
+        field(2; LastSuccessfulVerification; DateTime)
+        {
+            Caption = 'Access Verified';
+            DataClassification = SystemMetadata;
+        }
+    }
+
+    keys
+    {
+        key(PrimaryKey; AccountName)
+        {
+            Clustered = false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index d77102f3e3..3fb5e11fdc 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -14,6 +14,7 @@ codeunit 7767 "AOAI Authorization"
     Access = Internal;
     InherentEntitlements = X;
     InherentPermissions = X;
+    Permissions = tabledata AOAIAccountVerificationLog = RIMD;
 
     var
         [NonDebuggable]
@@ -143,7 +144,7 @@ codeunit 7767 "AOAI Authorization"
     end;
 
     [NonDebuggable]
-    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
+    local procedure PerformAOAIAccountVerification(AOAIAccountName: Text; NewApiKey: Text): Boolean
     var
         HttpClient: HttpClient;
         HttpRequestMessage: HttpRequestMessage;
@@ -176,4 +177,51 @@ codeunit 7767 "AOAI Authorization"
 
         exit(true);
     end;
+
+    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
+    var
+        AOAIAccountVerificationLog: Record "AOAIAccountVerificationLog";
+        GracePeriod: Duration;
+        CachePeriod: Duration;
+        LastSuccessfulVerification: DateTime;
+        IsVerified: Boolean;
+        TruncatedAccountName: Text[100];
+    begin
+        GracePeriod := 14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
+        CachePeriod := 24 * 60 * 60 * 1000; // 1 day in milliseconds
+
+        TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
+
+        // Check if the account has been successfully verified within the cache period
+        if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
+            LastSuccessfulVerification := AOAIAccountVerificationLog.LastSuccessfulVerification;
+            if CurrentDateTime - LastSuccessfulVerification <= CachePeriod then
+                exit(true);
+        end;
+
+        IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
+
+        if not IsVerified then begin
+            // If verification fails, check if the last successful verification is within the grace period
+            if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
+                LastSuccessfulVerification := AOAIAccountVerificationLog.LastSuccessfulVerification;
+                if CurrentDateTime - LastSuccessfulVerification <= GracePeriod then
+                    exit(true);
+            end;
+            exit(false);
+        end;
+
+        // Save verification date
+        if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
+            AOAIAccountVerificationLog.LastSuccessfulVerification := CurrentDateTime;
+            AOAIAccountVerificationLog.Modify(true);
+        end else begin
+            AOAIAccountVerificationLog.Init();
+            AOAIAccountVerificationLog.AccountName := TruncatedAccountName;
+            AOAIAccountVerificationLog.LastSuccessfulVerification := CurrentDateTime;
+            AOAIAccountVerificationLog.Insert(true);
+        end;
+
+        exit(true);
+    end;
 }
\ No newline at end of file

From 54ad50aaa65ffd108fc4a4dd61c792a52b2707e2 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Mon, 13 Jan 2025 17:57:36 +0100
Subject: [PATCH 08/30] Added notification and telemetry to verification logic
 as well as reorganized function

---
 .../AOAIAuthorization.Codeunit.al             | 92 ++++++++++++++-----
 1 file changed, 67 insertions(+), 25 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 3fb5e11fdc..150821410b 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -5,7 +5,7 @@
 namespace System.AI;
 
 using System;
-
+using System.Telemetry;
 /// <summary>
 /// Store the authorization information for the AOAI service.
 /// </summary>
@@ -180,11 +180,10 @@ codeunit 7767 "AOAI Authorization"
 
     local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
     var
-        AOAIAccountVerificationLog: Record "AOAIAccountVerificationLog";
+        Notif: Notification;
+        IsVerified: Boolean;
         GracePeriod: Duration;
         CachePeriod: Duration;
-        LastSuccessfulVerification: DateTime;
-        IsVerified: Boolean;
         TruncatedAccountName: Text[100];
     begin
         GracePeriod := 14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
@@ -192,36 +191,79 @@ codeunit 7767 "AOAI Authorization"
 
         TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
 
-        // Check if the account has been successfully verified within the cache period
-        if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
-            LastSuccessfulVerification := AOAIAccountVerificationLog.LastSuccessfulVerification;
-            if CurrentDateTime - LastSuccessfulVerification <= CachePeriod then
-                exit(true);
-        end;
+        if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then
+            exit(true);
 
         IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
 
+        // Handle failed verification
         if not IsVerified then begin
-            // If verification fails, check if the last successful verification is within the grace period
-            if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
-                LastSuccessfulVerification := AOAIAccountVerificationLog.LastSuccessfulVerification;
-                if CurrentDateTime - LastSuccessfulVerification <= GracePeriod then
-                    exit(true);
-            end;
+            SendNotification(Notif);
+
+            LogTelemetry(AOAIAccountName, Today); // Replace Today with the actual deprecation date
+
+            if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then
+                // Verified if within grace period
+                exit(true);
+            // Failed verification if grace period has been exceeded
             exit(false);
         end;
 
-        // Save verification date
-        if AOAIAccountVerificationLog.Get(TruncatedAccountName) then begin
-            AOAIAccountVerificationLog.LastSuccessfulVerification := CurrentDateTime;
-            AOAIAccountVerificationLog.Modify(true);
+        SaveVerificationTime(TruncatedAccountName);
+
+        exit(true);
+    end;
+
+    local procedure IsAccountVerifiedWithinPeriod(AccountName: Text[100]; Period: Duration): Boolean
+    var
+        Rec: Record "AOAIAccountVerificationLog";
+    begin
+        if Rec.Get(AccountName) then
+            exit(CurrentDateTime - Rec.LastSuccessfulVerification <= Period);
+
+        exit(false);
+    end;
+
+    local procedure SaveVerificationTime(AccountName: Text[100])
+    var
+        Rec: Record "AOAIAccountVerificationLog";
+    begin
+        if Rec.Get(AccountName) then begin
+            Rec.LastSuccessfulVerification := CurrentDateTime;
+            Rec.Modify(true);
         end else begin
-            AOAIAccountVerificationLog.Init();
-            AOAIAccountVerificationLog.AccountName := TruncatedAccountName;
-            AOAIAccountVerificationLog.LastSuccessfulVerification := CurrentDateTime;
-            AOAIAccountVerificationLog.Insert(true);
+            Rec.Init();
+            Rec.AccountName := AccountName;
+            Rec.LastSuccessfulVerification := CurrentDateTime;
+            Rec.Insert(true);
         end;
+    end;
 
-        exit(true);
+    local procedure SendNotification(var Notif: Notification)
+    var
+        MessageLbl: Label 'Azure Open AI authorization failed. AI functionality will be disabled within 2 weeks. Please contact your system administrator or the extension developer for assistance.';
+    begin
+        Notif.Message := MessageLbl;
+        Notif.Scope := NotificationScope::LocalScope;
+        Notif.Send();
+    end;
+
+    local procedure LogTelemetry(AccountName: Text; VerificationDate: Date)
+    var
+        Telemetry: Codeunit Telemetry;
+        MessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated within 2 weeks if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
+        CustomDimensions: Dictionary of [Text, Text];
+    begin
+        CustomDimensions.Add('AccountName', AccountName);
+        CustomDimensions.Add('VerificationDate', Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
+
+        Telemetry.LogMessage(
+            '0000AA1', // Event ID
+            StrSubstNo(MessageLbl, AccountName, VerificationDate), // Message
+            Verbosity::Warning,
+            DataClassification::SystemMetadata,
+            Enum::"AL Telemetry Scope"::All,
+            CustomDimensions
+        );
     end;
 }
\ No newline at end of file

From 02bd29ca9240834489c9919a3ab0d762097e3d5a Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Mon, 13 Jan 2025 18:02:58 +0100
Subject: [PATCH 09/30] corercted comments

---
 .../AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 150821410b..4a160f4cc1 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -199,18 +199,14 @@ codeunit 7767 "AOAI Authorization"
         // Handle failed verification
         if not IsVerified then begin
             SendNotification(Notif);
-
-            LogTelemetry(AOAIAccountName, Today); // Replace Today with the actual deprecation date
+            LogTelemetry(AOAIAccountName, Today);
 
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then
-                // Verified if within grace period
-                exit(true);
-            // Failed verification if grace period has been exceeded
-            exit(false);
+                exit(true); // Verified if within grace period
+            exit(false); // Failed verification if grace period has been exceeded
         end;
 
         SaveVerificationTime(TruncatedAccountName);
-
         exit(true);
     end;
 
@@ -220,7 +216,6 @@ codeunit 7767 "AOAI Authorization"
     begin
         if Rec.Get(AccountName) then
             exit(CurrentDateTime - Rec.LastSuccessfulVerification <= Period);
-
         exit(false);
     end;
 

From 16e7169e6279ebf89ba9612aff5b9a16f4b210d9 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Tue, 14 Jan 2025 11:49:31 +0100
Subject: [PATCH 10/30] added debugging messages

---
 .../Azure OpenAI/AOAIAuthorization.Codeunit.al   | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 4a160f4cc1..597bb41f3f 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -186,13 +186,15 @@ codeunit 7767 "AOAI Authorization"
         CachePeriod: Duration;
         TruncatedAccountName: Text[100];
     begin
-        GracePeriod := 14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
-        CachePeriod := 24 * 60 * 60 * 1000; // 1 day in milliseconds
+        GracePeriod := 15 * 60 * 100;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
+        CachePeriod := 1 * 60 * 100;//24 * 60 * 60 * 1000; // 1 day in milliseconds
 
         TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
 
-        if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then
+        if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then begin
+            Message('Verification skipped (within cache period).');
             exit(true);
+        end;
 
         IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
 
@@ -200,13 +202,15 @@ codeunit 7767 "AOAI Authorization"
         if not IsVerified then begin
             SendNotification(Notif);
             LogTelemetry(AOAIAccountName, Today);
-
-            if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then
+            if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
+                Message('Verification failed, but account is still valid (within grace period).');
                 exit(true); // Verified if within grace period
+            end;
+            Message('Verification failed, and account is no longer valid (grace period expired).');
             exit(false); // Failed verification if grace period has been exceeded
         end;
-
         SaveVerificationTime(TruncatedAccountName);
+        Message('Verification successful. Record saved.');
         exit(true);
     end;
 

From 8c41f6d73778dfda3cea91bd91b19c8ee4269e36 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Tue, 14 Jan 2025 13:13:20 +0100
Subject: [PATCH 11/30] removed wrong reference

---
 .../App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al          | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index 5d5020183e..383d4f4976 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -11,7 +11,6 @@ using System.Environment;
 using System.Globalization;
 using System.Privacy;
 using System.Telemetry;
-using System.Security.AccessControl;
 
 codeunit 7772 "Azure OpenAI Impl"
 {

From 10ec3e816789c48f2005e7106783ca475ebcfcd8 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Tue, 14 Jan 2025 14:21:53 +0100
Subject: [PATCH 12/30] Added execution permissions to table and increased test
 timeouts

---
 .../AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al   | 3 ++-
 .../App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al     | 4 ++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al
index c61350a9f2..3b4446b9f9 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
@@ -5,7 +5,8 @@ table 7767 "AOAIAccountVerificationLog"
     Caption = 'AOAI Account Verification Log';
     Access = Internal;
     Extensible = false;
-    InherentEntitlements = RIMD;
+    InherentEntitlements = RIMDX;
+    InherentPermissions = X;
     DataPerCompany = false;
     ReplicateData = false;
 
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 597bb41f3f..bd4ea580df 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -186,8 +186,8 @@ codeunit 7767 "AOAI Authorization"
         CachePeriod: Duration;
         TruncatedAccountName: Text[100];
     begin
-        GracePeriod := 15 * 60 * 100;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
-        CachePeriod := 1 * 60 * 100;//24 * 60 * 60 * 1000; // 1 day in milliseconds
+        GracePeriod := 15 * 60 * 1000;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
+        CachePeriod := 1 * 60 * 1000;//24 * 60 * 60 * 1000; // 1 day in milliseconds
 
         TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
 

From e6ce4501ef48632c6d52215eaa51009f552985da Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 15 Jan 2025 17:18:43 +0100
Subject: [PATCH 13/30] added additional debugging messages

---
 .../AOAIAuthorization.Codeunit.al             | 33 +++++++++++++++----
 1 file changed, 27 insertions(+), 6 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index bd4ea580df..30d89c57e7 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -186,40 +186,55 @@ codeunit 7767 "AOAI Authorization"
         CachePeriod: Duration;
         TruncatedAccountName: Text[100];
     begin
+        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AOAIAccountName + ', NewApiKey=' + NewApiKey);
+
         GracePeriod := 15 * 60 * 1000;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
         CachePeriod := 1 * 60 * 1000;//24 * 60 * 60 * 1000; // 1 day in milliseconds
 
         TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
 
+        Message('Variables: GracePeriod=' + Format(GracePeriod, 0, '<Duration>') + ', CachePeriod=' + Format(CachePeriod, 0, '<Duration>') + ', TruncatedAccountName=' + TruncatedAccountName);
+
         if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then begin
-            Message('Verification skipped (within cache period).');
+            Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification skipped (within cache period).');
             exit(true);
         end;
 
         IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
 
+        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(IsVerified, 0, '<Boolean>'));
+
         // Handle failed verification
         if not IsVerified then begin
             SendNotification(Notif);
             LogTelemetry(AOAIAccountName, Today);
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
-                Message('Verification failed, but account is still valid (within grace period).');
+                Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, but account is still valid (within grace period).');
                 exit(true); // Verified if within grace period
             end;
-            Message('Verification failed, and account is no longer valid (grace period expired).');
+            Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, and account is no longer valid (grace period expired).');
             exit(false); // Failed verification if grace period has been exceeded
         end;
         SaveVerificationTime(TruncatedAccountName);
-        Message('Verification successful. Record saved.');
+        Message('Function SaveVerificationTime called. Verification successful. Record saved.');
         exit(true);
     end;
 
     local procedure IsAccountVerifiedWithinPeriod(AccountName: Text[100]; Period: Duration): Boolean
     var
         Rec: Record "AOAIAccountVerificationLog";
+        IsVerified: Boolean;
     begin
-        if Rec.Get(AccountName) then
-            exit(CurrentDateTime - Rec.LastSuccessfulVerification <= Period);
+        Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + Format(Period, 0, '<Duration>'));
+
+        if Rec.Get(AccountName) then begin
+            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime, 0, '<DateTime>') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
+            IsVerified := CurrentDateTime - Rec.LastSuccessfulVerification <= Period;
+            Message('Verification result: ' + Format(IsVerified, 0, '<Boolean>'));
+            exit(IsVerified);
+        end;
+
+        Message('Record not found. Exiting with false.');
         exit(false);
     end;
 
@@ -227,14 +242,17 @@ codeunit 7767 "AOAI Authorization"
     var
         Rec: Record "AOAIAccountVerificationLog";
     begin
+        Message('Starting SaveVerificationTime procedure. Variables: AccountName=' + AccountName);
         if Rec.Get(AccountName) then begin
             Rec.LastSuccessfulVerification := CurrentDateTime;
             Rec.Modify(true);
+            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
         end else begin
             Rec.Init();
             Rec.AccountName := AccountName;
             Rec.LastSuccessfulVerification := CurrentDateTime;
             Rec.Insert(true);
+            Message('Record inserted. Variables: Rec.AccountName=' + Rec.AccountName + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
         end;
     end;
 
@@ -253,6 +271,8 @@ codeunit 7767 "AOAI Authorization"
         MessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated within 2 weeks if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
         CustomDimensions: Dictionary of [Text, Text];
     begin
+        Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
+
         CustomDimensions.Add('AccountName', AccountName);
         CustomDimensions.Add('VerificationDate', Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
 
@@ -264,5 +284,6 @@ codeunit 7767 "AOAI Authorization"
             Enum::"AL Telemetry Scope"::All,
             CustomDimensions
         );
+        Message('Telemetry logged successfully. CustomDimensions: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
     end;
 }
\ No newline at end of file

From b6d084831d644123c72824dbbf8af989bdc206b1 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 15 Jan 2025 18:58:23 +0100
Subject: [PATCH 14/30] Added obsolete to old SetManagedResourceAuthorization,
 Removed NonDebuggable, Updated table name with spaces

---
 .../AOAIAccountVerificationLog.Table.al          |  2 +-
 .../Azure OpenAI/AOAIAuthorization.Codeunit.al   | 16 ++++++++--------
 .../AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al  |  1 +
 3 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al
index 3b4446b9f9..b1e3d4d38b 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
@@ -1,6 +1,6 @@
 namespace System.AI;
 
-table 7767 "AOAIAccountVerificationLog"
+table 7767 "AOAI Account Verification Log"
 {
     Caption = 'AOAI Account Verification Log';
     Access = Internal;
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 30d89c57e7..4075f67b47 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -5,6 +5,7 @@
 namespace System.AI;
 
 using System;
+
 using System.Telemetry;
 /// <summary>
 /// Store the authorization information for the AOAI service.
@@ -14,7 +15,7 @@ codeunit 7767 "AOAI Authorization"
     Access = Internal;
     InherentEntitlements = X;
     InherentPermissions = X;
-    Permissions = tabledata AOAIAccountVerificationLog = RIMD;
+    Permissions = tabledata "AOAI Account Verification Log" = RIMD;
 
     var
         [NonDebuggable]
@@ -26,7 +27,6 @@ codeunit 7767 "AOAI Authorization"
         [NonDebuggable]
         ManagedResourceDeployment: Text;
         ResourceUtilization: Enum "AOAI Resource Utilization";
-        [NonDebuggable]
         FirstPartyAuthorization: Boolean;
         SelfManagedAuthorization: Boolean;
         MicrosoftManagedAuthorization: Boolean;
@@ -71,7 +71,7 @@ codeunit 7767 "AOAI Authorization"
         IsVerified: Boolean;
     begin
         ClearVariables();
-        IsVerified := VerifyAOAIAccount(AOAIAccountName, NewApiKey.Unwrap());
+        IsVerified := VerifyAOAIAccount(AOAIAccountName, NewApiKey);
 
         if IsVerified then begin
             ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
@@ -144,7 +144,7 @@ codeunit 7767 "AOAI Authorization"
     end;
 
     [NonDebuggable]
-    local procedure PerformAOAIAccountVerification(AOAIAccountName: Text; NewApiKey: Text): Boolean
+    local procedure PerformAOAIAccountVerification(AOAIAccountName: Text; NewApiKey: SecretText): Boolean
     var
         HttpClient: HttpClient;
         HttpRequestMessage: HttpRequestMessage;
@@ -178,7 +178,7 @@ codeunit 7767 "AOAI Authorization"
         exit(true);
     end;
 
-    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: Text): Boolean
+    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: SecretText): Boolean
     var
         Notif: Notification;
         IsVerified: Boolean;
@@ -186,7 +186,7 @@ codeunit 7767 "AOAI Authorization"
         CachePeriod: Duration;
         TruncatedAccountName: Text[100];
     begin
-        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AOAIAccountName + ', NewApiKey=' + NewApiKey);
+        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AOAIAccountName);
 
         GracePeriod := 15 * 60 * 1000;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
         CachePeriod := 1 * 60 * 1000;//24 * 60 * 60 * 1000; // 1 day in milliseconds
@@ -222,7 +222,7 @@ codeunit 7767 "AOAI Authorization"
 
     local procedure IsAccountVerifiedWithinPeriod(AccountName: Text[100]; Period: Duration): Boolean
     var
-        Rec: Record "AOAIAccountVerificationLog";
+        Rec: Record "AOAI Account Verification Log";
         IsVerified: Boolean;
     begin
         Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + Format(Period, 0, '<Duration>'));
@@ -240,7 +240,7 @@ codeunit 7767 "AOAI Authorization"
 
     local procedure SaveVerificationTime(AccountName: Text[100])
     var
-        Rec: Record "AOAIAccountVerificationLog";
+        Rec: Record "AOAI Account Verification Log";
     begin
         Message('Starting SaveVerificationTime procedure. Variables: AccountName=' + AccountName);
         if Rec.Get(AccountName) then begin
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al
index 9ce398c680..0b24397137 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
@@ -100,6 +100,7 @@ codeunit 7771 "Azure OpenAI"
     /// Deployment would look like: gpt-35-turbo-16k
     /// </remarks>
     [NonDebuggable]
+    [Obsolete('Using Managed AI resources now requires different input parameters. Use the other overload for SetManagedResourceAuthorization instead.', '26.0')]
     procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
     begin
         AzureOpenAIImpl.SetManagedResourceAuthorization(ModelType, Endpoint, Deployment, ApiKey, ManagedResourceDeployment);

From 91c85c75079b1afef97f8fb7c771a3f9daa2b620 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 16 Jan 2025 17:55:10 +0100
Subject: [PATCH 15/30] Updated debugging messages to handle duration

---
 .../AOAIAuthorization.Codeunit.al             | 30 +++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 4075f67b47..e982f7fd47 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -178,6 +178,31 @@ codeunit 7767 "AOAI Authorization"
         exit(true);
     end;
 
+    local procedure FormatDurationAsString(DurationValue: Duration): Text
+    var
+        Hours: Integer;
+        Minutes: Integer;
+        Seconds: Integer;
+        Milliseconds: Integer;
+    begin
+        // Convert milliseconds into hours, minutes, seconds
+        Hours := DurationValue div (60 * 60 * 1000);
+        DurationValue := DurationValue mod (60 * 60 * 1000);
+
+        Minutes := DurationValue div (60 * 1000);
+        DurationValue := DurationValue mod (60 * 1000);
+
+        Seconds := DurationValue div 1000;
+        Milliseconds := DurationValue mod 1000;
+
+        // Format as HH:MM:SS.mmm
+        exit(StrSubstNo('%1:%2:%3.%4',
+            Format(Hours, 2, '<Sign><Integer,2>'),
+            Format(Minutes, 2, '<Sign><Integer,2>'),
+            Format(Seconds, 2, '<Sign><Integer,2>'),
+            Format(Milliseconds, 3, '<Sign><Integer,3>')));
+    end;
+
     local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: SecretText): Boolean
     var
         Notif: Notification;
@@ -193,7 +218,7 @@ codeunit 7767 "AOAI Authorization"
 
         TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
 
-        Message('Variables: GracePeriod=' + Format(GracePeriod, 0, '<Duration>') + ', CachePeriod=' + Format(CachePeriod, 0, '<Duration>') + ', TruncatedAccountName=' + TruncatedAccountName);
+        Message('Variables: GracePeriod=' + FormatDurationAsString(GracePeriod) + ', CachePeriod=' + FormatDurationAsString(CachePeriod) + ', TruncatedAccountName=' + TruncatedAccountName);
 
         if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then begin
             Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification skipped (within cache period).');
@@ -225,7 +250,8 @@ codeunit 7767 "AOAI Authorization"
         Rec: Record "AOAI Account Verification Log";
         IsVerified: Boolean;
     begin
-        Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + Format(Period, 0, '<Duration>'));
+        ;
+        Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + FormatDurationAsString(Period));
 
         if Rec.Get(AccountName) then begin
             Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime, 0, '<DateTime>') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));

From 8d03c7f0153999734e3b2cb097cb712c3c727931 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 17 Jan 2025 13:45:07 +0100
Subject: [PATCH 16/30] fixed errors in debugging formatting

---
 .../Azure OpenAI/AOAIAuthorization.Codeunit.al   | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index e982f7fd47..4540a6a49e 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -227,7 +227,7 @@ codeunit 7767 "AOAI Authorization"
 
         IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
 
-        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(IsVerified, 0, '<Boolean>'));
+        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(IsVerified));
 
         // Handle failed verification
         if not IsVerified then begin
@@ -254,9 +254,9 @@ codeunit 7767 "AOAI Authorization"
         Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + FormatDurationAsString(Period));
 
         if Rec.Get(AccountName) then begin
-            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime, 0, '<DateTime>') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
+            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime) + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
             IsVerified := CurrentDateTime - Rec.LastSuccessfulVerification <= Period;
-            Message('Verification result: ' + Format(IsVerified, 0, '<Boolean>'));
+            Message('Verification result: ' + Format(IsVerified));
             exit(IsVerified);
         end;
 
@@ -272,13 +272,13 @@ codeunit 7767 "AOAI Authorization"
         if Rec.Get(AccountName) then begin
             Rec.LastSuccessfulVerification := CurrentDateTime;
             Rec.Modify(true);
-            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
+            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
         end else begin
             Rec.Init();
             Rec.AccountName := AccountName;
             Rec.LastSuccessfulVerification := CurrentDateTime;
             Rec.Insert(true);
-            Message('Record inserted. Variables: Rec.AccountName=' + Rec.AccountName + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '<DateTime>'));
+            Message('Record inserted. Variables: Rec.AccountName=' + Rec.AccountName + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
         end;
     end;
 
@@ -297,10 +297,10 @@ codeunit 7767 "AOAI Authorization"
         MessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated within 2 weeks if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
         CustomDimensions: Dictionary of [Text, Text];
     begin
-        Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
+        Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
 
         CustomDimensions.Add('AccountName', AccountName);
-        CustomDimensions.Add('VerificationDate', Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
+        CustomDimensions.Add('VerificationDate', Format(VerificationDate));
 
         Telemetry.LogMessage(
             '0000AA1', // Event ID
@@ -310,6 +310,6 @@ codeunit 7767 "AOAI Authorization"
             Enum::"AL Telemetry Scope"::All,
             CustomDimensions
         );
-        Message('Telemetry logged successfully. CustomDimensions: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate, 0, '<Year4>-<Month,2>-<Day,2>'));
+        Message('Telemetry logged successfully. CustomDimensions: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
     end;
 }
\ No newline at end of file

From b7750b2bb1948d9701eac0a2af4f6d2a72b3e89a Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 22 Jan 2025 16:57:15 +0100
Subject: [PATCH 17/30] Updated quality and functionality of logging messages
 and user notifications, fixed minor issues with record fetching and saving

---
 .../AOAIAuthorization.Codeunit.al             | 153 +++++++++++-------
 1 file changed, 99 insertions(+), 54 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 4540a6a49e..06bd5ed9d4 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -26,10 +26,13 @@ codeunit 7767 "AOAI Authorization"
         ApiKey: SecretText;
         [NonDebuggable]
         ManagedResourceDeployment: Text;
+        [NonDebuggable]
+        AOAIAccountName: Text;
         ResourceUtilization: Enum "AOAI Resource Utilization";
         FirstPartyAuthorization: Boolean;
         SelfManagedAuthorization: Boolean;
-        MicrosoftManagedAuthorization: Boolean;
+        MicrosoftManagedAuthorizationWithDeployment: Boolean;
+        MicrosoftManagedAuthorizationWithAOAIAccount: Boolean;
 
     [NonDebuggable]
     procedure IsConfigured(CallerModule: ModuleInfo): Boolean
@@ -37,6 +40,7 @@ codeunit 7767 "AOAI Authorization"
         AzureOpenAiImpl: Codeunit "Azure OpenAI Impl";
         CurrentModule: ModuleInfo;
         ALCopilotFunctions: DotNet ALCopilotFunctions;
+        AOAIAccountIsVerified: Boolean;
     begin
         NavApp.GetCurrentModuleInfo(CurrentModule);
 
@@ -46,9 +50,14 @@ codeunit 7767 "AOAI Authorization"
             Enum::"AOAI Resource Utilization"::"Self-Managed":
                 exit(SelfManagedAuthorization);
             Enum::"AOAI Resource Utilization"::"Microsoft Managed":
-                exit(MicrosoftManagedAuthorization and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
+                if MicrosoftManagedAuthorizationWithAOAIAccount then begin
+                    AOAIAccountIsVerified := VerifyAOAIAccount(AOAIAccountName, ApiKey);
+                    exit(AOAIAccountIsVerified and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
+                end
+                else
+                    if MicrosoftManagedAuthorizationWithDeployment then
+                        exit(AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
         end;
-
         exit(false);
     end;
 
@@ -62,22 +71,19 @@ codeunit 7767 "AOAI Authorization"
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
         ManagedResourceDeployment := NewManagedResourceDeployment;
-        MicrosoftManagedAuthorization := true;
+        MicrosoftManagedAuthorizationWithDeployment := true;
     end;
 
     [NonDebuggable]
-    procedure SetMicrosoftManagedAuthorization(AOAIAccountName: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
-    var
-        IsVerified: Boolean;
+    procedure SetMicrosoftManagedAuthorization(NewAOAIAccountName: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
     begin
         ClearVariables();
-        IsVerified := VerifyAOAIAccount(AOAIAccountName, NewApiKey);
 
-        if IsVerified then begin
-            ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
-            ManagedResourceDeployment := NewManagedResourceDeployment;
-            MicrosoftManagedAuthorization := true;
-        end;
+        ResourceUtilization := Enum::"AOAI Resource Utilization"::"Microsoft Managed";
+        AOAIAccountName := NewAOAIAccountName;
+        ApiKey := NewApiKey;
+        ManagedResourceDeployment := NewManagedResourceDeployment;
+        MicrosoftManagedAuthorizationWithAOAIAccount := true;
     end;
 
     [NonDebuggable]
@@ -136,15 +142,17 @@ codeunit 7767 "AOAI Authorization"
         Clear(Endpoint);
         Clear(ApiKey);
         Clear(Deployment);
+        Clear(AOAIAccountName);
         Clear(ManagedResourceDeployment);
         Clear(ResourceUtilization);
         Clear(FirstPartyAuthorization);
         clear(SelfManagedAuthorization);
-        Clear(MicrosoftManagedAuthorization);
+        Clear(MicrosoftManagedAuthorizationWithDeployment);
+        Clear(MicrosoftManagedAuthorizationWithAOAIAccount);
     end;
 
     [NonDebuggable]
-    local procedure PerformAOAIAccountVerification(AOAIAccountName: Text; NewApiKey: SecretText): Boolean
+    local procedure PerformAOAIAccountVerification(AOAIAccountNameToVerify: Text; NewApiKey: SecretText): Boolean
     var
         HttpClient: HttpClient;
         HttpRequestMessage: HttpRequestMessage;
@@ -155,7 +163,7 @@ codeunit 7767 "AOAI Authorization"
         IsSuccessful: Boolean;
         UrlFormatTxt: Label 'https://%1.openai.azure.com/openai/models?api-version=2024-06-01', Locked = true;
     begin
-        Url := StrSubstNo(UrlFormatTxt, AOAIAccountName);
+        Url := StrSubstNo(UrlFormatTxt, AOAIAccountNameToVerify);
 
         HttpContent.GetHeaders(ContentHeaders);
         if ContentHeaders.Contains('Content-Type') then
@@ -178,6 +186,7 @@ codeunit 7767 "AOAI Authorization"
         exit(true);
     end;
 
+    // FOR DEBUGGING ONLY
     local procedure FormatDurationAsString(DurationValue: Duration): Text
     var
         Hours: Integer;
@@ -203,43 +212,60 @@ codeunit 7767 "AOAI Authorization"
             Format(Milliseconds, 3, '<Sign><Integer,3>')));
     end;
 
-    local procedure VerifyAOAIAccount(AOAIAccountName: Text; NewApiKey: SecretText): Boolean
+    local procedure VerifyAOAIAccount(AccountName: Text; NewApiKey: SecretText): Boolean
     var
-        Notif: Notification;
-        IsVerified: Boolean;
+        VerificationLog: Record "AOAI Account Verification Log";
+        AccountVerified: Boolean;
         GracePeriod: Duration;
         CachePeriod: Duration;
         TruncatedAccountName: Text[100];
-    begin
-        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AOAIAccountName);
+        IsWithinCachePeriod: Boolean;
+        RemainingGracePeriod: Duration;
+        AuthFailedWithinGracePeriodLogMessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated in %3 if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name, %2 is the date where verification has taken place, and %3 is the remaining time until the grace period expires';
+        AuthFailedOutsideGracePeriodLogMessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The grace period has been exceeded and the connection has been terminated', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
+        AuthFailedWithinGracePeriodUserNotificationLbl: Label 'Azure Open AI authorization failed. AI functionality will be disabled in %1. Please contact your system administrator or the extension developer for assistance.', Comment = 'User notification explaining that AI functionality will be disabled soon, where %1 is the remaining time until the grace period expires';
+        AuthFailedOutsideGracePeriodUserNotificationLbl: Label 'Azure Open AI authorization failed and the AI functionality has been disabled. Please contact your system administrator or the extension developer for assistance.', Comment = 'User notification explaining that AI functionality has been disabled';
 
+    begin
+        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AccountName);
         GracePeriod := 15 * 60 * 1000;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
         CachePeriod := 1 * 60 * 1000;//24 * 60 * 60 * 1000; // 1 day in milliseconds
-
-        TruncatedAccountName := CopyStr(AOAIAccountName, 1, 100);
-
+        TruncatedAccountName := CopyStr(DelChr(AccountName, '<>', ' '), 1, 100);
         Message('Variables: GracePeriod=' + FormatDurationAsString(GracePeriod) + ', CachePeriod=' + FormatDurationAsString(CachePeriod) + ', TruncatedAccountName=' + TruncatedAccountName);
 
-        if IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod) then begin
+        // Within CACHE period
+        IsWithinCachePeriod := IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod);
+        if IsWithinCachePeriod then begin
             Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification skipped (within cache period).');
             exit(true);
         end;
 
-        IsVerified := PerformAOAIAccountVerification(AOAIAccountName, NewApiKey);
+        AccountVerified := PerformAOAIAccountVerification(AccountName, NewApiKey);
+        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(AccountVerified));
 
-        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(IsVerified));
+        if not AccountVerified then begin
+            // Calculate remaining grace period
+            if VerificationLog.Get(TruncatedAccountName) then
+                RemainingGracePeriod := GracePeriod - (CurrentDateTime - VerificationLog.LastSuccessfulVerification)
+            else
+                RemainingGracePeriod := GracePeriod;
 
-        // Handle failed verification
-        if not IsVerified then begin
-            SendNotification(Notif);
-            LogTelemetry(AOAIAccountName, Today);
+            // Within GRACE period
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
+                ShowUserNotification(StrSubstNo(AuthFailedWithinGracePeriodUserNotificationLbl, FormatDurationAsDays(RemainingGracePeriod)));
+                LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedWithinGracePeriodLogMessageLbl, AccountName, Today, FormatDurationAsDays(RemainingGracePeriod)));
                 Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, but account is still valid (within grace period).');
                 exit(true); // Verified if within grace period
+            end
+            // Outside GRACE period
+            else begin
+                ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
+                LogTelemetry(AccountName, Today, AuthFailedOutsideGracePeriodLogMessageLbl);
+                Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, and account is no longer valid (grace period expired).');
+                exit(false); // Failed verification if grace period has been exceeded
             end;
-            Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, and account is no longer valid (grace period expired).');
-            exit(false); // Failed verification if grace period has been exceeded
         end;
+
         SaveVerificationTime(TruncatedAccountName);
         Message('Function SaveVerificationTime called. Verification successful. Record saved.');
         exit(true);
@@ -247,15 +273,14 @@ codeunit 7767 "AOAI Authorization"
 
     local procedure IsAccountVerifiedWithinPeriod(AccountName: Text[100]; Period: Duration): Boolean
     var
-        Rec: Record "AOAI Account Verification Log";
+        VerificationLog: Record "AOAI Account Verification Log";
         IsVerified: Boolean;
     begin
-        ;
         Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + FormatDurationAsString(Period));
 
-        if Rec.Get(AccountName) then begin
-            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime) + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
-            IsVerified := CurrentDateTime - Rec.LastSuccessfulVerification <= Period;
+        if VerificationLog.Get(AccountName) then begin
+            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime) + ', Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification));
+            IsVerified := CurrentDateTime - VerificationLog.LastSuccessfulVerification <= Period;
             Message('Verification result: ' + Format(IsVerified));
             exit(IsVerified);
         end;
@@ -266,50 +291,70 @@ codeunit 7767 "AOAI Authorization"
 
     local procedure SaveVerificationTime(AccountName: Text[100])
     var
-        Rec: Record "AOAI Account Verification Log";
+        VerificationLog: Record "AOAI Account Verification Log";
     begin
+
         Message('Starting SaveVerificationTime procedure. Variables: AccountName=' + AccountName);
-        if Rec.Get(AccountName) then begin
-            Rec.LastSuccessfulVerification := CurrentDateTime;
-            Rec.Modify(true);
-            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
+        if VerificationLog.Get(AccountName) then begin
+            VerificationLog.LastSuccessfulVerification := CurrentDateTime;
+            VerificationLog.Modify();
+            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification));
         end else begin
-            Rec.Init();
-            Rec.AccountName := AccountName;
-            Rec.LastSuccessfulVerification := CurrentDateTime;
-            Rec.Insert(true);
-            Message('Record inserted. Variables: Rec.AccountName=' + Rec.AccountName + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification));
+            VerificationLog.Init();
+            VerificationLog.AccountName := AccountName;
+            VerificationLog.LastSuccessfulVerification := CurrentDateTime;
+            if VerificationLog.Insert() then
+                Message('Record inserted. Variables: Rec.AccountName=' + VerificationLog.AccountName + ', Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification))
         end;
     end;
 
-    local procedure SendNotification(var Notif: Notification)
+    local procedure ShowUserNotification(Message: Text)
     var
-        MessageLbl: Label 'Azure Open AI authorization failed. AI functionality will be disabled within 2 weeks. Please contact your system administrator or the extension developer for assistance.';
+        Notif: Notification;
     begin
-        Notif.Message := MessageLbl;
+        Notif.Message := Message;
         Notif.Scope := NotificationScope::LocalScope;
         Notif.Send();
     end;
 
-    local procedure LogTelemetry(AccountName: Text; VerificationDate: Date)
+    local procedure LogTelemetry(AccountName: Text; VerificationDate: Date; LogMessage: Text)
     var
         Telemetry: Codeunit Telemetry;
-        MessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated within 2 weeks if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
         CustomDimensions: Dictionary of [Text, Text];
     begin
         Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
 
+        // Add default dimensions
         CustomDimensions.Add('AccountName', AccountName);
         CustomDimensions.Add('VerificationDate', Format(VerificationDate));
 
+        // Log the telemetry with the custom message
         Telemetry.LogMessage(
             '0000AA1', // Event ID
-            StrSubstNo(MessageLbl, AccountName, VerificationDate), // Message
+            StrSubstNo(LogMessage, AccountName, VerificationDate),
             Verbosity::Warning,
             DataClassification::SystemMetadata,
             Enum::"AL Telemetry Scope"::All,
             CustomDimensions
         );
+
         Message('Telemetry logged successfully. CustomDimensions: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
     end;
+
+    local procedure FormatDurationAsDays(DurationValue: Duration): Text
+    var
+        Days: Decimal;
+        Hours: Decimal;
+        DaysLabelLbl: Label '%1 days', Comment = '%1 is the number of days';
+        HoursLabelLbl: Label '%1 hours', Comment = '%1 is the number of hours';
+    begin
+        // Convert milliseconds into days and hours
+        Days := DurationValue / (24 * 60 * 60 * 1000); // Total days
+        Hours := (DurationValue mod (24 * 60 * 60 * 1000)) / (60 * 60 * 1000); // Remaining hours
+
+        if Days >= 1 then
+            exit(StrSubstNo(DaysLabelLbl, Format(Days, 0, 9))) // Display days if more than 1 day
+        else
+            exit(StrSubstNo(HoursLabelLbl, Format(Hours, 0, 9))); // Display hours if less than 1 day
+    end;
 }
\ No newline at end of file

From 69ae41a409f6cb212a56a8a97113ea1bfc8f7294 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 14:55:06 +0100
Subject: [PATCH 18/30] Notification text improved. Logic regarding grace
 period updated. Debugging features removed.

---
 .../AOAIAuthorization.Codeunit.al             | 86 ++++---------------
 1 file changed, 18 insertions(+), 68 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 06bd5ed9d4..6e47ca6994 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -186,32 +186,6 @@ codeunit 7767 "AOAI Authorization"
         exit(true);
     end;
 
-    // FOR DEBUGGING ONLY
-    local procedure FormatDurationAsString(DurationValue: Duration): Text
-    var
-        Hours: Integer;
-        Minutes: Integer;
-        Seconds: Integer;
-        Milliseconds: Integer;
-    begin
-        // Convert milliseconds into hours, minutes, seconds
-        Hours := DurationValue div (60 * 60 * 1000);
-        DurationValue := DurationValue mod (60 * 60 * 1000);
-
-        Minutes := DurationValue div (60 * 1000);
-        DurationValue := DurationValue mod (60 * 1000);
-
-        Seconds := DurationValue div 1000;
-        Milliseconds := DurationValue mod 1000;
-
-        // Format as HH:MM:SS.mmm
-        exit(StrSubstNo('%1:%2:%3.%4',
-            Format(Hours, 2, '<Sign><Integer,2>'),
-            Format(Minutes, 2, '<Sign><Integer,2>'),
-            Format(Seconds, 2, '<Sign><Integer,2>'),
-            Format(Milliseconds, 3, '<Sign><Integer,3>')));
-    end;
-
     local procedure VerifyAOAIAccount(AccountName: Text; NewApiKey: SecretText): Boolean
     var
         VerificationLog: Record "AOAI Account Verification Log";
@@ -221,30 +195,25 @@ codeunit 7767 "AOAI Authorization"
         TruncatedAccountName: Text[100];
         IsWithinCachePeriod: Boolean;
         RemainingGracePeriod: Duration;
-        AuthFailedWithinGracePeriodLogMessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated in %3 if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name, %2 is the date where verification has taken place, and %3 is the remaining time until the grace period expires';
+        AuthFailedWithinGracePeriodLogMessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The connection will be terminated within %3 if not rectified', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name, %2 is the date where verification has taken place, and %3 is the remaining time until the grace period expires';
         AuthFailedOutsideGracePeriodLogMessageLbl: Label 'Azure Open AI authorization failed for account %1 on %2 because it is not authorized to access AI services. The grace period has been exceeded and the connection has been terminated', Comment = 'Telemetry message where %1 is the name of the Azure Open AI account name and %2 is the date where verification has taken place';
-        AuthFailedWithinGracePeriodUserNotificationLbl: Label 'Azure Open AI authorization failed. AI functionality will be disabled in %1. Please contact your system administrator or the extension developer for assistance.', Comment = 'User notification explaining that AI functionality will be disabled soon, where %1 is the remaining time until the grace period expires';
+        AuthFailedWithinGracePeriodUserNotificationLbl: Label 'Azure Open AI authorization failed. AI functionality will be disabled within %1. Please contact your system administrator or the extension developer for assistance.', Comment = 'User notification explaining that AI functionality will be disabled soon, where %1 is the remaining time until the grace period expires';
         AuthFailedOutsideGracePeriodUserNotificationLbl: Label 'Azure Open AI authorization failed and the AI functionality has been disabled. Please contact your system administrator or the extension developer for assistance.', Comment = 'User notification explaining that AI functionality has been disabled';
 
     begin
-        Message('Starting VerifyAOAIAccount procedure. Variables: AOAIAccountName=' + AccountName);
-        GracePeriod := 15 * 60 * 1000;//14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
-        CachePeriod := 1 * 60 * 1000;//24 * 60 * 60 * 1000; // 1 day in milliseconds
+        GracePeriod := 14 * 24 * 60 * 60 * 1000; // 2 weeks in milliseconds
+        CachePeriod := 24 * 60 * 60 * 1000; // 1 day in milliseconds
         TruncatedAccountName := CopyStr(DelChr(AccountName, '<>', ' '), 1, 100);
-        Message('Variables: GracePeriod=' + FormatDurationAsString(GracePeriod) + ', CachePeriod=' + FormatDurationAsString(CachePeriod) + ', TruncatedAccountName=' + TruncatedAccountName);
 
-        // Within CACHE period
         IsWithinCachePeriod := IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod);
-        if IsWithinCachePeriod then begin
-            Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification skipped (within cache period).');
+        // Within CACHE period
+        if IsWithinCachePeriod then
             exit(true);
-        end;
 
+        // Outside CACHE period
         AccountVerified := PerformAOAIAccountVerification(AccountName, NewApiKey);
-        Message('Function PerformAOAIAccountVerification called. Result: IsVerified=' + Format(AccountVerified));
 
         if not AccountVerified then begin
-            // Calculate remaining grace period
             if VerificationLog.Get(TruncatedAccountName) then
                 RemainingGracePeriod := GracePeriod - (CurrentDateTime - VerificationLog.LastSuccessfulVerification)
             else
@@ -254,20 +223,17 @@ codeunit 7767 "AOAI Authorization"
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
                 ShowUserNotification(StrSubstNo(AuthFailedWithinGracePeriodUserNotificationLbl, FormatDurationAsDays(RemainingGracePeriod)));
                 LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedWithinGracePeriodLogMessageLbl, AccountName, Today, FormatDurationAsDays(RemainingGracePeriod)));
-                Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, but account is still valid (within grace period).');
-                exit(true); // Verified if within grace period
+                exit(true);
             end
             // Outside GRACE period
             else begin
                 ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
                 LogTelemetry(AccountName, Today, AuthFailedOutsideGracePeriodLogMessageLbl);
-                Message('Function IsAccountVerifiedWithinPeriod called. Result: Verification failed, and account is no longer valid (grace period expired).');
-                exit(false); // Failed verification if grace period has been exceeded
+                exit(false);
             end;
         end;
 
         SaveVerificationTime(TruncatedAccountName);
-        Message('Function SaveVerificationTime called. Verification successful. Record saved.');
         exit(true);
     end;
 
@@ -276,16 +242,11 @@ codeunit 7767 "AOAI Authorization"
         VerificationLog: Record "AOAI Account Verification Log";
         IsVerified: Boolean;
     begin
-        Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + FormatDurationAsString(Period));
-
         if VerificationLog.Get(AccountName) then begin
-            Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime) + ', Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification));
             IsVerified := CurrentDateTime - VerificationLog.LastSuccessfulVerification <= Period;
-            Message('Verification result: ' + Format(IsVerified));
             exit(IsVerified);
         end;
 
-        Message('Record not found. Exiting with false.');
         exit(false);
     end;
 
@@ -293,18 +254,14 @@ codeunit 7767 "AOAI Authorization"
     var
         VerificationLog: Record "AOAI Account Verification Log";
     begin
-
-        Message('Starting SaveVerificationTime procedure. Variables: AccountName=' + AccountName);
         if VerificationLog.Get(AccountName) then begin
             VerificationLog.LastSuccessfulVerification := CurrentDateTime;
             VerificationLog.Modify();
-            Message('Record updated. Variables: Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification));
         end else begin
             VerificationLog.Init();
             VerificationLog.AccountName := AccountName;
             VerificationLog.LastSuccessfulVerification := CurrentDateTime;
-            if VerificationLog.Insert() then
-                Message('Record inserted. Variables: Rec.AccountName=' + VerificationLog.AccountName + ', Rec.LastSuccessfulVerification=' + Format(VerificationLog.LastSuccessfulVerification))
+            VerificationLog.Insert()
         end;
     end;
 
@@ -322,13 +279,9 @@ codeunit 7767 "AOAI Authorization"
         Telemetry: Codeunit Telemetry;
         CustomDimensions: Dictionary of [Text, Text];
     begin
-        Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
-
-        // Add default dimensions
         CustomDimensions.Add('AccountName', AccountName);
         CustomDimensions.Add('VerificationDate', Format(VerificationDate));
 
-        // Log the telemetry with the custom message
         Telemetry.LogMessage(
             '0000AA1', // Event ID
             StrSubstNo(LogMessage, AccountName, VerificationDate),
@@ -337,24 +290,21 @@ codeunit 7767 "AOAI Authorization"
             Enum::"AL Telemetry Scope"::All,
             CustomDimensions
         );
-
-        Message('Telemetry logged successfully. CustomDimensions: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate));
     end;
 
     local procedure FormatDurationAsDays(DurationValue: Duration): Text
     var
         Days: Decimal;
-        Hours: Decimal;
-        DaysLabelLbl: Label '%1 days', Comment = '%1 is the number of days';
-        HoursLabelLbl: Label '%1 hours', Comment = '%1 is the number of hours';
+        DaysLabelLbl: Label '%1 days', Comment = 'Days in plural. %1 is the number of days';
+        DayLabelLbl: Label '1 day', Comment = 'A single day';
     begin
-        // Convert milliseconds into days and hours
-        Days := DurationValue / (24 * 60 * 60 * 1000); // Total days
-        Hours := (DurationValue mod (24 * 60 * 60 * 1000)) / (60 * 60 * 1000); // Remaining hours
+        Days := DurationValue / (24 * 60 * 60 * 1000);
 
-        if Days >= 1 then
-            exit(StrSubstNo(DaysLabelLbl, Format(Days, 0, 9))) // Display days if more than 1 day
+        if Days <= 1 then
+            exit(DayLabelLbl)
         else
-            exit(StrSubstNo(HoursLabelLbl, Format(Hours, 0, 9))); // Display hours if less than 1 day
+            // Round up to the nearest whole day
+            Days := Round(Days, 1, '>');
+        exit(StrSubstNo(DaysLabelLbl, Format(Days, 0, 0)));
     end;
 }
\ No newline at end of file

From a1bf0725e3ed6234c6bea1a40de3429b90af3894 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 14:57:43 +0100
Subject: [PATCH 19/30] Variables sorted alphabetically

---
 .../AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al
index b1e3d4d38b..866b226e38 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
@@ -2,12 +2,12 @@ namespace System.AI;
 
 table 7767 "AOAI Account Verification Log"
 {
-    Caption = 'AOAI Account Verification Log';
     Access = Internal;
+    Caption = 'AOAI Account Verification Log';
+    DataPerCompany = false;
     Extensible = false;
     InherentEntitlements = RIMDX;
     InherentPermissions = X;
-    DataPerCompany = false;
     ReplicateData = false;
 
     fields

From 41f99e65fb5eaf59dd9fe8405a1576b04c904acf Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 15:00:11 +0100
Subject: [PATCH 20/30] field caption clarified

---
 .../App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al
index 866b226e38..e38e8380a4 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAccountVerificationLog.Table.al	
@@ -20,7 +20,7 @@ table 7767 "AOAI Account Verification Log"
 
         field(2; LastSuccessfulVerification; DateTime)
         {
-            Caption = 'Access Verified';
+            Caption = 'Time of last successful verification';
             DataClassification = SystemMetadata;
         }
     }

From 2df6e9a3fc8084cab94e0ebe86dd649b234496df Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 15:12:53 +0100
Subject: [PATCH 21/30] Description of parameters improved

---
 .../App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al
index 0b24397137..6a8bc4f1bf 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
@@ -111,8 +111,8 @@ codeunit 7771 "Azure OpenAI"
     /// This will send the Azure OpenAI call to the deployment specified in <paramref name="ManagedResourceDeployment"/>, and will use the other parameters to verify that you have access to Azure OpenAI.
     /// </summary>
     /// <param name="ModelType">The model type to set authorization for.</param>
-    /// <param name="AOAIAccountName">Azure OpenAI account name)</param>
-    /// <param name="ApiKey">The API key to use  to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls.</param>
+    /// <param name="AOAIAccountName">Name of the Azure Open AI resource like "MyAzureOpenAIResource"</param>
+    /// <param name="ApiKey">The API key to access the Azure Open AI resource. This is used only for verification of access, not for actual Azure Open AI calls.</param>
     /// <param name="ManagedResourceDeployment">The managed deployment to use for the model type.</param>
     [NonDebuggable]
     procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; AOAIAccountName: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)

From 1d2a7602f2624649b50238d2481b08e6a222be50 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 16:12:41 +0100
Subject: [PATCH 22/30] Added session telemetry

---
 .../AOAIAuthorization.Codeunit.al             | 20 ++++++++++++++-----
 1 file changed, 15 insertions(+), 5 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 6e47ca6994..53a72d4007 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -18,6 +18,7 @@ codeunit 7767 "AOAI Authorization"
     Permissions = tabledata "AOAI Account Verification Log" = RIMD;
 
     var
+        CopilotCapabilityImpl: Codeunit "Copilot Capability Impl";
         [NonDebuggable]
         Endpoint: Text;
         [NonDebuggable]
@@ -33,6 +34,11 @@ codeunit 7767 "AOAI Authorization"
         SelfManagedAuthorization: Boolean;
         MicrosoftManagedAuthorizationWithDeployment: Boolean;
         MicrosoftManagedAuthorizationWithAOAIAccount: Boolean;
+        TelemetryAOAIVerificationFailedTxt: Label 'Failed to authenticate account against Azure Open AI', Locked = true;
+        TelemetryAOAIVerificationSucceededTxt: Label 'Successfully authenticated account against Azure Open AI', Locked = true;
+        TelemetryAccessWithinCachePeriodTxt: Label 'Cached access to Azure Open AI was used', Locked = true;
+        TelemetryAccessTokenWithinGracePeriodTxt: Label 'Failed to authenticate against Azure Open AI but last successful authentication is within grace period. System still has access for %1', Locked = true;
+        TelemetryAccessTokenOutsideCachePeriodTxt: Label 'Failed to authenticate against Azure Open AI and last successful authentication is outside grace period. System no longer has access', Locked = true;
 
     [NonDebuggable]
     procedure IsConfigured(CallerModule: ModuleInfo): Boolean
@@ -177,12 +183,12 @@ codeunit 7767 "AOAI Authorization"
 
         IsSuccessful := HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
 
-        if not IsSuccessful then
-            exit(false);
-
-        if not HttpResponseMessage.IsSuccessStatusCode() then
+        if not IsSuccessful or not HttpResponseMessage.IsSuccessStatusCode() then begin
+            Session.LogMessage('', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(false);
+        end;
 
+        Session.LogMessage('', TelemetryAOAIVerificationSucceededTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
         exit(true);
     end;
 
@@ -207,8 +213,10 @@ codeunit 7767 "AOAI Authorization"
 
         IsWithinCachePeriod := IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod);
         // Within CACHE period
-        if IsWithinCachePeriod then
+        if IsWithinCachePeriod then begin
+            Session.LogMessage('', TelemetryAccessWithinCachePeriodTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(true);
+        end;
 
         // Outside CACHE period
         AccountVerified := PerformAOAIAccountVerification(AccountName, NewApiKey);
@@ -223,12 +231,14 @@ codeunit 7767 "AOAI Authorization"
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
                 ShowUserNotification(StrSubstNo(AuthFailedWithinGracePeriodUserNotificationLbl, FormatDurationAsDays(RemainingGracePeriod)));
                 LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedWithinGracePeriodLogMessageLbl, AccountName, Today, FormatDurationAsDays(RemainingGracePeriod)));
+                Session.LogMessage('', StrSubstNo(TelemetryAccessTokenWithinGracePeriodTxt, FormatDurationAsDays(RemainingGracePeriod)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
                 exit(true);
             end
             // Outside GRACE period
             else begin
                 ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
                 LogTelemetry(AccountName, Today, AuthFailedOutsideGracePeriodLogMessageLbl);
+                Session.LogMessage('', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
                 exit(false);
             end;
         end;

From 09e7ac798248c3a070f27e8d842d99342c75b487 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Thu, 23 Jan 2025 16:48:26 +0100
Subject: [PATCH 23/30] Added telemetry tags

---
 .../AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al  | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 53a72d4007..6e2950f938 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -184,11 +184,11 @@ codeunit 7767 "AOAI Authorization"
         IsSuccessful := HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
 
         if not IsSuccessful or not HttpResponseMessage.IsSuccessStatusCode() then begin
-            Session.LogMessage('', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OLQ', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 
-        Session.LogMessage('', TelemetryAOAIVerificationSucceededTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+        Session.LogMessage('0000OLR', TelemetryAOAIVerificationSucceededTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
         exit(true);
     end;
 
@@ -214,7 +214,7 @@ codeunit 7767 "AOAI Authorization"
         IsWithinCachePeriod := IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod);
         // Within CACHE period
         if IsWithinCachePeriod then begin
-            Session.LogMessage('', TelemetryAccessWithinCachePeriodTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OLS', TelemetryAccessWithinCachePeriodTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(true);
         end;
 
@@ -231,14 +231,14 @@ codeunit 7767 "AOAI Authorization"
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
                 ShowUserNotification(StrSubstNo(AuthFailedWithinGracePeriodUserNotificationLbl, FormatDurationAsDays(RemainingGracePeriod)));
                 LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedWithinGracePeriodLogMessageLbl, AccountName, Today, FormatDurationAsDays(RemainingGracePeriod)));
-                Session.LogMessage('', StrSubstNo(TelemetryAccessTokenWithinGracePeriodTxt, FormatDurationAsDays(RemainingGracePeriod)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+                Session.LogMessage('0000OLT', StrSubstNo(TelemetryAccessTokenWithinGracePeriodTxt, FormatDurationAsDays(RemainingGracePeriod)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
                 exit(true);
             end
             // Outside GRACE period
             else begin
                 ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
                 LogTelemetry(AccountName, Today, AuthFailedOutsideGracePeriodLogMessageLbl);
-                Session.LogMessage('', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+                Session.LogMessage('0000OLU', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
                 exit(false);
             end;
         end;

From 804705453d0556119afeff1077332a53afca110d Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 7 Feb 2025 17:51:49 +0100
Subject: [PATCH 24/30] Implemented suggestions from PR

---
 .../AOAIAuthorization.Codeunit.al             | 44 ++++++++-----------
 .../src/Azure OpenAI/AzureOpenAI.Codeunit.al  |  2 +
 .../Azure OpenAI/AzureOpenAIImpl.Codeunit.al  |  2 +
 3 files changed, 22 insertions(+), 26 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 6e2950f938..fbcd881f7d 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -30,10 +30,6 @@ codeunit 7767 "AOAI Authorization"
         [NonDebuggable]
         AOAIAccountName: Text;
         ResourceUtilization: Enum "AOAI Resource Utilization";
-        FirstPartyAuthorization: Boolean;
-        SelfManagedAuthorization: Boolean;
-        MicrosoftManagedAuthorizationWithDeployment: Boolean;
-        MicrosoftManagedAuthorizationWithAOAIAccount: Boolean;
         TelemetryAOAIVerificationFailedTxt: Label 'Failed to authenticate account against Azure Open AI', Locked = true;
         TelemetryAOAIVerificationSucceededTxt: Label 'Successfully authenticated account against Azure Open AI', Locked = true;
         TelemetryAccessWithinCachePeriodTxt: Label 'Cached access to Azure Open AI was used', Locked = true;
@@ -46,24 +42,26 @@ codeunit 7767 "AOAI Authorization"
         AzureOpenAiImpl: Codeunit "Azure OpenAI Impl";
         CurrentModule: ModuleInfo;
         ALCopilotFunctions: DotNet ALCopilotFunctions;
-        AOAIAccountIsVerified: Boolean;
     begin
         NavApp.GetCurrentModuleInfo(CurrentModule);
 
         case ResourceUtilization of
             Enum::"AOAI Resource Utilization"::"First Party":
-                exit(FirstPartyAuthorization and ALCopilotFunctions.IsPlatformAuthorizationConfigured(CallerModule.Publisher(), CurrentModule.Publisher()));
+                exit((ManagedResourceDeployment <> '') and ALCopilotFunctions.IsPlatformAuthorizationConfigured(CallerModule.Publisher(), CurrentModule.Publisher()));
             Enum::"AOAI Resource Utilization"::"Self-Managed":
-                exit(SelfManagedAuthorization);
+                exit((Deployment <> '') and (Endpoint <> '') and (not ApiKey.IsEmpty()));
             Enum::"AOAI Resource Utilization"::"Microsoft Managed":
-                if MicrosoftManagedAuthorizationWithAOAIAccount then begin
-                    AOAIAccountIsVerified := VerifyAOAIAccount(AOAIAccountName, ApiKey);
-                    exit(AOAIAccountIsVerified and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
-                end
+#if CLEAN26
+                if (AOAIAccountName <> '') and (ManagedResourceDeployment <> '') and (not ApiKey.IsEmpty()) then
+                    exit(VerifyAOAIAccount(AOAIAccountName, ApiKey) and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls())
+#else
+                if (AOAIAccountName <> '') and (ManagedResourceDeployment <> '') and (not ApiKey.IsEmpty()) then
+                    exit(VerifyAOAIAccount(AOAIAccountName, ApiKey) and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls())
                 else
-                    if MicrosoftManagedAuthorizationWithDeployment then
-                        exit(AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
+                    exit((Deployment <> '') and (Endpoint <> '') and (not ApiKey.IsEmpty()) and (ManagedResourceDeployment <> '') and AzureOpenAiImpl.IsTenantAllowlistedForFirstPartyCopilotCalls());
+#endif
         end;
+
         exit(false);
     end;
 
@@ -77,7 +75,6 @@ codeunit 7767 "AOAI Authorization"
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
         ManagedResourceDeployment := NewManagedResourceDeployment;
-        MicrosoftManagedAuthorizationWithDeployment := true;
     end;
 
     [NonDebuggable]
@@ -89,9 +86,9 @@ codeunit 7767 "AOAI Authorization"
         AOAIAccountName := NewAOAIAccountName;
         ApiKey := NewApiKey;
         ManagedResourceDeployment := NewManagedResourceDeployment;
-        MicrosoftManagedAuthorizationWithAOAIAccount := true;
     end;
 
+#if not CLEAN26
     [NonDebuggable]
     procedure SetSelfManagedAuthorization(NewEndpoint: Text; NewDeployment: Text; NewApiKey: SecretText)
     begin
@@ -101,8 +98,8 @@ codeunit 7767 "AOAI Authorization"
         Endpoint := NewEndpoint;
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
-        SelfManagedAuthorization := true;
     end;
+#endif
 
     [NonDebuggable]
     procedure SetFirstPartyAuthorization(NewDeployment: Text)
@@ -111,7 +108,6 @@ codeunit 7767 "AOAI Authorization"
 
         ResourceUtilization := Enum::"AOAI Resource Utilization"::"First Party";
         ManagedResourceDeployment := NewDeployment;
-        FirstPartyAuthorization := true;
     end;
 
     [NonDebuggable]
@@ -151,10 +147,6 @@ codeunit 7767 "AOAI Authorization"
         Clear(AOAIAccountName);
         Clear(ManagedResourceDeployment);
         Clear(ResourceUtilization);
-        Clear(FirstPartyAuthorization);
-        clear(SelfManagedAuthorization);
-        Clear(MicrosoftManagedAuthorizationWithDeployment);
-        Clear(MicrosoftManagedAuthorizationWithAOAIAccount);
     end;
 
     [NonDebuggable]
@@ -225,7 +217,7 @@ codeunit 7767 "AOAI Authorization"
             if VerificationLog.Get(TruncatedAccountName) then
                 RemainingGracePeriod := GracePeriod - (CurrentDateTime - VerificationLog.LastSuccessfulVerification)
             else
-                RemainingGracePeriod := GracePeriod;
+                exit(false);
 
             // Within GRACE period
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
@@ -237,7 +229,7 @@ codeunit 7767 "AOAI Authorization"
             // Outside GRACE period
             else begin
                 ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
-                LogTelemetry(AccountName, Today, AuthFailedOutsideGracePeriodLogMessageLbl);
+                LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedOutsideGracePeriodLogMessageLbl, AccountName, Today));
                 Session.LogMessage('0000OLU', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
                 exit(false);
             end;
@@ -284,7 +276,7 @@ codeunit 7767 "AOAI Authorization"
         Notif.Send();
     end;
 
-    local procedure LogTelemetry(AccountName: Text; VerificationDate: Date; LogMessage: Text)
+    local procedure LogTelemetry(AccountName: Text; VerificationDate: Date; FormattedLogMessage: Text)
     var
         Telemetry: Codeunit Telemetry;
         CustomDimensions: Dictionary of [Text, Text];
@@ -294,9 +286,9 @@ codeunit 7767 "AOAI Authorization"
 
         Telemetry.LogMessage(
             '0000AA1', // Event ID
-            StrSubstNo(LogMessage, AccountName, VerificationDate),
+            FormattedLogMessage,
             Verbosity::Warning,
-            DataClassification::SystemMetadata,
+            DataClassification::OrganizationIdentifiableInformation,
             Enum::"AL Telemetry Scope"::All,
             CustomDimensions
         );
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al
index 6a8bc4f1bf..e4c5dbb73b 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAI.Codeunit.al	
@@ -86,6 +86,7 @@ codeunit 7771 "Azure OpenAI"
         exit(AzureOpenAIImpl.IsInitialized(CopilotCapability, ModelType, CallerModuleInfo));
     end;
 
+#if not CLEAN26
     /// <summary>
     /// Sets the managed Azure OpenAI API authorization to use for a specific model type.
     /// This will send the Azure OpenAI call to the deployment specified in <paramref name="ManagedResourceDeployment"/>, and will use the other parameters to verify that you have access to Azure OpenAI.
@@ -105,6 +106,7 @@ codeunit 7771 "Azure OpenAI"
     begin
         AzureOpenAIImpl.SetManagedResourceAuthorization(ModelType, Endpoint, Deployment, ApiKey, ManagedResourceDeployment);
     end;
+#endif
 
     /// <summary>
     /// Sets the managed Azure OpenAI API authorization to use for a specific model type.
diff --git a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al
index 6d43985055..aa4397d99e 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AzureOpenAIImpl.Codeunit.al	
@@ -192,6 +192,7 @@ codeunit 7772 "Azure OpenAI Impl"
         end;
     end;
 
+#if not CLEAN26
     [NonDebuggable]
     procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; Endpoint: Text; Deployment: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)
     begin
@@ -206,6 +207,7 @@ codeunit 7772 "Azure OpenAI Impl"
                 Error(InvalidModelTypeErr);
         end;
     end;
+#endif
 
     [NonDebuggable]
     procedure SetManagedResourceAuthorization(ModelType: Enum "AOAI Model Type"; AOAIAccountName: Text; ApiKey: SecretText; ManagedResourceDeployment: Text)

From cb7580e46fb9ab3f0434fea62e212954f40d1056 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 12 Feb 2025 15:12:17 +0100
Subject: [PATCH 25/30] Clean tag moved to the correct procedure

---
 .../App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index fbcd881f7d..768481a193 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -65,6 +65,7 @@ codeunit 7767 "AOAI Authorization"
         exit(false);
     end;
 
+#if not CLEAN26
     [NonDebuggable]
     procedure SetMicrosoftManagedAuthorization(NewEndpoint: Text; NewDeployment: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
     begin
@@ -76,6 +77,7 @@ codeunit 7767 "AOAI Authorization"
         ApiKey := NewApiKey;
         ManagedResourceDeployment := NewManagedResourceDeployment;
     end;
+#endif
 
     [NonDebuggable]
     procedure SetMicrosoftManagedAuthorization(NewAOAIAccountName: Text; NewApiKey: SecretText; NewManagedResourceDeployment: Text)
@@ -88,7 +90,6 @@ codeunit 7767 "AOAI Authorization"
         ManagedResourceDeployment := NewManagedResourceDeployment;
     end;
 
-#if not CLEAN26
     [NonDebuggable]
     procedure SetSelfManagedAuthorization(NewEndpoint: Text; NewDeployment: Text; NewApiKey: SecretText)
     begin
@@ -99,7 +100,6 @@ codeunit 7767 "AOAI Authorization"
         Deployment := NewDeployment;
         ApiKey := NewApiKey;
     end;
-#endif
 
     [NonDebuggable]
     procedure SetFirstPartyAuthorization(NewDeployment: Text)

From 76ae32ecc43078f4a8a9e67762aa61e17fb42c4a Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 12 Feb 2025 16:12:27 +0100
Subject: [PATCH 26/30] Validates aoai account name and aoai url before account
 validation

---
 .../AOAIAuthorization.Codeunit.al             | 47 +++++++++++++++++--
 1 file changed, 44 insertions(+), 3 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 768481a193..41a5c7e56b 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -5,8 +5,9 @@
 namespace System.AI;
 
 using System;
-
 using System.Telemetry;
+using System.Utilities;
+
 /// <summary>
 /// Store the authorization information for the AOAI service.
 /// </summary>
@@ -30,6 +31,8 @@ codeunit 7767 "AOAI Authorization"
         [NonDebuggable]
         AOAIAccountName: Text;
         ResourceUtilization: Enum "AOAI Resource Utilization";
+        TelemetryInvalidAOAIAccountNameFormatTxt: Label 'Attempted use of invalid Azure Open AI account name', Locked = true;
+        TelemetryInvalidAOAIUrlTxt: Label 'Attempted call with invalid URL', Locked = true;
         TelemetryAOAIVerificationFailedTxt: Label 'Failed to authenticate account against Azure Open AI', Locked = true;
         TelemetryAOAIVerificationSucceededTxt: Label 'Successfully authenticated account against Azure Open AI', Locked = true;
         TelemetryAccessWithinCachePeriodTxt: Label 'Cached access to Azure Open AI was used', Locked = true;
@@ -160,21 +163,30 @@ codeunit 7767 "AOAI Authorization"
         Url: Text;
         IsSuccessful: Boolean;
         UrlFormatTxt: Label 'https://%1.openai.azure.com/openai/models?api-version=2024-06-01', Locked = true;
+        TrustedDomainTxt: Label 'openai.azure.com', Locked = true;
     begin
+        if not IsValidAOAIAccountName(AOAIAccountNameToVerify) then begin
+            Session.LogMessage('', TelemetryInvalidAOAIAccountNameFormatTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            exit(false);
+        end;
+
         Url := StrSubstNo(UrlFormatTxt, AOAIAccountNameToVerify);
 
+        if not IsValidUrl(Url, TrustedDomainTxt) then begin
+            Session.LogMessage('', TelemetryInvalidAOAIUrlTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            exit(false);
+        end;
+
         HttpContent.GetHeaders(ContentHeaders);
         if ContentHeaders.Contains('Content-Type') then
             ContentHeaders.Remove('Content-Type');
         ContentHeaders.Add('Content-Type', 'application/json');
         ContentHeaders.Add('api-key', NewApiKey);
-
         HttpRequestMessage.Method := 'GET';
         HttpRequestMessage.SetRequestUri(Url);
         HttpRequestMessage.Content(HttpContent);
 
         IsSuccessful := HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
-
         if not IsSuccessful or not HttpResponseMessage.IsSuccessStatusCode() then begin
             Session.LogMessage('0000OLQ', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(false);
@@ -184,6 +196,35 @@ codeunit 7767 "AOAI Authorization"
         exit(true);
     end;
 
+    local procedure IsValidAOAIAccountName(Subdomain: Text): Boolean
+    var
+        RegexPattern: Codeunit Regex;
+    begin
+        // Regular expression to validate the Azure OpenAI Instance name according to these requirements "Only alphanumeric characters and hyphens are allowed. The value must be 2-64 characters long and cannot start or end with a hyphen."
+        // ^[a-zA-Z0-9]     : Starts with an alphanumeric character
+        // [a-zA-Z0-9\-]{0,62} : Allows alphanumeric characters and hyphens, up to 62 characters
+        // [a-zA-Z0-9]$     : Ends with an alphanumeric character
+        // Total length: 2-64 characters (1 + 62 + 1)
+        exit(RegexPattern.IsMatch(Subdomain, '^[a-zA-Z0-9][a-zA-Z0-9\-]{0,62}[a-zA-Z0-9]$'));
+    end;
+
+    local procedure IsValidUrl(Url: Text; TrustedDomain: Text): Boolean
+    var
+        UriBuilder: Codeunit "Uri Builder";
+        HostName: Text;
+    begin
+        if (Url = '') or not Url.StartsWith('https://') then
+            exit(false);
+
+        UriBuilder.Init(Url);
+        HostName := UriBuilder.GetHost();
+
+        if HostName <> TrustedDomain then
+            exit(false);
+
+        exit(true);
+    end;
+
     local procedure VerifyAOAIAccount(AccountName: Text; NewApiKey: SecretText): Boolean
     var
         VerificationLog: Record "AOAI Account Verification Log";

From 485d1f8ca014cb6b02e0967c9abb4f4e6ef54979 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 12 Feb 2025 16:40:33 +0100
Subject: [PATCH 27/30] fixed hostname verification

---
 .../App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 41a5c7e56b..d21ec3357b 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -219,7 +219,7 @@ codeunit 7767 "AOAI Authorization"
         UriBuilder.Init(Url);
         HostName := UriBuilder.GetHost();
 
-        if HostName <> TrustedDomain then
+        if HostName.EndsWith(TrustedDomain) then
             exit(false);
 
         exit(true);

From 659f07eae2db4613f37f095e5168d9716ae4e622 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Wed, 12 Feb 2025 16:42:36 +0100
Subject: [PATCH 28/30] Telemetry tags added

---
 .../App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index d21ec3357b..22cce3fb8d 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -166,14 +166,14 @@ codeunit 7767 "AOAI Authorization"
         TrustedDomainTxt: Label 'openai.azure.com', Locked = true;
     begin
         if not IsValidAOAIAccountName(AOAIAccountNameToVerify) then begin
-            Session.LogMessage('', TelemetryInvalidAOAIAccountNameFormatTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OQL', TelemetryInvalidAOAIAccountNameFormatTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 
         Url := StrSubstNo(UrlFormatTxt, AOAIAccountNameToVerify);
 
         if not IsValidUrl(Url, TrustedDomainTxt) then begin
-            Session.LogMessage('', TelemetryInvalidAOAIUrlTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OQM', TelemetryInvalidAOAIUrlTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 

From cf1f046716e40f9151c403486f54f42d82371519 Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 14 Feb 2025 14:25:34 +0100
Subject: [PATCH 29/30]  Replaced CopilotCapability reference with AzureOpenAI

---
 .../Azure OpenAI/AOAIAuthorization.Codeunit.al  | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al
index 22cce3fb8d..b4a6b90311 100644
--- a/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
+++ b/src/System Application/App/AI/src/Azure OpenAI/AOAIAuthorization.Codeunit.al	
@@ -19,7 +19,7 @@ codeunit 7767 "AOAI Authorization"
     Permissions = tabledata "AOAI Account Verification Log" = RIMD;
 
     var
-        CopilotCapabilityImpl: Codeunit "Copilot Capability Impl";
+        AzureOpenAIImpl: Codeunit "Azure OpenAI Impl";
         [NonDebuggable]
         Endpoint: Text;
         [NonDebuggable]
@@ -42,7 +42,6 @@ codeunit 7767 "AOAI Authorization"
     [NonDebuggable]
     procedure IsConfigured(CallerModule: ModuleInfo): Boolean
     var
-        AzureOpenAiImpl: Codeunit "Azure OpenAI Impl";
         CurrentModule: ModuleInfo;
         ALCopilotFunctions: DotNet ALCopilotFunctions;
     begin
@@ -166,14 +165,14 @@ codeunit 7767 "AOAI Authorization"
         TrustedDomainTxt: Label 'openai.azure.com', Locked = true;
     begin
         if not IsValidAOAIAccountName(AOAIAccountNameToVerify) then begin
-            Session.LogMessage('0000OQL', TelemetryInvalidAOAIAccountNameFormatTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OQL', TelemetryInvalidAOAIAccountNameFormatTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 
         Url := StrSubstNo(UrlFormatTxt, AOAIAccountNameToVerify);
 
         if not IsValidUrl(Url, TrustedDomainTxt) then begin
-            Session.LogMessage('0000OQM', TelemetryInvalidAOAIUrlTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OQM', TelemetryInvalidAOAIUrlTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 
@@ -188,11 +187,11 @@ codeunit 7767 "AOAI Authorization"
 
         IsSuccessful := HttpClient.Send(HttpRequestMessage, HttpResponseMessage);
         if not IsSuccessful or not HttpResponseMessage.IsSuccessStatusCode() then begin
-            Session.LogMessage('0000OLQ', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OLQ', TelemetryAOAIVerificationFailedTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
             exit(false);
         end;
 
-        Session.LogMessage('0000OLR', TelemetryAOAIVerificationSucceededTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+        Session.LogMessage('0000OLR', TelemetryAOAIVerificationSucceededTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
         exit(true);
     end;
 
@@ -247,7 +246,7 @@ codeunit 7767 "AOAI Authorization"
         IsWithinCachePeriod := IsAccountVerifiedWithinPeriod(TruncatedAccountName, CachePeriod);
         // Within CACHE period
         if IsWithinCachePeriod then begin
-            Session.LogMessage('0000OLS', TelemetryAccessWithinCachePeriodTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+            Session.LogMessage('0000OLS', TelemetryAccessWithinCachePeriodTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
             exit(true);
         end;
 
@@ -264,14 +263,14 @@ codeunit 7767 "AOAI Authorization"
             if IsAccountVerifiedWithinPeriod(TruncatedAccountName, GracePeriod) then begin
                 ShowUserNotification(StrSubstNo(AuthFailedWithinGracePeriodUserNotificationLbl, FormatDurationAsDays(RemainingGracePeriod)));
                 LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedWithinGracePeriodLogMessageLbl, AccountName, Today, FormatDurationAsDays(RemainingGracePeriod)));
-                Session.LogMessage('0000OLT', StrSubstNo(TelemetryAccessTokenWithinGracePeriodTxt, FormatDurationAsDays(RemainingGracePeriod)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+                Session.LogMessage('0000OLT', StrSubstNo(TelemetryAccessTokenWithinGracePeriodTxt, FormatDurationAsDays(RemainingGracePeriod)), Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
                 exit(true);
             end
             // Outside GRACE period
             else begin
                 ShowUserNotification(AuthFailedOutsideGracePeriodUserNotificationLbl);
                 LogTelemetry(AccountName, Today, StrSubstNo(AuthFailedOutsideGracePeriodLogMessageLbl, AccountName, Today));
-                Session.LogMessage('0000OLU', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', CopilotCapabilityImpl.GetAzureOpenAICategory());
+                Session.LogMessage('0000OLU', TelemetryAccessTokenOutsideCachePeriodTxt, Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', AzureOpenAIImpl.GetAzureOpenAICategory());
                 exit(false);
             end;
         end;

From a48ce4240fdd789ebac981a058986903cfdeb85b Mon Sep 17 00:00:00 2001
From: Christian Andersen <christian.andersen@microsoft.com>
Date: Fri, 14 Feb 2025 16:25:14 +0100
Subject: [PATCH 30/30] added references to URI and Regex

---
 src/System Application/App/AI/app.json | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/System Application/App/AI/app.json b/src/System Application/App/AI/app.json
index 8067d9b1b2..e27234a9f3 100644
--- a/src/System Application/App/AI/app.json	
+++ b/src/System Application/App/AI/app.json	
@@ -11,6 +11,18 @@
   "url": "https://go.microsoft.com/fwlink/?linkid=724011",
   "logo": "",
   "dependencies": [
+    {
+      "id": "1b2efb4b-8c44-4d74-a56f-60646645bb21",
+      "name": "URI",
+      "publisher": "Microsoft",
+      "version": "26.0.0.0"
+    },
+    {
+      "id": "b185fd4a-677b-48d3-a701-768de7563df0",
+      "name": "Regex",
+      "publisher": "Microsoft",
+      "version": "26.0.0.0"
+    },
     {
       "id": "daa5d70e-eaf5-4256-bf80-53545ef7629a",
       "name": "Privacy Notice",