Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add approval by default to privacy notices #2368

Merged
merged 12 commits into from
Nov 26, 2024
7 changes: 7 additions & 0 deletions src/System Application/App/Privacy Notice/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,10 @@ begin
end;
```

### How to have a privacy notice approved by default
In `PrivacyNoticeImpl` codeunit, there is a procedure called `ShouldApproveByDefault`. Return true from this method if the integration ID matches
the ID of the scenario you want to approve by default. In the following scenarios where the procedure returns true, it will have a default approval for the entire organization if:
1. `PrivacyNotice.GetPrivacyNoticeApprovalState` is called for an integration with no privacy notice record created or,
2. A privacy notice for the scenario is created and no approvals exist for it yet.

An admin can later agree/disagree to this approval on the Privacy Notices page.
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,19 @@ codeunit 1563 "Privacy Notice"
var
PrivacyNoticeImpl: Codeunit "Privacy Notice Impl.";
begin
exit(PrivacyNoticeImpl.CheckPrivacyNoticeApprovalState(Id) = "Privacy Notice Approval State"::Disagreed);
exit(PrivacyNoticeImpl.IsApprovalStateDisagreed(Id));
end;

/// <summary>
/// Determines whether the admin or user has disagreed with the Privacy Notice.
/// </summary>
/// <param name="State">The approval state.</param>
/// <returns>Whether the Privacy Notice was disagreed to.</returns>
procedure IsApprovalStateDisagreed(State: Enum "Privacy Notice Approval State"): Boolean
var
PrivacyNoticeImpl: Codeunit "Privacy Notice Impl.";
begin
exit(PrivacyNoticeImpl.IsApprovalStateDisagreed(State));
end;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ codeunit 1565 "Privacy Notice Impl."

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
if ShouldApproveByDefault(PrivacyNoticeId) or (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 or approve by default
exit("Privacy Notice Approval State"::"Not set"); // If there are no Privacy Notice then it is by default "Not set".
end;

Expand Down Expand Up @@ -176,8 +176,8 @@ codeunit 1565 "Privacy Notice Impl."
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);
if not IsApprovalStateDisagreed(PrivacyNoticeApprovalState) then // We do not store rejected user approvals
PrivacyNoticeApproval.SetApprovalState(PrivacyNoticeId, UserSecurityId(), PrivacyNoticeApprovalState)
end;

procedure ShowOneTimePrivacyNotice(IntegrationName: Text[250]): Enum "Privacy Notice Approval State"
Expand Down Expand Up @@ -222,11 +222,27 @@ codeunit 1565 "Privacy Notice Impl."
if PrivacyNotice.Link = '' then
PrivacyNotice.Link := MicrosoftPrivacyLinkTxt;
if not PrivacyNotice.Insert() then
Session.LogMessage('0000GMF', PrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', TelemetryCategoryTxt);
Session.LogMessage('0000GMF', PrivacyNoticeNotCreatedTelemetryErr, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', this.TelemetryCategoryTxt)
else
TryCreateDefaultApproval(PrivacyNotice);
end;
until TempPrivacyNotice.Next() = 0;
end;

/// <summary>
/// Creates a default approval for the given privacy notice if it can be approved by default and there is not already an approval record for it.
/// </summary>
/// <param name="PrivacyNotice">The notice to save approval under.</param>
local procedure TryCreateDefaultApproval(PrivacyNotice: Record "Privacy Notice")
var
PrivacyNoticeApproval: Codeunit "Privacy Notice Approval";
begin
if ShouldApproveByDefault(PrivacyNotice.ID) then begin
PrivacyNoticeApproval.SetApprovalState(PrivacyNotice.ID, EmptyGuid, "Privacy Notice Approval State"::Agreed);
PrivacyNotice.CalcFields(Enabled);
end;
end;

[TryFunction]
local procedure TryGetAllPrivacyNotices(var PrivacyNotice: Record "Privacy Notice" temporary)
var
Expand All @@ -245,7 +261,14 @@ codeunit 1565 "Privacy Notice Impl."
PrivacyNotice.Id := Id;
PrivacyNotice."Integration Service Name" := IntegrationName;
PrivacyNotice.Link := Link;
exit(PrivacyNotice.Insert());

if PrivacyNotice.Insert() then begin
TryCreateDefaultApproval(PrivacyNotice);

exit(true);
end;

exit(false);
end;

local procedure ShowPrivacyNotice(PrivacyNotice: Record "Privacy Notice"): Boolean
Expand Down Expand Up @@ -303,6 +326,55 @@ codeunit 1565 "Privacy Notice Impl."
IsApproved := false;
end;

/// <summary>
/// Checks if the IDs are equal.
/// </summary>
/// <param name="ID">The first ID.</param>
/// <param name="IDToCheck">The ID to check against the first ID parameter.</param>
/// <returns>true if equal; otherwise false.</returns>
local procedure CheckIntegrationIDEquality(ID: Text; IDToCheck: Text): Boolean
begin
exit(CopyStr(UpperCase(ID), 1, 50) = CopyStr(UpperCase(IDToCheck), 1, 50));
end;

/// <summary>
/// Indicates if the integration should be enabled by default.
/// </summary>
/// <param name="IntegrationID">The integration ID/</param>
/// <returns>true if it should be approved by default; otherwise false.</returns>
local procedure ShouldApproveByDefault(IntegrationID: Text): Boolean
var
SystemPrivacyNoticeReg: Codeunit "System Privacy Notice Reg.";
begin
if CheckIntegrationIDEquality(SystemPrivacyNoticeReg.GetMicrosoftLearnID(), IntegrationID) then
exit(true);

exit(false);
end;

/// <summary>
/// Determines whether the admin or user has disagreed with the Privacy Notice.
/// </summary>
/// <param name="Id">Identification of an existing privacy notice.</param>
/// <returns>Whether the Privacy Notice was disagreed to.</returns>
procedure IsApprovalStateDisagreed(Id: Code[50]): Boolean
var
State: Enum "Privacy Notice Approval State";
begin
State := CheckPrivacyNoticeApprovalState(Id);
exit(IsApprovalStateDisagreed(State));
end;

/// <summary>
/// Determines whether the admin or user has disagreed with the Privacy Notice.
/// </summary>
/// <param name="State">The approval state.</param>
/// <returns>Whether the Privacy Notice was disagreed to.</returns>
procedure IsApprovalStateDisagreed(State: Enum "Privacy Notice Approval State"): Boolean
begin
exit(State = "Privacy Notice Approval State"::Disagreed);
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetPrivacyNoticeApprovalState, '', true, true)]
local procedure GetPrivacyNoticeApprovalState(PrivacyNoticeIntegrationName: Text; var PrivacyNoticeApprovalState: Integer)
var
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ page 1565 "Privacy Notices"
field(Accepted; Accepted)
{
Caption = 'Agree for Everyone';
ToolTip = 'Specifies whether an administrator has accepted the integration''s privacy notice on behalf of all users.';
ToolTip = 'Specifies whether an administrator (or the system by default) has accepted the integration''s privacy notice on behalf of all users.';
ApplicationArea = All;

trigger OnValidate()
Expand Down Expand Up @@ -92,6 +92,7 @@ page 1565 "Privacy Notices"
SetRecordApprovalState();
end;
}

#pragma warning disable AA0218
field(Accepted2; Rec.Enabled)
{
Expand Down Expand Up @@ -175,6 +176,8 @@ page 1565 "Privacy Notices"
else
PrivacyNotice.SetApprovalState(Rec.ID, "Privacy Notice Approval State"::"Not set");
end;

CurrPage.Update();
end;

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@ codeunit 1566 "System Privacy Notice Reg."
MicrosoftTeamsTxt: Label 'Microsoft Teams', Locked = true; // Product names are not translated and it's important this entry exists.
PowerAutomateIdTxt: Label 'Power Automate', Locked = true; // Product names are not translated and it's important this entry exists.
PowerAutomateLabelTxt: Label 'Microsoft Power Automate', Locked = true; // Product names are not translated and it's important this entry exists.
MicrosoftLearnTxt: Label 'Microsoft Learn', Locked = true; // Product names are not translated and it's important this entry exists.

procedure GetMicrosoftLearnID(): Text
begin
exit(MicrosoftLearnTxt);
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::"Privacy Notice", OnRegisterPrivacyNotices, '', false, false)]
local procedure CreatePrivacyNoticeRegistrations(var TempPrivacyNotice: Record "Privacy Notice" temporary)
begin
TempPrivacyNotice.Init();
TempPrivacyNotice."ID" := MicrosoftLearnTxt;
TempPrivacyNotice."Integration Service Name" := MicrosoftLearnTxt;
if not TempPrivacyNotice.Insert() then;
TempPrivacyNotice.ID := MicrosoftTeamsTxt;
TempPrivacyNotice."Integration Service Name" := MicrosoftTeamsTxt;
if not TempPrivacyNotice.Insert() then;
Expand Down
Loading