Codeunit 1565 Privacy Notice Impl. in 25

App
System Application
Namespace
System.Privacy

Procedures, 13

Versions171819202122232425262728latest

Source242526272829

Source in 25

src/System Application/App/Privacy Notice/src/PrivacyNoticeImpl.Codeunit.al315 lines, Copyright (c) Microsoft Corporation. MIT

// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------

namespace System.Privacy;

using System.Environment;

codeunit 1565 "Privacy Notice Impl."
{
    Access = Internal;
    InherentEntitlements = X;
    InherentPermissions = X;
    Permissions = tabledata Company = r,
                  tabledata "Privacy Notice" = im;

    var
        EmptyGuid: Guid;
        PrivacyAgreementTxt: Label 'By using %1, you consent to your data being shared with Microsoft services that might be outside of your organization''s selected geographic boundaries and might have different compliance and security standards than %2. Your privacy is important to us, and you can choose whether to share data with the service. To learn more, follow the link below.', Comment = '%1 = the integration service name, ex. Microsoft Sharepoint, %2 = the full marketing name, such as Microsoft Dynamics 365 Business Central.';
        MicrosoftPrivacyLinkTxt: Label 'https://go.microsoft.com/fwlink/?linkid=521839';
        AdminDisabledIntegrationMsg: Label 'Your admin has disabled the integration with %1, please contact your administrator to approve this integration.', Comment = '%1 = a service name such as Microsoft Teams';
        MissingLinkErr: Label 'No privacy notice link was specified';
        PrivacyNoticeDoesNotExistErr: Label 'The privacy notice %1 does not exist.', Comment = '%1 = the identifier of a privacy notice';
        TelemetryCategoryTxt: Label 'Privacy Notice', Locked = true;
        CreatePrivacyNoticeTelemetryTxt: Label 'Creating privacy notice', Locked = true;
        ConfirmPrivacyNoticeTelemetryTxt: Label 'Confirming privacy notice', Locked = true;
        PrivacyNoticeAutoApprovedByAdminTelemetryTxt: Label 'The privacy notice was auto-approved by the admin', Locked = true;
        PrivacyNoticeAutoRejectedByAdminTelemetryTxt: Label 'The privacy notice was auto-rejected by the admin', Locked = true;
        PrivacyNoticeAutoApprovedByUserTelemetryTxt: Label 'The privacy notice was auto-approved by the user', Locked = true;
        ShowingPrivacyNoticeTelemetryTxt: Label 'Showing privacy notice', Locked = true;
        PrivacyNoticeApprovalResultTelemetryTxt: Label 'Approval State after showing privacy notice: %1', Locked = true;
        CheckPrivacyNoticeApprovalStateTelemetryTxt: Label 'Checking privacy approval state', Locked = true;
        AdminPrivacyApprovalStateTelemetryTxt: Label 'Admin privacy approval state: %1', Locked = true;
        UserPrivacyApprovalStateTelemetryTxt: Label 'User privacy approval state: %1', Locked = true;
        RegisteringPrivacyNoticesFailedTelemetryErr: Label 'Privacy notices could not be registered', Locked = true;
        PrivacyNoticeNotCreatedTelemetryErr: Label 'A privacy notice could not be created', Locked = true;
        PrivacyNoticeDoesNotExistTelemetryTxt: Label 'The Privacy Notice %1 does not exist.', Locked = true;
        SystemEventPrivacyNoticeNotCreatedTelemetryErr: Label 'System event privacy notice could be created.', Locked = true;

    trigger OnRun()
    begin
        CreateDefaultPrivacyNotices();
    end;

    procedure CreatePrivacyNotice(Id: Code[50]; IntegrationName: Text[250]; Link: Text[2048]): Boolean
    var
        PrivacyNotice: Record "Privacy Notice";
    begin
        exit(CreatePrivacyNotice(PrivacyNotice, Id, IntegrationName, Link));
    end;

    procedure CreatePrivacyNotice(Id: Code[50]; IntegrationName: Text[250]): Boolean
    begin
        exit(CreatePrivacyNotice(Id, IntegrationName, MicrosoftPrivacyLinkTxt));
    end;

    procedure GetDefaultPrivacyAgreementTxt(): Text
    begin
        exit(PrivacyAgreementTxt);
    end;

    procedure ConfirmPrivacyNoticeApproval(PrivacyNoticeId: Code[50]): Boolean
    begin
        exit(ConfirmPrivacyNoticeApproval(PrivacyNoticeId, true));
    end;

    procedure ConfirmPrivacyNoticeApproval(PrivacyNoticeId: Code[50]; SkipCheckInEval: Boolean): Boolean
    var
        Company: Record Company;
        PrivacyNotice: Record "Privacy Notice";
    begin
        Session.LogMessage('0000GK8', ConfirmPrivacyNoticeTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        PrivacyNotice.SetAutoCalcFields(Enabled, Disabled);
        PrivacyNotice.SetRange("User SID Filter", EmptyGuid);

        // If the Privacy Notice does not exist then re-initialize all Privacy Notices
        if not PrivacyNotice.Get(PrivacyNoticeId) then begin
            CreateDefaultPrivacyNoticesInSeparateThread();
            if not PrivacyNotice.Get(PrivacyNoticeId) then
                Error(PrivacyNoticeDoesNotExistErr, PrivacyNoticeId);
        end;

        // First check if admin has made decision on this privacy notice and return that
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GK9', PrivacyNoticeAutoApprovedByAdminTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit(true);
        end;
        if PrivacyNotice.Disabled then begin
            Session.LogMessage('0000GKA', PrivacyNoticeAutoRejectedByAdminTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            if CanCurrentUserApproveForOrganization() then
                exit(ShowPrivacyNotice(PrivacyNotice)); // User is admin so show the privacy notice again for them to re-approve
            Message(AdminDisabledIntegrationMsg, PrivacyNotice."Integration Service Name");
            exit(false);
        end;

        // Admin did not make any decision
        if SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company" then
            exit(true); // Auto-agree for evaluation companies if admin has not explicitly disagreed

        // Check if user made a decision and if so, return that
        PrivacyNotice.SetRange("User SID Filter", UserSecurityId());
        PrivacyNotice.CalcFields(Enabled, Disabled);
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKB', PrivacyNoticeAutoApprovedByUserTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit(true); // If user clicked no, they will still be notified until admin makes a decision
        end;

        // Show privacy notice and store user decision
        // if the user is admin then show an approval message for everyone
        // if the user is not admin then show an approval message for this specific user
        exit(ShowPrivacyNotice(PrivacyNotice));
    end;

    procedure CheckPrivacyNoticeApprovalState(PrivacyNoticeId: Code[50]): Enum "Privacy Notice Approval State"
    begin
        exit(CheckPrivacyNoticeApprovalState(PrivacyNoticeId, true));
    end;

    procedure CheckPrivacyNoticeApprovalState(PrivacyNoticeId: Code[50]; SkipCheckInEval: Boolean): Enum "Privacy Notice Approval State"
    var
        Company: Record Company;
        PrivacyNotice: Record "Privacy Notice";
    begin
        Session.LogMessage('0000GKC', CheckPrivacyNoticeApprovalStateTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        PrivacyNotice.SetAutoCalcFields(Enabled, Disabled);
        PrivacyNotice.SetRange("User SID Filter", EmptyGuid);

        if not PrivacyNotice.Get(PrivacyNoticeId) then begin
            Session.LogMessage('0000GN7', StrSubstNo(PrivacyNoticeDoesNotExistTelemetryTxt, PrivacyNoticeId), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            if SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company" then
                exit("Privacy Notice Approval State"::Agreed); // Auto-agree for evaluation companies if admin has not explicitly disagreed
            exit("Privacy Notice Approval State"::"Not set"); // If there are no Privacy Notice then it is by default "Not set".
        end;

        // First check if admin has made decision on this privacy notice and return that
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKD', StrSubstNo(AdminPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::Agreed), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Agreed);
        end;
        if PrivacyNotice.Disabled then begin
            Session.LogMessage('0000GKE', StrSubstNo(AdminPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::Disagreed), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Disagreed);
        end;

        // Admin did not make any decision
        if SkipCheckInEval and Company.Get(CompanyName()) and Company."Evaluation Company" then
            exit("Privacy Notice Approval State"::Agreed); // Auto-agree for evaluation companies if admin has not explicitly disagreed

        // Check if user made a decision and if so, return that
        PrivacyNotice.SetRange("User SID Filter", UserSecurityId());
        PrivacyNotice.CalcFields(Enabled);
        if PrivacyNotice.Enabled then begin
            Session.LogMessage('0000GKF', StrSubstNo(UserPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::Agreed), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit("Privacy Notice Approval State"::Agreed); // If user clicked no, they will still be notified until admin makes a decision
        end;
        Session.LogMessage('0000GKG', StrSubstNo(UserPrivacyApprovalStateTelemetryTxt, "Privacy Notice Approval State"::"Not set"), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        exit("Privacy Notice Approval State"::"Not set");
    end;

    procedure CanCurrentUserApproveForOrganization(): Boolean
    var
        [SecurityFiltering(SecurityFilter::Ignored)]
        PrivacyNoticeApproval: Record "Privacy Notice Approval";
    begin
        exit(PrivacyNoticeApproval.WritePermission());
    end;

    procedure SetApprovalState(PrivacyNoticeId: Code[50]; PrivacyNoticeApprovalState: Enum "Privacy Notice Approval State")
    var
        PrivacyNoticeApproval: Codeunit "Privacy Notice Approval";
    begin
        CreateDefaultPrivacyNotices(); // Ensure all default Privacy Notices are created
        if CanCurrentUserApproveForOrganization() then
            PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, EmptyGuid, PrivacyNoticeApprovalState)
        else
            if PrivacyNoticeApprovalState <> "Privacy Notice Approval State"::Disagreed then // We do not store rejected user approvals
                PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, UserSecurityId(), PrivacyNoticeApprovalState);
    end;

    procedure ShowOneTimePrivacyNotice(IntegrationName: Text[250]): Enum "Privacy Notice Approval State"
    begin
        exit(ShowOneTimePrivacyNotice(IntegrationName, MicrosoftPrivacyLinkTxt));
    end;

    procedure ShowOneTimePrivacyNotice(IntegrationName: Text[250]; Link: Text[2048]): Enum "Privacy Notice Approval State"
    var
        TempPrivacyNotice: Record "Privacy Notice" temporary;
        PrivacyNoticePage: Page "Privacy Notice";
    begin
        CreatePrivacyNotice(TempPrivacyNotice, '', IntegrationName, Link);

        PrivacyNoticePage.SetRecord(TempPrivacyNotice);
        PrivacyNoticePage.RunModal();
        PrivacyNoticePage.GetRecord(TempPrivacyNotice);
        exit(PrivacyNoticePage.GetUserApprovalState());
    end;

    procedure CreateDefaultPrivacyNoticesInSeparateThread()
    begin
        if Codeunit.Run(Codeunit::"Privacy Notice Impl.") then;
    end;

    procedure CreateDefaultPrivacyNotices()
    var
        TempPrivacyNotice: Record "Privacy Notice" temporary;
        PrivacyNotice: Record "Privacy Notice";
    begin
        if not TryGetAllPrivacyNotices(TempPrivacyNotice) then begin
            Session.LogMessage('0000GME', RegisteringPrivacyNoticesFailedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
            exit;
        end;

        TempPrivacyNotice.Reset();
        if TempPrivacyNotice.FindSet() then
            repeat
                PrivacyNotice.SetRange(ID, TempPrivacyNotice.ID);
                if PrivacyNotice.IsEmpty() then begin
                    PrivacyNotice := TempPrivacyNotice;
                    if PrivacyNotice.Link = '' then
                        PrivacyNotice.Link := MicrosoftPrivacyLinkTxt;
                    if not PrivacyNotice.Insert() then
                        Session.LogMessage('0000GMF', PrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
                end;
            until TempPrivacyNotice.Next() = 0;
    end;

    [TryFunction]
    local procedure TryGetAllPrivacyNotices(var PrivacyNotice: Record "Privacy Notice" temporary)
    var
        PrivacyNoticeInterface: Codeunit "Privacy Notice";
    begin
        PrivacyNoticeInterface.OnRegisterPrivacyNotices(PrivacyNotice);
    end;

    local procedure CreatePrivacyNotice(var PrivacyNotice: Record "Privacy Notice"; Id: Code[50]; IntegrationName: Text[250]; Link: Text[2048]): Boolean
    begin
        Session.LogMessage('0000GK7', CreatePrivacyNoticeTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);

        if Link = '' then
            Error(MissingLinkErr);

        PrivacyNotice.Id := Id;
        PrivacyNotice."Integration Service Name" := IntegrationName;
        PrivacyNotice.Link := Link;
        exit(PrivacyNotice.Insert());
    end;

    local procedure ShowPrivacyNotice(PrivacyNotice: Record "Privacy Notice"): Boolean
    var
        PrivacyNoticeCodeunit: Codeunit "Privacy Notice";
        PrivacyNoticePage: Page "Privacy Notice";
        Handled: Boolean;
    begin
        Session.LogMessage('0000GKH', ShowingPrivacyNoticeTelemetryTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        // Allow overriding of the privacy notice
        PrivacyNoticeCodeunit.OnBeforeShowPrivacyNotice(PrivacyNotice, Handled);
        if Handled then begin
            PrivacyNotice.CalcFields(Enabled); // Refresh the enabled field from the database
            exit(PrivacyNotice.Enabled); // The user either accepted, rejected or cancelled the privacy notice. No matter the case we only return true if the privacy notice was accepted.
        end;

        PrivacyNoticePage.SetRecord(PrivacyNotice);
        PrivacyNoticePage.RunModal();
        PrivacyNoticePage.GetRecord(PrivacyNotice);
        Session.LogMessage('0000GKI', StrSubstNo(PrivacyNoticeApprovalResultTelemetryTxt, PrivacyNoticePage.GetUserApprovalState()), Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        exit(PrivacyNoticePage.GetUserApprovalState() = "Privacy Notice Approval State"::Agreed); // The user either accepted, rejected or cancelled the privacy notice. No matter the case we only return true if the privacy notice was accepted.
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", ConfirmPrivacyNoticeApproval, '', false, false)]
    local procedure ConfirmSystemPrivacyNoticeApproval(PrivacyNoticeIntegrationName: Text; var IsApproved: Boolean)
    var
        PrivacyNotice: Record "Privacy Notice";
        PrivacyNoticeId: Code[50];
        PrivacyNoticeName: Text[250];
    begin
        if IsApproved then
            exit;

        PrivacyNoticeId := CopyStr(PrivacyNoticeIntegrationName, 1, 50);
        PrivacyNoticeName := CopyStr(PrivacyNoticeIntegrationName, 1, 250);
        PrivacyNotice.SetRange(ID, PrivacyNoticeId);
        if not PrivacyNotice.IsEmpty() then begin
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        CreateDefaultPrivacyNoticesInSeparateThread(); // First attempt creating the system privacy notice by creating default privacy notices
        if not PrivacyNotice.IsEmpty() then begin
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        if CreatePrivacyNotice(PrivacyNoticeId, PrivacyNoticeName) then begin // Manually create the privacy notice.
            Commit(); // Below may show a privacy notice, so make sure we are not in a write transaction.
            IsApproved := ConfirmPrivacyNoticeApproval(PrivacyNoticeId);
            exit;
        end;

        Session.LogMessage('0000GP9', SystemEventPrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
        IsApproved := false;
    end;

    [EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetPrivacyNoticeApprovalState, '', true, true)]
    local procedure GetPrivacyNoticeApprovalState(PrivacyNoticeIntegrationName: Text; var PrivacyNoticeApprovalState: Integer)
    var
        PrivacyNoticeId: Code[50];
    begin
        PrivacyNoticeId := CopyStr(PrivacyNoticeIntegrationName, 1, 50);
        PrivacyNoticeApprovalState := CheckPrivacyNoticeApprovalState(PrivacyNoticeId).AsInteger();
    end;
}

Procedures, 13

NameParametersReturnsAccessObsolete
CreatePrivacyNotice(Code[50], Text[250], Text[2048])Booleanpublic-
CreatePrivacyNotice(Code[50], Text[250])Booleanpublic-
ConfirmPrivacyNoticeApproval(Code[50])Booleanpublic-
CheckPrivacyNoticeApprovalState(Code[50])Enum Privacy Notice Approval Statepublic-
CanCurrentUserApproveForOrganization()Booleanpublic-
SetApprovalState(Code[50], Enum Privacy Notice Approval State)public-
ShowOneTimePrivacyNotice(Text[250])Enum Privacy Notice Approval Statepublic-
ShowOneTimePrivacyNotice(Text[250], Text[2048])Enum Privacy Notice Approval Statepublic-
CreateDefaultPrivacyNoticesInSeparateThread()public-
CreateDefaultPrivacyNotices()public-
GetDefaultPrivacyAgreementTxt()Textpublic-
ConfirmPrivacyNoticeApproval(Code[50], Boolean)Booleanpublic-
CheckPrivacyNoticeApprovalState(Code[50], Boolean)Enum Privacy Notice Approval Statepublic-