问题描述
我的目标是将数据从 Xero API 获取到我的组织的 Power BI。为此,我相信我需要创建一个自定义连接器,因为 PBI 没有用于 Xero 的认证连接器。我有 Power BI 和 M 语言方面的经验,但这是我第一次尝试为 Power BI 构建自定义连接器。
由于 Xero API 需要 OAuth2 身份验证,我使用以下资源在 Visual Studio 中使用 Power Query SDK 模板创建自定义连接器。我相信我已经成功设置了身份验证,因为连接器允许我登录到我的 Xero 帐户并“允许访问”所请求的资源。似乎连接成功,因为在 Xero 中,我的应用名称出现在“设置”>“已连接的应用”中。
一旦我登录,我就会收到一个错误;详细信息:“禁止访问该资源。”
我能想到的可能原因:
- 我使用的是代码流而不是 PKCE 流(我认为这是正确的,因为我是从 Power BI Desktop 连接的)
- 我的代码有问题
- 我发送的 API 网址不完整或不正确(我目前正在尝试:https://api.xero.com/api.xro/2.0/Reports/AgedReceivablesByContact?fromDate=2020-01-01&toDate=2021-01-01)
我在 Xero 中拥有访问我请求的数据所需的权限。我还包括了相关的范围。任何人都可以告诉他们是否已成功为 Power BI 构建自定义连接器或提供一些有关如何解决此错误的建议?
谢谢!
// This file contains your Data Connector logic
section JaimesXeroConnector;
//JaimesXeroConnector OAuth2 values;
client_id = Text.FromBinary(Extension.Contents("client_id.txt"));
client_secret = Text.FromBinary(Extension.Contents("client_secret.txt"));
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
token_uri = "https://identity.xero.com/connect/token";
authorize_uri = "https://login.xero.com/identity/connect/authorize";
logout_uri = "https://login.microsoftonline.com/logout.srf";
scopes = {"offline_access openid profile email accounting.transactions accounting.reports.read accounting.transactions.read accounting.reports.read accounting.journals.read accounting.settings accounting.settings.read accounting.contacts accounting.contacts.read accounting.attachments accounting.attachments.read assets projects"
};
System.Net.ServicePointManager.SecurityProtocol= "SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12";
// Login modal window dimensions
windowWidth = 720;
windowHeight = 1024;
[DataSource.Kind="JaimesXeroConnector",Publish="JaimesXeroConnector.Publish"]
shared JaimesXeroConnector.Contents = (url as text) =>
let
source = Json.Document(Web.Contents(url))
in
source;
// Data Source Kind description
JaimesXeroConnector= [
TestConnection = (dataSourcePath) => { "JaimesXeroConnector.Contents",dataSourcePath },Authentication = [
OAuth = [
StartLogin=StartLogin,FinishLogin=FinishLogin,Refresh=Refresh,logout=logout
]
],Label = Extension.LoadString("DataSourceLabel")
];
// Data Source UI publishing description
JaimesXeroConnector.Publish = [
Beta = true,Category = "Other",ButtonText = { Extension.LoadString("ButtonTitle"),Extension.LoadString("ButtonHelp") },LearnMoreUrl = "https://powerbi.microsoft.com/",SourceImage = JaimesXeroConnector.Icons,SourceTypeImage = JaimesXeroConnector.Icons
];
// Helper functions for OAuth2: StartLogin,FinishLogin,Refresh,logout
StartLogin = (resourceUrl,state,display) =>
let
authorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
response_type = "code",client_id = client_id,redirect_uri = redirect_uri,state = state,scope = scopes
])
in
[
LoginUri = authorizeUrl,CallbackUri = redirect_uri,WindowHeight = 720,WindowWidth = 1024,Context = null
];
FinishLogin = (context,callbackUri,state) =>
let
// parse the full callbackUri,and extract the Query string
parts = Uri.Parts(callbackUri)[Query],// if the query string contains an "error" field,raise an error
// otherwise call TokenMethod to exchange our code for an access_token
result = if (Record.HasFields(parts,{"error","error_description"})) then
error Error.Record(parts[error],parts[error_description],parts)
else
TokenMethod("authorization_code","code",parts[code])
in
result;
Refresh = (resourceUrl,refresh_token) => TokenMethod("refresh_token","refresh_token",refresh_token);
logout = (token) => logout_uri;
// see "Exchange code for access token: POST /oauth/token" for details
TokenMethod = (grantType,tokenField,code) =>
let
queryString = [
grant_type = "authorization_code",client_secret = client_secret
],queryWithCode = Record.AddField(queryString,code),tokenResponse = Web.Contents(token_uri,[
Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),Headers = [
#"Content-type" = "application/x-www-form-urlencoded",#"Accept" = "application/json"
],ManualStatusHandling = {400}
]),body = Json.Document(tokenResponse),result = if (Record.HasFields(body,"error_description"})) then
error Error.Record(body[error],body[error_description],body)
else
body
in
result;
Value.IfNull = (a,b) => if a <> null then a else b;
GetScopestring = (scopes as list,optional scopePrefix as text) as text =>
let
prefix = Value.IfNull(scopePrefix,""),addPrefix = List.Transform(scopes,each prefix & _),asText = Text.Combine(addPrefix," ")
in
asText;
JaimesXeroConnector.Icons = [
Icon16 = { Extension.Contents("JaimesXeroConnector16.png"),Extension.Contents("JaimesXeroConnector20.png"),Extension.Contents("JaimesXeroConnector24.png"),Extension.Contents("JaimesXeroConnector32.png") },Icon32 = { Extension.Contents("JaimesXeroConnector32.png"),Extension.Contents("JaimesXeroConnector40.png"),Extension.Contents("JaimesXeroConnector48.png"),Extension.Contents("JaimesXeroConnector64.png") }
];
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)