src/graphservice/ApplicationAPI.ps1
# Copyright 2019, Adam Edwards # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. . (import-script ../cmdlets/Invoke-GraphRequest) . (import-script ../common/GraphApplicationCertificate) . (import-script ../common/ScopeHelper) enum AppTenancy { Auto SingleTenant AnyTenant } ScriptClass ApplicationAPI { static { const DefaultApplicationApiVersion beta $TenantToGraphServicePrincipal = @{} } $version = $null $connection = $null function __initialize($connection, $version) { $this.connection = $connection $this.version = if ( $version ) { $version } else { $this.scriptclass.DefaultApplicationApiVersion } } function CreateApp($appObject) { Invoke-GraphRequest applications -method POST -body $appObject -version $this.version -connection $this.connection } function AddKeyCredentials($appObject, $appCertificate) { # This should be additive, but methods to add to the collection # don't seem to work $keyCredentials = @() $encodedCertificate = $appCertificate |=> GetEncodedPublicCertificate $keyCredentials += [PSCustomObject] @{ type = 'AsymmetricX509Cert' usage = 'Verify' key = $encodedCertificate } $appPatch = ( [PSCustomObject] @{ keyCredentials = $keyCredentials } ) | convertto-json -depth 6 Invoke-GraphRequest "applications/$($appObject.Id)" -method PATCH -Body $appPatch -version $this.version -connection $this.connection } function SetKeyCredentials($appId, $keyCredentials) { $keyCredentialPatch = [PSCustomObject] @{ keyCredentials = $keyCredentials } Invoke-GraphRequest "applications/$appId" -method PATCH -Body $keyCredentialPatch -version $this.version -connection $this.connection | out-null } function RegisterApplication($appId, $isExternal) { write-verbose "Attempting to register existing appliation '$appId', isExternalTenant: '$isExternal'" if ( ! $isExternal ) { write-verbose "Looking for existing application '$appId' in this tenant" $existingApp = GetApplicationByAppId $appId if ( ! $existingApp ) { throw "An application with AppId '$AppId' could not be found in this tenant." } write-verbose "Found existing application '$appId' in this tenant" } write-verbose "Looking for existing service principal for application '$appId' in this tenant" $appSP = GetAppServicePrincipal $appId if ( $appSP ) { throw "Application with Application Id '$appID' is already registered with service principal id = '$($appSP.id)'" } write-verbose "No existing service principal found for application '$appId', registering it" $newSP = NewAppServicePrincipal $appId write-verbose "Registered application '$appId' with service principal '$($newSP.id)'" $newSP } function NewAppServicePrincipal($appId) { invoke-graphrequest /servicePrincipals -method POST -body @{appId=$appId} -Version $this.version -connection $this.connection -erroraction stop } function GetAppServicePrincipal($appId, $properties, $errorAction = 'stop') { $selectArguments = @{} if ( $properties ) { $selectArguments['select'] = $properties } $result = invoke-graphrequest /servicePrincipals -method GET -ODataFilter "appId eq '$appId'" -Version $this.version -connection $this.connection -erroraction $errorAction @selectArguments __NormalizeResult $result } function GetApplicationByAppId($appId, $errorAction = 'stop') { $result = invoke-graphrequest /Applications -method GET -ODataFilter "appId eq '$appId'" -Version $this.version -connection $this.connection -erroraction $errorAction __NormalizeResult $result } function GetApplicationByObjectId($objectId, $errorAction = 'stop') { invoke-graphrequest "/Applications/$objectId" -method GET -Version $this.version -connection $this.connection -erroraction $errorAction } function RemoveApplicationByObjectId($objectId, $errorAction = 'stop') { invoke-graphrequest "/Applications/$objectId" -method DELETE -Version $this.version -connection $this.connection -erroraction $erroraction| out-null } function GetReducedPermissionsString($permissionsString, $permissionsToRemove) { $permissions = $permissionsString -split ' ' $newPermissions = $permissions | where { $permissionsToRemove -notcontains $_ } $reducedPermissionsString = $newPermissions -join ' ' if ( $permissionsString -ne $reducedPermissionsString ) { $reducedPermissionsString } } function SetConsent ( $appId, [string[]] $delegatedPermissions, [string[]] $appOnlyPermissions, $allPermissions, $consentForTenant, $userConsentRequired, $userIdToConsent, $appWithRequiredResource, $appSP ) { $consentUser = if ( $userIdToConsent ) { write-verbose "User '$userIdToConsent' specified for consent" $userIdToConsent } elseif ( ! $consentForTenant ) { write-verbose "No user was specified for consent, and consent for the entire tenant was not specified, so consent will be made for the user making this Graph API call" $userObjectId = $this.connection.Identity.GetUserInformation().userObjectId if ( ! $userObjectId -and $userConsentRequired ) { throw "User consent required but no user was specified and user id of current user could not be obtained" } write-verbose "Attempting to grant consent to app '$appId' for current user '$userObjectId'" $userObjectId } else { write-verbose "User consent was not specified, and tenant consent was specified, will attempt to consent all app permissions for the tenant" } if ( $userConsentRequired -and ! $consentUser ) { write-verbose "No user was specified for consent, and user consent was required, so skipping consent completely" return } $grant = GetConsentGrantForApp $appId $consentUser $DelegatedPermissions $AppOnlyPermissions $allPermissions $appWithRequiredResource $appSP Invoke-GraphRequest /oauth2PermissionGrants -method POST -body $grant -version $this.version -connection $this.connection | out-null } function GetConsentGrantForApp( $appId, $consentUser, $scopes = @(), $roles = @(), $ConsentRequiredPermissions, $appWithRequiredResource, $appSP ) { $targetPermissions = if ( ! $ConsentRequiredPermissions ) { $scopes + $roles } else { $permissions = @() if ( $appWithRequiredResource -and $appWithRequiredResource | gm requiredResourceAccess ) { $graphResourceAccess = $appWithRequiredResource.requiredResourceAccess | where resourceAppid -eq 00000003-0000-0000-c000-000000000000 $graphResourceAccess.resourceAccess | foreach { $permissionId = $_.id $permissionName = $::.ScopeHelper |=> GraphPermissionIdToName $permissionId $null $this.connection $permissions += $permissionName } } $permissions } __NewOauth2Grant $appId ($targetPermissions -join ' ') $consentUser } function GetGraphServicePrincipalId($connection) { $tenantId = $connection.identity.TenantDisplayId.tostring() $spId = $this.scriptclass.TenantToGraphServicePrincipal[$tenantId] if ( ! $spId ) { $spResult = GetAppServicePrincipal $::.ScopeHelper.GraphApplicationId @('id') if ( ! $spResult ) { throw 'Unable to find service principal for Microsoft Graph in the tenant' } $spId = $spResult.id $this.scriptclass.TenantToGraphServicePrincipal[$tenantId] = $spId write-verbose "Retrieved Graph service principal id '$spId' for tenant '$tenantId' from Graph" } else { write-verbose "Found Graph service principal id '$spId' for tenant '$tenantId' in cache" } $spId } function __NewOauth2Grant($appId, [string] $permissionName, $consentUserId) { $appSP = GetAppServicePrincipal $appId if ( ! $appSP -or ! ($appSP | gm id -erroraction ignore) ) { throw "Application '$AppId' was not found" } $consentType = if ( $consentUserId ) { 'Principal' } else { 'AllPrincipals' } @{ clientId = $appSP.id consentType = $consentType resourceId = GetGraphServicePrincipalId $this.connection principalId = $consentUserId scope = $permissionName startTime = (([DateTime]::UtcNow) - ([TimeSpan]::FromDays(1))).tostring('s') expiryTime = (([DateTime]::UtcNow) + ([TimeSpan]::FromDays(365))).tostring('s') } } function __NormalizeResult($result) { if ( $result -and ( $result | gm id ) ) { $result } } } |