From 1b2eec94a9376f21d807474321e660622394cd67 Mon Sep 17 00:00:00 2001 From: ranbreuer Date: Mon, 9 Jan 2017 12:10:49 +0200 Subject: [PATCH] add dataset & scopes to token (#20) --- README.md | 44 ++++++++++++++++- lib/core/powerBIToken.ts | 26 ++++++++-- package.json | 2 +- test/core/PowerBIToken.test.ts | 89 ++++++++++++++++++++++++++++++++-- 4 files changed, 151 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dd84975..c47bed6 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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. + +### 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 +} diff --git a/lib/core/powerBIToken.ts b/lib/core/powerBIToken.ts index 22641c5..be0dd0d 100644 --- a/lib/core/powerBIToken.ts +++ b/lib/core/powerBIToken.ts @@ -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); + } + if(username != null) { token.addClaim('username', username); @@ -34,6 +44,14 @@ export class PowerBIToken { } } + if (scopes) { + if (Array.isArray(scopes)) { + scopes = (scopes).join(' '); + } + + token.addClaim('scp', scopes); + } + return token; } diff --git a/package.json b/package.json index b13291b..0925372 100644 --- a/package.json +++ b/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", diff --git a/test/core/PowerBIToken.test.ts b/test/core/PowerBIToken.test.ts index f4efd01..26ed0e6 100644 --- a/test/core/PowerBIToken.test.ts +++ b/test/core/PowerBIToken.test.ts @@ -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; @@ -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; @@ -78,12 +89,75 @@ 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); + + 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.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); @@ -95,17 +169,18 @@ describe('Power BI Token', () => { 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); @@ -113,6 +188,12 @@ describe('Power BI Token', () => { 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; @@ -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'); }