Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Unconsent permissions #2063

Merged
merged 97 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
08f736c
Modify permissions tab user interface
MaryannGitonga Aug 23, 2022
b1e3a13
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga Aug 23, 2022
e00096d
Add unconsent permissions logic
MaryannGitonga Aug 26, 2022
568a0dc
update redux with new scope
Onokaev Aug 26, 2022
0dabe41
put returned permissions in an array before updating redux store
Onokaev Aug 26, 2022
aa263c0
get new token
Onokaev Aug 29, 2022
5aa85db
add revoking state
Onokaev Aug 30, 2022
bf1f78d
do default checks before unconsenting
Onokaev Aug 30, 2022
493568e
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga Aug 30, 2022
14587d3
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga Aug 30, 2022
07bb907
update status message
Onokaev Aug 30, 2022
daa5d42
log user in to get a fresh token
Onokaev Aug 30, 2022
2796f99
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga Aug 30, 2022
54e8ff4
Add default scopes for unconsenting permissions as a graph constant
MaryannGitonga Aug 31, 2022
e74094e
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga Aug 31, 2022
f50ba35
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga Sep 1, 2022
804622e
Merge branch 'dev' into feature/unconsent-permissions
MaryannGitonga Sep 1, 2022
df42943
Add check for required permissions, remove the button on the panel li…
MaryannGitonga Sep 1, 2022
3b41856
Merge branch 'dev' of https://github.com/microsoftgraph/microsoft-gra…
MaryannGitonga Sep 1, 2022
832c74d
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga Sep 1, 2022
900a11a
Refactor logic
MaryannGitonga Sep 2, 2022
00dae51
Refactor graph client
MaryannGitonga Sep 5, 2022
70bbd0a
streamline functions
thewahome Sep 5, 2022
0228fa6
Refactor unconsenting permissions code
MaryannGitonga Sep 5, 2022
dda725d
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga Sep 5, 2022
a000cf3
fix filtering on getting grants
Onokaev Sep 6, 2022
cc57352
Remove comments & remove patchQuery
MaryannGitonga Sep 7, 2022
b0f8ad2
add status code to received errors
Onokaev Sep 7, 2022
30dd437
retry getting an access token once
Onokaev Sep 7, 2022
c293348
Make changes suggested on PR
MaryannGitonga Sep 7, 2022
7b7d90e
Merge branch 'dev' into feature/unconsent-permissions
MaryannGitonga Sep 7, 2022
9149a7b
Remove unused code
MaryannGitonga Sep 7, 2022
8bb61df
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
MaryannGitonga Sep 7, 2022
4defa7b
Remove unused code
MaryannGitonga Sep 7, 2022
456ff99
Change unconsent to revoke
MaryannGitonga Sep 7, 2022
e09db37
fix failing test
Onokaev Sep 8, 2022
a7d31d7
remove unused import
Onokaev Sep 8, 2022
f12a532
fix crashing on permissions panel
Onokaev Sep 8, 2022
1b07855
add padding to Modify Permissions tab
Onokaev Sep 12, 2022
505d5cb
use the abstracted makeGraphRequest function
Onokaev Sep 12, 2022
712dae8
change getPermissionResponse to a more generic name
Onokaev Sep 12, 2022
a8bf214
remove unused import
Onokaev Sep 12, 2022
036aeb8
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Sep 14, 2022
307df29
refactor revokeScopes function
Onokaev Sep 15, 2022
8770911
add telemetry for revoking permissions
Onokaev Sep 15, 2022
f134b17
remove comments
Onokaev Sep 15, 2022
7313eb4
update failing test
Onokaev Sep 15, 2022
a429a32
add Consented string
Onokaev Sep 16, 2022
b0a3316
remove selection on panel list
Onokaev Oct 6, 2022
5eb5a28
remove overflow from cellName
Onokaev Oct 6, 2022
e0b1aff
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 6, 2022
2d6d36e
add required text
Onokaev Oct 6, 2022
8edfd06
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 11, 2022
9ef7da4
prevent users from dissenting to admin pre-consented permissions
Onokaev Oct 11, 2022
140e62d
fix code smells
Onokaev Oct 11, 2022
45b573f
remove unnecessary await
Onokaev Oct 11, 2022
391f039
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 14, 2022
f136f7d
check if user dissenting is an admin
Onokaev Oct 17, 2022
da3a174
allow admins to revoke all principal grants
Onokaev Oct 18, 2022
bc92d2a
prevent all users from dissenting to AllPrincipal permissions
Onokaev Oct 18, 2022
ad50d68
add messages to Ge.json file
Onokaev Oct 18, 2022
aacd5c8
remove admin roles from graph constants
Onokaev Oct 18, 2022
d97346e
add telemetry for all principal scopes
Onokaev Oct 18, 2022
eebcd91
allow tenant admin to revoke allprincipal permissions
Onokaev Oct 24, 2022
7361106
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 24, 2022
3202ed3
check for required scopes within allprincipal grant
Onokaev Oct 25, 2022
030bdb5
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
Onokaev Oct 25, 2022
51c9003
Task: Add consent type column (#2180)
Onokaev Oct 31, 2022
52983a1
remove styling on status column
Onokaev Oct 31, 2022
cbcad9f
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 31, 2022
38ed307
fix merge conflicts
Onokaev Oct 31, 2022
49bd93a
fix case
Onokaev Oct 31, 2022
14b49b4
add tests
Onokaev Oct 31, 2022
953b361
fix code smells
Onokaev Oct 31, 2022
38602df
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Oct 31, 2022
b0fdd53
fix failing test
Onokaev Oct 31, 2022
58302cc
Merge branch 'feature/unconsent-permissions' of https://github.com/mi…
Onokaev Oct 31, 2022
7e86d1f
fix failing tests
Onokaev Oct 31, 2022
3bb5481
add link telemetry
Onokaev Oct 31, 2022
d50fe6e
update error status reporting
Onokaev Oct 31, 2022
49e2c69
add check for allgrants availability
Onokaev Nov 1, 2022
9ba635a
adjust tests
Onokaev Nov 1, 2022
c7fa6d5
refactor revokeScopes function
Onokaev Nov 1, 2022
650bbea
show consentType regardless of tiken being present
Onokaev Nov 1, 2022
3399c89
add icon to Unconsent button
Onokaev Nov 1, 2022
d2bcfe9
fix button size
Onokaev Nov 1, 2022
3a74f45
fix tests
Onokaev Nov 1, 2022
0c549b5
fix code smells
Onokaev Nov 2, 2022
f49b263
remove string[] assertion
Onokaev Nov 3, 2022
b474166
update message when revoking scopes fails
Onokaev Nov 3, 2022
0cea417
add more styling to info icon
Onokaev Nov 3, 2022
7648117
update message when permission propagation delays
Onokaev Nov 7, 2022
d23717d
Merge branch 'dev' into feature/unconsent-permissions
Onokaev Nov 7, 2022
267c649
fix merge conflicts
Onokaev Nov 7, 2022
8802bdd
fix failing test
Onokaev Nov 8, 2022
c3c0ee2
increase tab header height
Onokaev Nov 8, 2022
696ccf9
dynamically adjust header height
Onokaev Nov 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 295 additions & 11 deletions src/app/services/actions/permissions-action-creator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,60 @@
import {
FETCH_SCOPES_ERROR,
FETCH_FULL_SCOPES_PENDING,
FETCH_FULL_SCOPES_SUCCESS,
FETCH_URL_SCOPES_PENDING
FETCH_URL_SCOPES_PENDING,
QUERY_GRAPH_STATUS
} from '../../../app/services/redux-constants';

import {
fetchFullScopesSuccess, fetchFullScopesPending, fetchUrlScopesPending, fetchScopesError, getPermissionsScopeType
fetchFullScopesSuccess, fetchScopesError, getPermissionsScopeType, fetchScopes,
consentToScopes,
fetchUrlScopesPending,
fetchFullScopesPending,
revokeScopes
} from './permissions-action-creator';
import { IPermissionsResponse } from '../../../types/permissions';
import { store } from '../../../store/index';
import { ApplicationState } from '../../../types/root';
import { Mode } from '../../../types/enums';
import { AppAction } from '../../../types/action';
import configureMockStore from 'redux-mock-store';
import { authenticationWrapper } from '../../../modules/authentication';
import thunk from 'redux-thunk';
import { ACCOUNT_TYPE } from '../graph-constants';
import { RevokePermissionsUtil } from './permissions-action-creator.util';
import { cleanup } from '@testing-library/react';
const middleware = [thunk];
let mockStore = configureMockStore(middleware);

afterEach(cleanup);
beforeEach(() => {
const mockStore_ = configureMockStore(middleware);
mockStore = mockStore_
})
window.open = jest.fn();
window.fetch = jest.fn();

const mockState: ApplicationState = {
devxApi: {
baseUrl: 'https://graph.microsoft.com/v1.0/me',
parameters: '$count=true'
},
permissionsPanelOpen: true,
profile: null,
profile: {
id: '123',
displayName: 'test',
emailAddress: 'johndoe@ms.com',
profileImageUrl: 'https://graph.microsoft.com/v1.0/me/photo/$value',
ageGroup: 0,
tenant: 'binaryDomain',
profileType: ACCOUNT_TYPE.MSA
},
sampleQuery: {
sampleUrl: 'http://localhost:8080/api/v1/samples/1',
selectedVerb: 'GET',
selectedVersion: 'v1',
sampleHeaders: []
},
authToken: { token: false, pending: false },
consentedScopes: [],
consentedScopes: ['profile.read User.Read Files.Read'],
isLoadingData: false,
queryRunnerStatus: null,
termsOfUse: true,
Expand Down Expand Up @@ -128,7 +151,7 @@ describe('Permissions action creators', () => {
}
}

const expectedAction: AppAction = {
const expectedAction = {
type: FETCH_FULL_SCOPES_SUCCESS,
response
}
Expand All @@ -146,7 +169,7 @@ describe('Permissions action creators', () => {
error: {}
}

const expectedAction: AppAction = {
const expectedAction = {
type: FETCH_SCOPES_ERROR,
response
}
Expand All @@ -162,7 +185,7 @@ describe('Permissions action creators', () => {
it('should dispatch FETCH_FULL_SCOPES_PENDING or FETCH_URL_SCOPES_PENDING depending on type passed to fetchScopesPending', () => {
// Arrange
const expectedFullScopesAction = {
type: FETCH_FULL_SCOPES_PENDING,
type: 'FETCH_SCOPES_PENDING',
response: 'full'
}

Expand All @@ -173,7 +196,7 @@ describe('Permissions action creators', () => {

// Act
const fullScopesAction = fetchFullScopesPending();
const urlScopesAction = fetchUrlScopesPending()
const urlScopesAction = fetchUrlScopesPending();

// Assert
expect(fullScopesAction).toEqual(expectedFullScopesAction);
Expand All @@ -191,4 +214,265 @@ describe('Permissions action creators', () => {
expect(result).toEqual(expectedResult);

});

it('should fetch scopes', () => {
// Arrange
const expectedResult = {}
const expectedAction: any = [
{
type: 'FETCH_SCOPES_PENDING',
response: 'full'
},
{
type: 'FULL_SCOPES_FETCH_SUCCESS',
response: {
scopes: {
fullPermissions: {}
}
}
}
];

const store_ = mockStore(mockState);

const mockFetch = jest.fn().mockImplementation(() => {
return Promise.resolve({
ok: true,
json: () => Promise.resolve(expectedResult)
})
});

window.fetch = mockFetch;

// Act and Assert
// @ts-ignore
return store_.dispatch(fetchScopes())
// @ts-ignore
.then(() => {
expect(store_.getActions()).toEqual(expectedAction);
});
});

it('should consent to scopes', () => {
// Arrange
jest.spyOn(authenticationWrapper, 'consentToScopes').mockResolvedValue({
accessToken: 'jkkkkkkkkkkkkkkkkkkkksdss',
authority: 'string',
uniqueId: 'string',
tenantId: 'string',
scopes: ['profile.Read User.Read'],
account: null,
idToken: 'string',
idTokenClaims: {},
fromCache: true,
expiresOn: new Date(),
tokenType: 'AAD',
correlationId: 'string'
})
const expectedAction: any = [
{
type: 'GET_AUTH_TOKEN_SUCCESS',
response: true
},
{
type: 'GET_CONSENTED_SCOPES_SUCCESS',
response: ['profile.Read User.Read']
},
{
type: 'QUERY_GRAPH_STATUS',
response: {
statusText: 'Success',
status: 'Scope consent successful',
ok: true,
messageType: 4
}
}
];

const store_ = mockStore(mockState);

// Act and Assert
// @ts-ignore
return store_.dispatch(consentToScopes())
// @ts-ignore
.then(() => {
expect(store_.getActions()).toEqual(expectedAction);
});
});

describe('Revoke scopes', () => {
it('should return Default Scope error when user tries to dissent to default scope', () => {
// Arrange
const store_ = mockStore(mockState);
jest.spyOn(RevokePermissionsUtil, 'getServicePrincipalId').mockResolvedValue('1234');
jest.spyOn(RevokePermissionsUtil, 'getSignedInPrincipalGrant').mockReturnValue({
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'profile.read User.Read',
id: 'SomeNiceId'
})
jest.spyOn(RevokePermissionsUtil, 'getTenantPermissionGrants').mockResolvedValue({
value: [
{
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'profile.read User.Read',
id: 'SomeNiceId'
},
{
clientId: '',
consentType: 'AllPrincipal',
principalId: null,
resourceId: '1234',
scope: 'profile.read User.Read Directory.Read.All'
}
],
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#permissionGrants'
})

const expectedActions = [
{ type: 'REVOKE_SCOPES_PENDING', response: null },
{ type: 'REVOKE_SCOPES_ERROR', response: null },
{
type: QUERY_GRAPH_STATUS,
response: {
statusText: 'Failed',
status: 'An error occurred when unconsenting. Please try again',
ok: false,
messageType: 1
}
}
]


// Act and Assert
// @ts-ignore
return store_.dispatch(revokeScopes('User.Read'))
// @ts-ignore
.then(() => {
expect(store_.getActions()).toEqual(expectedActions);
});
});

it('should return 401 when user does not have required permissions', () => {
// Arrange
const store_ = mockStore(mockState);
jest.spyOn(RevokePermissionsUtil, 'getServicePrincipalId').mockResolvedValue('1234');
jest.spyOn(RevokePermissionsUtil, 'getSignedInPrincipalGrant').mockReturnValue({
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'profile.read User.Read Access.Read',
id: 'SomeNiceId'
})
jest.spyOn(RevokePermissionsUtil, 'getTenantPermissionGrants').mockResolvedValue({
value: [
{
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'profile.read User.Read Access.Read',
id: 'SomeNiceId'
},
{
clientId: '',
consentType: 'AllPrincipals',
principalId: null,
resourceId: '1234',
scope: 'profile.read User.Read Directory.Reaqd.All Access.Read'
}
],
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#permissionGrants'
});

const expectedActions = [
{ type: 'REVOKE_SCOPES_PENDING', response: null },
{ type: 'REVOKE_SCOPES_ERROR', response: null },
{
type: QUERY_GRAPH_STATUS,
response: {
statusText: 'Failed',
status: 'An error occurred when unconsenting. Please try again',
ok: false,
messageType: 1
}
}
]

// Act and Assert
// @ts-ignore
return store_.dispatch(revokeScopes('Access.Read'))
// @ts-ignore
.then(() => {
expect(store_.getActions()).toEqual(expectedActions);
});

});

//revisit
it('should raise error when user attempts to dissent to an admin granted permission', () => {
// Arrange
const store_ = mockStore(mockState);
jest.spyOn(RevokePermissionsUtil, 'getServicePrincipalId').mockResolvedValue('1234');
jest.spyOn(RevokePermissionsUtil, 'getSignedInPrincipalGrant').mockReturnValue({
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'profile.read User.Read Access.Read Files.Read',
id: 'SomeNiceId'
})
jest.spyOn(RevokePermissionsUtil, 'getTenantPermissionGrants').mockResolvedValue({
value: [
{
clientId: '1234',
consentType: 'Principal',
principalId: '1234',
resourceId: '1234',
scope: 'User.Read Access.Read DelegatedPermissionGrant.ReadWrite.All Directory.Read.All Files.Read',
id: 'SomeNiceId'
},
{
clientId: '',
consentType: 'AllPrincipals',
principalId: null,
resourceId: '1234',
// eslint-disable-next-line max-len
scope: 'profile.read User.Read Directory.Reaqd.All Access.Read DelegatedPermissionGrant.ReadWrite.All Directory.Read.All'
}
],
'@odata.context': 'https://graph.microsoft.com/v1.0/$metadata#permissionGrants'
});

jest.spyOn(RevokePermissionsUtil, 'isSignedInUserTenantAdmin').mockResolvedValue(false);

const expectedActions = [
{ type: 'REVOKE_SCOPES_PENDING', response: null },
{ type: 'REVOKE_SCOPES_ERROR', response: null },
{
type: QUERY_GRAPH_STATUS,
response: {
statusText: 'Failed',
status: 'An error occurred when unconsenting. Please try again',
ok: false,
messageType: 1
}
}
]

// Act and Assert
// @ts-ignore
return store_.dispatch(revokeScopes('Access.Read'))
// @ts-ignore
.then(() => {
expect(store_.getActions()).toEqual(expectedActions);
});
});
})
})
Loading