How can a webapp using the Google Drive REST API share files with another user using the same app?

牧云@^-^@ 提交于 2020-04-18 12:37:20

问题


My webapp is a 3D virtual gaming tabletop with no back-end server... instead, it uses Google Drive to store user data and images. I have a specific question, but I'll describe my situation more fully in case there's a solution somewhere above the level of my question.

The Use-Case

  1. User A, the Game Master (GM), uses the app to upload an image of a map, and some images of some monsters. The app uploads the images to a folder structure it has created in User A's Google Drive, and the app marks them as readable by link.
  2. After User A configures things, the app shows a virtual tabletop, a 3D space containing a map with some monsters on it. The JSON data describing the tabletop is also stored in Google Drive, and the app marks it as readable by link.
  3. User A shares a URL with User B, a player. The link is to my app, but includes the Google Drive file ID of the JSON file in User A's drive describing the tabletop.
  4. User B accesses the link. The app loads up, uses the ID to read the JSON file from User A's Drive, and then renders the tabletop, loading the images from User A's Drive as required.

The Problem

Google Drive's drive.files oAuth scope is sufficient to do steps 1-3 of the use case - the app is able to create and read Google Drive files for User A.

However, with just drive.files scope it does not appear to be possible to do step 4.

  • User B has granted the app drive.files access to their Drive.
  • The files were created by the app (in steps 1-2)
  • User B has read access to the files (granted in steps 1-2)
  • Google Drive does not allow the app to access the files, because User B has not granted the app permission to access the files.

The documentation for drive.files describes it as "Per-file access to files created or opened by the app. File authorization is granted on a per-user basis and is revoked when the user deauthorizes the app." However, this does not appear to be strictly true, because Drive does not appear to record whether a file is created by the app. It seems that instead, when the app creates files, access is implicitly granted to the app for the current user for those files, and then the fact that the file was created by the app is forgotten.

The current workaround is for the app to also require drive.readonly oAuth scope. This is an unreasonable level of access, and I know of numerous users who have (quite reasonably) decided they're not willing to grant my app read-only access to their entire Drive. It is also a "restricted" oAuth scope, but I've gone through the app verification process with Google.

The Question

Is it possible to make my app grant User B read access to the files without using restricted-level oAuth scopes, without requiring too much work from User B, and while remaining purely client-side? If so, how?

The Problematic Solutions

Using drive.readonly oAuth scope works, but is unreasonable, as discussed above.

I believe that it's possible to create Drive integrations for my app which would allow a user to right-click a file and "Open with" my app, which would grant the app access to the file. However,

  • There are numerous files involved - the JSON file describing the tabletop, individual images for each map and creature on the tabletop, and other files as well. Also, new images can be dropped on the tabletop mid-game.
  • User B is a player, and does not have (and should not have) permission to browse the GM's files in the Drive GUI in order to right-click them and "Open With" the app. They should not be able to see monsters or maps that haven't yet been added to the tabletop. To this end, the app grants read access by link for the files, but not for the directories containing those files.

It would be possible to have a custom server which serves up the content from Drive, but I'm trying to keep the app purely client-side.

The Technology

In case it's relevant, the app is written in Javascript (actually, Typescript), and Drive API calls are done via the Javascript Google Drive REST API.


回答1:


As you can see in the official documentation, drive.files lets you:

View and manage Google Drive files and folders that you have opened or created with this app

Since the files were not created on behalf of User B, the app doesn't have permission to access them.

I'm afraid there is no non-restricted scope that will grant this permission, so you should use drive.readonly, or redesign the sharing process completely.

Reference:

  • OAuth 2.0 Scopes for Google APIs: Drive API, v3



回答2:


I have found a workaround which allows all four steps of my use-case to be handled. The workaround only works because the files I want to share are "readable by anyone with the link" - it would not work if the files were shared only with certain people.

I create a second GAPI clients in an iframe, which is left unauthenticated (it isn't even configured with the oAuth client ID or the scope). The main GAPI client is used as before to log in the user (with drive.file oAuth scope).

function addGapiScript() {
    return new Promise((resolve, reject) => {
        const iframe = document.createElement('iframe');
        iframe.onload = () => {
            if (!iframe || !iframe.contentDocument || !iframe.contentWindow) {
                reject(new Error('Failed to add iframe'));
                return;
            }
            const script = iframe.contentDocument.createElement('script');
            script.onload = () => {
                resolve(iframe.contentWindow['gapi']);
            };
            script.onerror = reject;
            script.src = 'https://apis.google.com/js/api.js';
            iframe.contentDocument.head.appendChild(script);
        };
        iframe.onerror = reject;
        iframe.src = '/blank.html'; // A src is required because gapi refuses to init in an iframe with a location of about:blank.
        document.body.appendChild(iframe);
    });
}

// Discovery docs for the Google Drive API.
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'];
// Authorization scopes required by the API; multiple scopes can be included, separated by spaces.
const SCOPES = 'https://www.googleapis.com/auth/drive.file';

let anonymousGapi;

async function initialiseFileAPI(signInHandler, onerror) {
    // Jump through some hoops to get two gapi clients.
    // The first is "anonymous", i.e. does not log in
    anonymousGapi = window['anonymousGapi'] = await addGapiScript();
    anonymousGapi.load('client', {
        callback: async () => {
            await anonymousGapi.client.init({
                apiKey: API_KEY,
                discoveryDocs: DISCOVERY_DOCS
            });
        },
        onerror
    });
    // The second is the normal gapi that we log in.
    gapi.load('client:auth2', {
        callback: async () => {
            await gapi.client.init({
                apiKey: API_KEY,
                discoveryDocs: DISCOVERY_DOCS,
                clientId: CLIENT_ID,
                scope: SCOPES
            });
            // Listen for sign-in state changes.
            gapi.auth2.getAuthInstance().isSignedIn.listen(signInHandler);
            // Handle initial sign-in state.
            signInHandler(gapi.auth2.getAuthInstance().isSignedIn.get());
        },
        onerror
    });
}

If the authenticated client fails to read a file, the code falls back on the unauthenticated client, which is able to read the file anonymously.

async function driveFilesGet(params) {
    // Do a regular drive.files.get, but fall back to anonymous if it throws a 404 error
    try {
        return await gapi.client.drive.files.get(params);
    } catch (err) {
        if (err.status === 404) {
            // Attempt to get the file data anonymously
            return await anonymousGapi.client.drive.files.get(params);
        }
        throw err;
    }
}

Unfortunately, it appears that the Drive file metadata in response to an anonymous request will not contain any appProperties data for the app, despite the app's API_KEY being present in the request. The metadata can contain properties however.



来源:https://stackoverflow.com/questions/60698552/how-can-a-webapp-using-the-google-drive-rest-api-share-files-with-another-user-u

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!