Skip to content

Commit 2b9295f

Browse files
authored
Merge pull request #243941 from omondiatieno/restore-permissions
new article on how to restore previously revoked permissions
2 parents 7d0161d + 7033c74 commit 2b9295f

File tree

4 files changed

+361
-1
lines changed

4 files changed

+361
-1
lines changed

articles/active-directory/manage-apps/manage-consent-requests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Before you grant tenant-wide admin consent, it's important to ensure that you tr
6666

6767
When you're evaluating a request to grant admin consent, here are some recommendations to consider:
6868

69-
- Understand the [permissions and consent framework](../develop/consent-framework.md) in the Microsoft identity platform.
69+
- Understand the [permissions and consent framework](../develop/permissions-consent-overview.md) in the Microsoft identity platform.
7070

7171
- Understand the difference between [delegated permissions and application permissions](../develop/v2-permissions-and-consent.md#permission-types).
7272

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
---
2+
title: Restore revoked permissions granted to applications in Azure Active Directory
3+
description: Learn how to review and restore revoked permissions for an application in Azure Active Directory.
4+
services: active-directory
5+
author: Jackson-Woods
6+
manager: CelesteDG
7+
ms.service: active-directory
8+
ms.subservice: app-mgmt
9+
ms.workload: identity
10+
ms.topic: how-to
11+
ms.date: 07/05/2023
12+
ms.author: jomondi
13+
ms.reviewer: phsignor
14+
ms.collection: M365-identity-device-management
15+
zone_pivot_groups: delegated-app-permissions
16+
ms.custom: enterprise-apps
17+
18+
#customer intent: As an admin, I want to review previously revoked permissions so that I can restore the permissions for a given application.
19+
---
20+
21+
# Restore revoked permissions granted to applications
22+
23+
In this article, you learn how to restore previously revoked permissions that were granted to an application. You can restore permissions for an application that was granted permissions to access your organization's data. You can also restore permissions for an application that was granted permissions to act as a user.
24+
25+
Currently, restoring permissions is only possible through Microsoft Graph PowerShell and Microsoft Graph API calls. You can't restore permissions through the Azure portal. In this article, you learn how to restore permissions using Microsoft Graph PowerShell.
26+
27+
## Prerequisites
28+
29+
To restore previously revoked permissions for an application, you need:
30+
31+
- An Azure account with an active subscription. [Create an account for free](https://azure.microsoft.com/free/?WT.mc_id=A261C142F).
32+
- One of the following roles: Global Administrator, Cloud Application Administrator, Application Administrator.
33+
- A Service principal owner who isn't an administrator is able to invalidate refresh tokens.
34+
35+
## Restore revoked permissions for an application
36+
37+
You can try different methods for restoring permissions:
38+
39+
- Use the **Grant admin consent** button on the **Permissions** page for the app to apply consent again. This consent applies the set of permissions that the app's developer originally requested in the app manifest.
40+
41+
>[!NOTE]
42+
>Regranting admin consent will remove any granted permissions that are not part of the default set configured by the developer.
43+
44+
- If you know the specific permission that was revoked, you can grant it again manually using [PowerShell](/powershell/microsoftgraph/tutorial-grant-delegated-api-permissions?view=graph-powershell-1.0&preserve-view=true) or the [Microsoft Graph API](/graph/permissions-grant-via-msgraph?tabs=http&pivots=grant-delegated-permissions).
45+
- If you don't know the revoked permissions, you can use the scripts provided in this article to detect and restore revoked permissions.
46+
47+
First, set the servicePrincipalId value in the script to the ID value for the enterprise app whose permissions you want to restore. This ID is also called the `object ID` in the Azure portal **Enterprise applications** page.
48+
49+
Then, run each script with `$ForceGrantUpdate = $false` in order to see a list of delegated or app-only permissions that maybe have been removed. Even if the permissions have already been restored, revoke events from your audit logs may still appear in the script results.
50+
51+
Leave `$ForceGrantUpdate` set to `$true` if you want the script to attempt to restore any revoked permissions it detects. The scripts ask for confirmation, but don't ask for individual approval for each permission that it restores.
52+
53+
Be cautious when granting permissions to apps. To learn more on how to evaluate permissions, see [Evaluate permissions](manage-consent-requests.md#evaluate-a-request-for-tenant-wide-admin-consent).
54+
55+
:::zone pivot="delegated-perms"
56+
57+
### Restore delegated permissions
58+
59+
```powershell
60+
# WARNING: Setting $ForceGrantUpdate to true will modify permission grants without
61+
# prompting for confirmation. This can result in unintended changes to your
62+
# application's security settings. Use with caution!
63+
$ForceGrantUpdate = $false
64+
65+
# Set the start and end dates for the audit log search
66+
# If setting date use yyyy-MM-dd format
67+
# endDate is set to tomorrow to include today's audit logs
68+
$startDate = (Get-Date).AddDays(-7).ToString('yyyy-MM-dd')
69+
$endDate = (Get-Date).AddDays(1).ToString('yyyy-MM-dd')
70+
71+
# Set the service principal ID
72+
$servicePrincipalId = "efe87e5d-05cb-4b19-9b36-1eb923448697"
73+
74+
Write-Host "Searching for audit logs between $startDate and $endDate" -ForegroundColor Green
75+
Write-Host "Searching for audit logs for service principal $servicePrincipalId" -ForegroundColor Green
76+
77+
if ($ForceGrantUpdate -eq $true) {
78+
Write-Host "WARNING: ForceGrantUpdate is set to true. This will modify permission grants without prompting for confirmation. This can result in unintended changes to your application's security settings. Use with caution!" -ForegroundColor Red
79+
$continue = Read-Host "Do you want to continue? (Y/N)"
80+
if ($continue -eq "Y" -or $continue -eq "y") {
81+
Write-Host "Continuing..."
82+
} else {
83+
Write-Host "Exiting..."
84+
exit
85+
}
86+
}
87+
88+
# Connect to MS Graph
89+
Connect-MgGraph -Scopes "AuditLog.Read.All","DelegatedPermissionGrant.ReadWrite.All" -ErrorAction Stop | Out-Null
90+
91+
# Create a hashtable to store the OAuth2PermissionGrants
92+
$oAuth2PermissionGrants = @{}
93+
94+
function Merge-Scopes($oldScopes, $newScopes) {
95+
$oldScopes = $oldScopes.Trim() -split '\s+'
96+
$newScopes = $newScopes.Trim() -split '\s+'
97+
$mergedScopesArray = $oldScopes + $newScopes | Select-Object -Unique
98+
$mergedScopes = $mergedScopesArray -join ' '
99+
return $mergedScopes.Trim()
100+
}
101+
102+
# Function to merge scopes if multiple OAuth2PermissionGrants are found in the audit logs
103+
function Add-Scopes($resourceId, $newScopes) {
104+
if($oAuth2PermissionGrants.ContainsKey($resourceId)) {
105+
$oldScopes = $oAuth2PermissionGrants[$resourceId]
106+
$oAuth2PermissionGrants[$resourceId] = Merge-Scopes $oldScopes $newScopes
107+
}
108+
else {
109+
$oAuth2PermissionGrants[$resourceId] = $newScopes
110+
}
111+
}
112+
113+
function Get-ScopeDifference ($generatedScope, $currentScope) {
114+
$generatedScopeArray = $generatedScope.Trim() -split '\s+'
115+
$currentScopeArray = $currentScope.Trim() -split '\s+'
116+
$difference = $generatedScopeArray | Where-Object { $_ -notin $currentScopeArray }
117+
$difference = $difference -join ' '
118+
return $difference.Trim()
119+
}
120+
121+
# Set the filter for the audit log search
122+
$filterOAuth2PermissionGrant = "activityDateTime ge $startDate and activityDateTime le $endDate" +
123+
" and Result eq 'success'" +
124+
" and ActivityDisplayName eq 'Remove delegated permission grant'" +
125+
" and targetResources/any(x: x/id eq '$servicePrincipalId')"
126+
try {
127+
# Retrieve the audit logs for removed OAuth2PermissionGrants
128+
$oAuth2PermissionGrantsAuditLogs = Get-MgAuditLogDirectoryAudit -Filter $filterOAuth2PermissionGrant -All -ErrorAction Stop
129+
}
130+
catch {
131+
Disconnect-MgGraph | Out-Null
132+
throw $_
133+
}
134+
135+
# Remove User Delegated Permission Grants
136+
$oAuth2PermissionGrantsAuditLogs = $oAuth2PermissionGrantsAuditLogs | Where-Object {
137+
-not ($_.TargetResources.ModifiedProperties.OldValue -eq '"Principal"')
138+
}
139+
140+
# Merge duplicate OAuth2PermissionGrants from AuditLogs using Add-Scopes
141+
foreach ($auditLog in $oAuth2PermissionGrantsAuditLogs) {
142+
$resourceId = $auditLog.TargetResources[0].Id
143+
# We only want to process OAuth2PermissionGrant Audit Logs where $servicePrincipalId is the clientId not the resourceId
144+
if ($resourceId -eq $servicePrincipalId) {
145+
continue
146+
}
147+
$oldScope = $auditLog.TargetResources[0].ModifiedProperties | Where-Object { $_.DisplayName -eq "DelegatedPermissionGrant.Scope" } | Select-Object -ExpandProperty OldValue
148+
if ($oldScope -eq $null) {
149+
$oldScope = ""
150+
}
151+
$oldScope = $oldScope.Replace('"', '')
152+
$newScope = $auditLog.TargetResources[0].ModifiedProperties | Where-Object { $_.DisplayName -eq "DelegatedPermissionGrant.Scope" } | Select-Object -ExpandProperty NewValue
153+
if ($newScope -eq $null) {
154+
$newScope = ""
155+
}
156+
$newScope = $newScope.Replace('"', '')
157+
$scope = Merge-Scopes $oldScope $newScope
158+
Add-Scopes $resourceId $scope
159+
}
160+
161+
$permissionCount = 0
162+
foreach ($resourceId in $oAuth2PermissionGrants.keys) {
163+
$scope = $oAuth2PermissionGrants[$resourceId]
164+
$params = @{
165+
clientId = $servicePrincipalId
166+
consentType = "AllPrincipals"
167+
resourceId = $resourceId
168+
scope = $scope
169+
}
170+
171+
try {
172+
$currentOAuth2PermissionGrant = Get-MgOauth2PermissionGrant -Filter "clientId eq '$servicePrincipalId' and consentType eq 'AllPrincipals' and resourceId eq '$resourceId'" -ErrorAction Stop
173+
$action = "Creating"
174+
if ($currentOAuth2PermissionGrant -ne $null) {
175+
$action = "Updating"
176+
}
177+
Write-Host "--------------------------"
178+
if ($ForceGrantUpdate -eq $true) {
179+
Write-Host "$action OAuth2PermissionGrant with the following parameters:"
180+
} else {
181+
Write-Host "Potentially removed OAuth2PermissionGrant scopes with the following parameters:"
182+
}
183+
Write-Host " clientId: $($params.clientId)"
184+
Write-Host " consentType: $($params.consentType)"
185+
Write-Host " resourceId: $($params.resourceId)"
186+
if ($currentOAuth2PermissionGrant -ne $null) {
187+
$scopeDifference = Get-ScopeDifference $scope $currentOAuth2PermissionGrant.Scope
188+
if ($scopeDifference -eq "") {
189+
Write-Host "OAuth2PermissionGrant already exists with the same scope" -ForegroundColor Yellow
190+
if ($ForceGrantUpdate -eq $true) {
191+
Write-Host "Skipping Update" -ForegroundColor Yellow
192+
}
193+
continue
194+
}
195+
else {
196+
Write-Host " scope diff: '$scopeDifference'"
197+
}
198+
}
199+
else {
200+
Write-Host " scope: '$($params.scope)'"
201+
}
202+
if ($ForceGrantUpdate -eq $true -and $currentOAuth2PermissionGrant -eq $null) {
203+
New-MgOauth2PermissionGrant -BodyParameter $params -ErrorAction Stop | Out-Null
204+
Write-Host "OAuth2PermissionGrant was created successfully" -ForegroundColor Green
205+
}
206+
if ($ForceGrantUpdate -eq $true -and $currentOAuth2PermissionGrant -ne $null) {
207+
Write-Host " Current Scope: '$($currentOAuth2PermissionGrant.scope)'" -ForegroundColor Yellow
208+
Write-Host " Merging with scopes from audit logs" -ForegroundColor Yellow
209+
$params.scope = Merge-Scopes $currentOAuth2PermissionGrant.scope $params.scope
210+
Write-Host " New Scope: '$($params.scope)'" -ForegroundColor Yellow
211+
Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $currentOAuth2PermissionGrant.id -BodyParameter $params -ErrorAction Stop | Out-Null
212+
Write-Host "OAuth2PermissionGrant was updated successfully" -ForegroundColor Green
213+
}
214+
$permissionCount++
215+
}
216+
catch {
217+
Disconnect-MgGraph | Out-Null
218+
throw $_
219+
}
220+
}
221+
222+
Disconnect-MgGraph | Out-Null
223+
224+
if ($ForceGrantUpdate -eq $true) {
225+
Write-Host "--------------------------"
226+
Write-Host "$permissionCount OAuth2PermissionGrants were created/updated successfully" -ForegroundColor Green
227+
} else {
228+
Write-Host "--------------------------"
229+
Write-Host "$permissionCount OAuth2PermissionGrants were found" -ForegroundColor Green
230+
}
231+
232+
```
233+
234+
:::zone-end
235+
236+
:::zone pivot="app-perms"
237+
238+
### Restore app-only permissions
239+
240+
>[!NOTE]
241+
>Granting app-only Microsoft Graph permissions requires the global administrator role.
242+
243+
```powershell
244+
# WARNING: Setting $ForceGrantUpdate to true will modify permission grants without
245+
# prompting for confirmation. This can result in unintended changes to your
246+
# application's security settings. Use with caution!
247+
$ForceGrantUpdate = $false
248+
249+
# Set the start and end dates for the audit log search
250+
# If setting date use yyyy-MM-dd format
251+
# endDate is set to tomorrow to include today's audit logs
252+
$startDate = (Get-Date).AddDays(-7).ToString('yyyy-MM-dd')
253+
$endDate = (Get-Date).AddDays(1).ToString('yyyy-MM-dd')
254+
255+
# Set the service principal ID
256+
$servicePrincipalId = "efe87e5d-05cb-4b19-9b36-1eb923448697"
257+
258+
Write-Host "Searching for audit logs between $startDate and $endDate" -ForegroundColor Green
259+
Write-Host "Searching for audit logs for service principal $servicePrincipalId" -ForegroundColor Green
260+
261+
if ($ForceGrantUpdate -eq $true) {
262+
Write-Host "WARNING: ForceGrantUpdate is set to true. This will modify permission grants without prompting for confirmation. This can result in unintended changes to your application's security settings. Use with caution!" -ForegroundColor Red
263+
$continue = Read-Host "Do you want to continue? (Y/N)"
264+
if ($continue -eq "Y" -or $continue -eq "y") {
265+
Write-Host "Continuing..."
266+
} else {
267+
Write-Host "Exiting..."
268+
exit
269+
}
270+
}
271+
272+
# Connect to MS Graph
273+
Connect-MgGraph -Scopes "AuditLog.Read.All","Application.Read.All","AppRoleAssignment.ReadWrite.All" -ErrorAction Stop | Out-Null
274+
275+
# Set the filter for the audit log search
276+
$filterAppRoleAssignment = "activityDateTime ge $startDate and activityDateTime le $endDate" +
277+
" and Result eq 'success'" +
278+
" and ActivityDisplayName eq 'Remove app role assignment from service principal'" +
279+
" and targetResources/any(x: x/id eq '$servicePrincipalId')"
280+
281+
try {
282+
# Retrieve the audit logs for removed AppRoleAssignments
283+
$appRoleAssignmentsAuditLogs = Get-MgAuditLogDirectoryAudit -Filter $filterAppRoleAssignment -All -ErrorAction Stop
284+
}
285+
catch {
286+
Disconnect-MgGraph | Out-Null
287+
throw $_
288+
}
289+
290+
$permissionCount = 0
291+
foreach ($auditLog in $appRoleAssignmentsAuditLogs) {
292+
$resourceId = $auditLog.TargetResources[0].Id
293+
# We only want to process AppRoleAssignments Audit Logs where $servicePrincipalId is the principalId not the resourceId
294+
if ($resourceId -eq $servicePrincipalId) {
295+
continue
296+
}
297+
$appRoleId = $auditLog.TargetResources[0].ModifiedProperties | Where-Object { $_.DisplayName -eq "AppRole.Id" } | Select-Object -ExpandProperty OldValue
298+
$appRoleId = $appRoleId.Replace('"', '')
299+
$params = @{
300+
principalId = $servicePrincipalId
301+
resourceId = $resourceId
302+
appRoleId = $appRoleId
303+
}
304+
305+
try {
306+
$sp = Get-MgServicePrincipal -ServicePrincipalId $resourceId
307+
$appRole = $sp.AppRoles | Where-Object { $_.Id -eq $appRoleId }
308+
309+
Write-Host "--------------------------"
310+
if ($ForceGrantUpdate -eq $true) {
311+
Write-Host "Creating AppRoleAssignment with the following parameters:"
312+
} else {
313+
Write-Host "Potentially removed AppRoleAssignment with the following parameters:"
314+
}
315+
Write-Host " principalId: $($params.principalId)"
316+
Write-Host " resourceId: $($params.resourceId)"
317+
Write-Host " appRoleId: $($params.appRoleId)"
318+
Write-Host " appRoleValue: $($appRole.Value)"
319+
Write-Host " appRoleDisplayName: $($appRole.DisplayName)"
320+
if ($ForceGrantUpdate -eq $true) {
321+
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $servicePrincipalId -BodyParameter $params -ErrorAction Stop | Out-Null
322+
Write-Host "AppRoleAssignment was created successfully" -ForegroundColor Green
323+
}
324+
$permissionCount++
325+
}
326+
catch {
327+
if ($_.Exception.Message -like "*Permission being assigned already exists on the object*") {
328+
Write-Host "AppRoleAssignment already exists skipping creation" -ForegroundColor Yellow
329+
}
330+
else {
331+
Disconnect-MgGraph | Out-Null
332+
throw $_
333+
}
334+
}
335+
}
336+
337+
Disconnect-MgGraph | Out-Null
338+
339+
if ($ForceGrantUpdate -eq $true) {
340+
Write-Host "--------------------------"
341+
Write-Host "$permissionCount AppRoleAssignments were created successfully" -ForegroundColor Green
342+
} else {
343+
Write-Host "--------------------------"
344+
Write-Host "$permissionCount AppRoleAssignments were found" -ForegroundColor Green
345+
}
346+
347+
```
348+
349+
:::zone-end

articles/active-directory/manage-apps/toc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@
144144
href: grant-consent-single-user.md
145145
- name: Review permissions granted to apps
146146
href: manage-application-permissions.md
147+
- name: Restore revoked permissions
148+
href: restore-permissions.md
147149
- name: Manage user assignment
148150
href: assign-user-or-group-access-portal.md
149151
- name: Assign custom security attributes

articles/zone-pivot-groups.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2308,6 +2308,15 @@ groups:
23082308
title: Microsoft graph PowerShell
23092309
- id: ms-graph
23102310
title: Microsoft Graph
2311+
## revoke permissions
2312+
- id: delegated-app-permissions
2313+
title: Permissions
2314+
prompt: Choose an option
2315+
pivots:
2316+
- id: delegated-perms
2317+
title: Delegated permissions
2318+
- id: app-perms
2319+
title: App-only permissions
23112320
# Owner: juliakm
23122321
- id: pipelines-version
23132322
title: Pipelines version

0 commit comments

Comments
 (0)