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

Extension data sync #940

Merged
merged 4 commits into from
Jul 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Modules/CIPPCore/Public/Add-CIPPApplicationPermission.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ function Add-CIPPApplicationPermission {
$Tenantfilter
)
if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
#return @('Cannot modify application permissions for CIPP-SAM on partner tenant')
$RequiredResourceAccess = 'CIPPDefaults'
}
Set-Location (Get-Item $PSScriptRoot).FullName
if ($RequiredResourceAccess -eq 'CIPPDefaults') {
Expand Down
16 changes: 13 additions & 3 deletions Modules/CIPPCore/Public/Add-CIPPDelegatedPermission.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,14 @@ function Add-CIPPDelegatedPermission {
Set-Location (Get-Item $PSScriptRoot).FullName

if ($ApplicationId -eq $ENV:ApplicationID -and $Tenantfilter -eq $env:TenantID) {
return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
#return @('Cannot modify delgated permissions for CIPP-SAM on partner tenant')
$RequiredResourceAccess = 'CIPPDefaults'
}

if ($RequiredResourceAccess -eq 'CIPPDefaults') {
$RequiredResourceAccess = (Get-Content '.\SAMManifest.json' | ConvertFrom-Json).requiredResourceAccess
$AdditionalPermissions = Get-Content '.\AdditionalPermissions.json' | ConvertFrom-Json
$RequiredResourceAccess = $RequiredResourceAccess + ($AdditionalPermissions | Where-Object { $RequiredResourceAccess.resourceAppId -notcontains $_.resourceAppId })
}
$Translator = Get-Content '.\PermissionsTranslator.json' | ConvertFrom-Json
$ServicePrincipalList = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$select=AppId,id,displayName&`$top=999" -tenantid $Tenantfilter -skipTokenCache $true
Expand All @@ -22,10 +25,17 @@ function Add-CIPPDelegatedPermission {

$CurrentDelegatedScopes = New-GraphGETRequest -uri "https://graph.microsoft.com/beta/servicePrincipals/$($ourSVCPrincipal.id)/oauth2PermissionGrants" -skipTokenCache $true -tenantid $Tenantfilter

foreach ($App in $requiredResourceAccess) {
foreach ($App in $RequiredResourceAccess) {
$svcPrincipalId = $ServicePrincipalList | Where-Object -Property AppId -EQ $App.resourceAppId
$AdditionalScopes = ($AdditionalPermissions | Where-Object -Property resourceAppId -EQ $App.resourceAppId).resourceAccess
if (!$svcPrincipalId) { continue }
$NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
if ($AdditionalScopes) {
$NewScope = (($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value + $AdditionalScopes.id | Select-Object -Unique) -join ' '
Write-Host "NEW SCOPE: $NewScope"
} else {
$NewScope = ($Translator | Where-Object { $_.id -in $App.ResourceAccess.id }).value -join ' '
}

$OldScope = ($CurrentDelegatedScopes | Where-Object -Property Resourceid -EQ $svcPrincipalId.id)

if (!$OldScope) {
Expand Down
8 changes: 6 additions & 2 deletions Modules/CIPPCore/Public/Add-CIPPScheduledTask.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ function Add-CIPPScheduledTask {
[CmdletBinding()]
param(
[pscustomobject]$Task,
[bool]$Hidden
[bool]$Hidden,
[string]$SyncType = $null
)

$Table = Get-CIPPTable -TableName 'ScheduledTasks'
Expand Down Expand Up @@ -49,10 +50,13 @@ function Add-CIPPScheduledTask {
Hidden = [bool]$Hidden
Results = 'Planned'
}
if ($SyncType) {
$entity.SyncType = $SyncType
}
try {
Add-CIPPAzDataTableEntity @Table -Entity $entity -Force
} catch {
return "Could not add task: $($_.Exception.Message)"
}
return "Successfully added task: $($entity.Name)"
}
}
15 changes: 15 additions & 0 deletions Modules/CIPPCore/Public/AdditionalPermissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [{ "id": "AllProfiles.Manage", "type": "Scope" }]
},
{
"resourceAppId": "fb78d390-0c51-40cd-8e17-fdbfab77341b",
"resourceAccess": [
{ "id": "AdminApi.AccessAsUser.All", "type": "Scope" },
{ "id": "FfoPowerShell.AccessAsUser.All", "type": "Scope" },
{ "id": "RemotePowerShell.AccessAsUser.All", "type": "Scope" },
{ "id": "VivaFeatureAccessPolicy.Manage.All", "type": "Scope" }
]
}
]
3 changes: 1 addition & 2 deletions Modules/CIPPCore/Public/SAMManifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@
{
"resourceAppId": "00000003-0000-0ff1-ce00-000000000000",
"resourceAccess": [
{ "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" },
{ "id": "ec4fc4c8-872e-442b-a2a2-d095575807b3", "type": "Scope" }
{ "id": "56680e0d-d2a3-4ae1-80d8-3c4f2100e3d0", "type": "Scope" }
]
},
{
Expand Down
10 changes: 7 additions & 3 deletions Modules/CIPPCore/Public/Set-CIPPSPOTenant.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,14 @@ function Set-CIPPSPOTenant {
# Get property type
$PropertyType = $Properties[$Property].GetType().Name
if ($PropertyType -in $AllowedTypes) {
if ($PropertyType -eq 'Boolean') { $Properties[$Property] = $Properties[$Property].ToString().ToLower() }
if ($PropertyType -eq 'Boolean') {
$PropertyToSet = $Properties[$Property].ToString().ToLower()
} else {
$PropertyToSet = $Properties[$Property]
}
$xml = @"
<SetProperty Id="$x" ObjectPathId="110" Name="$Property">
<Parameter Type="Boolean">$($Properties[$Property])</Parameter>
<Parameter Type="Boolean">$($PropertyToSet)</Parameter>
</SetProperty>
"@
$SetProperty.Add($xml)
Expand All @@ -85,4 +89,4 @@ function Set-CIPPSPOTenant {
New-GraphPostRequest -scope "$AdminURL/.default" -tenantid $TenantFilter -Uri "$AdminURL/_vti_bin/client.svc/ProcessQuery" -Type POST -Body $XML -ContentType 'text/xml' -AddedHeaders $AdditionalHeaders
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function Push-ExtensionSyncData {
param(
$TenantFilter,
$Extension
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
function Register-CIPPExtensionScheduledTasks {
Param(
[switch]$Reschedule
)

# get extension configuration and mappings table
$Table = Get-CIPPTable -TableName Extensionsconfig
$Config = ((Get-CIPPAzDataTableEntity @Table).config | ConvertFrom-Json -ea stop)
$MappingsTable = Get-CIPPTable -TableName CippMapping

# Get existing scheduled usertasks
$ScheduledTasksTable = Get-CIPPTable -TableName ScheduledTasks
$ScheduledTasks = Get-CIPPAzDataTableEntity @ScheduledTasksTable -Filter 'Hidden eq true' | Where-Object { $_.Command -match 'Sync-CippExtensionData' }
$Tenants = Get-Tenants -IncludeErrors

$Extensions = @('Hudu')

foreach ($Extension in $Extensions) {
$ExtensionConfig = $Config.$Extension
if ($ExtensionConfig.Enabled -eq $true) {
$Mappings = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)Mapping'"
$FieldMapping = Get-CIPPAzDataTableEntity @MappingsTable -Filter "PartitionKey eq '$($Extension)FieldMapping'"
$FieldSync = @{}
$SyncTypes = [System.Collections.Generic.List[string]]::new()

foreach ($Mapping in $FieldMapping) {
$FieldSync[$Mapping.RowKey] = !([string]::IsNullOrEmpty($Mapping.IntegrationId))
}

$SyncTypes.Add('Overview')

if ($FieldSync.Users) {
$SyncTypes.Add('Users')
$SyncTypes.Add('Mailboxes')
}
if ($FieldSync.Devices) {
$SyncTypes.Add('Devices')
}

foreach ($Mapping in $Mappings) {
$Tenant = $Tenants | Where-Object { $_.customerId -eq $Mapping.RowKey }

foreach ($SyncType in $SyncTypes) {
$ExistingTask = $ScheduledTasks | Where-Object { $_.Tenant -eq $Tenant.defaultDomainName -and $_.SyncType -eq $SyncType }
if (!$ExistingTask -or $Reschedule.IsPresent) {
$unixtime = [int64](([datetime]::UtcNow) - (Get-Date '1/1/1970')).TotalSeconds
$Task = @{
Name = "Extension Sync - $SyncType"
Command = @{
value = 'Sync-CippExtensionData'
label = 'Sync-CippExtensionData'
}
Parameters = @{
TenantFilter = $Tenant.defaultDomainName
SyncType = $SyncType
}
Recurrence = '1d'
ScheduledTime = $unixtime
TenantFilter = $Tenant.defaultDomainName
}
if ($ExistingTask) {
$Task.RowKey = $ExistingTask.RowKey
}
$null = Add-CIPPScheduledTask -Task $Task -hidden $true -SyncType $SyncType
}
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
function Sync-CippExtensionData {
<#
.FUNCTIONALITY
Internal
#>
[CmdletBinding()]
param(
$TenantFilter,
$SyncType
)

$Table = Get-CIPPTable -TableName ExtensionSync
$Extensions = Get-CIPPAzDataTableEntity @Table -Filter "PartitionKey eq '$($SyncType)'"
$LastSync = $Extensions | Where-Object { $_.RowKey -eq $TenantFilter }
$CacheTable = Get-CIPPTable -tablename 'CacheExtensionSync'

if (!$LastSync) {
$LastSync = @{
PartitionKey = $SyncType
RowKey = $TenantFilter
Status = 'Not Synced'
Error = ''
LastSync = 'Never'
}
Add-CIPPAzDataTableEntity @Table -Entity $LastSync
}

try {
switch ($SyncType) {
'Overview' {
# Build bulk requests array.
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
@{
id = 'TenantDetails'
method = 'GET'
url = '/organization'
},
@{
id = 'AllRoles'
method = 'GET'
url = '/directoryRoles?$top=999'
},
@{
id = 'Domains'
method = 'GET'
url = '/domains$top=999'
},
@{
id = 'Licenses'
method = 'GET'
url = '/subscribedSkus?$top=999'
},
@{
id = 'Groups'
method = 'GET'
url = '/groups?$top=999&$select=id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes,onPremisesSyncEnabled,resourceProvisioningOptions,userPrincipalName'
},
@{
id = 'ConditionalAccess'
method = 'GET'
url = '/identity/conditionalAccess/policies'
},
@{
id = 'SecureScoreControlProfiles'
method = 'GET'
url = '/security/secureScoreControlProfiles?$top=999'
},
@{
id = 'Subscriptions'
method = 'GET'
url = '/directory/subscriptions?$top=999'
}
)

$SingleGraphQueries = @(@{
id = 'SecureScore'
graphRequest = @{
uri = 'https://graph.microsoft.com/beta/security/secureScores?$top=1'
noPagination = $true
}
})
}
'Users' {
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
@{
id = 'Users'
method = 'GET'
url = '/users?$top=999&$select=id,accountEnabled,businessPhones,city,createdDateTime,companyName,country,department,displayName,faxNumber,givenName,isResourceAccount,jobTitle,mail,mailNickname,mobilePhone,onPremisesDistinguishedName,officeLocation,onPremisesLastSyncDateTime,otherMails,postalCode,preferredDataLocation,preferredLanguage,proxyAddresses,showInAddressList,state,streetAddress,surname,usageLocation,userPrincipalName,userType,assignedLicenses,onPremisesSyncEnabled'
}
)
}
'Devices' {
[System.Collections.Generic.List[PSCustomObject]]$TenantRequests = @(
@{
id = 'Devices'
method = 'GET'
url = '/deviceManagement/managedDevices?$top=999'
},
@{
id = 'DeviceCompliancePolicies'
method = 'GET'
url = '/deviceManagement/deviceCompliancePolicies'
},
@{
id = 'DeviceApps'
method = 'GET'
url = '/deviceAppManagement/mobileApps'
}
)
}
'Mailboxes' {
$Select = 'id,ExchangeGuid,ArchiveGuid,UserPrincipalName,DisplayName,PrimarySMTPAddress,RecipientType,RecipientTypeDetails,EmailAddresses,WhenSoftDeleted,IsInactiveMailbox'
$ExoRequest = @{
tenantid = $TenantFilter
cmdlet = 'Get-Mailbox'
cmdParams = @{}
Select = $Select
}
$Mailboxes = (New-ExoRequest @ExoRequest) | Select-Object id, ExchangeGuid, ArchiveGuid, WhenSoftDeleted, @{ Name = 'UPN'; Expression = { $_.'UserPrincipalName' } },

@{ Name = 'displayName'; Expression = { $_.'DisplayName' } },
@{ Name = 'primarySmtpAddress'; Expression = { $_.'PrimarySMTPAddress' } },
@{ Name = 'recipientType'; Expression = { $_.'RecipientType' } },
@{ Name = 'recipientTypeDetails'; Expression = { $_.'RecipientTypeDetails' } },
@{ Name = 'AdditionalEmailAddresses'; Expression = { ($_.'EmailAddresses' | Where-Object { $_ -clike 'smtp:*' }).Replace('smtp:', '') -join ', ' } }

$Entity = @{
PartitionKey = $TenantFilter
SyncType = 'Mailboxes'
RowKey = 'Mailboxes'
Data = [string]($Mailboxes | ConvertTo-Json -Depth 10 -Compress)
}
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
}
}

if ($TenantRequests) {
try {
$TenantResults = New-GraphBulkRequest -Requests $TenantRequests -tenantid $TenantFilter
} catch {
Throw "Failed to fetch bulk company data: $_"
}

if ($SingleGraphQueries) {
foreach ($SingleGraphQuery in $SingleGraphQueries) {
$Request = $SingleGraphQuery.graphRequest
$Data = New-GraphGetRequest @Request -tenantid $TenantFilter
$Entity = @{
PartitionKey = $TenantFilter
SyncType = $SyncType
RowKey = $SingleGraphQuery.id
Data = [string]($Data | ConvertTo-Json -Depth 10 -Compress)
}
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
}
}

$TenantResults | Select-Object id, body | ForEach-Object {
$Entity = @{
PartitionKey = $TenantFilter
RowKey = $_.id
SyncType = $SyncType
Data = [string]($_.body.value | ConvertTo-Json -Depth 10 -Compress)
}
Add-CIPPAzDataTableEntity @CacheTable -Entity $Entity -Force
}
}
$LastSync.LastSync = [datetime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
$LastSync.Status = 'Completed'
$LastSync.Error = ''
} catch {
$LastSync.Status = 'Failed'
$LastSync.Error = [string](Get-CippException -Exception $_ | ConvertTo-Json -Compress)
throw "Failed to sync data: $($_.Exception.Message)"
} finally {
Add-CIPPAzDataTableEntity @Table -Entity $LastSync -Force
}
}
Loading