Modified Identity Scripts to reflect automation credentials, pass ARM Application ID, and onboard multiple directories to ARM at the same time

This commit is contained in:
Shriram Natarajan 2017-05-05 17:00:17 -07:00
Родитель 8cea0d4262
Коммит 1ecc194244
3 изменённых файлов: 842 добавлений и 237 удалений

Просмотреть файл

@ -126,14 +126,14 @@ function Initialize-AzureRmEnvironment([string]$EnvironmentName, [string] $Resou
$directoryTenantId = (New-Object uri(Invoke-RestMethod "$($endpoints.authentication.loginEndpoint.TrimEnd('/'))/$DirectoryTenantName/.well-known/openid-configuration").token_endpoint).AbsolutePath.Split('/')[1]
$azureEnvironmentParams = @{
Name = $EnvironmentName
ActiveDirectoryEndpoint = $endpoints.authentication.loginEndpoint.TrimEnd('/') + "/"
Name = $EnvironmentName
ActiveDirectoryEndpoint = $endpoints.authentication.loginEndpoint.TrimEnd('/') + "/"
ActiveDirectoryServiceEndpointResourceId = $endpoints.authentication.audiences[0]
AdTenant = $directoryTenantId
ResourceManagerEndpoint = $ResourceManagerEndpoint
GalleryEndpoint = $endpoints.galleryEndpoint
GraphEndpoint = $endpoints.graphEndpoint
GraphAudience = $endpoints.graphEndpoint
AdTenant = $directoryTenantId
ResourceManagerEndpoint = $ResourceManagerEndpoint
GalleryEndpoint = $endpoints.galleryEndpoint
GraphEndpoint = $endpoints.graphEndpoint
GraphAudience = $endpoints.graphEndpoint
}
Remove-AzureRmEnvironment -Name $EnvironmentName -Force -ErrorAction Ignore | Out-Null
@ -157,30 +157,35 @@ function Resolve-AzureEnvironment([Microsoft.Azure.Commands.Profile.Models.PSAzu
return $azureEnvironment
}
function Initialize-AzureRmUserAccount([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment, [string] $SubscriptionName, [string] $SubscriptionId) {
# Prompts the user for interactive login flow
$azureAccount = Add-AzureRmAccount -EnvironmentName $azureEnvironment.Name -TenantId $azureEnvironment.AdTenant
function Initialize-AzureRmUserAccount([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment, [string] $SubscriptionName, [string] $SubscriptionId, [pscredential] $AutomationCredential) {
if ($SubscriptionName) {
$params = @{
EnvironmentName = $azureEnvironment.Name
TenantId = $azureEnvironment.AdTenant
}
if ($AutomationCredential)
{
$params += @{ Credential = $AutomationCredential }
}
# Prompts the user for interactive login flow if automation credential is not specified
$azureAccount = Add-AzureRmAccount @params
if ($SubscriptionName)
{
Select-AzureRmSubscription -SubscriptionName $SubscriptionName | Out-Null
}
elseif ($SubscriptionId) {
elseif ($SubscriptionId)
{
Select-AzureRmSubscription -SubscriptionId $SubscriptionId | Out-Null
}
return $azureAccount
}
function Get-IdentityApplicationData {
# Import and read application data
Write-Host "Loading identity application data..."
$xmlData = [xml](Get-ChildItem -Path C:\EceStore -Recurse -Force -File | Sort Length | Select -Last 1 | Get-Content | Out-String)
$xmlIdentityApplications = $xmlData.SelectNodes('//IdentityApplication')
return $xmlIdentityApplications
}
function Resolve-GraphEnvironment([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment) {
function Resolve-GraphEnvironment([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment)
{
$graphEnvironment = switch ($azureEnvironment.ActiveDirectoryAuthority) {
'https://login.microsoftonline.com/' { 'AzureCloud' }
'https://login.chinacloudapi.cn/' { 'AzureChinaCloud' }
@ -193,9 +198,20 @@ function Resolve-GraphEnvironment([Microsoft.Azure.Commands.Profile.Models.PSAzu
return $graphEnvironment
}
function Get-AzureRmUserRefreshToken([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment, [string]$directoryTenantId) {
# Prompts the user for interactive login flow
$azureAccount = Add-AzureRmAccount -EnvironmentName $azureEnvironment.Name -TenantId $directoryTenantId
function Get-AzureRmUserRefreshToken([Microsoft.Azure.Commands.Profile.Models.PSAzureEnvironment]$azureEnvironment, [string]$directoryTenantId, [pscredential]$AutomationCredential)
{
$params = @{
EnvironmentName = $azureEnvironment.Name
TenantId = $directoryTenantId
}
if ($AutomationCredential)
{
$params += @{ Credential = $AutomationCredential }
}
# Prompts the user for interactive login flow if automation credential is not specified
$azureAccount = Add-AzureRmAccount @params
# Retrieve the refresh token
$tokens = [Microsoft.IdentityModel.Clients.ActiveDirectory.TokenCache]::DefaultShared.ReadItems()
@ -203,12 +219,15 @@ function Get-AzureRmUserRefreshToken([Microsoft.Azure.Commands.Profile.Models.PS
Where Resource -EQ $azureEnvironment.ActiveDirectoryServiceEndpointResourceId |
Where IsMultipleResourceRefreshToken -EQ $true |
Where DisplayableId -EQ $azureAccount.Context.Account.Id |
Select -ExpandProperty RefreshToken |
Sort ExpiresOn |
Select -Last 1 -ExpandProperty RefreshToken |
ConvertTo-SecureString -AsPlainText -Force
return $refreshToken
}
# Exposed Functions
<#
.Synopsis
Adds a Guest Directory Tenant to Azure Stack.
@ -236,10 +255,15 @@ function Register-GuestDirectoryTenantToAzureStack {
[ValidateNotNullOrEmpty()]
[string] $DirectoryTenantName,
# The name of the guest Directory Tenant which is to be onboarded.
# The names of the guest Directory Tenants which are to be onboarded.
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $GuestDirectoryTenantName,
[string[]] $GuestDirectoryTenantName,
# The location of your Azure Stack deployment.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $Location,
# The identifier of the Administrator Subscription. If not specified, the script will attempt to use the set default subscription.
[ValidateNotNull()]
@ -253,9 +277,10 @@ function Register-GuestDirectoryTenantToAzureStack {
[ValidateNotNullOrEmpty()]
[string] $ResourceGroupName = 'system',
# Optional: A credential used to authenticate with Azure Stack. Must support a non-interactive authentication flow. If not provided, the script will prompt for user credentials.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $Location = 'local'
[ValidateNotNull()]
[pscredential] $AutomationCredential = $null
)
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
@ -265,134 +290,24 @@ function Register-GuestDirectoryTenantToAzureStack {
# Initialize the Azure PowerShell module to communicate with Azure Stack. Will prompt user for credentials.
$azureEnvironment = Initialize-AzureRmEnvironment -EnvironmentName 'AzureStackAdmin' -ResourceManagerEndpoint $AdminResourceManagerEndpoint -DirectoryTenantName $DirectoryTenantName
$azureAccount = Initialize-AzureRmUserAccount -azureEnvironment $azureEnvironment -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId
$azureAccount = Initialize-AzureRmUserAccount -azureEnvironment $azureEnvironment -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId -AutomationCredential $AutomationCredential
# resolve the guest directory tenant ID from the name
$guestDirectoryTenantId = (New-Object uri(Invoke-RestMethod "$($azureEnvironment.ActiveDirectoryAuthority.TrimEnd('/'))/$GuestDirectoryTenantName/.well-known/openid-configuration").token_endpoint).AbsolutePath.Split('/')[1]
# Add (or update) the new directory tenant to the Azure Stack deployment
$params = @{
ApiVersion = '2015-11-01' # needed if using "latest" / later version of Azure Powershell
ResourceType = "Microsoft.Subscriptions.Admin/directoryTenants"
ResourceGroupName = $ResourceGroupName
ResourceName = $GuestDirectoryTenantName
Location = $Location
Properties = @{ tenantId = $guestDirectoryTenantId }
}
$directoryTenant = New-AzureRmResource @params -Force -Verbose -ErrorAction Stop
Write-Verbose -Message "Directory Tenant onboarded: $(ConvertTo-Json $directoryTenant)" -Verbose
}
<#
.Synopsis
Publishes the list of applications to the Azure Stack ARM.
.DESCRIPTION
.EXAMPLE
$adminARMEndpoint = "https://adminmanagement.local.azurestack.external"
$azureStackDirectoryTenant = "<homeDirectoryTenant>.onmicrosoft.com"
$guestDirectoryTenantToBeOnboarded = "<guestDirectoryTenant>.onmicrosoft.com"
Publish-AzureStackApplicationsToARM -AdminResourceManagerEndpoint $adminARMEndpoint -DirectoryTenantName $azureStackDirectoryTenant
#>
function Publish-AzureStackApplicationsToARM {
[CmdletBinding()]
param
(
# The endpoint of the Azure Stack Resource Manager service.
[Parameter(Mandatory = $true)]
[ValidateNotNull()]
[ValidateScript( {$_.Scheme -eq [System.Uri]::UriSchemeHttps})]
[uri] $AdminResourceManagerEndpoint,
# The name of the home Directory Tenant in which the Azure Stack Administrator subscription resides.
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $DirectoryTenantName,
# The identifier of the Administrator Subscription. If not specified, the script will attempt to use the set default subscription.
[Parameter()]
[ValidateNotNull()]
[string] $SubscriptionId = $null,
# The display name of the Administrator Subscription. If not specified, the script will attempt to use the set default subscription.
[Parameter()]
[ValidateNotNull()]
[string] $SubscriptionName = $null,
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $ResourceGroupName = 'system',
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $Location = 'local',
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateScript( {Test-Path -Path $_ -PathType Container -ErrorAction Stop})]
[string] $InfrastructureSharePath = '\\SU1FileServer\SU1_Infrastructure_1'
)
$ErrorActionPreference = 'Stop'
$VerbosePreference = 'Continue'
# Install-Module AzureRm -RequiredVersion '1.2.8'
Import-Module 'AzureRm.Profile' -Force -Verbose:$false 4> $null
Write-Warning "This script is intended to work only with the initial TP3 release of Azure Stack and will be deprecated."
# Initialize the Azure PowerShell module to communicate with Azure Stack. Will prompt user for credentials.
$azureEnvironment = Initialize-AzureRmEnvironment -EnvironmentName 'AzureStackAdmin' -ResourceManagerEndpoint $AdminResourceManagerEndpoint -DirectoryTenantName $DirectoryTenantName
$azureAccount = Initialize-AzureRmUserAccount -azureEnvironment $azureEnvironment -SubscriptionName $SubscriptionName -SubscriptionId $SubscriptionId
# Register each identity application for future onboarding.
$xmlIdentityApplications = Get-IdentityApplicationData
foreach ($xmlIdentityApplication in $xmlIdentityApplications) {
$applicationData = Get-Content -Path ($xmlIdentityApplication.ConfigPath.Replace('{Infrastructure}', $InfrastructureSharePath)) | Out-String | ConvertFrom-Json
# Note - 'Admin' applications do not need to be registered for replication into a new directory tenant
if ($xmlIdentityApplication.Name.StartsWith('Admin', [System.StringComparison]::OrdinalIgnoreCase)) {
Write-Warning "Skipping registration of Admin application: $('{0}.{1}' -f $xmlIdentityApplication.Name, $xmlIdentityApplication.DisplayName)"
continue
}
# Advertise any necessary OAuth2PermissionGrants for the application
$oauth2PermissionGrants = @()
foreach ($applicationFriendlyName in $xmlIdentityApplication.OAuth2PermissionGrants.FirstPartyApplication.FriendlyName) {
$oauth2PermissionGrants += [pscustomobject]@{
Resource = $applicationData.ApplicationInfo.appId
Client = $applicationData.GraphInfo.Applications."$applicationFriendlyName".Id
ConsentType = 'AllPrincipals'
Scope = 'user_impersonation'
}
}
foreach ($directoryTenantName in $GuestDirectoryTenantName)
{
# Resolve the guest directory tenant ID from the name
$directoryTenantId = (New-Object uri(Invoke-RestMethod "$($azureEnvironment.ActiveDirectoryAuthority.TrimEnd('/'))/$directoryTenantName/.well-known/openid-configuration").token_endpoint).AbsolutePath.Split('/')[1]
# Add (or update) the new directory tenant to the Azure Stack deployment
$params = @{
ApiVersion = '2015-11-01' # needed if using "latest" / later version of Azure Powershell
ResourceType = "Microsoft.Subscriptions.Providers/applicationRegistrations"
ApiVersion = '2015-11-01'
ResourceType = "Microsoft.Subscriptions.Admin/directoryTenants"
ResourceGroupName = $ResourceGroupName
ResourceName = '{0}.{1}' -f $xmlIdentityApplication.Name, $xmlIdentityApplication.DisplayName
Location = $Location
Properties = @{
"objectId" = $applicationData.ApplicationInfo.objectId
"appId" = $applicationData.ApplicationInfo.appId
"oauth2PermissionGrants" = $oauth2PermissionGrants
"directoryRoles" = @()
"tags" = @()
}
ResourceName = $directoryTenantName
Location = $Location
Properties = @{ tenantId = $directoryTenantId }
}
# Advertise 'ReadDirectoryData' workaround for applications which require this permission of type 'Role'
if ($xmlIdentityApplication.AADPermissions.ApplicationPermission.Name -icontains 'ReadDirectoryData') {
$params.Properties.directoryRoles = @('Directory Readers')
}
# Advertise any specified tags required for application integration scenarios
if ($xmlIdentityApplication.tags) {
$params.Properties.tags += $xmlIdentityApplication.tags
}
$registeredApplication = New-AzureRmResource @params -Force -Verbose -ErrorAction Stop
Write-Verbose -Message "Identity application registered: $(ConvertTo-Json $registeredApplication)" -Verbose
$directoryTenant = New-AzureRmResource @params -Force -Verbose -ErrorAction Stop
Write-Verbose -Message "Directory Tenant onboarded: $(ConvertTo-Json $directoryTenant)" -Verbose
}
}
@ -421,7 +336,17 @@ function Register-AzureStackWithMyDirectoryTenant {
# The name of the directory tenant being onboarded.
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[string] $DirectoryTenantName
[string] $DirectoryTenantName,
# Optional: The identifier (GUID) of the Resource Manager application. Pass this parameter to skip the need to complete the guest signup flow via the portal.
[Parameter(Mandatory=$false)]
[ValidateNotNullOrEmpty()]
[string] $ResourceManagerApplicationId,
# Optional: A credential used to authenticate with Azure Stack. Must support a non-interactive authentication flow. If not provided, the script will prompt for user credentials.
[Parameter()]
[ValidateNotNull()]
[pscredential] $AutomationCredential = $null
)
$ErrorActionPreference = 'Stop'
@ -429,17 +354,28 @@ function Register-AzureStackWithMyDirectoryTenant {
# Install-Module AzureRm -RequiredVersion '1.2.8'
Import-Module 'AzureRm.Profile' -Force -Verbose:$false 4> $null
Import-Module "$PSScriptRoot\GraphAPI\GraphAPI.psm1" -Force -Verbose:$false 4> $null
Import-Module "$PSScriptRoot\GraphAPI\GraphAPI.psm1" -Force -Verbose:$false 4> $null
# Initialize the Azure PowerShell module to communicate with the Azure Resource Manager corresponding to their home Graph Service. Will prompt user for credentials.
$azureStackEnvironment = Initialize-AzureRmEnvironment -EnvironmentName 'AzureStack' -ResourceManagerEndpoint $TenantResourceManagerEndpoint -DirectoryTenantName $DirectoryTenantName
$azureEnvironment = Resolve-AzureEnvironment $azureStackEnvironment
$refreshToken = Get-AzureRmUserRefreshToken $azureEnvironment $azureStackEnvironment.AdTenant
$refreshToken = Get-AzureRmUserRefreshToken -azureEnvironment $azureEnvironment -directoryTenantId $azureStackEnvironment.AdTenant -AutomationCredential $AutomationCredential
# Initialize the Graph PowerShell module to communicate with the correct graph service
$graphEnvironment = Resolve-GraphEnvironment $azureEnvironment
Initialize-GraphEnvironment -Environment $graphEnvironment -DirectoryTenantId $DirectoryTenantName -RefreshToken $refreshToken
# Initialize the service principal for the Azure Stack Resource Manager application (allows us to acquire a token to ARM). If not specified, the sign-up flow must be completed via the Azure Stack portal first.
if ($ResourceManagerApplicationId)
{
$resourceManagerServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $ResourceManagerApplicationId
}
# Authorize the Azure Powershell module to act as a client to call the Azure Stack Resource Manager in the onboarding directory tenant
Initialize-GraphOAuth2PermissionGrant -ClientApplicationId (Get-GraphEnvironmentInfo).Applications.PowerShell.Id -ResourceApplicationIdentifierUri $azureStackEnvironment.ActiveDirectoryServiceEndpointResourceId
Write-Host "Delaying for 15 seconds to allow the permission for Azure PowerShell to be initialized..."
Start-Sleep -Seconds 15
# Authorize the Azure Powershell module to act as a client to call the Azure Stack Resource Manager in the onboarded tenant
Initialize-GraphOAuth2PermissionGrant -ClientApplicationId (Get-GraphEnvironmentInfo).Applications.PowerShell.Id -ResourceApplicationIdentifierUri $azureStackEnvironment.ActiveDirectoryServiceEndpointResourceId
@ -452,27 +388,63 @@ function Register-AzureStackWithMyDirectoryTenant {
}
$applicationRegistrations = Invoke-RestMethod @applicationRegistrationParams | Select -ExpandProperty value
# Initialize each registered application in the onboarding directory tenant
foreach ($applicationRegistration in $applicationRegistrations) {
# Initialize the service principal for the registered application, updating any tags as necessary
# Identify which permissions have already been granted to each registered application and which additional permissions need consent
$permissions = @()
foreach ($applicationRegistration in $applicationRegistrations)
{
# Initialize the service principal for the registered application
$applicationServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $applicationRegistration.appId
if ($applicationRegistration.tags) {
# Initialize the necessary tags for the registered application
if ($applicationRegistration.tags)
{
Update-GraphApplicationServicePrincipalTags -ApplicationId $applicationRegistration.appId -Tags $applicationRegistration.tags
}
# Initialize the necessary oauth2PermissionGrants for the registered application
foreach ($oauth2PermissionGrant in $applicationRegistration.oauth2PermissionGrants) {
$oauth2PermissionGrantParams = @{
ClientApplicationId = $oauth2PermissionGrant.client
ResourceApplicationId = $oauth2PermissionGrant.resource
Scope = $oauth2PermissionGrant.scope
# Lookup the permission consent status for the application permissions (either to or from) that the registered application requires
foreach($appRoleAssignment in $applicationRegistration.appRoleAssignments)
{
$params = @{
ClientApplicationId = $appRoleAssignment.client
ResourceApplicationId = $appRoleAssignment.resource
PermissionType = 'Application'
PermissionId = $appRoleAssignment.roleId
}
Initialize-GraphOAuth2PermissionGrant @oauth2PermissionGrantParams
$permissions += New-GraphPermissionDescription @params -LookupConsentStatus
}
# Initialize the necessary directory role membership(s) for the registered application
foreach ($directoryRole in $applicationRegistration.directoryRoles) {
Initialize-GraphDirectoryRoleMembership -ApplicationId $applicationRegistration.appId -RoleDisplayName $directoryRole
# Lookup the permission consent status for the delegated permissions (either to or from) that the registered application requires
foreach($oauth2PermissionGrant in $applicationRegistration.oauth2PermissionGrants)
{
$resourceApplicationServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $oauth2PermissionGrant.resource
foreach ($scope in $oauth2PermissionGrant.scope.Split(' '))
{
$params = @{
ClientApplicationId = $oauth2PermissionGrant.client
ResourceApplicationServicePrincipal = $resourceApplicationServicePrincipal
PermissionType = 'Delegated'
PermissionId = ($resourceApplicationServicePrincipal.oauth2Permissions | Where value -EQ $scope).id
}
$permissions += New-GraphPermissionDescription @params -LookupConsentStatus
}
}
}
# Show the user a display of the required permissions
$permissions | Show-GraphApplicationPermissionDescriptions
if ($permissions | Where isConsented -EQ $false | Select -First 1)
{
# Grant the required permissions to the corresponding applications
$permissions | Where isConsented -EQ $false | Grant-GraphApplicationPermission
}
Write-Host "`r`nAll permissions required for registered Azure Stack applications or scenarios have been granted!" -ForegroundColor Green
}
Export-ModuleMember -Function @(
"Register-AzureStackWithMyDirectoryTenant",
"Register-GuestDirectoryTenantToAzureStack",
"Get-DirectoryTenantIdentifier",
"New-ADGraphServicePrincipal"
)

Просмотреть файл

@ -1,5 +1,7 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# See LICENSE.txt in the project root for license information.
# {FileName} {Version} {DateTime}
# {BuildRepo} {BuildBranch} {BuildType}-{BuildArchitecture}
# (Manually updated from Solution Deploy repository)
<#
.Synopsis
@ -21,6 +23,11 @@ function Initialize-GraphEnvironment
[ValidateNotNull()]
[pscredential] $UserCredential = $null,
# Indicates that the script should prompt the user to input a credential with which to acquire an access token targeting Graph.
[Parameter(ParameterSetName='Credential_AAD')]
[Parameter(ParameterSetName='Credential_ADFS')]
[switch] $PromptForUserCredential,
# The refresh token to use to acquire an access token targeting Graph.
[Parameter(ParameterSetName='RefreshToken_AAD')]
[Parameter(ParameterSetName='RefreshToken_ADFS')]
@ -63,6 +70,11 @@ function Initialize-GraphEnvironment
Write-Warning "Parameters for ADFS have been specified; please note that only a subset of Graph APIs are available to be used in conjuction with ADFS."
}
if ($PromptForUserCredential)
{
$UserCredential = Get-Credential -Message "Please provide a credential used to access Graph. Must support non-interactive authentication flows."
}
if ($UserCredential)
{
Write-Verbose "Initializing the module to use Graph environment '$Environment' for user '$($UserCredential.UserName)' in directory tenant '$DirectoryTenantId'." -Verbose
@ -77,7 +89,7 @@ function Initialize-GraphEnvironment
}
else
{
Write-Warning "A user credential, refresh token, or service principal info was not provided. Graph API calls cannot be made until one is provided. Please run 'Initialize-GraphEnvironment' again with valid credentials."
Write-Warning "A user credential, refresh token, or service principal info was not provided. Graph API calls cannot be made until one is provided. Please run 'Initialize-GraphEnvironment' again with valid credentials."
}
$graphEnvironmentTemplate = @{}
@ -212,12 +224,23 @@ function Initialize-GraphEnvironment
}
AadPermissions = [HashTable]@{
AccessDirectoryAsSignedInUser = "a42657d6-7f20-40e3-b6f0-cee03008a62a"
EnableSignOnAndReadUserProfiles = "311a71cc-e848-46a1-bdf8-97ff7156d8e6"
ReadAllGroups = "6234d376-f627-4f0f-90e0-dff25c5211a3"
ReadAllUsersBasicProfile = "cba73afc-7f69-4d86-8450-4978e04ecd1a"
ReadAllUsersFullProfile = "c582532d-9d9e-43bd-a97c-2667a28ce295"
ReadDirectoryData = "5778995a-e1bf-45b8-affa-663a9f3f4d04"
AccessDirectoryAsSignedInUser = "a42657d6-7f20-40e3-b6f0-cee03008a62a"
EnableSignOnAndReadUserProfiles = "311a71cc-e848-46a1-bdf8-97ff7156d8e6"
ReadAllGroups = "6234d376-f627-4f0f-90e0-dff25c5211a3"
ReadAllUsersBasicProfile = "cba73afc-7f69-4d86-8450-4978e04ecd1a"
ReadAllUsersFullProfile = "c582532d-9d9e-43bd-a97c-2667a28ce295"
ReadDirectoryData = "5778995a-e1bf-45b8-affa-663a9f3f4d04"
ManageAppsThatThisAppCreatesOrOwns = "824c81eb-e3f8-4ee6-8f6d-de7f50d565b7"
}
AadPermissionScopes = [HashTable]@{
AccessDirectoryAsSignedInUser = "Directory.AccessAsUser.All"
EnableSignOnAndReadUserProfiles = "User.Read"
ReadAllGroups = "Group.Read.All"
ReadAllUsersBasicProfile = "User.ReadBasic.All"
ReadAllUsersFullProfile = "User.Read.All"
ReadDirectoryData = "Directory.Read.All"
ManageAppsThatThisAppCreatesOrOwns = "Application.ReadWrite.OwnedBy"
}
}
@ -303,7 +326,7 @@ function Assert-GraphConnection
# Trace the message to verbose stream as well in case error is not traced in same file as other verbose logs
$traceMessage = "An error occurred while trying to verify connection to the graph endpoint '$($Script:GraphEnvironment.OpenIdMetadata)': $_`r`n`r`nAdditional details: $traceResponse"
Write-Verbose "ERROR: $traceMessage"
throw New-Object System.InvalidOperationException($traceMessage)
}
}
@ -735,7 +758,7 @@ function Find-GraphApplication
)
$filter = if ($DisplayName) {"displayName eq '$DisplayName'"} elseif($AppUri) {"identifierUris/any(i:i eq '$AppUri')"} else {"appId eq '$AppId'"}
$response = Invoke-GraphApi -ApiPath "applications()" -QueryParameters @{ '$filter' = $filter } -ErrorAction Stop
$response = Invoke-GraphApi -ApiPath "applications" -QueryParameters @{ '$filter' = $filter } -ErrorAction Stop
Write-Output $response.value
}
@ -745,20 +768,25 @@ function Find-GraphApplication
#>
function Get-GraphApplication
{
[CmdletBinding()]
[CmdletBinding(DefaultParameterSetName='ByUri')]
[OutputType([pscustomobject])]
param
(
# The application identifier URI.
[Parameter(Mandatory=$true)]
[Parameter(Mandatory=$true, ParameterSetName='ByUri')]
[ValidateNotNullOrEmpty()]
[string] $AppUri
[string] $AppUri,
# The application identifer.
[Parameter(Mandatory=$true, ParameterSetName='ById')]
[ValidateNotNullOrEmpty()]
[string] $AppId
)
$application = Find-GraphApplication -AppUri $AppUri
$application = Find-GraphApplication @PSBoundParameters
if (-not $application)
{
Write-Error "Application with identifier '$AppUri' not found"
Write-Error "Application with identifier '${AppUri}${AppId}' not found"
}
else
{
@ -969,7 +997,7 @@ function Initialize-GraphApplicationServicePrincipal
{
$getScript = { (Invoke-GraphApi -ApiPath servicePrincipals -QueryParameters @{ '$filter' = "appId eq '$ApplicationId'" }).value }
# Create a service principal for the application (if one doesn't already exist) # TODO: support update tags
# Create a service principal for the application (if one doesn't already exist)
if (-not ($primaryServicePrincipal = & $getScript))
{
Write-Verbose "Creating service principal for application '$ApplicationId' in AAD..." -Verbose
@ -1096,32 +1124,39 @@ function Initialize-GraphOAuth2PermissionGrant
[string] $ConsentType = 'AllPrincipals'
)
# https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/entity-and-complex-type-reference#oauth2permissiongrant-entity
# Ensure the application service principals exist in the directory tenant
$clientApplicationServicePrincipal = if ($ClientApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ClientApplicationId
Initialize-GraphApplicationServicePrincipal -ApplicationId $ClientApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ClientApplicationIdentifierUri
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ClientApplicationIdentifierUri -ErrorAction Stop
}
$resourceApplicationServicePrincipal = if ($ResourceApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ResourceApplicationId
Initialize-GraphApplicationServicePrincipal -ApplicationId $ResourceApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ResourceApplicationIdentifierUri
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ResourceApplicationIdentifierUri -ErrorAction Stop
}
# TODO: Do we need to support updating expired permission grants? The documentation appears to say these properties should be ignored: "https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/entity-and-complex-type-reference#oauth2permissiongrant-entity"
# Note: value=Invalid characters found in scope. Allowed characters are %x20 / %x21 / %x23-5B / %x5D-7E
$scopesToGrant = $Scope.Split(' ')
# Note: the permission grants do not expire, but we must provide an expiration date to the API
$queryParameters = @{
'$filter' = "resourceId eq '$($resourceApplicationServicePrincipal.objectId)' and clientId eq '$($clientApplicationServicePrincipal.objectId)'"
'$top' = '999'
}
if (-not (Invoke-GraphApi -ApiPath oauth2PermissionGrants -QueryParameters $queryParameters).Value)
$existingGrant = (Invoke-GraphApi -ApiPath oauth2PermissionGrants -QueryParameters $queryParameters).Value | Select -First 1
if (-not $existingGrant)
{
Write-Verbose "Granting OAuth2Permission '$Scope' to application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of application '$($resourceApplicationServicePrincipal.appDisplayName)'..." -Verbose
$response = Invoke-GraphApi -Method Post -ApiPath oauth2PermissionGrants -Body (ConvertTo-Json ([pscustomobject]@{
@ -1133,10 +1168,112 @@ function Initialize-GraphOAuth2PermissionGrant
startTime = [DateTime]::UtcNow.ToString('o')
expiryTime = [DateTime]::UtcNow.AddYears(1).ToString('o')
}))
Write-Verbose "Sleeping for 3 seconds to allow the permission grant to propagate..." -Verbose
Start-Sleep -Seconds 3
}
else
{
Write-Verbose "OAuth2Permission '$Scope' already granted to client application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of resource application '$($resourceApplicationServicePrincipal.appDisplayName)'." -Verbose
Write-Verbose "Existing OAuth2PermissionGrant found: $(ConvertTo-Json $existingGrant)" -Verbose
$existingScopes = $existingGrant.scope.Split(' ')
$missingScopes = $scopesToGrant | Where { $_ -inotin $existingScopes }
if ($missingScopes.Count)
{
$fullScopes = $existingGrant.scope += (' ' + [string]::Join(' ', $missingScopes))
Write-Verbose "Updating OAuth2PermissionGrant scopes to include missing scopes '$([string]::Join(' ', $missingScopes))' to client application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of resource application '$($resourceApplicationServicePrincipal.appDisplayName)'. Full Scopes will include: '$fullScopes'" -Verbose
$response = Invoke-GraphApi -Method Patch -ApiPath "oauth2PermissionGrants/$($existingGrant.objectId)" -Body (ConvertTo-Json ([pscustomobject]@{ scope = $fullScopes }))
}
else
{
Write-Verbose "OAuth2Permission '$Scope' already granted to client application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of resource application '$($resourceApplicationServicePrincipal.appDisplayName)'. Full Scopes: '$($existingGrant.scope)'" -Verbose
}
}
}
function Initialize-GraphAppRoleAssignment
{
[CmdletBinding(DefaultParameterSetName='ClientAppId_ResourceAppId')]
param
(
# The application identifier of the client application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceIdentifierUri')]
[ValidateNotNullOrEmpty()]
[string] $ClientApplicationId,
# The application identifier URI of the client application.
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceIdentifierUri')]
[ValidateNotNullOrEmpty()]
[string] $ClientApplicationIdentifierUri,
# The application identifier of the resource application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceAppId')]
[ValidateNotNullOrEmpty()]
[string] $ResourceApplicationId,
# The application identifier URI of the resource application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceIdentifierUri')]
[ValidateNotNullOrEmpty()]
[string] $ResourceApplicationIdentifierUri,
# The identifier of the app role permission to grant.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $RoleId,
# The type of the service principal to with the permission will be granted (e.g. 'ServicePrincipal' [default value]).
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateSet('ServicePrincipal', 'User', 'Group')]
[string] $PrincipalType = 'ServicePrincipal'
)
# https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/entity-and-complex-type-reference#approleassignment-entity
# Ensure the application service principals exist in the directory tenant
$clientApplicationServicePrincipal = if ($ClientApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ClientApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ClientApplicationIdentifierUri -ErrorAction Stop
}
$resourceApplicationServicePrincipal = if ($ResourceApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ResourceApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ResourceApplicationIdentifierUri -ErrorAction Stop
}
$existingAssignments = (Invoke-GraphApi -ApiPath "servicePrincipals/$($clientApplicationServicePrincipal.objectId)/appRoleAssignedTo").value
$existingAssignment = $existingAssignments |
Where id -EQ $RoleId |
Where resourceId -EQ $resourceApplicationServicePrincipal.objectId
if (-not $existingAssignment)
{
Write-Verbose "Granting AppRoleAssignment '$RoleId' to application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of application '$($resourceApplicationServicePrincipal.appDisplayName)'..." -Verbose
$response = Invoke-GraphApi -Method Post -ApiPath "servicePrincipals/$($clientApplicationServicePrincipal.objectId)/appRoleAssignments" -Body (ConvertTo-Json ([pscustomobject]@{
principalId = $clientApplicationServicePrincipal.objectId
principalType = $PrincipalType
resourceId = $resourceApplicationServicePrincipal.objectId
id = $RoleId
}))
}
else
{
Write-Verbose "AppRoleAssignment '$RoleId' already granted to client application service principal '$($clientApplicationServicePrincipal.appDisplayName)' on behalf of resource application '$($resourceApplicationServicePrincipal.appDisplayName)'." -Verbose
}
}
@ -1161,7 +1298,7 @@ function Initialize-GraphDirectoryRoleMembership
[string] $RoleDisplayName
)
# Ensure the application service principal exist in the directory tenant
# Ensure the application service principal exists in the directory tenant
$applicationServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $ApplicationId
# https://msdn.microsoft.com/en-us/Library/Azure/Ad/Graph/api/directoryroles-operations#AddDirectoryRoleMembers
@ -1191,7 +1328,7 @@ function Initialize-GraphDirectoryRoleMembership
$response = Invoke-GraphApi -Method Post -ApiPath $apiPath -Body (ConvertTo-Json ([pscustomobject]@{
securityEnabledOnly = $false
}))
if ($response.value -icontains $roleObjectId)
{
Write-Verbose "Membership already granted to directory role '$RoleDisplayName' ($($roleObjectId)) for application service principal '$($applicationServicePrincipal.appDisplayName)'." -Verbose
@ -1206,6 +1343,461 @@ function Initialize-GraphDirectoryRoleMembership
}
}
<#
.Synopsis
Creates a new representation of a permission exposed by a resource application and grantable to a client application.
#>
function New-GraphPermissionDescription
{
[CmdletBinding(DefaultParameterSetName='ClientAppId_ResourceAppId')]
param
(
# The application identifier of the client application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_Resource')]
[ValidateNotNullOrEmpty()]
[string] $ClientApplicationId,
# The application identifier URI of the client application.
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_Resource')]
[ValidateNotNullOrEmpty()]
[string] $ClientApplicationIdentifierUri,
# The object reprsentation client application service principal.
[Parameter(Mandatory=$true, ParameterSetName='Client_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='Client_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='Client_Resource')]
[pscustomobject] $ClientApplicationServicePrincipal,
# The application identifier of the resource application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceAppId')]
[Parameter(Mandatory=$true, ParameterSetName='Client_ResourceAppId')]
[ValidateNotNullOrEmpty()]
[string] $ResourceApplicationId,
# The application identifier URI of the resource application.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_ResourceIdentifierUri')]
[Parameter(Mandatory=$true, ParameterSetName='Client_ResourceIdentifierUri')]
[ValidateNotNullOrEmpty()]
[string] $ResourceApplicationIdentifierUri,
# The object reprsentation resource application service principal.
[Parameter(Mandatory=$true, ParameterSetName='ClientAppId_Resource')]
[Parameter(Mandatory=$true, ParameterSetName='ClientIdentifierUri_Resource')]
[Parameter(Mandatory=$true, ParameterSetName='Client_Resource')]
[pscustomobject] $ResourceApplicationServicePrincipal,
# The type of the permission.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[ValidateSet('Application', 'Delegated')]
[string] $PermissionType,
# The identifier of the permission.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $PermissionId,
# Indicates whether the permission has been granted (consented).
[Parameter()]
[ValidateNotNull()]
[switch] $IsConsented,
# Indicates whether the current permission consent status should be queried.
[Parameter()]
[ValidateNotNull()]
[switch] $LookupConsentStatus
)
# Lookup / initialize client service principal
$ClientApplicationServicePrincipal = if ($ClientApplicationServicePrincipal)
{
$ClientApplicationServicePrincipal
}
elseif ($ClientApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ClientApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ClientApplicationIdentifierUri -ErrorAction Stop
}
# Lookup / initialize resource service principal
$ResourceApplicationServicePrincipal = if ($ResourceApplicationServicePrincipal)
{
$ResourceApplicationServicePrincipal
}
elseif ($ResourceApplicationId)
{
Initialize-GraphApplicationServicePrincipal -ApplicationId $ResourceApplicationId -ErrorAction Stop
}
else
{
Get-GraphApplicationServicePrincipal -ApplicationIdentifierUri $ResourceApplicationIdentifierUri -ErrorAction Stop
}
$permissionProperties = [ordered]@{
clientApplicationId = $ClientApplicationServicePrincipal.appId
clientApplicationDisplayName = $ClientApplicationServicePrincipal.appDisplayName
resourceApplicationId = $ResourceApplicationServicePrincipal.appId
resourceApplicationDisplayName = $ResourceApplicationServicePrincipal.appDisplayName
}
$permissionProperties += [ordered]@{
isConsented = $IsConsented
permissionType = $PermissionType
permissionId = $PermissionId
}
switch ($PermissionType)
{
'Application'
{
$appRole = $ResourceApplicationServicePrincipal.appRoles | Where id -EQ $PermissionId
$permissionProperties += [ordered]@{
permissionName = $appRole.value
permissionDisplayName = $appRole.displayName
permissionDescription = $appRole.description
}
if ($LookupConsentStatus)
{
$existingAppRoleAssignments = (Invoke-GraphApi -ApiPath "servicePrincipals/$($ClientApplicationServicePrincipal.objectId)/appRoleAssignedTo").value
$permissionProperties.isConsented = if ($existingAppRoleAssignments | Where id -EQ $PermissionId) {$true} else {$false}
}
}
'Delegated'
{
$oAuth2Permission = $ResourceApplicationServicePrincipal.oauth2Permissions | Where id -EQ $PermissionId
$permissionProperties += [ordered]@{
permissionName = $oAuth2Permission.value
permissionDisplayName = $oAuth2Permission.adminConsentDisplayName
permissionDescription = $oAuth2Permission.adminConsentDescription
}
if ($LookupConsentStatus)
{
$queryParameters = @{
'$filter' = "resourceId eq '$($ResourceApplicationServicePrincipal.objectId)' and clientId eq '$($ClientApplicationServicePrincipal.objectId)'"
'$top' = '999'
}
$existingOAuth2PermissionGrants = (Invoke-GraphApi -ApiPath oauth2PermissionGrants -QueryParameters $queryParameters).Value
$permissionProperties.isConsented = if ($existingOAuth2PermissionGrants) {$true} else {$false}
}
}
}
Write-Output ([pscustomobject]$permissionProperties)
}
<#
.Synopsis
Gets all permissions which have been granted to the specified application. If the application was created in the current directory tenant, also returns permissions which have not been consented but which are advertised as "required" in the application's manifest.
#>
function Get-GraphApplicationPermissions
{
[CmdletBinding()]
[OutputType([pscustomobject])]
param
(
# The application identifier for which all consented permissions should be retrieved.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[Alias('appId')]
[string] $ApplicationId
)
# Ensure the application service principal exists in the directory tenant
$applicationServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $ApplicationId -ErrorAction Stop
# Identify which permissions have already been granted
$existingAppRoleAssignments = (Invoke-GraphApi -ApiPath "servicePrincipals/$($applicationServicePrincipal.objectId)/appRoleAssignedTo").value
$existingClientOAuth2PermissionGrants = (Invoke-GraphApi -ApiPath oauth2PermissionGrants -QueryParameters @{ '$filter' = "clientId eq '$($applicationServicePrincipal.objectId)'"; '$top' = '999' }).Value
$existingResourceOAuth2PermissionGrants = @() # Note - there is an issue with this API at the moment, it returns 404 on the provided nextLink URI # (Invoke-GraphApi -ApiPath oauth2PermissionGrants -QueryParameters @{ '$filter' = "resourceId eq '$($applicationServicePrincipal.objectId)'"; '$top' = '999' }).Value
# Build a representation of each permission which has been granted
$permissions = @()
foreach ($existingAppRoleAssignment in $existingAppRoleAssignments)
{
$permissionParams = @{
ClientApplicationServicePrincipal = $applicationServicePrincipal
ResourceApplicationServicePrincipal = Invoke-GraphApi -ApiPath "directoryObjects/$($existingAppRoleAssignment.resourceId)" -ErrorAction Stop
PermissionType = 'Application'
PermissionId = $existingAppRoleAssignment.id
IsConsented = $true
}
$permissions += New-GraphPermissionDescription @permissionParams
}
foreach ($existingOAuth2PermissionGrant in $existingClientOAuth2PermissionGrants)
{
$permissionParams = @{
ClientApplicationServicePrincipal = $applicationServicePrincipal
ResourceApplicationServicePrincipal = Invoke-GraphApi -ApiPath "directoryObjects/$($existingOAuth2PermissionGrant.resourceId)" -ErrorAction Stop
PermissionType = 'Delegated'
IsConsented = $true
}
foreach ($scope in $existingOAuth2PermissionGrant.scope.split(' '))
{
$oAuth2Permission = $permissionParams.ResourceApplicationServicePrincipal.oauth2Permissions | Where value -EQ $scope
$permissions += New-GraphPermissionDescription @permissionParams -PermissionId $oAuth2Permission.id
}
}
foreach ($existingOAuth2PermissionGrant in $existingResourceOAuth2PermissionGrants)
{
$permissionParams = @{
ClientApplicationServicePrincipal = Invoke-GraphApi -ApiPath "directoryObjects/$($existingOAuth2PermissionGrant.clientId)" -ErrorAction Stop
ResourceApplicationServicePrincipal = $applicationServicePrincipal
PermissionType = 'Delegated'
IsConsented = $true
}
foreach ($scope in $existingOAuth2PermissionGrant.scope.split(' '))
{
$oAuth2Permission = $permissionParams.ResourceApplicationServicePrincipal.oauth2Permissions | Where value -EQ $scope
$permissions += New-GraphPermissionDescription @permissionParams -PermissionId $oAuth2Permission.id
}
}
# Attempt to get unconsented permissions if we can access the application object (e.g. if the application exists in the same directory in which we are currently authenticated)
if (($application = Find-GraphApplication -AppId $ApplicationId))
{
foreach ($requiredResource in $application.requiredResourceAccess)
{
$permissionParams = @{
ClientApplicationServicePrincipal = $applicationServicePrincipal
ResourceApplicationServicePrincipal = Initialize-GraphApplicationServicePrincipal -ApplicationId $requiredResource.resourceAppId
IsConsented = $false
}
foreach ($resourceAccess in $requiredResource.resourceAccess)
{
$relatedConsentedPermissions = $permissions | Where resourceApplicationId -EQ $requiredResource.resourceAppId | Where permissionId -EQ $resourceAccess.id
# $resourceAccess.type is one of: 'Role', 'Scope', 'Role,Scope', or 'Scope,Role'
if ($resourceAccess.type -ilike '*Role*')
{
if (-not ($relatedConsentedPermissions | Where permissionType -EQ 'Application'))
{
$permissions += New-GraphPermissionDescription @permissionParams -PermissionType Application -PermissionId $resourceAccess.id
}
else
{
Write-Verbose "Application permission '$($resourceAccess.id)' of type 'AppRoleAssignment' already consented for application '$($applicationServicePrincipal.appDisplayName)' ('$ApplicationId')."
}
}
if ($resourceAccess.type -ilike '*Scope*')
{
if (-not ($relatedConsentedPermissions | Where permissionType -EQ 'Delegated'))
{
$permissions += New-GraphPermissionDescription @permissionParams -PermissionType Delegated -PermissionId $resourceAccess.id
}
else
{
Write-Verbose "Application permission '$($resourceAccess.id)' of type 'OAuth2PermissionGrant' already consented for application '$($applicationServicePrincipal.appDisplayName)' ('$ApplicationId')."
}
}
}
}
}
else
{
Write-Verbose "Unable to retrieve application with appId '$ApplicationId' and will be unable to retrieve information on any additional required permissions which have not been consented." -Verbose
}
if (-not $permissions.Count)
{
if ($application)
{
Write-Verbose "Application '$($applicationServicePrincipal.appDisplayName)' ('$ApplicationId') does not have any consented permissions, nor does it advertise any additional required permissions in its application manifest." -Verbose
}
else
{
Write-Verbose "Application '$($applicationServicePrincipal.appDisplayName)' ('$ApplicationId') does not have any consented permissions in the current directory tenant." -Verbose
}
}
Write-Output $permissions
}
<#
.Synopsis
Grants a permission to a graph application. Use the 'New-GraphApplicationPermission' or 'Get-GraphApplicationPermissions' cmdlets to create an instance of the permission object or to see its structure.
#>
function Grant-GraphApplicationPermission
{
[CmdletBinding()]
param
(
# The graph permission description object.
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[pscustomobject] $PermissionDescription
)
process
{
Write-Verbose "Granting permission '$($PermissionDescription.permissionName)' ($($PermissionDescription.PermissionId)) exposed by application '$($PermissionDescription.resourceApplicationDisplayName)' ($($PermissionDescription.resourceApplicationId)) of type '$($PermissionDescription.PermissionType)' to application '$($PermissionDescription.clientApplicationDisplayName)' ($($PermissionDescription.clientApplicationId))" -Verbose
$params = @{ ClientApplicationId = $PermissionDescription.clientApplicationId; ResourceApplicationId = $PermissionDescription.resourceApplicationId }
switch ($PermissionDescription.permissionType)
{
'Application' { Initialize-GraphAppRoleAssignment @params -RoleId $PermissionDescription.permissionId -Verbose }
'Delegated' { Initialize-GraphOAuth2PermissionGrant @params -Scope $PermissionDescription.permissionName -Verbose }
}
}
}
<#
.Synopsis
Grants all permissions required by an application which are specified in the application manifest. Only applies to the home directory of the application.
#>
function Grant-GraphApplicationPermissions
{
[CmdletBinding()]
param
(
# The application identifier for which all required permissions should be granted.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $ApplicationId
)
# Ensure the application can be retrieved in the current directory tenant
$application = Get-GraphApplication -AppId $ApplicationId -ErrorAction Stop
$permissions = Get-GraphApplicationPermissions -ApplicationId $ApplicationId
foreach ($permission in $permissions)
{
if ($permission.isConsented)
{
Write-Verbose "Permission '$($permission.permissionName)' ($($permission.PermissionId)) exposed by application '$($permission.resourceApplicationDisplayName)' ($($permission.resourceApplicationId)) of type '$($permission.PermissionType)' has already been granted to application '$($permission.clientApplicationDisplayName)' ($($permission.clientApplicationId))" -Verbose
}
else
{
Grant-GraphApplicationPermission -PermissionDescription $permission
}
}
}
<#
.Synopsis
Writes a representation of the specified Graph permission descriptions to the current PowerShell host console window.
#>
function Show-GraphApplicationPermissionDescriptions
{
[CmdletBinding()]
param
(
# The graph permission description object.
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
[pscustomobject[]] $PermissionDescription,
# The text display to use above the permission descriptions.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $DisplayHeader = 'Microsoft Azure Stack - Required Directory Permissions',
# The text display to use below the permission descriptions.
[Parameter()]
[ValidateNotNullOrEmpty()]
[string] $DisplayFooter = '(X) = Consent given, ( ) = Consent not given',
# Indicates that any duplicate permissions should be filtered-out from the display.
[Parameter()]
[Switch] $FilterDuplicates = $true
)
begin
{
$permissions = @()
}
process
{
foreach ($permission in $PermissionDescription)
{
if ($FilterDuplicates -and ($permissions |
Where clientApplicationId -EQ $permission.clientApplicationId |
Where resourceApplicationId -EQ $permission.resourceApplicationId |
Where permissionId -EQ $permission.permissionId |
Where permissionType -EQ $permission.permissionType))
{
continue
}
$permissions += $permission
}
}
end
{
<# Writes a textual consent display to the console similar to this:
+------------------------------------------------------------+
| Microsoft Azure Stack - Required Directory Permissions |
+------------------------------------------------------------+
| Access Azure Stack [bryanr-env] |
| (X) Delegated to: Microsoft Azure Stack |
| |
| Access the directory as the signed-in user |
| (X) Delegated to: Azure Stack |
| |
| Read all users' basic profiles |
| (X) Delegated to: Azure Stack |
| |
| Read all users' full profiles |
| (X) Delegated to: Azure Stack |
| |
| Read directory data |
| (X) Granted to: Azure Stack |
| (X) Granted to: AzureStack - Policy |
| (X) Delegated to: Azure Stack |
| (X) Delegated to: Microsoft Azure Stack |
| |
| Sign in and read user profile |
| (X) Delegated to: Azure Stack |
| (X) Delegated to: Microsoft Azure Stack |
| |
+------------------------------------------------------------+
| (X) = Consent given, ( ) = Consent not given |
+------------------------------------------------------------+
#>
$header = $DisplayHeader
$footer = $DisplayFooter
$lines = @()
foreach ($permissionGroup in @($permissions | Sort resourceApplicationDisplayName, permissionDisplayName | Group permissionId))
{
$lines += "{0}" -f $permissionGroup.Group[0].permissionDisplayName
foreach ($permission in @($permissionGroup.Group | Sort permissionType, clientApplicationDisplayName))
{
$lines += " {0} {1} {2}" -f @(
($consentDisplay = if ($permission.isConsented) {'(X)'} else {'( )'})
($typeDisplay = switch ($permission.permissionType) { 'Application' { 'Granted to: ' }; 'Delegated' { 'Delegated to:' } })
$permission.clientApplicationDisplayName
)
}
$lines += ''
}
$max = (($lines + @($header, $footer)) | Measure Length -Maximum).Maximum
$div = '+-{0}-+' -f (New-Object string('-', $max))
$lines = @(
$div
'| {0} |' -f "$header".PadRight($max)
$div
$lines | ForEach { '| {0} |' -f "$_".PadRight($max) }
$div
'| {0} |' -f "$footer".PadRight($max)
$div
)
foreach ($line in $lines)
{
Write-Host $line
}
}
}
<#
.Synopsis
Creates or updates an application in Graph with an implicit service principal and the specified properties.
@ -1251,7 +1843,8 @@ function Initialize-GraphApplication
[Parameter()]
[ValidateNotNullOrEmpty()]
[ValidateSet(
'ReadDirectoryData'
'ReadDirectoryData',
'ManageAppsThatThisAppCreatesOrOwns'
)]
[String[]] $ApplicationAadPermissions = @(),
@ -1307,9 +1900,13 @@ function Initialize-GraphApplication
[Parameter()]
[Switch] $AvailableToOtherTenants = $true,
# Indicates that the application service principal should be given membership in the directory readers role to activate the role permission ReadDirectoryData. True by default.
# Indicates that the application service principal should have all declared application permissions consented-to. True by default.
[Parameter()]
[Switch] $UseDirectoryReadersRolePermission = $true
[Switch] $ConsentToAppPermissions = $true,
# Indicates that any existing client certificates associated to this application should be removed. False by default.
[Parameter()]
[Switch] $RemoveExistingClientCertificates
)
if ($ClientCertificateThumbprint)
@ -1393,17 +1990,20 @@ function Initialize-GraphApplication
}
# Initialize the application key credentials with which it can authenticate
$requestBody['keyCredentials@odata.type'] = "Collection(Microsoft.DirectoryServices.KeyCredential)"
$requestBody['keyCredentials'] = @(@($existingApplication.keyCredentials) | Where { $_ -ne $null })
if ($RemoveExistingClientCertificates)
{
$requestBody['keyCredentials'] = @()
}
if ($ClientCertificate)
{
$customKeyIdentifier = [Convert]::ToBase64String($ClientCertificate.GetCertHash())
if (-not (@($existingApplication.keyCredentials) | Where customKeyIdentifier -EQ $customKeyIdentifier))
if (-not (@($requestBody['keyCredentials']) | Where customKeyIdentifier -EQ $customKeyIdentifier))
{
Write-Verbose "Adding new key credentials to application using client certificate '$($ClientCertificate.Subject)' ($($ClientCertificate.Thumbprint))" -Verbose
$requestBody['keyCredentials@odata.type'] = "Collection(Microsoft.DirectoryServices.KeyCredential)"
$requestBody['keyCredentials'] = @(@($existingApplication.keyCredentials) | Where { $_ -ne $null })
$requestBody['keyCredentials'] += @(@{
$requestBody['keyCredentials'] += @(,([pscustomobject]@{
keyId = [Guid]::NewGuid()
type = "AsymmetricX509Cert"
usage = "Verify"
@ -1411,7 +2011,7 @@ function Initialize-GraphApplication
value = [Convert]::ToBase64String($ClientCertificate.GetRawCertData())
startDate = $ClientCertificate.NotBefore.ToUniversalTime().ToString('o')
endDate = $ClientCertificate.NotAfter.ToUniversalTime().ToString('o')
})
}))
}
else
{
@ -1617,21 +2217,61 @@ function Initialize-GraphApplication
Initialize-GraphOAuth2PermissionGrant @params
}
# Initialize directory role membership
if ($UseDirectoryReadersRolePermission -and ($ApplicationAadPermissions -icontains 'ReadDirectoryData'))
# "Consent" to application permissions
if ($ConsentToAppPermissions)
{
$params = @{
ApplicationId = $application.appId
RoleDisplayName = 'Directory Readers'
}
Initialize-GraphDirectoryRoleMembership @params
Grant-GraphApplicationPermissions -ApplicationId $application.appId
}
# Return the application in its final (current) state
Get-GraphApplication -AppUri $IdentifierUri | Write-Output
}
<#
.Synopsis
Creates or updates an application in Graph with an implicit service principal and the specified properties.
#>
function Initialize-GraphApplicationOwner
{
[CmdletBinding(DefaultParameterSetName='ById')]
param
(
# The application identifier.
[Parameter(Mandatory=$true, ParameterSetName='ById')]
[ValidateNotNullOrEmpty()]
[string] $ApplicationId,
# The application identifier URI.
[Parameter(Mandatory=$true, ParameterSetName='ByUri')]
[ValidateNotNullOrEmpty()]
[string] $ApplicationIdentifierUri,
# The identifier of the object (user, service principal, etc.) to which ownership of the target application should be granted.
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string] $OwnerObjectId
)
# Lookup the target objects
$params = if ($ApplicationId) {@{ AppId = $ApplicationId }} else {@{ AppUri = $ApplicationIdentifierUri }}
$application = Get-GraphApplication @params -ErrorAction Stop
$owner = Invoke-GraphApi -ApiPath "directoryObjects/$OwnerObjectId" -ErrorAction Stop
# Lookup the existing owners and grant ownership if not already granted
$owners = (Invoke-GraphApi -Method Get -ApiPath "applications/$($application.objectId)/owners" -ErrorAction Stop).value
if ($owners | Where objectId -EQ $OwnerObjectId)
{
Write-Verbose "Object '$($owner.objectId)' of type '$($owner.objectType)' is already an owner of the application '$($application.displayName)' ($($application.appId))" -Verbose
}
else
{
Write-Verbose "Granting ownership of application '$($application.displayName)' ($($application.appId)) to object '$($owner.objectId)' of type '$($owner.objectType)'." -Verbose
Invoke-GraphApi -Method Post -ApiPath "applications/$($application.objectId)/`$links/owners" -Verbose -ErrorAction Stop -Body (ConvertTo-Json ([pscustomobject]@{
url = '{0}/directoryObjects/{1}' -f $Script:GraphEnvironment.GraphEndpoint.AbsoluteUri.TrimEnd('/'), $OwnerObjectId
}))
}
}
[System.Reflection.Assembly]::LoadWithPartialName('System.Web') | Out-Null
<#
@ -1671,7 +2311,14 @@ Export-ModuleMember -Function @(
'Initialize-GraphApplicationServicePrincipal'
'Update-GraphApplicationServicePrincipalTags'
'Initialize-GraphOAuth2PermissionGrant'
'Initialize-GraphAppRoleAssignment'
'Initialize-GraphDirectoryRoleMembership'
'New-GraphPermissionDescription'
'Get-GraphApplicationPermissions'
'Grant-GraphApplicationPermission'
'Grant-GraphApplicationPermissions'
'Show-GraphApplicationPermissionDescriptions'
'Initialize-GraphApplication'
'Initialize-GraphApplicationOwner'
#'ConvertTo-QueryString'
)

Просмотреть файл

@ -1,7 +1,7 @@
# Azure Stack Identity
```powershell
Install-Module -Name 'AzureRm.Bootstrapper' -Scope CurrentUser
// Place your settings in this file to overwrite the default settings
{
"workbench.colorTheme": "Abyss"
}nstall-Module -Name 'AzureRm.Bootstrapper' -Scope CurrentUser
Install-AzureRmProfile -profile '2017-03-09-profile' -Force -Scope CurrentUser
Install-Module -Name AzureStack -RequiredVersion 1.2.9 -Scope CurrentUser
```
@ -48,22 +48,6 @@ There are two personas involved in implementing this scenario.
### Azure Stack Administrator
#### Pre-Requisite: Populate Azure Resource Manager with AzureStack Applications
- This step is a temporary workaround and needed only for the TP3 (March) release of Azure Stack
- Execute this cmdlet as the **Azure Stack Service Administrator**, from the Console VM or the DVM replacing ```$azureStackDirectoryTenant``` with the directory tenant that Azure Stack is registered to and ```$guestDirectoryTenant``` with the directory that needs to be onboarded to Azure Stack.
__NOTE:__ This cmd needs to be run **only once** throughout the entire life cycle of that Azure Stack installation. You do **not** have to run this step every time you need to add a new directory.
```powershell
$adminARMEndpoint = "https://adminmanagement.<region>.<domain>"
$azureStackDirectoryTenant = "<homeDirectoryTenant>.onmicrosoft.com"
$guestDirectoryTenantToBeOnboarded = "<guestDirectoryTenant>.onmicrosoft.com"
Publish-AzureStackApplicationsToARM -AdminResourceManagerEndpoint $adminARMEndpoint `
-DirectoryTenantName $azureStackDirectoryTenant
```
#### Step 1: Onboard the Guest Directory Tenant to Azure Stack
This step will let Azure Resource manager know that it can accept users and service principals from the guest directory tenant.
@ -72,9 +56,11 @@ This step will let Azure Resource manager know that it can accept users and serv
$adminARMEndpoint = "https://adminmanagement.<region>.<domain>"
$azureStackDirectoryTenant = "<homeDirectoryTenant>.onmicrosoft.com" # this is the primary tenant Azure Stack is registered to
$guestDirectoryTenantToBeOnboarded = "<guestDirectoryTenant>.onmicrosoft.com" # this is the new tenant that needs to be onboarded to Azure Stack
$location = "local"
Register-GuestDirectoryTenantToAzureStack -AdminResourceManagerEndpoint $adminARMEndpoint `
-DirectoryTenantName $azureStackDirectoryTenant -GuestDirectoryTenantName $guestDirectoryTenantToBeOnboarded
-DirectoryTenantName $azureStackDirectoryTenant `
-GuestDirectoryTenantName $guestDirectoryTenantToBeOnboarded `
-Location $location
```
With this step, the work of the Azure Stack administrator is done.
@ -85,7 +71,7 @@ The following steps need to be completed by the **Directory Tenant Administrator
#### Step 2: Providing UI-based consent to Azure Stack Portal and ARM
- This is an important step. Open up a web browser, and go to `https://portal.<region>.<domain>/guest/signup/<guestDirectoryName>`. Note that this is the directory tenant that needs to be onboarded to Azure Stack.
- This is an important step. Open up a web browser, and go to `https://portal.<region>.<domain>/guest/signup/<guestDirectoryName>`. Note that this is the directory tenant that needs to be onboarded to Azure Stack.
- This will take you to an AAD sign in page where you need to enter your credentials and click on 'Accept' on the consent screen.
#### Step 3: Registering Azure Stack applications with the Guest Directory
@ -97,5 +83,5 @@ $tenantARMEndpoint = "https://management.<region>.<domain>"
$guestDirectoryTenantName = "<guestDirectoryTenant>.onmicrosoft.com" # this is the new tenant that needs to be onboarded to Azure Stack
Register-AzureStackWithMyDirectoryTenant -TenantResourceManagerEndpoint $tenantARMEndpoint `
-DirectoryTenantName $guestDirectoryTenantName -Verbose -Debug
-DirectoryTenantName $guestDirectoryTenantName
```