为什么 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.