为什么 Google API (目录列表)一直返回 403?有进化吗?新版本? GPDR / LGPD 问题?
Why Google API (Directory List) keeps returning 403? Are there evolutions? New Versions? GPDR / LGPD issues?
我有一个应用程序由于用户列表 (https://content.googleapis.com/admin/directory/v1/users?orderBy=email&viewType=domain_public&maxResults=200&customer=my_customer&domain=XXX&key=XXX) 不断返回而突然停止工作:
{
"error": {
"code": 403,
"message": "Not Authorized to access this resource/api",
"errors": [
{
"message": "Not Authorized to access this resource/api",
"domain": "global",
"reason": "forbidden"
}
]
}
}
此应用程序提供给 2 个不同的域。它适用于一个但不适用于其他。相同的代码...只是 appId、apiKey 和 clientId 发生了变化。我配置了范围广泛的域委托 (https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/admin.directory.user),还配置了 API KEY,OAuth 2.0 客户端。
我尝试重新创建整个项目,所有凭据,尝试了 http 和 https...同样的错误。
API 规范有什么变化吗?我缺少什么吗?
已编辑 - 请求的源代码:
我只是复制了整个代码,使用凭据更改了“设置”并在浏览器控制台中执行。该代码在身份验证之前或之后工作,但有 403 响应:-(
// This first part I got from https://github.com/lord22shark/google
(function (__window) {
const Google = function (configuration, __callback) {
if ((!configuration) || !(configuration instanceof Object)) {
throw new Error('Google API Wrapper - "configuration" must be defined!');
}
if ((!configuration.apiKey) || !(typeof(configuration.apiKey) === 'string') || (configuration.apiKey === '')) {
throw new Error('Google API Wrapper - "apiKey" must be defined!');
}
if ((!configuration.discoveryDocs) || !(configuration.discoveryDocs instanceof Array) || (configuration.discoveryDocs.length === 0)) {
throw new Error('Google API Wrapper - "discoveryDocs" must be a defined array!');
}
if ((!configuration.clientId) || !(typeof(configuration.clientId) === 'string') || (configuration.clientId === '')) {
throw new Error('Google API Wrapper - "clientId" must be defined!');
}
if ((!configuration.scope) || !(typeof(configuration.scope) === 'string') || (configuration.scope === '')) {
throw new Error('Google API Wrapper - "scope" must be defined!');
}
const thiz = this;
/**
*
*/
this.element = document.createElement('script');
this.element.type = 'text/javascript';
this.element.async = true;
this.element.defer = true;
this.element.src = 'https://apis.google.com/js/api.js';
/**
*
*/
this.element.onload = function () {
gapi.load('client:auth2', function () {
gapi.client.init({
'apiKey': configuration.apiKey,
'discoveryDocs': configuration.discoveryDocs,
'clientId': configuration.clientId,
'scope': configuration.scope
}).then(function () {
thiz.googleAuthInstance = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
// The callback function must be a global named function
thiz.googleAuthInstance.isSignedIn.listen(onUpdateGoogleSignInStatus);
thiz.setSigninStatus();
}).catch(function (error) {
__callback(error);
});
}.bind(thiz));
};
/**
*
*/
this.element.onreadystatechange = function () {
if (this.readyState === 'complete') {
this.onload();
}
};
/**
*
*/
this.setSigninStatus = function (isSignedIn) {
if ((isSignedIn === true) || (isSignedIn === undefined)) {
this.user = this.googleAuthInstance.currentUser.get();
this.authorized = this.user.hasGrantedScopes(configuration.scope);
this.authorizationToken = (this.authorized) ? this.user.getAuthResponse().access_token : null;
if (!this.authorized) {
this.googleAuthInstance.signIn().then(function (authenticatedUser) {
__callback(this.user, this.authorized, this.authorizationToken, this.googleAuthInstance);
}.bind(this)).catch(function (error) {
__callback(error);
});
} else {
__callback(this.user, this.authorized, this.authorizationToken, this.googleAuthInstance);
}
}
};
/**
*
*/
this.getAuthInstance = function () {
return this.googleAuthInstance || null;
};
/**
*
*/
this.getUser = function () {
return this.user || null;
};
/**
*
*/
this.getAuthorizationToken = function () {
return this.authorizationToken;
};
/**
*
*/
this.getConfiguration = function () {
return configuration;
};
/**
*
*/
this.hasGrantedScopes = function () {
return this.authorized;
};
/**
*
*/
this.disconnect = function (deAuthenticate, callback) {
if (this.googleAuthInstance.isSignedIn.get()) {
if (deAuthenticate === true) {
this.googleAuthInstance.disconnect().then(function () {
if ((callback) && (callback instanceof Function)) {
callback(true);
}
});
} else {
this.googleAuthInstance.signOut().then(function () {
if ((callback) && (callback instanceof Function)) {
callback(false);
}
});
}
}
};
/**
*
*/
__window.onUpdateGoogleSignInStatus = function onUpdateGoogleSignInStatus (isSignedIn) {
this.setSigninStatus(isSignedIn);
}.bind(this);
document.body.appendChild(this.element);
};
__window.Google = Google;
})(window);
// This second part is part of my project - Settings
var settings = {
appId: 'xxx',
apiKey: 'xxx',
discoveryDocs: [
'https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest'
],
clientId: 'xxx',
scope: 'https://www.googleapis.com/auth/admin.directory.user.readonly profile email'
};
var domains = [
'mydomain1.com', 'mydomain2.com'
];
// This is a bootstrap that I make some requests to sync information retrieved from oauth2 and my application, where I make the API call after authorized
function bootstrap (google) {
if (google instanceof Error) {
console.log(google);
} else {
// Here I Start Angular angular.element(document).ready.... get the user domain...
var userDomain = 'mydomain1';
var profile = google.getUser().getBasicProfile();
console.log(profile);
gapi.client.directory.users.list({
'viewType': 'domain_public',
'customer': 'my_customer',
'maxResults': 200,
'orderBy': 'email',
'domain': userDomain
}).then(function (response) {
console.log(response.result.users);
}).catch(function (error) {
console.log(error);
});
}
}
// Invokes Google with settings and if the authenticated user is part of one of my domains, I call "bootstrap" function
var google = new Google(settings, function (user, authorized, authorizationToken, authInstance) {
if (authorized !== undefined && authorizationToken !== undefined) {
var email = user.getBasicProfile().getEmail();
var allowed = domains.reduce(function (previous, current) {
if (email.indexOf(current) !== -1) {
previous += 1;
}
return previous;
}, 0);
if (allowed === 0) {
authInstance.disconnect();
bootstrap(new Error('User does not belong to XXX domain.'));
} else {
bootstrap(google);
}
} else {
bootstrap(new Error(user.details));
}
});
为了 运行 users.list() 您应该是具有适当权限的管理员,因为有不同类型的 Google Workspace 管理员。这就是为什么当你从超级管理员帐户 运行 时你可以 运行 成功你的代码,为什么如果你将 viewType
设置为 admin_view
你可以 运行 它也成功了。
但是,虽然用户帐户只能由管理员修改,但域中的任何用户都可以读取用户配置文件(在您的情况下,这是可能的,因为您的方法是列出它们)。根据域的documentation detailing this in order to do this setting viewType
to domain_public
you must first enable contact sharing(禁用联系人共享的个人用户将不会被检索)。另请注意,以下范围应该足够 https://www.googleapis.com/auth/admin.directory.user.readonly
.
我有一个应用程序由于用户列表 (https://content.googleapis.com/admin/directory/v1/users?orderBy=email&viewType=domain_public&maxResults=200&customer=my_customer&domain=XXX&key=XXX) 不断返回而突然停止工作:
{
"error": {
"code": 403,
"message": "Not Authorized to access this resource/api",
"errors": [
{
"message": "Not Authorized to access this resource/api",
"domain": "global",
"reason": "forbidden"
}
]
}
}
此应用程序提供给 2 个不同的域。它适用于一个但不适用于其他。相同的代码...只是 appId、apiKey 和 clientId 发生了变化。我配置了范围广泛的域委托 (https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/admin.directory.user),还配置了 API KEY,OAuth 2.0 客户端。
我尝试重新创建整个项目,所有凭据,尝试了 http 和 https...同样的错误。
API 规范有什么变化吗?我缺少什么吗?
已编辑 - 请求的源代码:
我只是复制了整个代码,使用凭据更改了“设置”并在浏览器控制台中执行。该代码在身份验证之前或之后工作,但有 403 响应:-(
// This first part I got from https://github.com/lord22shark/google
(function (__window) {
const Google = function (configuration, __callback) {
if ((!configuration) || !(configuration instanceof Object)) {
throw new Error('Google API Wrapper - "configuration" must be defined!');
}
if ((!configuration.apiKey) || !(typeof(configuration.apiKey) === 'string') || (configuration.apiKey === '')) {
throw new Error('Google API Wrapper - "apiKey" must be defined!');
}
if ((!configuration.discoveryDocs) || !(configuration.discoveryDocs instanceof Array) || (configuration.discoveryDocs.length === 0)) {
throw new Error('Google API Wrapper - "discoveryDocs" must be a defined array!');
}
if ((!configuration.clientId) || !(typeof(configuration.clientId) === 'string') || (configuration.clientId === '')) {
throw new Error('Google API Wrapper - "clientId" must be defined!');
}
if ((!configuration.scope) || !(typeof(configuration.scope) === 'string') || (configuration.scope === '')) {
throw new Error('Google API Wrapper - "scope" must be defined!');
}
const thiz = this;
/**
*
*/
this.element = document.createElement('script');
this.element.type = 'text/javascript';
this.element.async = true;
this.element.defer = true;
this.element.src = 'https://apis.google.com/js/api.js';
/**
*
*/
this.element.onload = function () {
gapi.load('client:auth2', function () {
gapi.client.init({
'apiKey': configuration.apiKey,
'discoveryDocs': configuration.discoveryDocs,
'clientId': configuration.clientId,
'scope': configuration.scope
}).then(function () {
thiz.googleAuthInstance = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
// The callback function must be a global named function
thiz.googleAuthInstance.isSignedIn.listen(onUpdateGoogleSignInStatus);
thiz.setSigninStatus();
}).catch(function (error) {
__callback(error);
});
}.bind(thiz));
};
/**
*
*/
this.element.onreadystatechange = function () {
if (this.readyState === 'complete') {
this.onload();
}
};
/**
*
*/
this.setSigninStatus = function (isSignedIn) {
if ((isSignedIn === true) || (isSignedIn === undefined)) {
this.user = this.googleAuthInstance.currentUser.get();
this.authorized = this.user.hasGrantedScopes(configuration.scope);
this.authorizationToken = (this.authorized) ? this.user.getAuthResponse().access_token : null;
if (!this.authorized) {
this.googleAuthInstance.signIn().then(function (authenticatedUser) {
__callback(this.user, this.authorized, this.authorizationToken, this.googleAuthInstance);
}.bind(this)).catch(function (error) {
__callback(error);
});
} else {
__callback(this.user, this.authorized, this.authorizationToken, this.googleAuthInstance);
}
}
};
/**
*
*/
this.getAuthInstance = function () {
return this.googleAuthInstance || null;
};
/**
*
*/
this.getUser = function () {
return this.user || null;
};
/**
*
*/
this.getAuthorizationToken = function () {
return this.authorizationToken;
};
/**
*
*/
this.getConfiguration = function () {
return configuration;
};
/**
*
*/
this.hasGrantedScopes = function () {
return this.authorized;
};
/**
*
*/
this.disconnect = function (deAuthenticate, callback) {
if (this.googleAuthInstance.isSignedIn.get()) {
if (deAuthenticate === true) {
this.googleAuthInstance.disconnect().then(function () {
if ((callback) && (callback instanceof Function)) {
callback(true);
}
});
} else {
this.googleAuthInstance.signOut().then(function () {
if ((callback) && (callback instanceof Function)) {
callback(false);
}
});
}
}
};
/**
*
*/
__window.onUpdateGoogleSignInStatus = function onUpdateGoogleSignInStatus (isSignedIn) {
this.setSigninStatus(isSignedIn);
}.bind(this);
document.body.appendChild(this.element);
};
__window.Google = Google;
})(window);
// This second part is part of my project - Settings
var settings = {
appId: 'xxx',
apiKey: 'xxx',
discoveryDocs: [
'https://www.googleapis.com/discovery/v1/apis/admin/directory_v1/rest'
],
clientId: 'xxx',
scope: 'https://www.googleapis.com/auth/admin.directory.user.readonly profile email'
};
var domains = [
'mydomain1.com', 'mydomain2.com'
];
// This is a bootstrap that I make some requests to sync information retrieved from oauth2 and my application, where I make the API call after authorized
function bootstrap (google) {
if (google instanceof Error) {
console.log(google);
} else {
// Here I Start Angular angular.element(document).ready.... get the user domain...
var userDomain = 'mydomain1';
var profile = google.getUser().getBasicProfile();
console.log(profile);
gapi.client.directory.users.list({
'viewType': 'domain_public',
'customer': 'my_customer',
'maxResults': 200,
'orderBy': 'email',
'domain': userDomain
}).then(function (response) {
console.log(response.result.users);
}).catch(function (error) {
console.log(error);
});
}
}
// Invokes Google with settings and if the authenticated user is part of one of my domains, I call "bootstrap" function
var google = new Google(settings, function (user, authorized, authorizationToken, authInstance) {
if (authorized !== undefined && authorizationToken !== undefined) {
var email = user.getBasicProfile().getEmail();
var allowed = domains.reduce(function (previous, current) {
if (email.indexOf(current) !== -1) {
previous += 1;
}
return previous;
}, 0);
if (allowed === 0) {
authInstance.disconnect();
bootstrap(new Error('User does not belong to XXX domain.'));
} else {
bootstrap(google);
}
} else {
bootstrap(new Error(user.details));
}
});
为了 运行 users.list() 您应该是具有适当权限的管理员,因为有不同类型的 Google Workspace 管理员。这就是为什么当你从超级管理员帐户 运行 时你可以 运行 成功你的代码,为什么如果你将 viewType
设置为 admin_view
你可以 运行 它也成功了。
但是,虽然用户帐户只能由管理员修改,但域中的任何用户都可以读取用户配置文件(在您的情况下,这是可能的,因为您的方法是列出它们)。根据域的documentation detailing this in order to do this setting viewType
to domain_public
you must first enable contact sharing(禁用联系人共享的个人用户将不会被检索)。另请注意,以下范围应该足够 https://www.googleapis.com/auth/admin.directory.user.readonly
.