From 1702f14ac06699cf409389359b0b1f3cabe6d0f6 Mon Sep 17 00:00:00 2001 From: Christian Andersen Date: Tue, 12 Nov 2024 12:59:11 +0100 Subject: [PATCH 01/24] 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 , and will use the other parameters to verify that you have access to Azure OpenAI. /// /// The model type to set authorization for. - /// The endpoint to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. - /// The deployment to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. + /// Azure OpenAI account name) /// The API key to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. /// The managed deployment to use for the model type. - /// 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 - /// [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; /// 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 Date: Tue, 12 Nov 2024 15:13:36 +0100 Subject: [PATCH 02/24] 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; + /// + /// 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 , and will use the other parameters to verify that you have access to Azure OpenAI. + /// + /// The model type to set authorization for. + /// The endpoint to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. + /// The deployment to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. + /// The API key to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. + /// The managed deployment to use for the model type. + /// 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 + /// + [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; + /// /// 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 , 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 Date: Tue, 12 Nov 2024 15:37:55 +0100 Subject: [PATCH 03/24] 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 Date: Mon, 16 Dec 2024 16:29:44 +0100 Subject: [PATCH 04/24] 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 Date: Wed, 8 Jan 2025 16:51:47 +0100 Subject: [PATCH 05/24] 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 Date: Fri, 10 Jan 2025 13:54:37 +0100 Subject: [PATCH 06/24] 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 Date: Fri, 10 Jan 2025 17:00:57 +0100 Subject: [PATCH 07/24] 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 Date: Mon, 13 Jan 2025 17:57:36 +0100 Subject: [PATCH 08/24] 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; /// /// Store the authorization information for the AOAI service. /// @@ -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, '--')); + + 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 Date: Mon, 13 Jan 2025 18:02:58 +0100 Subject: [PATCH 09/24] 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 Date: Tue, 14 Jan 2025 11:49:31 +0100 Subject: [PATCH 10/24] 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 Date: Tue, 14 Jan 2025 13:13:20 +0100 Subject: [PATCH 11/24] 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 Date: Tue, 14 Jan 2025 14:21:53 +0100 Subject: [PATCH 12/24] 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 Date: Wed, 15 Jan 2025 17:18:43 +0100 Subject: [PATCH 13/24] 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, '') + ', CachePeriod=' + Format(CachePeriod, 0, '') + ', 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, '')); + // 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, '')); + + if Rec.Get(AccountName) then begin + Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime, 0, '') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '')); + IsVerified := CurrentDateTime - Rec.LastSuccessfulVerification <= Period; + Message('Verification result: ' + Format(IsVerified, 0, '')); + 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, '')); 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, '')); 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, '--')); + CustomDimensions.Add('AccountName', AccountName); CustomDimensions.Add('VerificationDate', Format(VerificationDate, 0, '--')); @@ -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, '--')); end; } \ No newline at end of file From b6d084831d644123c72824dbbf8af989bdc206b1 Mon Sep 17 00:00:00 2001 From: Christian Andersen Date: Wed, 15 Jan 2025 18:58:23 +0100 Subject: [PATCH 14/24] 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; /// /// 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, '')); @@ -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 /// [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 Date: Thu, 16 Jan 2025 17:55:10 +0100 Subject: [PATCH 15/24] 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, ''), + Format(Minutes, 2, ''), + Format(Seconds, 2, ''), + Format(Milliseconds, 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, '') + ', CachePeriod=' + Format(CachePeriod, 0, '') + ', 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, '')); + ; + Message('Starting IsAccountVerifiedWithinPeriod procedure. Variables: AccountName=' + AccountName + ', Period=' + FormatDurationAsString(Period)); if Rec.Get(AccountName) then begin Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime, 0, '') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '')); From 8d03c7f0153999734e3b2cb097cb712c3c727931 Mon Sep 17 00:00:00 2001 From: Christian Andersen Date: Fri, 17 Jan 2025 13:45:07 +0100 Subject: [PATCH 16/24] 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, '')); + 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, '') + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification, 0, '')); + Message('Record found. Variables: CurrentDateTime=' + Format(CurrentDateTime) + ', Rec.LastSuccessfulVerification=' + Format(Rec.LastSuccessfulVerification)); IsVerified := CurrentDateTime - Rec.LastSuccessfulVerification <= Period; - Message('Verification result: ' + Format(IsVerified, 0, '')); + 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, '')); + 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, '')); + 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, '--')); + Message('Starting LogTelemetry procedure. Variables: AccountName=' + AccountName + ', VerificationDate=' + Format(VerificationDate)); CustomDimensions.Add('AccountName', AccountName); - CustomDimensions.Add('VerificationDate', Format(VerificationDate, 0, '--')); + 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, '--')); + 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 Date: Wed, 22 Jan 2025 16:57:15 +0100 Subject: [PATCH 17/24] 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, ''))); 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 Date: Thu, 23 Jan 2025 14:55:06 +0100 Subject: [PATCH 18/24] 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, ''), - Format(Minutes, 2, ''), - Format(Seconds, 2, ''), - Format(Milliseconds, 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 Date: Thu, 23 Jan 2025 14:57:43 +0100 Subject: [PATCH 19/24] 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 Date: Thu, 23 Jan 2025 15:00:11 +0100 Subject: [PATCH 20/24] 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 Date: Thu, 23 Jan 2025 15:12:53 +0100 Subject: [PATCH 21/24] 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 , and will use the other parameters to verify that you have access to Azure OpenAI. /// /// The model type to set authorization for. - /// Azure OpenAI account name) - /// The API key to use to verify access to Azure OpenAI. This is used only for verification, not for actual Azure OpenAI calls. + /// Name of the Azure Open AI resource like "MyAzureOpenAIResource" + /// 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. /// The managed deployment to use for the model type. [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 Date: Thu, 23 Jan 2025 16:12:41 +0100 Subject: [PATCH 22/24] 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 Date: Thu, 23 Jan 2025 16:48:26 +0100 Subject: [PATCH 23/24] 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 Date: Fri, 7 Feb 2025 17:51:49 +0100 Subject: [PATCH 24/24] 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 /// /// 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 , 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 /// /// 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)