Skip to content
This repository has been archived by the owner on Dec 22, 2020. It is now read-only.

add dataset & scopes to token #20

Merged
merged 1 commit into from Jan 9, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 43 additions & 1 deletion README.md
Expand Up @@ -43,7 +43,7 @@ Each API call sets the following HTTP header:

## Creating Embed Tokens
Power BI Embedded uses embed token, which are HMAC signed JSON Web Tokens. The tokens are signed with the access key from your Azure Power BI Embedded workspace collection.
Embed tokens are used to provide read only access to a report to embed into an application.
Embed tokens, by default, are used to provide read only access to a report to embed into an application.

### Required Claims
- ver: 0.2.0
Expand Down Expand Up @@ -87,3 +87,45 @@ The following decoded JSON web token
"nbf": 1360043456
}
```

## Adding Permission Scopes to Embed Tokens
When using Embed tokens, one might want to restrict usage of the resources he gives access to. For this reason, you can generate a token with scoped permissions.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is public documentation - do we need to review it with technical writers?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we do, will do this as part of merge to master


### Required Claims - Scopes
- scp: {scopesClaim}
scopesClaim can be either a string or array of strings, noting the allowed permissions to workspace resources (Report, Dataset, etc.)

```javascript
var powerbi = require('powerbi-api');
var reportReadScope = 'Report.Read';
var token = powerbi.PowerBIToken.createReportEmbedToken('{WorkspaceCollection}', '{workspaceId}', '{reportId}', '{scopes}');

var jwt = token.generate('{AccessKey}');
```

## Token Example - With Scopes
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ2ZXIiOiIwLjIuMCIsIndjbiI6IlN1cHBvcnREZW1vIiwid2lkIjoiY2E2NzViMTktNmMzYy00MDAzLTg4MDgtMWM3ZGRjNmJkODA5IiwicmlkIjoiOTYyNDFmMGYtYWJhZS00ZWE5LWEwNjUtOTNiNDI4ZWRkYjE3Iiwic2NwIjoiUmVwb3J0LlJlYWQiLCJpc3MiOiJQb3dlckJJU0RLIiwiYXVkIjoiaHR0cHM6Ly9hbmFseXNpcy53aW5kb3dzLm5ldC9wb3dlcmJpL2FwaSIsImV4cCI6MTM2MDA0NzA1NiwibmJmIjoxMzYwMDQzNDU2fQ.M1jkWXnkfwJeGQqh1x0vIAYB4EBKbHSZFoDB6n_LZyA

### Decoded
The following decoded JSON web token
**Header**
```javascript
{
"typ": "JWT",
"alg": "HS256"
}
```

**Payload**
```javascript
{
"ver": "0.2.0",
"wcn": "SupportDemo",
"wid": "ca675b19-6c3c-4003-8808-1c7ddc6bd809",
"rid": "96241f0f-abae-4ea9-a065-93b428eddb17",
"scp": "Report.Read",
"iss": "PowerBISDK",
"aud": "https://analysis.windows.net/powerbi/api",
"exp": 1360047056,
"nbf": 1360043456
}
26 changes: 22 additions & 4 deletions lib/core/powerBIToken.ts
Expand Up @@ -14,18 +14,28 @@ export class PowerBIToken {
};
}

public static createReportEmbedToken(workspaceCollectionName: string, workspaceId: string, reportId: string, username: string = null, roles: string|string[] = null, expiration: Date = null): PowerBIToken {
public static createReportEmbedToken(workspaceCollectionName: string, workspaceId: string, reportId?: string, datasetId?: string, scopes: string|string[] = '', username: string = null, roles: string|string[] = null, expiration: Date = null): PowerBIToken {

if (roles && !username)
{
if (roles && !username) {
throw new Error('Cannot have an empty or null Username claim with the non-empty Roles claim');
}

if (!reportId && !datasetId) {
throw new Error('Token must contain either reportId or datasetId claim');
}

var token = new PowerBIToken(expiration);
token.addClaim('wcn', workspaceCollectionName);
token.addClaim('wid', workspaceId);
token.addClaim('rid', reportId);

if (reportId) {
token.addClaim('rid', reportId);
}

if (datasetId) {
token.addClaim('did', datasetId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you have both rid and did?


if(username != null) {
token.addClaim('username', username);

Expand All @@ -34,6 +44,14 @@ export class PowerBIToken {
}
}

if (scopes) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it valid not passing any scopes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverting to defaults

if (Array.isArray(scopes)) {
scopes = (<string[]>scopes).join(' ');
}

token.addClaim('scp', scopes);
}

return token;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "powerbi-api",
"version": "1.0.1",
"version": "1.0.2",
"description": "Node client library for Power BI API",
"main": "./lib/index.js",
"typings": "./lib/index.d.ts",
Expand Down
87 changes: 84 additions & 3 deletions test/core/PowerBIToken.test.ts
Expand Up @@ -11,8 +11,12 @@ describe('Power BI Token', () => {
const workspacCollection: string = 'TestCollection';
const workspaceId: string = 'fd41b1db-9e26-4103-99a7-f9ad336b99a7';
const reportId: string = 'fe607ad3-97bf-4dd5-98eb-db4a4d5de4e0';
const datasetId: string = 'fe123ad3-97bf-4dd5-98eb-db7c432de4e0';
const accessKey: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
const username: string = 'TestUser';
const scopes: string = 'Scope1';
const scopesArray: string[] = ['Scope1', 'Scope2'];
const scopesArrayDecoded: string = 'Scope1 Scope2';

it('is defined', () => {
expect(powerbi.PowerBIToken).to.exist;
Expand Down Expand Up @@ -66,6 +70,13 @@ describe('Power BI Token', () => {
expect(jwt).not.to.be.null;
});

it('can be created for datasetId', () => {
let token = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, null, datasetId);
let jwt = token.generate(accessKey);

expect(jwt).not.to.be.null;
});

it('are created with a default expiration', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
Expand All @@ -78,12 +89,29 @@ describe('Power BI Token', () => {
expect(decoded.exp).to.equal(exp)
});

it('are created with the correct claims - minimal params', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
let expiration = new Date(exp * 1000);

let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId);
let token = embedToken.generate(accessKey);
let decoded = jwt.decode(token, accessKey);

expect(decoded.ver).to.equal(version);
expect(decoded.aud).to.equal(resource);
expect(decoded.iss).to.equal(issuer);
expect(decoded.wcn).to.equal(workspacCollection);
expect(decoded.wid).to.equal(workspaceId);
expect(decoded.rid).to.equal(reportId);
});

it('are created with the correct claims', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
let expiration = new Date(exp * 1000);

let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId, username, 'TestRole', expiration);
let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId, datasetId, scopes, username, 'TestRole', expiration);
let token = embedToken.generate(accessKey);
let decoded = jwt.decode(token, accessKey);

Expand All @@ -93,26 +121,79 @@ describe('Power BI Token', () => {
expect(decoded.wcn).to.equal(workspacCollection);
expect(decoded.wid).to.equal(workspaceId);
expect(decoded.rid).to.equal(reportId);
expect(decoded.did).to.equal(datasetId);
expect(decoded.nbf).to.equal(nbf);
expect(decoded.exp).to.equal(exp);
expect(decoded.scp).to.equal(scopes);
expect(decoded.username).to.equal(username);
expect(decoded.roles).to.equal('TestRole');
});

it('are created with the correct claims - datasetId only', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
let expiration = new Date(exp * 1000);

let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, null, datasetId, scopes, username, 'TestRole', expiration);
let token = embedToken.generate(accessKey);
let decoded = jwt.decode(token, accessKey);

expect(decoded.ver).to.equal(version);
expect(decoded.aud).to.equal(resource);
expect(decoded.iss).to.equal(issuer);
expect(decoded.wcn).to.equal(workspacCollection);
expect(decoded.wid).to.equal(workspaceId);
expect(decoded.rid).to.be.undefined;
expect(decoded.did).to.equal(datasetId);
expect(decoded.nbf).to.equal(nbf);
expect(decoded.exp).to.equal(exp);
expect(decoded.scp).to.equal(scopes);
expect(decoded.username).to.equal(username);
expect(decoded.roles).to.equal('TestRole');
});

it('are created with the correct claims - scopes as array', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
let expiration = new Date(exp * 1000);

let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId, null, scopesArray, username, 'TestRole', expiration);
let token = embedToken.generate(accessKey);
let decoded = jwt.decode(token, accessKey);

expect(decoded.ver).to.equal(version);
expect(decoded.aud).to.equal(resource);
expect(decoded.iss).to.equal(issuer);
expect(decoded.wcn).to.equal(workspacCollection);
expect(decoded.wid).to.equal(workspaceId);
expect(decoded.rid).to.equal(reportId);
expect(decoded.nbf).to.equal(nbf);
expect(decoded.exp).to.equal(exp);
expect(decoded.scp).to.equal(scopesArrayDecoded);
expect(decoded.username).to.equal(username);
expect(decoded.roles).to.equal('TestRole');
});

it('are created with multiple RLS roles', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
let expiration = new Date(exp * 1000);
let roles = ['TestRole1', 'TestRole2'];

let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId, username, roles, expiration);
let embedToken = powerbi.PowerBIToken.createReportEmbedToken(workspacCollection, workspaceId, reportId, datasetId, scopes, username, roles, expiration);
let token = embedToken.generate(accessKey);
let decoded = jwt.decode(token, accessKey);

expect(decoded.username).to.equal(username);
expect(decoded.roles).to.eql(roles);
});

it('fail to create when missing reportId and datasetId', () => {
expect(powerbi.PowerBIToken.createReportEmbedToken.bind(powerbi.PowerBIToken, workspacCollection, workspaceId))
.to
.throw('Token must contain either reportId or datasetId claim');
});

it('fail to create when RLS username is empty and roles is not', () => {
let nbf = powerbi.Util.getUnixTime(new Date());
let exp = nbf + 3600;
Expand All @@ -121,7 +202,7 @@ describe('Power BI Token', () => {
let badUsernames = [null, undefined, ''];

badUsernames.forEach(badUsername => {
expect(powerbi.PowerBIToken.createReportEmbedToken.bind(powerbi.PowerBIToken, workspacCollection, workspaceId, reportId, badUsername, role, expiration))
expect(powerbi.PowerBIToken.createReportEmbedToken.bind(powerbi.PowerBIToken, workspacCollection, workspaceId, reportId, datasetId, scopes, badUsername, role, expiration))
.to
.throw('Cannot have an empty or null Username claim with the non-empty Roles claim');
}
Expand Down