Skip to content

Commit

Permalink
Added the 'spo serviceprincipal grant list' command solving #155
Browse files Browse the repository at this point in the history
  • Loading branch information
waldekmastykarz committed Dec 28, 2017
1 parent 7ad8af2 commit 93be29c
Show file tree
Hide file tree
Showing 6 changed files with 495 additions and 0 deletions.
@@ -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
```
1 change: 1 addition & 0 deletions docs/manual/mkdocs.yml
Expand Up @@ -28,6 +28,7 @@ pages:
- customaction get: 'cmd/spo/customaction/customaction-get.md'
- customaction list: 'cmd/spo/customaction/customaction-list.md'
- serviceprincipal:
- serviceprincipal grant list: 'cmd/spo/serviceprincipal/serviceprincipal-grant-list.md'
- serviceprincipal permissionrequest approve: 'cmd/spo/serviceprincipal/serviceprincipal-permissionrequest-approve.md'
- serviceprincipal permissionrequest deny: 'cmd/spo/serviceprincipal/serviceprincipal-permissionrequest-deny.md'
- serviceprincipal permissionrequest list: 'cmd/spo/serviceprincipal/serviceprincipal-permissionrequest-list.md'
Expand Down
2 changes: 2 additions & 0 deletions src/o365/spo/commands.ts
Expand Up @@ -26,9 +26,11 @@ export default {
CDN_POLICY_SET: `${prefix} cdn policy set`,
CUSTOMACTION_GET: `${prefix} customaction get`,
CUSTOMACTION_LIST: `${prefix} customaction list`,
SERVICEPRINCIPAL_GRANT_LIST: `${prefix} serviceprincipal grant list`,
SERVICEPRINCIPAL_PERMISSIONREQUEST_APPROVE: `${prefix} serviceprincipal permissionrequest approve`,
SERVICEPRINCIPAL_PERMISSIONREQUEST_DENY: `${prefix} serviceprincipal permissionrequest deny`,
SERVICEPRINCIPAL_PERMISSIONREQUEST_LIST: `${prefix} serviceprincipal permissionrequest list`,
SP_GRANT_LIST: `${prefix} sp grant list`,
SP_PERMISSIONREQUEST_APPROVE: `${prefix} sp permissionrequest approve`,
SP_PERMISSIONREQUEST_DENY: `${prefix} sp permissionrequest deny`,
SP_PERMISSIONREQUEST_LIST: `${prefix} sp permissionrequest list`,
Expand Down
@@ -0,0 +1,9 @@
export interface SPOWebAppServicePrincipalPermissionGrant {
_ObjectType_: string;
ClientId: string;
ConsentType: string;
ObjectId: string;
Resource: string;
ResourceId: string;
Scope: string;
}
@@ -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);
}
});
});
});

0 comments on commit 93be29c

Please sign in to comment.