Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the 'spo serviceprincipal grant list' command solving #155
- Loading branch information
1 parent
7ad8af2
commit 93be29c
Showing
6 changed files
with
495 additions
and
0 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
docs/manual/docs/cmd/spo/serviceprincipal/serviceprincipal-grant-list.md
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 |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# spo serviceprincipal grant list | ||
|
||
Lists permissions granted to the service principal | ||
|
||
## Usage | ||
|
||
```sh | ||
spo serviceprincipal grant list [options] | ||
``` | ||
|
||
## Alias | ||
|
||
```sh | ||
spo sp grant list | ||
``` | ||
|
||
## Options | ||
|
||
Option|Description | ||
------|----------- | ||
`--help`|output usage information | ||
`-o, --output <output>`|Output type. `json|text`. Default `text` | ||
`--verbose`|Runs command with verbose logging | ||
`--debug`|Runs command with debug logging | ||
|
||
!!! important | ||
Before using this command, connect to a SharePoint Online tenant admin site, using the [spo connect](../connect.md) command. | ||
|
||
## Remarks | ||
|
||
To list permission granted to the service principal, you have to first connect to a tenant admin site using the [spo connect](../connect.md) command, eg. `spo connect https://contoso-admin.sharepoint.com`. | ||
|
||
## Examples | ||
|
||
List all permissions granted to the service principal | ||
|
||
```sh | ||
spo serviceprincipal grant list | ||
``` |
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
9 changes: 9 additions & 0 deletions
9
src/o365/spo/commands/serviceprincipal/SPOWebAppServicePrincipalPermissionGrant.ts
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 |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface SPOWebAppServicePrincipalPermissionGrant { | ||
_ObjectType_: string; | ||
ClientId: string; | ||
ConsentType: string; | ||
ObjectId: string; | ||
Resource: string; | ||
ResourceId: string; | ||
Scope: string; | ||
} |
325 changes: 325 additions & 0 deletions
325
src/o365/spo/commands/serviceprincipal/serviceprincipal-grant-list.spec.ts
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 |
---|---|---|
@@ -0,0 +1,325 @@ | ||
import commands from '../../commands'; | ||
import Command, { CommandOption, CommandError } from '../../../../Command'; | ||
import * as sinon from 'sinon'; | ||
import appInsights from '../../../../appInsights'; | ||
import auth, { Site } from '../../SpoAuth'; | ||
const command: Command = require('./serviceprincipal-grant-list'); | ||
import * as assert from 'assert'; | ||
import * as request from 'request-promise-native'; | ||
import config from '../../../../config'; | ||
import Utils from '../../../../Utils'; | ||
|
||
describe(commands.SERVICEPRINCIPAL_GRANT_LIST, () => { | ||
let vorpal: Vorpal; | ||
let log: string[]; | ||
let cmdInstance: any; | ||
let cmdInstanceLogSpy: sinon.SinonSpy; | ||
let trackEvent: any; | ||
let telemetry: any; | ||
|
||
before(() => { | ||
sinon.stub(auth, 'restoreAuth').callsFake(() => Promise.resolve()); | ||
sinon.stub(auth, 'ensureAccessToken').callsFake(() => { return Promise.resolve('ABC'); }); | ||
sinon.stub(command as any, 'getRequestDigest').callsFake(() => Promise.resolve({ FormDigestValue: 'ABC' })); | ||
trackEvent = sinon.stub(appInsights, 'trackEvent').callsFake((t) => { | ||
telemetry = t; | ||
}); | ||
}); | ||
|
||
beforeEach(() => { | ||
vorpal = require('../../../../vorpal-init'); | ||
log = []; | ||
cmdInstance = { | ||
log: (msg: string) => { | ||
log.push(msg); | ||
} | ||
}; | ||
cmdInstanceLogSpy = sinon.spy(cmdInstance, 'log'); | ||
auth.site = new Site(); | ||
telemetry = null; | ||
}); | ||
|
||
afterEach(() => { | ||
Utils.restore([ | ||
vorpal.find, | ||
request.post | ||
]); | ||
}); | ||
|
||
after(() => { | ||
Utils.restore([ | ||
appInsights.trackEvent, | ||
auth.ensureAccessToken, | ||
auth.restoreAuth, | ||
(command as any).getRequestDigest | ||
]); | ||
}); | ||
|
||
it('has correct name', () => { | ||
assert.equal(command.name.startsWith(commands.SERVICEPRINCIPAL_GRANT_LIST), true); | ||
}); | ||
|
||
it('has a description', () => { | ||
assert.notEqual(command.description, null); | ||
}); | ||
|
||
it('calls telemetry', (done) => { | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: {}, url: 'https://contoso-admin.sharepoint.com' }, () => { | ||
try { | ||
assert(trackEvent.called); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('logs correct telemetry event', (done) => { | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: {}, url: 'https://contoso-admin.sharepoint.com' }, () => { | ||
try { | ||
assert.equal(telemetry.name, commands.SERVICEPRINCIPAL_GRANT_LIST); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('aborts when not connected to a SharePoint site', (done) => { | ||
auth.site = new Site(); | ||
auth.site.connected = false; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: true } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith(new CommandError('Connect to a SharePoint Online site first'))); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('aborts when not connected to a SharePoint tenant admin site', (done) => { | ||
auth.site = new Site(); | ||
auth.site.connected = true; | ||
auth.site.url = 'https://contoso.sharepoint.com'; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: true } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith(new CommandError(`${auth.site.url} is not a tenant admin site. Connect to your tenant admin site and try again`))); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('lists permissions granted to the service principal (debug)', (done) => { | ||
sinon.stub(request, 'post').callsFake((opts) => { | ||
if (opts.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && | ||
opts.headers.authorization && | ||
opts.headers.authorization.indexOf('Bearer ') === 0 && | ||
opts.headers['X-RequestDigest'] && | ||
opts.body === `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="4" ObjectPathId="3" /><ObjectPath Id="6" ObjectPathId="5" /><Query Id="7" ObjectPathId="5"><Query SelectAllProperties="true"><Properties /></Query><ChildItemQuery SelectAllProperties="true"><Properties /></ChildItemQuery></Query></Actions><ObjectPaths><Constructor Id="3" TypeId="{104e8f06-1e00-4675-99c6-1b9b504ed8d8}" /><Property Id="5" ParentId="3" Name="PermissionGrants" /></ObjectPaths></Request>`) { | ||
return Promise.resolve(JSON.stringify([ | ||
{ | ||
"SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7213.1200", "ErrorInfo": null, "TraceCorrelationId": "a2a03a9e-602c-4000-879b-1783ec06ba67" | ||
}, 4, { | ||
"IsNull": false | ||
}, 6, { | ||
"IsNull": false | ||
}, 7, { | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrantCollection", "_Child_Items_": [ | ||
{ | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrant", "ClientId": "cd4043e7-b749-420b-bd07-aa7c3912ed22", "ConsentType": "AllPrincipals", "ObjectId": "50NAzUm3C0K9B6p8ORLtIhpPRByju_JCmZ9BBsWxwgw", "Resource": "Windows Azure Active Directory", "ResourceId": "1c444f1a-bba3-42f2-999f-4106c5b1c20c", "Scope": "Group.ReadWrite.All" | ||
}, { | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrant", "ClientId": "cd4043e7-b749-420b-bd07-aa7c3912ed22", "ConsentType": "AllPrincipals", "ObjectId": "50NAzUm3C0K9B6p8ORLtIvNe8tzf4ndKg51reFehHHg", "Resource": "Office 365 SharePoint Online", "ResourceId": "dcf25ef3-e2df-4a77-839d-6b7857a11c78", "Scope": "MyFiles.Read" | ||
} | ||
] | ||
} | ||
])); | ||
} | ||
|
||
return Promise.reject('Invalid request'); | ||
}); | ||
auth.site = new Site(); | ||
auth.site.connected = true; | ||
auth.site.url = 'https://contoso-admin.sharepoint.com'; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: true } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith([ | ||
{ | ||
ObjectId: '50NAzUm3C0K9B6p8ORLtIhpPRByju_JCmZ9BBsWxwgw', | ||
Resource: 'Windows Azure Active Directory', | ||
ResourceId: '1c444f1a-bba3-42f2-999f-4106c5b1c20c', | ||
Scope: 'Group.ReadWrite.All' | ||
}, | ||
{ | ||
ObjectId: '50NAzUm3C0K9B6p8ORLtIvNe8tzf4ndKg51reFehHHg', | ||
Resource: 'Office 365 SharePoint Online', | ||
ResourceId: 'dcf25ef3-e2df-4a77-839d-6b7857a11c78', | ||
Scope: 'MyFiles.Read' | ||
}])); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('lists permissions granted to the service principal', (done) => { | ||
sinon.stub(request, 'post').callsFake((opts) => { | ||
if (opts.url.indexOf('/_vti_bin/client.svc/ProcessQuery') > -1 && | ||
opts.headers.authorization && | ||
opts.headers.authorization.indexOf('Bearer ') === 0 && | ||
opts.headers['X-RequestDigest'] && | ||
opts.body === `<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="${config.applicationName}" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="4" ObjectPathId="3" /><ObjectPath Id="6" ObjectPathId="5" /><Query Id="7" ObjectPathId="5"><Query SelectAllProperties="true"><Properties /></Query><ChildItemQuery SelectAllProperties="true"><Properties /></ChildItemQuery></Query></Actions><ObjectPaths><Constructor Id="3" TypeId="{104e8f06-1e00-4675-99c6-1b9b504ed8d8}" /><Property Id="5" ParentId="3" Name="PermissionGrants" /></ObjectPaths></Request>`) { | ||
return Promise.resolve(JSON.stringify([ | ||
{ | ||
"SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7213.1200", "ErrorInfo": null, "TraceCorrelationId": "a2a03a9e-602c-4000-879b-1783ec06ba67" | ||
}, 4, { | ||
"IsNull": false | ||
}, 6, { | ||
"IsNull": false | ||
}, 7, { | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrantCollection", "_Child_Items_": [ | ||
{ | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrant", "ClientId": "cd4043e7-b749-420b-bd07-aa7c3912ed22", "ConsentType": "AllPrincipals", "ObjectId": "50NAzUm3C0K9B6p8ORLtIhpPRByju_JCmZ9BBsWxwgw", "Resource": "Windows Azure Active Directory", "ResourceId": "1c444f1a-bba3-42f2-999f-4106c5b1c20c", "Scope": "Group.ReadWrite.All" | ||
}, { | ||
"_ObjectType_": "Microsoft.Online.SharePoint.TenantAdministration.Internal.SPOWebAppServicePrincipalPermissionGrant", "ClientId": "cd4043e7-b749-420b-bd07-aa7c3912ed22", "ConsentType": "AllPrincipals", "ObjectId": "50NAzUm3C0K9B6p8ORLtIvNe8tzf4ndKg51reFehHHg", "Resource": "Office 365 SharePoint Online", "ResourceId": "dcf25ef3-e2df-4a77-839d-6b7857a11c78", "Scope": "MyFiles.Read" | ||
} | ||
] | ||
} | ||
])); | ||
} | ||
|
||
return Promise.reject('Invalid request'); | ||
}); | ||
auth.site = new Site(); | ||
auth.site.connected = true; | ||
auth.site.url = 'https://contoso-admin.sharepoint.com'; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: false } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith([ | ||
{ | ||
ObjectId: '50NAzUm3C0K9B6p8ORLtIhpPRByju_JCmZ9BBsWxwgw', | ||
Resource: 'Windows Azure Active Directory', | ||
ResourceId: '1c444f1a-bba3-42f2-999f-4106c5b1c20c', | ||
Scope: 'Group.ReadWrite.All' | ||
}, | ||
{ | ||
ObjectId: '50NAzUm3C0K9B6p8ORLtIvNe8tzf4ndKg51reFehHHg', | ||
Resource: 'Office 365 SharePoint Online', | ||
ResourceId: 'dcf25ef3-e2df-4a77-839d-6b7857a11c78', | ||
Scope: 'MyFiles.Read' | ||
}])); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('correctly handles error when retrieving permissions granted to the service principal', (done) => { | ||
sinon.stub(request, 'post').callsFake((opts) => { | ||
return Promise.resolve(JSON.stringify([ | ||
{ | ||
"SchemaVersion": "15.0.0.0", "LibraryVersion": "16.0.7018.1204", "ErrorInfo": { | ||
"ErrorMessage": "File Not Found.", "ErrorValue": null, "TraceCorrelationId": "9e54299e-208a-4000-8546-cc4139091b26", "ErrorCode": -2147024894, "ErrorTypeName": "System.IO.FileNotFoundException" | ||
}, "TraceCorrelationId": "9e54299e-208a-4000-8546-cc4139091b26" | ||
} | ||
])); | ||
}); | ||
auth.site = new Site(); | ||
auth.site.connected = true; | ||
auth.site.url = 'https://contoso-admin.sharepoint.com'; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: false } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith(new CommandError('File Not Found.'))); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
|
||
it('defines alias', () => { | ||
const alias = command.alias(); | ||
assert.notEqual(typeof alias, 'undefined'); | ||
}); | ||
|
||
it('supports debug mode', () => { | ||
const options = (command.options() as CommandOption[]); | ||
let containsOption = false; | ||
options.forEach(o => { | ||
if (o.option === '--debug') { | ||
containsOption = true; | ||
} | ||
}); | ||
assert(containsOption); | ||
}); | ||
|
||
it('has help referring to the right command', () => { | ||
const cmd: any = { | ||
log: (msg: string) => { }, | ||
prompt: () => { }, | ||
helpInformation: () => { } | ||
}; | ||
const find = sinon.stub(vorpal, 'find').callsFake(() => cmd); | ||
cmd.help = command.help(); | ||
cmd.help({}, () => { }); | ||
assert(find.calledWith(commands.SERVICEPRINCIPAL_GRANT_LIST)); | ||
}); | ||
|
||
it('has help with examples', () => { | ||
const _log: string[] = []; | ||
const cmd: any = { | ||
log: (msg: string) => { | ||
_log.push(msg); | ||
}, | ||
prompt: () => { }, | ||
helpInformation: () => { } | ||
}; | ||
sinon.stub(vorpal, 'find').callsFake(() => cmd); | ||
cmd.help = command.help(); | ||
cmd.help({}, () => { }); | ||
let containsExamples: boolean = false; | ||
_log.forEach(l => { | ||
if (l && l.indexOf('Examples:') > -1) { | ||
containsExamples = true; | ||
} | ||
}); | ||
Utils.restore(vorpal.find); | ||
assert(containsExamples); | ||
}); | ||
|
||
it('correctly handles lack of valid access token', (done) => { | ||
Utils.restore(auth.ensureAccessToken); | ||
sinon.stub(auth, 'ensureAccessToken').callsFake(() => { return Promise.reject(new Error('Error getting access token')); }); | ||
auth.site = new Site(); | ||
auth.site.connected = true; | ||
auth.site.url = 'https://contoso-admin.sharepoint.com'; | ||
cmdInstance.action = command.action(); | ||
cmdInstance.action({ options: { debug: true } }, () => { | ||
try { | ||
assert(cmdInstanceLogSpy.calledWith(new CommandError('Error getting access token'))); | ||
done(); | ||
} | ||
catch (e) { | ||
done(e); | ||
} | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.