Skip to content

Commit b581e29

Browse files
authored
Merge pull request #559 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents 90fef47 + 788ca88 commit b581e29

File tree

3 files changed

+225
-22
lines changed

3 files changed

+225
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
using namespace System.Net
2+
3+
function Invoke-ExecCreateAppTemplate {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Tenant.Application.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $TriggerMetadata.FunctionName
14+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message 'Accessed this API' -Sev 'Debug'
15+
16+
try {
17+
$TenantFilter = $Request.Body.TenantFilter
18+
$AppId = $Request.Body.AppId
19+
$DisplayName = $Request.Body.DisplayName
20+
$Type = $Request.Body.Type # 'servicePrincipal' or 'application'
21+
22+
if ([string]::IsNullOrWhiteSpace($AppId)) {
23+
throw 'AppId is required'
24+
}
25+
26+
if ([string]::IsNullOrWhiteSpace($DisplayName)) {
27+
throw 'DisplayName is required'
28+
}
29+
30+
# Get the app details based on type
31+
if ($Type -eq 'servicePrincipal') {
32+
# For enterprise apps (service principals)
33+
$AppDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$filter=appId eq '$AppId'&`$select=id,appId,displayName,appRoles,oauth2PermissionScopes,requiredResourceAccess" -tenantid $TenantFilter
34+
35+
if (-not $AppDetails -or $AppDetails.Count -eq 0) {
36+
throw "Service principal not found for AppId: $AppId"
37+
}
38+
39+
$App = $AppDetails[0]
40+
41+
# Get the application registration to access requiredResourceAccess
42+
try {
43+
$AppRegistration = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications?`$filter=appId eq '$AppId'&`$select=id,appId,displayName,requiredResourceAccess" -tenantid $TenantFilter
44+
if ($AppRegistration -and $AppRegistration.Count -gt 0) {
45+
$RequiredResourceAccess = $AppRegistration[0].requiredResourceAccess
46+
} else {
47+
$RequiredResourceAccess = @()
48+
}
49+
} catch {
50+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Could not retrieve app registration for $AppId - will extract from service principal" -Sev 'Warning'
51+
$RequiredResourceAccess = @()
52+
}
53+
54+
# Use requiredResourceAccess if available, otherwise we can't create a proper template
55+
if ($RequiredResourceAccess -and $RequiredResourceAccess.Count -gt 0) {
56+
$Permissions = $RequiredResourceAccess
57+
} else {
58+
# No permissions found - warn the user
59+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "No permissions found for $AppId. The app registration may not have configured API permissions." -Sev 'Warning'
60+
$Permissions = @()
61+
}
62+
} else {
63+
# For app registrations (applications)
64+
$AppDetails = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications?`$filter=appId eq '$AppId'&`$select=id,appId,displayName,requiredResourceAccess" -tenantid $TenantFilter
65+
if (-not $AppDetails -or $AppDetails.Count -eq 0) {
66+
throw "Application not found for AppId: $AppId"
67+
}
68+
$App = $AppDetails[0]
69+
$Permissions = if ($App.requiredResourceAccess) { $App.requiredResourceAccess } else { @() }
70+
}
71+
72+
# Transform requiredResourceAccess to the CIPP permission format
73+
# CIPP expects: { "resourceAppId": { "applicationPermissions": [], "delegatedPermissions": [] } }
74+
# Graph returns: [ { "resourceAppId": "...", "resourceAccess": [ { "id": "...", "type": "Role|Scope" } ] } ]
75+
$CIPPPermissions = @{}
76+
$PermissionSetId = $null
77+
$PermissionSetName = "$DisplayName (Auto-created)"
78+
79+
if ($Permissions -and $Permissions.Count -gt 0) {
80+
foreach ($Resource in $Permissions) {
81+
$ResourceAppId = $Resource.resourceAppId
82+
$AppPerms = [System.Collections.ArrayList]::new()
83+
$DelegatedPerms = [System.Collections.ArrayList]::new()
84+
85+
foreach ($Access in $Resource.resourceAccess) {
86+
$PermObj = [PSCustomObject]@{
87+
id = $Access.id
88+
value = $Access.id # In the permission set format, both id and value are the permission ID
89+
}
90+
91+
if ($Access.type -eq 'Role') {
92+
[void]$AppPerms.Add($PermObj)
93+
} elseif ($Access.type -eq 'Scope') {
94+
[void]$DelegatedPerms.Add($PermObj)
95+
}
96+
}
97+
98+
$CIPPPermissions[$ResourceAppId] = [PSCustomObject]@{
99+
applicationPermissions = @($AppPerms)
100+
delegatedPermissions = @($DelegatedPerms)
101+
}
102+
}
103+
104+
# Create the permission set in AppPermissions table
105+
$PermissionSetId = (New-Guid).Guid
106+
$PermissionsTable = Get-CIPPTable -TableName 'AppPermissions'
107+
108+
$PermissionEntity = @{
109+
'PartitionKey' = 'Templates'
110+
'RowKey' = [string]$PermissionSetId
111+
'TemplateName' = [string]$PermissionSetName
112+
'Permissions' = [string]($CIPPPermissions | ConvertTo-Json -Depth 10 -Compress)
113+
'UpdatedBy' = [string]'CIPP-API'
114+
}
115+
116+
Add-CIPPAzDataTableEntity @PermissionsTable -Entity $PermissionEntity -Force
117+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Permission set created with ID: $PermissionSetId for $($Permissions.Count) resource(s)" -Sev 'Info'
118+
}
119+
120+
# Create the template
121+
$Table = Get-CIPPTable -TableName 'templates'
122+
$TemplateId = (New-Guid).Guid
123+
124+
$TemplateJson = @{
125+
TemplateName = "$DisplayName (Auto-created)"
126+
AppId = $AppId
127+
AppName = $DisplayName
128+
AppType = 'EnterpriseApp'
129+
Permissions = $CIPPPermissions
130+
PermissionSetId = $PermissionSetId
131+
PermissionSetName = $PermissionSetName
132+
AutoCreated = $true
133+
SourceTenant = $TenantFilter
134+
CreatedDate = (Get-Date).ToString('yyyy-MM-ddTHH:mm:ss')
135+
} | ConvertTo-Json -Depth 10 -Compress
136+
137+
$Entity = @{
138+
JSON = "$TemplateJson"
139+
RowKey = "$TemplateId"
140+
PartitionKey = 'AppApprovalTemplate'
141+
}
142+
143+
Add-CIPPAzDataTableEntity @Table -Entity $Entity
144+
145+
$PermissionCount = 0
146+
if ($CIPPPermissions -and $CIPPPermissions.Count -gt 0) {
147+
foreach ($ResourceAppId in $CIPPPermissions.Keys) {
148+
$Resource = $CIPPPermissions[$ResourceAppId]
149+
if ($Resource.applicationPermissions) {
150+
$PermissionCount = $PermissionCount + $Resource.applicationPermissions.Count
151+
}
152+
if ($Resource.delegatedPermissions) {
153+
$PermissionCount = $PermissionCount + $Resource.delegatedPermissions.Count
154+
}
155+
}
156+
}
157+
158+
$Message = "Template created: $DisplayName with $PermissionCount permission(s)"
159+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message $Message -Sev 'Info'
160+
161+
$Body = @{
162+
Results = $Message
163+
TemplateId = $TemplateId
164+
}
165+
$StatusCode = [HttpStatusCode]::OK
166+
} catch {
167+
$ErrorMessage = Get-NormalizedError -Message $_.Exception.Message
168+
Write-LogMessage -user $Request.headers.'x-ms-client-principal' -API $APINAME -message "Failed to create template: $ErrorMessage" -Sev 'Error'
169+
170+
$Body = @{
171+
Results = "Failed to create template: $ErrorMessage"
172+
}
173+
$StatusCode = [HttpStatusCode]::BadRequest
174+
}
175+
176+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
177+
StatusCode = $StatusCode
178+
Body = ($Body | ConvertTo-Json -Depth 10)
179+
})
180+
}

Modules/CIPPCore/Public/Entrypoints/Invoke-ListLogs.ps1

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ function Invoke-ListLogs {
99
param($Request, $TriggerMetadata)
1010
$Table = Get-CIPPTable
1111

12+
$TemplatesTable = Get-CIPPTable -tablename 'templates'
13+
$Templates = Get-CIPPAzDataTableEntity @TemplatesTable
14+
1215
$ReturnedLog = if ($Request.Query.ListLogs) {
1316
Get-AzDataTableEntity @Table -Property PartitionKey | Sort-Object -Unique PartitionKey | Select-Object PartitionKey | ForEach-Object {
1417
@{
@@ -32,13 +35,11 @@ function Invoke-ListLogs {
3235
if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) {
3336

3437
if ($Row.StandardTemplateId) {
35-
$TemplatesTable = Get-CIPPTable -tablename 'templates'
36-
$Templates = Get-CIPPAzDataTableEntity @TemplatesTable
37-
3838
$Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json
3939

4040
$StandardInfo = @{
41-
Standard = $Standard.templateName
41+
Template = $Standard.templateName
42+
Standard = $Row.Standard
4243
}
4344

4445
if ($Row.IntuneTemplateId) {
@@ -84,6 +85,7 @@ function Invoke-ListLogs {
8485
$username = $Request.Query.User ?? '*'
8586
$TenantFilter = $Request.Query.Tenant
8687
$ApiFilter = $Request.Query.API
88+
$StandardFilter = $Request.Query.StandardTemplateId
8789

8890
$StartDate = $Request.Query.StartDate ?? $Request.Query.DateFilter
8991
$EndDate = $Request.Query.EndDate ?? $Request.Query.DateFilter
@@ -114,7 +116,8 @@ function Invoke-ListLogs {
114116
$_.Severity -in $LogLevel -and
115117
$_.Username -like $username -and
116118
($TenantFilter -eq $null -or $TenantFilter -eq 'AllTenants' -or $_.Tenant -like "*$TenantFilter*" -or $_.TenantID -eq $TenantFilter) -and
117-
($ApiFilter -eq $null -or $_.API -match "$ApiFilter")
119+
($ApiFilter -eq $null -or $_.API -match "$ApiFilter") -and
120+
($StandardFilter -eq $null -or $_.StandardTemplateId -eq $StandardFilter)
118121
}
119122

120123
if ($AllowedTenants -notcontains 'AllTenants') {
@@ -123,26 +126,46 @@ function Invoke-ListLogs {
123126

124127
foreach ($Row in $Rows) {
125128
if ($AllowedTenants -contains 'AllTenants' -or ($AllowedTenants -notcontains 'AllTenants' -and ($TenantList.defaultDomainName -contains $Row.Tenant -or $Row.Tenant -eq 'CIPP' -or $TenantList.customerId -contains $Row.TenantId)) ) {
129+
if ($Row.StandardTemplateId) {
130+
$Standard = ($Templates | Where-Object { $_.RowKey -eq $Row.StandardTemplateId }).JSON | ConvertFrom-Json
131+
132+
$StandardInfo = @{
133+
Template = $Standard.templateName
134+
Standard = $Row.Standard
135+
}
136+
137+
if ($Row.IntuneTemplateId) {
138+
$IntuneTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.IntuneTemplateId }).JSON | ConvertFrom-Json
139+
$StandardInfo.IntunePolicy = $IntuneTemplate.displayName
140+
}
141+
if ($Row.ConditionalAccessTemplateId) {
142+
$ConditionalAccessTemplate = ($Templates | Where-Object { $_.RowKey -eq $Row.ConditionalAccessTemplateId }).JSON | ConvertFrom-Json
143+
$StandardInfo.ConditionalAccessPolicy = $ConditionalAccessTemplate.displayName
144+
}
145+
} else {
146+
$StandardInfo = @{}
147+
}
126148

127149
$LogData = if ($Row.LogData -and (Test-Json -Json $Row.LogData -ErrorAction SilentlyContinue)) {
128150
$Row.LogData | ConvertFrom-Json
129151
} else { $Row.LogData }
130152
[PSCustomObject]@{
131-
DateTime = $Row.Timestamp
132-
Tenant = $Row.Tenant
133-
API = $Row.API
134-
Message = $Row.Message
135-
User = $Row.Username
136-
Severity = $Row.Severity
137-
LogData = $LogData
138-
TenantID = if ($Row.TenantID -ne $null) {
153+
DateTime = $Row.Timestamp
154+
Tenant = $Row.Tenant
155+
API = $Row.API
156+
Message = $Row.Message
157+
User = $Row.Username
158+
Severity = $Row.Severity
159+
LogData = $LogData
160+
TenantID = if ($Row.TenantID -ne $null) {
139161
$Row.TenantID
140162
} else {
141163
'None'
142164
}
143-
AppId = $Row.AppId
144-
IP = $Row.IP
145-
RowKey = $Row.RowKey
165+
AppId = $Row.AppId
166+
IP = $Row.IP
167+
RowKey = $Row.RowKey
168+
StandardInfo = $StandardInfo
146169
}
147170
}
148171
}

Modules/CIPPCore/Public/New-CIPPCAPolicy.ps1

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ function New-CIPPCAPolicy {
153153
name = ($CheckExisting | Where-Object -Property displayName -EQ $Location.displayName).displayName
154154
templateId = $location.id
155155
}
156-
Write-LogMessage -Headers $User -API $APINAME -message "Matched a CA policy with the existing Named Location: $($location.displayName)" -Sev 'Info'
156+
Write-LogMessage -Tenant $TenantFilter -Headers $User -API $APINAME -message "Matched a CA policy with the existing Named Location: $($location.displayName)" -Sev 'Info'
157157

158158
} else {
159159
if ($location.countriesAndRegions) { $location.countriesAndRegions = @($location.countriesAndRegions) }
@@ -169,7 +169,7 @@ function New-CIPPCAPolicy {
169169
Start-Sleep -Seconds 2
170170
$retryCount++
171171
} while ((!$LocationRequest -or !$LocationRequest.id) -and ($retryCount -lt 5))
172-
Write-LogMessage -Headers $User -API $APINAME -message "Created new Named Location: $($location.displayName)" -Sev 'Info'
172+
Write-LogMessage -Tenant $TenantFilter -Headers $User -API $APINAME -message "Created new Named Location: $($location.displayName)" -Sev 'Info'
173173
[pscustomobject]@{
174174
id = $GraphRequest.id
175175
name = $GraphRequest.displayName
@@ -248,7 +248,7 @@ function New-CIPPCAPolicy {
248248
}
249249
} catch {
250250
$ErrorMessage = Get-CippException -Exception $_
251-
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to replace displayNames for conditional access rule $($JSONobj.displayName). Error: $($ErrorMessage.NormalizedError)" -sev 'Error' -LogData $ErrorMessage
251+
Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Failed to replace displayNames for conditional access rule $($JSONobj.displayName). Error: $($ErrorMessage.NormalizedError)" -sev 'Error' -LogData $ErrorMessage
252252
throw "Failed to replace displayNames for conditional access rule $($JSONobj.displayName): $($ErrorMessage.NormalizedError)"
253253
}
254254
}
@@ -278,8 +278,8 @@ function New-CIPPCAPolicy {
278278
#Send request to disable security defaults.
279279
$body = '{ "isEnabled": false }'
280280
try {
281-
$null = New-GraphPostRequest -tenantid $tenant -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -asApp $true -ContentType 'application/json'
282-
Write-LogMessage -Headers $User -API 'Create CA Policy' -tenant $($Tenant) -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info'
281+
$null = New-GraphPostRequest -tenantid $TenantFilter -Uri 'https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy' -Type patch -Body $body -asApp $true -ContentType 'application/json'
282+
Write-LogMessage -Headers $User -API 'Create CA Policy' -tenant $TenantFilter -message "Disabled Security Defaults for tenant $($TenantFilter)" -Sev 'Info'
283283
Start-Sleep 3
284284
} catch {
285285
$ErrorMessage = Get-CippException -Exception $_
@@ -340,7 +340,7 @@ function New-CIPPCAPolicy {
340340
}
341341
} catch {
342342
$ErrorMessage = Get-CippException -Exception $_
343-
Write-LogMessage -API 'Standards' -tenant $tenant -message "Failed to create or update conditional access rule $($JSONobj.displayName): $($ErrorMessage.NormalizedError) " -sev 'Error' -LogData $ErrorMessage
343+
Write-LogMessage -API 'Standards' -tenant $TenantFilter -message "Failed to create or update conditional access rule $($JSONobj.displayName): $($ErrorMessage.NormalizedError) " -sev 'Error' -LogData $ErrorMessage
344344

345345
Write-Warning "Failed to create or update conditional access rule $($JSONobj.displayName): $($ErrorMessage.NormalizedError)"
346346
Write-Information $_.InvocationInfo.PositionMessage

0 commit comments

Comments
 (0)