-
Notifications
You must be signed in to change notification settings - Fork 91
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
Feature: Unconsent permissions #2063
Merged
Merged
Changes from 26 commits
Commits
Show all changes
97 commits
Select commit
Hold shift + click to select a range
08f736c
Modify permissions tab user interface
MaryannGitonga b1e3a13
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga e00096d
Add unconsent permissions logic
MaryannGitonga 568a0dc
update redux with new scope
Onokaev 0dabe41
put returned permissions in an array before updating redux store
Onokaev aa263c0
get new token
Onokaev 5aa85db
add revoking state
Onokaev bf1f78d
do default checks before unconsenting
Onokaev 493568e
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga 14587d3
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga 07bb907
update status message
Onokaev daa5d42
log user in to get a fresh token
Onokaev 2796f99
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga 54e8ff4
Add default scopes for unconsenting permissions as a graph constant
MaryannGitonga e74094e
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga f50ba35
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga 804622e
Merge branch 'dev' into feature/unconsent-permissions
MaryannGitonga df42943
Add check for required permissions, remove the button on the panel li…
MaryannGitonga 3b41856
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga 832c74d
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga 900a11a
Refactor logic
MaryannGitonga 00dae51
Refactor graph client
MaryannGitonga 70bbd0a
streamline functions
thewahome 0228fa6
Refactor unconsenting permissions code
MaryannGitonga dda725d
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga a000cf3
fix filtering on getting grants
Onokaev cc57352
Remove comments & remove patchQuery
MaryannGitonga b0f8ad2
add status code to received errors
Onokaev 30dd437
retry getting an access token once
Onokaev c293348
Make changes suggested on PR
MaryannGitonga 7b7d90e
Merge branch 'dev' into feature/unconsent-permissions
MaryannGitonga 9149a7b
Remove unused code
MaryannGitonga 8bb61df
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga 4defa7b
Remove unused code
MaryannGitonga 456ff99
Change unconsent to revoke
MaryannGitonga e09db37
fix failing test
Onokaev a7d31d7
remove unused import
Onokaev f12a532
fix crashing on permissions panel
Onokaev 1b07855
add padding to Modify Permissions tab
Onokaev 505d5cb
use the abstracted makeGraphRequest function
Onokaev 712dae8
change getPermissionResponse to a more generic name
Onokaev a8bf214
remove unused import
Onokaev 036aeb8
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 307df29
refactor revokeScopes function
Onokaev 8770911
add telemetry for revoking permissions
Onokaev f134b17
remove comments
Onokaev 7313eb4
update failing test
Onokaev a429a32
add Consented string
Onokaev b0a3316
remove selection on panel list
Onokaev 5eb5a28
remove overflow from cellName
Onokaev e0b1aff
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 2d6d36e
add required text
Onokaev 8edfd06
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 9ef7da4
prevent users from dissenting to admin pre-consented permissions
Onokaev 140e62d
fix code smells
Onokaev 45b573f
remove unnecessary await
Onokaev 391f039
Merge branch 'dev' into feature/unconsent-permissions
Onokaev f136f7d
check if user dissenting is an admin
Onokaev da3a174
allow admins to revoke all principal grants
Onokaev bc92d2a
prevent all users from dissenting to AllPrincipal permissions
Onokaev ad50d68
add messages to Ge.json file
Onokaev aacd5c8
remove admin roles from graph constants
Onokaev d97346e
add telemetry for all principal scopes
Onokaev eebcd91
allow tenant admin to revoke allprincipal permissions
Onokaev 7361106
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 3202ed3
check for required scopes within allprincipal grant
Onokaev 030bdb5
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
Onokaev 51c9003
Task: Add consent type column (#2180)
Onokaev 52983a1
remove styling on status column
Onokaev cbcad9f
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 38ed307
fix merge conflicts
Onokaev 49bd93a
fix case
Onokaev 14b49b4
add tests
Onokaev 953b361
fix code smells
Onokaev 38602df
Merge branch 'dev' into feature/unconsent-permissions
Onokaev b0fdd53
fix failing test
Onokaev 58302cc
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
Onokaev 7e86d1f
fix failing tests
Onokaev 3bb5481
add link telemetry
Onokaev d50fe6e
update error status reporting
Onokaev 49e2c69
add check for allgrants availability
Onokaev 9ba635a
adjust tests
Onokaev c7fa6d5
refactor revokeScopes function
Onokaev 650bbea
show consentType regardless of tiken being present
Onokaev 3399c89
add icon to Unconsent button
Onokaev d2bcfe9
fix button size
Onokaev 3a74f45
fix tests
Onokaev 0c549b5
fix code smells
Onokaev f49b263
remove string[] assertion
Onokaev b474166
update message when revoking scopes fails
Onokaev 0cea417
add more styling to info icon
Onokaev 7648117
update message when permission propagation delays
Onokaev d23717d
Merge branch 'dev' into feature/unconsent-permissions
Onokaev 267c649
fix merge conflicts
Onokaev 8802bdd
fix failing test
Onokaev c3c0ee2
increase tab header height
Onokaev 696ccf9
dynamically adjust header height
Onokaev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,20 +10,29 @@ import { sanitizeQueryUrl } from '../../utils/query-url-sanitization'; | |
import { parseSampleUrl } from '../../utils/sample-url-generation'; | ||
import { translateMessage } from '../../utils/translate-messages'; | ||
import { getConsentAuthErrorHint } from '../../../modules/authentication/authentication-error-hints'; | ||
import { ACCOUNT_TYPE, PERMS_SCOPE } from '../graph-constants'; | ||
import { | ||
ACCOUNT_TYPE, DEFAULT_USER_SCOPES, GRAPH_URL, PERMS_SCOPE, | ||
UNCONSENTING_PERMISSIONS_REQUIRED_SCOPES | ||
} from '../graph-constants'; | ||
import { | ||
FETCH_SCOPES_ERROR, | ||
FETCH_FULL_SCOPES_PENDING, | ||
FETCH_URL_SCOPES_PENDING, | ||
FETCH_FULL_SCOPES_SUCCESS, | ||
FETCH_URL_SCOPES_SUCCESS | ||
FETCH_URL_SCOPES_SUCCESS, | ||
REVOKE_PERMISSION_PENDING, | ||
REVOKE_PERMISSION_SUCCESS, | ||
REVOKE_PERMISSION_ERROR | ||
} from '../redux-constants'; | ||
import { | ||
getAuthTokenSuccess, | ||
getConsentedScopesSuccess | ||
} from './auth-action-creators'; | ||
import { getProfileInfo } from './profile-action-creators'; | ||
import { setQueryResponseStatus } from './query-status-action-creator'; | ||
import { GraphClient } from '../graph-client'; | ||
import { IQuery } from '../../../types/query-runner'; | ||
import { makeGraphRequest, parseResponse } from './query-action-creator-util'; | ||
|
||
export function fetchFullScopesSuccess(response: object): IAction { | ||
return { | ||
|
@@ -50,6 +59,27 @@ export function fetchScopesError(response: object): IAction { | |
}; | ||
} | ||
|
||
export function revokePermissionPending(response: boolean): any { | ||
return { | ||
type: REVOKE_PERMISSION_PENDING, | ||
response | ||
} | ||
} | ||
|
||
export function revokePermissionSuccess(response: boolean): any { | ||
return { | ||
type: REVOKE_PERMISSION_SUCCESS, | ||
response | ||
} | ||
} | ||
|
||
export function revokePermissionError(response: object): any { | ||
return { | ||
type: REVOKE_PERMISSION_ERROR, | ||
response | ||
} | ||
} | ||
|
||
export function fetchScopes(): Function { | ||
return async (dispatch: Function, getState: Function) => { | ||
try { | ||
|
@@ -142,3 +172,170 @@ export function consentToScopes(scopes: string[]): Function { | |
} | ||
}; | ||
} | ||
|
||
export function unconsentToScopes(permissionToDelete: string): Function { | ||
return async (dispatch: Function, getState: Function) => { | ||
const { consentedScopes, profile } = getState(); | ||
const requiredPermissions = UNCONSENTING_PERMISSIONS_REQUIRED_SCOPES.split(' '); | ||
const defaultScopes = DEFAULT_USER_SCOPES.split(' '); | ||
let response = null; | ||
|
||
if (userUnconsentingToDefaultScopes(defaultScopes, permissionToDelete)) { | ||
dispatch( | ||
setQueryResponseStatus({ | ||
statusText: translateMessage('Default scope'), | ||
status: translateMessage('Cannot delete default scope'), | ||
ok: false, | ||
messageType: MessageBarType.error | ||
}) | ||
); | ||
return; | ||
} | ||
|
||
if (!userHasRequiredPermissions(requiredPermissions, consentedScopes)) { | ||
dispatch( | ||
setQueryResponseStatus({ | ||
statusText: translateMessage('Unable to dissent'), | ||
status: translateMessage('You require the following permissions to unconsent'), | ||
ok: false, | ||
messageType: MessageBarType.error | ||
}) | ||
); | ||
return; | ||
} | ||
|
||
try { | ||
if (!consentedScopes && consentedScopes.length < 1) { | ||
return; | ||
} | ||
|
||
const newScopesArray: string[] = (consentedScopes.filter((scope: string) => scope !== permissionToDelete)); | ||
const newScopesString = newScopesArray.join(' '); | ||
|
||
const servicePrincipalAppId = await getCurrentAppId(consentedScopes); | ||
response = await getPermissionGrant(consentedScopes, servicePrincipalAppId, profile.id); | ||
const permissionGrantId = response.id; | ||
|
||
await revokePermission(consentedScopes, permissionGrantId, newScopesString); | ||
|
||
response = await getPermissionGrant(consentedScopes, servicePrincipalAppId, profile.id); | ||
const updatedScopes = response.scope.split(' '); | ||
|
||
if (updatedScopes.length !== newScopesArray.length) { | ||
return; | ||
} | ||
|
||
const authResponse = await getNewAuthObject(updatedScopes); | ||
console.log('We have the authentication response. Now going back') | ||
|
||
if (authResponse && authResponse.accessToken) { | ||
console.log('We are here now after getting the auth results') | ||
dispatch(getAuthTokenSuccess(true)); | ||
dispatch(getConsentedScopesSuccess(authResponse.scopes)); | ||
dispatch(revokePermissionSuccess(true)); | ||
dispatch( | ||
setQueryResponseStatus({ | ||
statusText: translateMessage('Success'), | ||
status: translateMessage('Permission unconsented'), | ||
ok: true, | ||
messageType: MessageBarType.success | ||
}) | ||
); | ||
// if (authResponse.account && authResponse.account.localAccountId !== profile?.id) { | ||
// dispatch(getProfileInfo()); | ||
// } | ||
} | ||
|
||
} | ||
catch (error: any) { | ||
dispatch(revokePermissionError(error)); | ||
dispatch( | ||
setQueryResponseStatus({ | ||
statusText: translateMessage('Unable to dissent'), | ||
// eslint-disable-next-line max-len | ||
status: error ? error : translateMessage('An error was encountered when dissenting. Confirm that you have the right permissions'), | ||
ok: false, | ||
messageType: MessageBarType.error | ||
}) | ||
); | ||
} | ||
} | ||
} | ||
|
||
const getQuery: IQuery = { | ||
selectedVerb: 'GET', | ||
sampleHeaders: [ | ||
{ | ||
name: 'Cache-Control', | ||
value: 'no-cache' | ||
} | ||
], | ||
selectedVersion: '', | ||
sampleUrl: '' | ||
}; | ||
|
||
const patchQuery: IQuery = { | ||
selectedVerb: 'PATCH', | ||
sampleHeaders: [ | ||
{ | ||
name: 'Cache-Control', | ||
value: 'no-cache' | ||
} | ||
], | ||
selectedVersion: '', | ||
sampleUrl: '' | ||
}; | ||
|
||
const userHasRequiredPermissions = (requiredPermissions: string[], | ||
consentedScopes: string[]) => { | ||
return requiredPermissions.every(scope => consentedScopes.includes(scope)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The functionality here can also be moved to the getPermissionResponse function so that we do not need to import the Graph Client. |
||
} | ||
|
||
const userUnconsentingToDefaultScopes = (currentScopes: string[], permissionToDelete: string) => { | ||
return currentScopes.includes(permissionToDelete); | ||
} | ||
|
||
const getCurrentAppId = async (scopes: string[]) => { | ||
const currentAppId = process.env.REACT_APP_CLIENT_ID; | ||
getQuery.sampleUrl = `${GRAPH_URL}/v1.0/servicePrincipals?$filter=appId eq '${currentAppId}'`; | ||
const response = await getPermissionResponse(scopes, getQuery); | ||
return response.value[0].id; | ||
} | ||
const revokePermission = async (oldScopes: string[], permissionGrantId: string, newScopes: string) => { | ||
patchQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants/${permissionGrantId}`; | ||
patchQuery.sampleBody = JSON.stringify({ | ||
scope: newScopes | ||
}); | ||
await getPermissionResponse(oldScopes, patchQuery); | ||
} | ||
|
||
const getPermissionGrant = async (scopes: string[], servicePrincipalAppId: string, principalid: string) => { | ||
getQuery.sampleUrl = `${GRAPH_URL}/v1.0/oauth2PermissionGrants?$filter=clientId eq '${servicePrincipalAppId}'`; | ||
const response = await getPermissionResponse(scopes, getQuery); | ||
|
||
if (response && response.value.length > 1) { | ||
const filteredResponse = response.value.filter((permissionGrant: any) => | ||
permissionGrant.principalId === principalid); | ||
return filteredResponse[0]; | ||
} | ||
return response.value[0]; | ||
} | ||
|
||
const getPermissionResponse = async (scopes: string[], query: IQuery) => { | ||
const respHeaders: any = {}; | ||
const response = await makeGraphRequest(scopes)(query); | ||
return parseResponse(response, respHeaders); | ||
} | ||
|
||
const getNewAuthObject = async (updatedScopes: string[]) => { | ||
let authResponse = await authenticationWrapper.consentToScopes(updatedScopes); | ||
let count = 4; | ||
|
||
while ((authResponse.scopes.length !== updatedScopes.length) && count > 0) { | ||
authResponse = await authenticationWrapper.consentToScopes(updatedScopes); | ||
count--; | ||
} | ||
|
||
return authResponse; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't need to be in the try catch as it has no chances of error. We can move it just above as it is defensive