feat: move from embed token api to share token api (#230)

* new mutation for token exchange

* variable based
This commit is contained in:
Dogukan Karatas
2026-04-15 13:09:12 +02:00
committed by GitHub
parent dbac5c013b
commit a9fd34831c
4 changed files with 102 additions and 110 deletions
@@ -66,7 +66,8 @@
powerfulToken,
{"profile:read", "streams:read", "users:read"},
parsedUrl[projectId],
parsedUrl[baseUrl]
parsedUrl[baseUrl],
parsedUrl[resourceIdString]
),
// throw error if token exchange failed - do NOT use powerful token as fallback
@@ -1,9 +1,6 @@
// Function to exchange powerful token for weak limited token
(powerfulToken as text, scopes as list, projectId as text, serverUrl as text) as record =>
(powerfulToken as text, scopes as list, projectId as text, serverUrl as text, optional resourceIdString as text) as record =>
let
// Import the parser function for URL handling
Parser = Extension.LoadFunction("Parser.pqm"),
// Helper function to load .pqm modules dynamically
Extension.LoadFunction = (fileName as text) =>
let
@@ -33,114 +30,106 @@
else
null,
// Token lifetime: 10 years (315,360,000,000 milliseconds)
TokenLifespanMs = 10 * 365 * 24 * 3600 * 1000,
// Generate token name with timestamp
TokenName = "Limited Power BI Visual Token - " & DateTime.ToText(DateTime.LocalNow(), "yyyy-MM-dd HH:mm"),
// Build scopes array string for GraphQL (e.g., ["profile:read", "streams:read"])
ScopesArray = Text.Combine(
List.Transform(scopes, each """" & _ & """"),
", "
),
// Build GraphQL mutation
GraphQLMutation = "
mutation {
apiTokenCreate(token: {
name: """ & TokenName & """,
scopes: [" & ScopesArray & "],
lifespan: " & Number.ToText(TokenLifespanMs) & ",
limitResources: [{
type: project,
id: """ & projectId & """
}]
})
}",
// Execute token exchange if validation passes
Result = if ValidationError <> null then
[
Success = false,
Token = null,
ErrorMessage = ValidationError
]
// Ensure serverUrl ends with /
NormalizedServerUrl = if Text.End(serverUrl, 1) = "/" then
serverUrl
else
try
let
// Ensure serverUrl ends with /
NormalizedServerUrl = if Text.End(serverUrl, 1) = "/" then
serverUrl
else
serverUrl & "/",
serverUrl & "/",
// Make GraphQL request
Response = Web.Contents(
NormalizedServerUrl & "graphql",
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & powerfulToken
],
Content = Json.FromValue([
query = GraphQLMutation
]),
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 10)
]
),
// New Share Token API mutation with variables
NewGraphQLQuery = "mutation CreateEmbedShareToken($input: CreateEmbedShareTokenInput!) {
sharingMutations {
createEmbedShareToken(input: $input) {
token
}
}
}",
NewGraphQLVariables = [
input = [
projectId = projectId,
resourceIdString = resourceIdString
]
],
StatusCode = Value.Metadata(Response)[Response.Status],
// Legacy apiTokenCreate mutation with variables
TokenLifespanMs = 10 * 365 * 24 * 3600 * 1000,
TokenName = "Limited Power BI Visual Token - " & DateTime.ToText(DateTime.LocalNow(), "yyyy-MM-dd HH:mm"),
LegacyGraphQLQuery = "mutation CreateApiToken($token: ApiTokenCreateInput!) {
apiTokenCreate(token: $token)
}",
LegacyGraphQLVariables = [
token = [
name = TokenName,
scopes = scopes,
lifespan = TokenLifespanMs,
limitResources = {[
type = "project",
id = projectId
]}
]
],
// Parse response if successful
ParsedResult = if StatusCode >= 200 and StatusCode < 300 then
let
JsonResponse = Json.Document(Response),
// Helper: execute a GraphQL query with variables and extract token
ExecuteGraphQL = (query as text, variables as record, extractToken as function) =>
let
Response = Web.Contents(
NormalizedServerUrl & "graphql",
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = "Bearer " & powerfulToken
],
Content = Json.FromValue([
query = query,
variables = variables
]),
ManualStatusHandling = {400, 401, 403, 404, 500, 502, 503, 504},
Timeout = #duration(0, 0, 0, 10)
]
),
StatusCode = Value.Metadata(Response)[Response.Status],
JsonResponse = if StatusCode >= 200 and StatusCode < 300 then
Json.Document(Response)
else
null,
HasErrors = JsonResponse <> null and Record.HasFields(JsonResponse, {"errors"}),
Token = if JsonResponse <> null and not HasErrors then
try extractToken(JsonResponse) otherwise null
else
null,
ErrorMsg = if HasErrors then
try JsonResponse[errors]{0}[message] otherwise "GraphQL mutation failed"
else if JsonResponse = null then
"Request failed with status " & Number.ToText(StatusCode)
else
null
in
[Success = Token <> null, Token = Token, ErrorMessage = ErrorMsg],
// Check for GraphQL errors
HasErrors = Record.HasFields(JsonResponse, {"errors"}),
// Try new API first, fall back to legacy
Result = if ValidationError <> null then
[Success = false, Token = null, ErrorMessage = ValidationError]
else
let
newResult = if resourceIdString <> null then
try ExecuteGraphQL(
NewGraphQLQuery,
NewGraphQLVariables,
each [data][sharingMutations][createEmbedShareToken][token]
) otherwise [Success = false, Token = null, ErrorMessage = "New API request failed"]
else
[Success = false, Token = null, ErrorMessage = null],
// Extract token from response
WeakToken = if not HasErrors then
JsonResponse[data][apiTokenCreate]
else
null,
ErrorMsg = if HasErrors then
try
JsonResponse[errors]{0}[message]
otherwise
"GraphQL mutation failed with unknown error"
else
null
in
if WeakToken <> null then
[
Success = true,
Token = WeakToken,
ErrorMessage = null
]
else
[
Success = false,
Token = null,
ErrorMessage = ErrorMsg
]
else
[
Success = false,
Token = null,
ErrorMessage = "GraphQL request failed with status " & Number.ToText(StatusCode)
]
in
ParsedResult
otherwise
[
Success = false,
Token = null,
ErrorMessage = "Token exchange request failed with exception"
]
finalResult = if newResult[Success] then
newResult
else
try ExecuteGraphQL(
LegacyGraphQLQuery,
LegacyGraphQLVariables,
each [data][apiTokenCreate]
) otherwise [Success = false, Token = null, ErrorMessage = "Token exchange request failed"]
in
finalResult
in
Result
@@ -54,5 +54,6 @@
modelId = if isFederated then null else processedModels{0}[modelId],
versionId = if isFederated then null else processedModels{0}[versionId],
isFederated = isFederated,
federatedModels = if isFederated then processedModels else null
federatedModels = if isFederated then processedModels else null,
resourceIdString = rawModelSegment
]
@@ -33,7 +33,8 @@
powerfulToken,
{"profile:read", "streams:read", "users:read"},
parsedUrl[projectId],
parsedUrl[baseUrl]
parsedUrl[baseUrl],
parsedUrl[resourceIdString]
),
// throw error if token exchange failed - do NOT use powerful token as fallback