Skip to content

Commit 0c5b7b7

Browse files
authored
Merge pull request #1104 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 2a4e160 + 68edc5c commit 0c5b7b7

17 files changed

Lines changed: 540 additions & 126 deletions

Config/standards.json

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,10 +1459,23 @@
14591459
"cat": "Entra (AAD) Standards",
14601460
"tag": ["CISA (MS.AAD.21.1v1)", "SMB1001 (2.8)"],
14611461
"appliesToTest": ["SMB1001_2_8", "ZTNA21868"],
1462-
"helpText": "Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc",
1463-
"docsDescription": "Users by default are allowed to create M365 groups. This restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc",
1464-
"executiveText": "Restricts the creation of Microsoft 365 groups, Teams, and SharePoint sites to authorized administrators, preventing uncontrolled proliferation of collaboration spaces. This ensures proper governance, naming conventions, and resource management while maintaining oversight of all collaborative environments.",
1465-
"addedComponent": [],
1462+
"helpText": "Restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc. Optionally allows members of a specific security group to keep creating groups (GroupCreationAllowedGroupId).",
1463+
"docsDescription": "Users by default are allowed to create M365 groups. This restricts M365 group creation to certain admin roles. This disables the ability to create Teams, SharePoint sites, Planner, etc. Optionally, a security group can be named whose members remain allowed to create groups; the group is resolved by display name in each tenant and can be created automatically when it does not exist. When no group is named, the existing GroupCreationAllowedGroupId value in the tenant is left untouched.",
1464+
"executiveText": "Restricts the creation of Microsoft 365 groups, Teams, and SharePoint sites to authorized administrators, preventing uncontrolled proliferation of collaboration spaces. This ensures proper governance, naming conventions, and resource management while maintaining oversight of all collaborative environments. An approved group of designated users can optionally retain the ability to create groups.",
1465+
"addedComponent": [
1466+
{
1467+
"type": "textField",
1468+
"name": "standards.DisableM365GroupUsers.AllowedGroupName",
1469+
"label": "Optional: name of the group whose members may still create M365 groups",
1470+
"required": false
1471+
},
1472+
{
1473+
"type": "switch",
1474+
"name": "standards.DisableM365GroupUsers.CreateGroup",
1475+
"label": "Create the allowed group if it does not exist",
1476+
"required": false
1477+
}
1478+
],
14661479
"label": "Disable M365 Group creation by users",
14671480
"impact": "Low Impact",
14681481
"impactColour": "info",
@@ -3219,6 +3232,21 @@
32193232
"powershellEquivalent": "Get-Mailbox & Update-MgUser",
32203233
"recommendedBy": ["CIS", "CIPP"]
32213234
},
3235+
{
3236+
"name": "standards.TeamsDisableResourceAccounts",
3237+
"cat": "Teams Standards",
3238+
"tag": ["NIST CSF 2.0 (PR.AA-01)"],
3239+
"helpText": "Blocks sign-in for all Teams resource accounts used by Auto Attendants and Call Queues. Microsoft's guidance is to block sign-in for resource accounts as they do not require an interactive login to function.",
3240+
"docsDescription": "Teams resource accounts (the accounts backing Auto Attendants and Call Queues) do not require interactive sign-in to function. If sign-in is enabled and the password is reset, the account can be logged into directly, which presents a security risk. Microsoft's guidance is to block sign-in for these accounts. Accounts that are synced from on-premises AD are excluded, as account state is managed in the on-premises AD.",
3241+
"executiveText": "Prevents direct login to the service accounts that power phone system features like Auto Attendants and Call Queues. These accounts work without anyone signing into them, so blocking sign-in removes an unnecessary attack surface while keeping the phone system fully functional.",
3242+
"addedComponent": [],
3243+
"label": "Block sign-in for Teams resource accounts",
3244+
"impact": "Medium Impact",
3245+
"impactColour": "warning",
3246+
"addedDate": "2026-07-17",
3247+
"powershellEquivalent": "Get-CsOnlineApplicationInstance & Update-MgUser",
3248+
"recommendedBy": ["Microsoft", "CIPP"]
3249+
},
32223250
{
32233251
"name": "standards.DisableResourceMailbox",
32243252
"cat": "Exchange Standards",

Modules/CIPPAlerts/Public/Alerts/Get-CIPPAlertQuotaUsed.ps1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function Get-CIPPAlertQuotaUsed {
1515
$Threshold = if ($InputValue.QuotaUsedQuota) { [int]$InputValue.QuotaUsedQuota } else { 90 }
1616
$ExcludedRaw = Get-CIPPTextReplacement -TenantFilter $TenantFilter -Text ([string]$InputValue.QuotaUsedExcludedMailboxes)
1717
$Excluded = @($ExcludedRaw -split ',' | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
18+
$MailboxTypes = @($InputValue.QuotaUsedMailboxTypes.value | Where-Object { $_ })
1819

1920
try {
2021
$AlertData = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/getMailboxUsageDetail(period='D7')?`$format=application/json&`$top=999" -tenantid $TenantFilter
@@ -27,8 +28,10 @@ function Get-CIPPAlertQuotaUsed {
2728
$OverQuota = $AlertData | ForEach-Object {
2829
if (!$_.StorageUsedInBytes -or !$_.prohibitSendReceiveQuotaInBytes) { return }
2930
if ($Excluded -contains $_.userPrincipalName.ToLower()) { return }
31+
# Report returns 'User'/'Shared' or 'UserMailbox'/'SharedMailbox' depending on tenant; normalize before matching
32+
if ($MailboxTypes.Count -gt 0 -and ($_.recipientType -replace 'Mailbox$') -notin $MailboxTypes) { return }
3033
$UsagePercent = [math]::Round(($_.storageUsedInBytes / $_.prohibitSendReceiveQuotaInBytes) * 100)
31-
if ($UsagePercent -gt $Threshold) {
34+
if ($UsagePercent -ge $Threshold) {
3235
[PSCustomObject]@{
3336
Message = "$($_.userPrincipalName): Mailbox is more than $($Threshold)% full. Mailbox is $UsagePercent% full"
3437
Owner = $_.userPrincipalName

Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ function Add-CIPPApplicationPermission {
3131
} else {
3232
if (!$RequiredResourceAccess -and $TemplateId) {
3333
Write-Information "Adding application permissions for template $TemplateId"
34-
$TemplateTable = Get-CIPPTable -TableName 'templates'
35-
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
36-
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
37-
$ApplicationId = $Template.AppId
38-
$Permissions = $Template.Permissions
34+
$TemplatePermissions = Get-CIPPAppApprovalPermissions -TemplateId $TemplateId
35+
$ApplicationId = $TemplatePermissions.ApplicationId
36+
$Permissions = $TemplatePermissions.Permissions
3937
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
4038
foreach ($AppId in $Permissions.PSObject.Properties.Name) {
4139
$AppPermissions = @($Permissions.$AppId.applicationPermissions)

Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,9 @@ function Add-CIPPDelegatedPermission {
4545
} else {
4646
if (!$RequiredResourceAccess -and $TemplateId) {
4747
Write-Information "Adding delegated permissions for template $TemplateId"
48-
$TemplateTable = Get-CIPPTable -TableName 'templates'
49-
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
50-
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
51-
$ApplicationId = $Template.AppId
52-
$Permissions = $Template.Permissions
48+
$TemplatePermissions = Get-CIPPAppApprovalPermissions -TemplateId $TemplateId
49+
$ApplicationId = $TemplatePermissions.ApplicationId
50+
$Permissions = $TemplatePermissions.Permissions
5351
$NoTranslateRequired = $true
5452
$RequiredResourceAccess = [System.Collections.Generic.List[object]]::new()
5553
foreach ($AppId in $Permissions.PSObject.Properties.Name) {
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
function Get-CIPPAppApprovalPermissions {
2+
<#
3+
.SYNOPSIS
4+
Resolves the effective permissions for an App Approval template.
5+
.DESCRIPTION
6+
App Approval templates persist a copy of the permission set at the time the template was saved.
7+
That copy goes stale the moment the linked permission set is edited, so when a template links to
8+
a permission set the set is treated as the source of truth. Templates without a PermissionSetId
9+
(older or hand-built ones) fall back to the copy stored on the template.
10+
.PARAMETER TemplateId
11+
RowKey of the AppApprovalTemplate to resolve.
12+
#>
13+
[CmdletBinding()]
14+
param(
15+
[Parameter(Mandatory = $true)]
16+
[string]$TemplateId
17+
)
18+
19+
$TemplateTable = Get-CIPPTable -TableName 'templates'
20+
$Filter = "RowKey eq '$TemplateId' and PartitionKey eq 'AppApprovalTemplate'"
21+
$Template = (Get-CIPPAzDataTableEntity @TemplateTable -Filter $Filter).JSON | ConvertFrom-Json -ErrorAction SilentlyContinue
22+
23+
if (!$Template) {
24+
Write-Information "App approval template $TemplateId not found"
25+
return $null
26+
}
27+
28+
$Permissions = $Template.Permissions
29+
30+
if ($Template.PermissionSetId) {
31+
$PermissionsTable = Get-CIPPTable -TableName 'AppPermissions'
32+
$SetFilter = "PartitionKey eq 'Templates' and RowKey eq '$($Template.PermissionSetId)'"
33+
$PermissionSet = Get-CIPPAzDataTableEntity @PermissionsTable -Filter $SetFilter
34+
35+
if ($PermissionSet.Permissions) {
36+
$SetPermissions = $PermissionSet.Permissions | ConvertFrom-Json -ErrorAction SilentlyContinue
37+
if ($SetPermissions) {
38+
$Permissions = $SetPermissions
39+
} else {
40+
Write-Information "Permission set $($Template.PermissionSetId) for template $TemplateId could not be parsed, falling back to the permissions stored on the template"
41+
}
42+
} else {
43+
Write-Information "Permission set $($Template.PermissionSetId) for template $TemplateId not found, falling back to the permissions stored on the template"
44+
}
45+
}
46+
47+
return [PSCustomObject]@{
48+
ApplicationId = $Template.AppId
49+
Permissions = $Permissions
50+
}
51+
}

0 commit comments

Comments
 (0)