Skip to content

Commit 3529092

Browse files
Dev to hf (#2144)
2 parents 9d71e07 + bc7ace6 commit 3529092

49 files changed

Lines changed: 2523 additions & 175 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Config/PermissionsTranslator.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5371,5 +5371,23 @@
53715371
"userConsentDescription": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
53725372
"userConsentDisplayName": "Allows the app to read your tenant's acquired telephone number details, without a signed-in user. Acquired telephone numbers may include attributes related to assigned object, emergency location, network site, etc.",
53735373
"value": "TeamsTelephoneNumber.ReadWrite.All"
5374+
},
5375+
{
5376+
"description": "Allows the app to read and write Copilot policy settings for the organization, on behalf of the signed-in user.",
5377+
"displayName": "Read and write Copilot policy settings",
5378+
"id": "e2edbde8-4448-4e49-8ebb-d53ba72df0f3",
5379+
"Origin": "Delegated",
5380+
"userConsentDescription": "Allows the app to read and write Copilot policy settings for the organization, on your behalf.",
5381+
"userConsentDisplayName": "Read and write Copilot policy settings",
5382+
"value": "CopilotPolicySettings.ReadWrite"
5383+
},
5384+
{
5385+
"description": "Allows the app to read and write Copilot policy settings for the organization, without a signed-in user.",
5386+
"displayName": "Read and write Copilot policy settings",
5387+
"id": "cc147c17-b8e8-4d3f-9f94-aa9e279a079a",
5388+
"Origin": "Application",
5389+
"userConsentDescription": "Allows the app to read and write Copilot policy settings for the organization, without a signed-in user.",
5390+
"userConsentDisplayName": "Read and write Copilot policy settings",
5391+
"value": "CopilotPolicySettings.ReadWrite"
53745392
}
53755393
]

Config/SAMManifest.json

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,42 @@
582582
{
583583
"id": "b7887744-6746-4312-813d-72daeaee7e2d",
584584
"type": "Scope"
585+
},
586+
{
587+
"id": "dd199f4a-f148-40a4-a2ec-f0069cc799ec",
588+
"type": "Role"
589+
},
590+
{
591+
"id": "8c026be3-8e26-4774-9372-8d5d6f21daff",
592+
"type": "Scope"
593+
},
594+
{
595+
"id": "fee28b28-e1f3-4841-818e-2704dc62245f",
596+
"type": "Role"
597+
},
598+
{
599+
"id": "62ade113-f8e0-4bf9-a6ba-5acb31db32fd",
600+
"type": "Scope"
601+
},
602+
{
603+
"id": "9e3f62cf-ca93-4989-b6ce-bf83c28f9fe8",
604+
"type": "Role"
605+
},
606+
{
607+
"id": "31e08e0a-d3f7-4ca2-ac39-7343fb83e8ad",
608+
"type": "Role"
609+
},
610+
{
611+
"id": "1ff1be21-34eb-448c-9ac9-ce1f506b2a68",
612+
"type": "Scope"
613+
},
614+
{
615+
"id": "cc147c17-b8e8-4d3f-9f94-aa9e279a079a",
616+
"type": "Role"
617+
},
618+
{
619+
"id": "e2edbde8-4448-4e49-8ebb-d53ba72df0f3",
620+
"type": "Scope"
585621
}
586622
]
587623
},
@@ -689,4 +725,4 @@
689725
"isEnabled": true,
690726
"allProperties": true
691727
}
692-
}
728+
}

Config/standards.json

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1371,21 +1371,38 @@
13711371
"cat": "Entra (AAD) Standards",
13721372
"tag": ["SMB1001 (2.5)"],
13731373
"appliesToTest": ["SMB1001_2_5", "ZTNA21889"],
1374-
"helpText": "Sets the state of the registration campaign for the tenant",
1375-
"docsDescription": "Sets the state of the registration campaign for the tenant. If enabled nudges users to set up the Microsoft Authenticator during sign-in.",
1374+
"helpText": "Sets the state of the registration campaign for the tenant, including the targeted authentication method, snooze settings and include/exclude groups. Leave include/exclude blank to keep the groups currently configured in the tenant, or use 'AllUsers' to target all users.",
1375+
"docsDescription": "Sets the state of the registration campaign for the tenant. If enabled nudges users to set up the targeted authentication method (Microsoft Authenticator or a Passkey) during sign-in. Supports limiting the number of snoozes, and including or excluding specific groups (by display name).",
13761376
"executiveText": "Prompts employees to set up multi-factor authentication during login, gradually improving the organization's security posture by encouraging adoption of stronger authentication methods. This helps achieve better security compliance without forcing immediate mandatory changes.",
13771377
"addedComponent": [
13781378
{
13791379
"type": "autoComplete",
13801380
"multiple": false,
13811381
"creatable": false,
1382-
"label": "Select value",
1382+
"label": "Registration campaign state",
13831383
"name": "standards.NudgeMFA.state",
13841384
"options": [
13851385
{ "label": "Enabled", "value": "enabled" },
13861386
{ "label": "Disabled", "value": "disabled" }
13871387
]
13881388
},
1389+
{
1390+
"type": "autoComplete",
1391+
"multiple": false,
1392+
"creatable": false,
1393+
"required": false,
1394+
"label": "Authentication method to nudge users to register (default is Microsoft Authenticator)",
1395+
"name": "standards.NudgeMFA.targetedAuthenticationMethod",
1396+
"options": [
1397+
{ "label": "Microsoft Authenticator", "value": "microsoftAuthenticator" },
1398+
{ "label": "Passkey (FIDO2)", "value": "fido2" }
1399+
],
1400+
"condition": {
1401+
"field": "standards.NudgeMFA.state",
1402+
"compareType": "valueEq",
1403+
"compareValue": "enabled"
1404+
}
1405+
},
13891406
{
13901407
"type": "number",
13911408
"name": "standards.NudgeMFA.snoozeDurationInDays",
@@ -1395,6 +1412,39 @@
13951412
"min": { "value": 0, "message": "Minimum value is 0" },
13961413
"max": { "value": 14, "message": "Maximum value is 14" }
13971414
}
1415+
},
1416+
{
1417+
"type": "switch",
1418+
"name": "standards.NudgeMFA.enforceRegistrationAfterAllowedSnoozes",
1419+
"label": "Limited number of snoozes (require registration after 3 snoozes)",
1420+
"defaultValue": true,
1421+
"condition": {
1422+
"field": "standards.NudgeMFA.state",
1423+
"compareType": "valueEq",
1424+
"compareValue": "enabled"
1425+
}
1426+
},
1427+
{
1428+
"type": "textField",
1429+
"name": "standards.NudgeMFA.includeTargets",
1430+
"label": "Include groups (comma separated group names, 'AllUsers' for everyone, blank = keep current targets)",
1431+
"required": false,
1432+
"condition": {
1433+
"field": "standards.NudgeMFA.state",
1434+
"compareType": "valueEq",
1435+
"compareValue": "enabled"
1436+
}
1437+
},
1438+
{
1439+
"type": "textField",
1440+
"name": "standards.NudgeMFA.excludeTargets",
1441+
"label": "Exclude groups (comma separated group names, blank = keep current exclusions)",
1442+
"required": false,
1443+
"condition": {
1444+
"field": "standards.NudgeMFA.state",
1445+
"compareType": "valueEq",
1446+
"compareValue": "enabled"
1447+
}
13981448
}
13991449
],
14001450
"label": "Sets the state for the request to setup Authenticator",
@@ -5070,6 +5120,7 @@
50705120
},
50715121
{
50725122
"name": "standards.SPDirectSharing",
5123+
"deprecated": true,
50735124
"cat": "SharePoint Standards",
50745125
"tag": [],
50755126
"helpText": "This standard has been deprecated in favor of the Default Sharing Link standard. ",
@@ -6530,7 +6581,7 @@
65306581
"label": "Conditional Access Template",
65316582
"cat": "Templates",
65326583
"multiple": true,
6533-
"disabledFeatures": { "report": true, "warn": true, "remediate": false },
6584+
"disabledFeatures": { "report": false, "warn": false, "remediate": false },
65346585
"impact": "High Impact",
65356586
"addedDate": "2023-12-30",
65366587
"tag": [
@@ -8043,7 +8094,7 @@
80438094
"tag": ["CIS M365 7.0.0 (5.2.3.8)", "CIS M365 7.0.0 (5.2.3.9)"],
80448095
"appliesToTest": ["CIS_5_2_3_8", "CIS_5_2_3_9"],
80458096
"helpText": "**Requires Entra ID P1.** Configures the Entra ID Smart Lockout settings including lockout duration, lockout threshold, and on-premises integration mode.",
8046-
"docsDescription": "Configures the Entra ID Smart Lockout policy which protects against brute-force password attacks. Smart Lockout locks out bad actors who try to guess user passwords or use brute-force methods. It recognizes sign-ins from valid users and treats them differently from attackers. Settings include lockout duration (seconds), lockout threshold (failed attempts before lockout), and on-premises password protection mode (Audit or Enforced).",
8097+
"docsDescription": "Configures the Entra ID Smart Lockout policy which protects against brute-force password attacks. Smart Lockout locks out bad actors who try to guess user passwords or use brute-force methods. It recognizes sign-ins from valid users and treats them differently from attackers. Settings include lockout duration (seconds), lockout threshold (failed attempts before lockout), and on-premises password protection mode (Audit or Enforce).",
80478098
"addedComponent": [
80488099
{
80498100
"type": "number",
@@ -8070,7 +8121,7 @@
80708121
"label": "On-Premises Mode",
80718122
"options": [
80728123
{ "label": "Audit", "value": "Audit" },
8073-
{ "label": "Enforced", "value": "Enforced" }
8124+
{ "label": "Enforce", "value": "Enforce" }
80748125
]
80758126
}
80768127
],

Modules/CIPPActivityTriggers/Public/Entrypoints/Activity Triggers/Push-CIPPDBCacheData.ps1

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ function Push-CIPPDBCacheData {
198198
} else {
199199
Write-Host "Skipping Compliance data collection for $TenantFilter - no required license"
200200
}
201-
201+
202202
if ($DefenderCapable) {
203203
$Tasks.Add(@{
204204
FunctionName = 'ExecCIPPDBCache'
@@ -219,15 +219,7 @@ function Push-CIPPDBCacheData {
219219
QueueId = $QueueId
220220
QueueName = "DB Cache SharePoint - $TenantFilter"
221221
})
222-
# SharePointSharingLinks runs as its own activity — it's heavy (scans every drive) and
223-
# spawns a child orchestrator (one activity per site) that needs its own time budget.
224-
$Tasks.Add(@{
225-
FunctionName = 'ExecCIPPDBCache'
226-
Name = 'SharePointSharingLinks'
227-
TenantFilter = $TenantFilter
228-
QueueId = $QueueId
229-
QueueName = "DB Cache SharePointSharingLinks - $TenantFilter"
230-
})
222+
# SharePointSharingLinks runs adhoc since it can take a long time to enumerate all sharing links for large tenants
231223
} else {
232224
Write-Host "Skipping SharePoint data collection for $TenantFilter - no required license"
233225
}

Modules/CIPPCore/Public/Get-CIPPDrift.ps1

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -377,12 +377,23 @@ function Get-CIPPDrift {
377377
$tenantPolicy.policy | Add-Member -MemberType NoteProperty -Name 'URLName' -Value $TenantPolicy.Type -Force
378378
$TenantPolicyName = if ($TenantPolicy.Policy.displayName) { $TenantPolicy.Policy.displayName } else { $TenantPolicy.Policy.name }
379379
foreach ($TemplatePolicy in $TemplateIntuneTemplates) {
380-
$TemplatePolicyName = if ($TemplatePolicy.displayName) { $TemplatePolicy.displayName } else { $TemplatePolicy.name }
381-
382-
if ($TemplatePolicy.displayName -eq $TenantPolicy.Policy.displayName -or
383-
$TemplatePolicy.name -eq $TenantPolicy.Policy.name -or
384-
$TemplatePolicy.displayName -eq $TenantPolicy.Policy.name -or
385-
$TemplatePolicy.name -eq $TenantPolicy.Policy.displayName) {
380+
# Compare displayName-to-displayName and name-to-name (plus the cross pairings,
381+
# since some policy types are captured under one property but deployed under the
382+
# other) but require BOTH sides of each pairing to be non-empty before treating it
383+
# as a match. Most Intune policy types (compliance policies, device configurations,
384+
# group policy configs, etc.) only expose displayName and have no .name property at
385+
# all, so comparing raw .name values directly (as before) compared $null -eq $null,
386+
# which is $true in PowerShell - causing every tenant policy to falsely "match" the
387+
# first template as soon as any template lacked a .name property, suppressing all
388+
# tenant-only deviations. Note: templates always get a .displayName forced onto them
389+
# (see Add-Member above) even for name-only policy types like Settings Catalog
390+
# (deviceManagement/configurationPolicies), so the name-to-name pairing must still be
391+
# compared directly from the raw properties - collapsing to a single "effective name
392+
# preferring displayName" per side would silently break matching for those policies.
393+
if (($TemplatePolicy.displayName -and $TenantPolicy.Policy.displayName -and $TemplatePolicy.displayName -eq $TenantPolicy.Policy.displayName) -or
394+
($TemplatePolicy.name -and $TenantPolicy.Policy.name -and $TemplatePolicy.name -eq $TenantPolicy.Policy.name) -or
395+
($TemplatePolicy.displayName -and $TenantPolicy.Policy.name -and $TemplatePolicy.displayName -eq $TenantPolicy.Policy.name) -or
396+
($TemplatePolicy.name -and $TenantPolicy.Policy.displayName -and $TemplatePolicy.name -eq $TenantPolicy.Policy.displayName)) {
386397
$PolicyFound = $true
387398
break
388399
}

Modules/CIPPCore/Public/Get-CIPPTestData.ps1

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,80 @@ function Get-CIPPTestData {
44
Cached wrapper around New-CIPPDbRequest for test functions
55
66
.DESCRIPTION
7-
Returns cached tenant data during test suite execution. The cache is
8-
backed by CIPP.TestDataCache (static ConcurrentDictionary in C#) so
9-
it is shared across all PowerShell runspaces within the worker process.
7+
Returns cached tenant data during test suite execution. The cache is backed by
8+
CIPP.TestDataCache (static ConcurrentDictionary in C#) so it is shared across all
9+
PowerShell runspaces within the worker process.
10+
11+
RECORDS ARE FIELD-PROJECTED. Only the fields listed for $Type in
12+
Get-CippTestDataFieldManifest are materialized. Reading an unlisted field returns $null
13+
with no error, so the test emits a wrong verdict silently. If you add a field read to a
14+
test, add it to the manifest first. A type absent from the manifest is fetched whole,
15+
which is the safe default.
16+
17+
Projection is record-level: a kept field keeps its entire subtree, so
18+
`$policy.conditions.users.includeRoles` only requires 'conditions'.
19+
20+
Records are PSCustomObject and must stay so. Hashtable output changes scalar semantics
21+
($x[0] becomes a key lookup returning $null; $x.Count returns the record's field count
22+
instead of 1) and only when a pipeline yields exactly one record — data-dependent, and
23+
easily missed in testing.
1024
1125
.PARAMETER TenantFilter
1226
The tenant domain or GUID to filter by
1327
1428
.PARAMETER Type
1529
The data type to retrieve (e.g., Users, Groups, ConditionalAccessPolicies)
30+
31+
.PARAMETER Fields
32+
Optional. Override the manifest for this call: keep only these top-level fields. Prefer
33+
the manifest; this is for one-off and diagnostic callers, not tests.
34+
35+
The field set is part of the cache key — it must be, or callers asking for the same
36+
tenant+type with different lists would receive each other's projection. Distinct lists
37+
therefore fragment the cache: the same rows parsed once per list, all alive together for
38+
the TTL. Per-call-site lists measured several times worse than one shared per-type entry.
39+
Fields belong to the type — see Get-CippTestDataFieldManifest.
40+
41+
.PARAMETER NoProjection
42+
Return every field, ignoring the manifest.
43+
44+
Required for the custom-script sandbox (Get-CippSandboxData): those scripts are
45+
customer-authored, so the fields they read are unknowable and a manifest built from our
46+
own tests would silently hand them incomplete records. Any caller fetching data on behalf
47+
of code we did not write must pass this.
48+
49+
.EXAMPLE
50+
Get-CIPPTestData -TenantFilter $Tenant -Type 'Users'
51+
Normal use: the manifest decides the fields. This is what tests should do.
52+
53+
.EXAMPLE
54+
Get-CIPPTestData -TenantFilter $Tenant -Type 'Users' -NoProjection
55+
Every field. For callers serving code we did not author.
56+
57+
.NOTES
58+
Verifying a change: "0 errored" proves nothing, because the failure modes here are silent
59+
— a broken test still reports success. Diff test verdicts against a baseline, across more
60+
than one tenant: a bug that needs exactly one matching record will not show on a tenant
61+
that has two.
62+
63+
Useful endpoints: ExecTestRun then ListTests for verdicts; ListDBCache to confirm a field
64+
exists and its real casing before adding it to the manifest (type=_availableTypes lists
65+
the types); ListWorkerHealth?Action=CacheDiag for entries, hit rate and a per-type
66+
breakdown whose keys carry the projected field list.
1667
#>
1768
[CmdletBinding()]
1869
param(
1970
[Parameter(Mandatory = $false)]
2071
[string]$TenantFilter,
2172

2273
[Parameter(Mandatory = $false)]
23-
[string]$Type
74+
[string]$Type,
75+
76+
[Parameter(Mandatory = $false)]
77+
[string[]]$Fields,
78+
79+
[Parameter(Mandatory = $false)]
80+
[switch]$NoProjection
2481
)
2582

2683
# Enforce tenant lock when running inside custom script execution
@@ -32,14 +89,36 @@ function Get-CIPPTestData {
3289
throw 'TenantFilter is required.'
3390
}
3491

35-
$CacheKey = '{0}|{1}' -f $TenantFilter, $Type
92+
# Resolve the field set: an explicit -Fields wins, otherwise consult the per-type manifest.
93+
# -NoProjection suppresses both (the sandbox path must never receive projected records).
94+
$EffectiveFields = if ($NoProjection) {
95+
$null
96+
} elseif ($Fields) {
97+
$Fields
98+
} else {
99+
Get-CippTestDataFieldManifest -Type $Type
100+
}
101+
102+
# Normalize the field set (sorted + de-duplicated) so that callers asking for the same
103+
# fields in a different order share one cache entry instead of parsing twice.
104+
$NormalizedFields = if ($EffectiveFields) { @($EffectiveFields | Sort-Object -Unique) } else { $null }
105+
106+
$CacheKey = if ($NormalizedFields) {
107+
'{0}|{1}|{2}' -f $TenantFilter, $Type, ($NormalizedFields -join ',')
108+
} else {
109+
'{0}|{1}' -f $TenantFilter, $Type
110+
}
36111

37112
$CachedValue = $null
38113
if ([CIPP.TestDataCache]::TryGet($CacheKey, [ref]$CachedValue)) {
39114
return $CachedValue
40115
}
41116

42-
$Data = New-CIPPDbRequest -TenantFilter $TenantFilter -Type $Type
117+
$Data = if ($NormalizedFields) {
118+
New-CIPPDbRequest -TenantFilter $TenantFilter -Type $Type -Fields $NormalizedFields
119+
} else {
120+
New-CIPPDbRequest -TenantFilter $TenantFilter -Type $Type
121+
}
43122

44123
[CIPP.TestDataCache]::Set($CacheKey, $Data)
45124

0 commit comments

Comments
 (0)