Steve Newton
Steve Newton

Reputation: 1078

Power BI Embedded with Roles, allowing Row Level Security

I have implemented the Microsoft Example for Embed for your customers from Github, which works perfectly. Link

I am now extending it which there are articles showing using both V1 and V2 of the API, both result in the same error:

Operation returned an invalid status code 'BadRequest'
at Microsoft.PowerBI.Api.ReportsOperations.GenerateTokenInGroupWithHttpMessagesAsync(Guid groupId, Guid reportId, GenerateTokenRequest requestParameters, Dictionary`2 customHeaders, CancellationToken cancellationToken)

        [HttpGet]
    public async Task<string> GetEmbedInfo()
    {
        try
        {
            // Validate whether all the required configurations are provided in appsettings.json
            string configValidationResult = ConfigValidatorService.ValidateConfig(azureAd, powerBI);
            if (configValidationResult != null)
            {
                HttpContext.Response.StatusCode = 400;
                return configValidationResult;
            }

            EmbedParams embedParams = await pbiEmbedService.GetEmbedParams(new Guid(powerBI.Value.WorkspaceId), new Guid(powerBI.Value.ReportId));
            //EmbedParams embedParams = await pbiEmbedService.GetEmbedToken4(new Guid(powerBI.Value.WorkspaceId), new Guid(powerBI.Value.ReportId));
            return JsonSerializer.Serialize<EmbedParams>(embedParams);
        }
        catch (Exception ex)
        {
            HttpContext.Response.StatusCode = 500;
            return ex.Message + "\n\n" + ex.StackTrace;
        }
    }

The above code is getting called and per the demo.

        public async Task<EmbedParams> GetEmbedParams(Guid workspaceId, Guid reportId, [Optional] Guid additionalDatasetId)
    {
        PowerBIClient pbiClient = this.GetPowerBIClient();

        // Get report info
        var pbiReport = await pbiClient.Reports.GetReportInGroupAsync(workspaceId, reportId);
        //var generateTokenRequestParameters = new GenerateTokenRequest("View", null, identities: new List<EffectiveIdentity> { new EffectiveIdentity(username: "**************", roles: new List<string> { "****", "****" }, datasets: new List<string> { "datasetId" }) });

        //var tokenResponse = pbiClient.Reports.GenerateTokenInGroupAsync("groupId", "reportId", generateTokenRequestParameters);

        // Create list of datasets
        var datasetIds = new List<Guid>();

        // Add dataset associated to the report
        datasetIds.Add(Guid.Parse(pbiReport.DatasetId));

        // Append additional dataset to the list to achieve dynamic binding later
        if (additionalDatasetId != Guid.Empty)
        {
            datasetIds.Add(additionalDatasetId);
        }

        // Add report data for embedding
        var embedReports = new List<EmbedReport>() {
            new EmbedReport
            {
                ReportId = pbiReport.Id, ReportName = pbiReport.Name, EmbedUrl = pbiReport.EmbedUrl
            }
        };

        // Get Embed token multiple resources
        var embedToken = await GetEmbedToken4(workspaceId, reportId);

        // Capture embed params
        var embedParams = new EmbedParams
        {
            EmbedReport = embedReports,
            Type = "Report",
            EmbedToken = embedToken
        };

        return embedParams;
    }

The above code is per the demo apart from one line, which is calling the next method:

var embedToken = await GetEmbedToken4(workspaceId, reportId);

        public EmbedToken GetEmbedToken(Guid reportId, IList<Guid> datasetIds, [Optional] Guid targetWorkspaceId)
    {
        PowerBIClient pbiClient = this.GetPowerBIClient();

        // Create a request for getting Embed token 
        // This method works only with new Power BI V2 workspace experience
        var tokenRequest = new GenerateTokenRequestV2(

            reports: new List<GenerateTokenRequestV2Report>() { new GenerateTokenRequestV2Report(reportId) },

            datasets: datasetIds.Select(datasetId => new GenerateTokenRequestV2Dataset(datasetId.ToString())).ToList(),

            targetWorkspaces: targetWorkspaceId != Guid.Empty ? new List<GenerateTokenRequestV2TargetWorkspace>() { new GenerateTokenRequestV2TargetWorkspace(targetWorkspaceId) } : null


        );

        // Generate Embed token
        var embedToken = pbiClient.EmbedToken.GenerateToken(tokenRequest);

        return embedToken;
    }

The above code is per the example with no roles being passed in or EffectiveIdentity. This works.

        public async Task<EmbedToken> GetEmbedToken4(Guid workspaceId, Guid reportId, string accessLevel = "view")
    {
        PowerBIClient pbiClient = this.GetPowerBIClient();

        var pbiReport = pbiClient.Reports.GetReportInGroup(workspaceId, reportId);

        string dataSet = pbiReport.DatasetId.ToString();

        // Generate token request for RDL Report
        var generateTokenRequestParameters = new GenerateTokenRequest(
            accessLevel: accessLevel,
            datasetId: dataSet,
            identities: new List<EffectiveIdentity> { new EffectiveIdentity(username: "******", roles: new List<string> { "********" }) }
            

        );

        // Generate Embed token
        var embedToken = pbiClient.Reports.GenerateTokenInGroup(workspaceId, reportId, generateTokenRequestParameters);

        return embedToken;
    }

This is the method to return the token with the roles and effective Identity. This results in the error, but no message or helpful feedback.

Upvotes: 1

Views: 2293

Answers (2)

Blisco
Blisco

Reputation: 620

The sample code is unhelpful when it comes to exposing API errors. The PowerBIClient actually throws a Microsoft.Rest.HttpOperationException that includes a Response property. If you catch this you can return the actual error response to the front end.

catch (HttpOperationException httpEx)
{
    HttpContext.Response.StatusCode = 500;
    return string.Join("\n\n", new[] { httpEx.Message, httpEx.Response.Content, httpEx.StackTrace });
}
catch (Exception ex)
{
    HttpContext.Response.StatusCode = 500;
    return ex.Message + "\n\n" + ex.StackTrace;
}

Upvotes: 2

Steve Newton
Steve Newton

Reputation: 1078

OK, after much research overnight the Bad Request response does hide an English message which is not show in the browser. The debugger doesn't have the symbols for the part that causes the error, but I found it by using Fiddler proxy when the actual API responded to the request. In my case, if you send an ID to enable RLS, but the version of the report on the server doesn't have it, this doesn't ignore it, it refuses to give a token to anything. From reading many posts, the Bad Request is just a poor error message when the actual response from the API itself (not the package or the example code that the sample uses with it presents). Hope this helps someone in the future.

Upvotes: 3

Related Questions