Ошибка 403 (недостаточно областей действия) для службы Google Drive API->files->listFiles()

Я пытаюсь разработать приложение, которое обеспечит синхронизацию папок между папкой файловой системы Linux и Google Диском. У меня проблема в том, что я не могу пройти мимо ошибки 403 с сообщением:

В запросе недостаточно областей проверки подлинности

У меня есть успех на странице Google «Попробуйте API» здесь, когда я заполняю эти поля/значения:

'corpa' => 'user'
'includeFilesFromAllDrives' => true
'supportsAllDrives' => true
'spaces' => 'drive'

Это возвращает список объектов. Но когда я пробую это в PHP, например:

$result = $drv->service->files->listFiles( [
                              'corpa' => 'user'
                            , 'includeFilesFromAllDrives' => true
                            , 'supportsAllDrives' => true
                            , 'spaces' => 'drive'
                                    ]) ;

результат:

  "error": {
    "code": 403,
      "message": "Request had insufficient authentication scopes.",
    "errors": [
      {
        "message": "Insufficient Permission",
        "domain": "global",
        "reason": "insufficientPermissions"
      }
    ],
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT",
        "domain": "googleapis.com",
        "metadata": {
          "method": "google.apps.drive.v3.DriveFiles.List",
          "service": "drive.googleapis.com"
        }
      }
    ]
  }
}

На этой странице комментарий:

Requires one of the following OAuth scopes:

https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/drive.appdata
https://www.googleapis.com/auth/drive.file
https://www.googleapis.com/auth/drive.metadata
https://www.googleapis.com/auth/drive.metadata.readonly
https://www.googleapis.com/auth/drive.photos.readonly
https://www.googleapis.com/auth/drive.readonly

Чтобы выйти за рамки этого, я фактически выбрал все эти значения (вместе) при создании клиента. Явных возражений против использования мною областей видимости нет, но авторизация все же отклонена.

Код для создания объекта $service находится в классе (DriveStuff) следующим образом:

public function GetService($clientSecretFile = null
                            , $scopes = null
                            , $useKey = false
                            , $debug = false
                            ) {
    if ($this->DEBUG)
        error_log("GetService() called") ;
    $this->loginEpoch = time() ;             // For automatic login before key expiration (hard limit 1 hour)
    if (is_null($this->firstLoginEpoch)) $this->firstLoginEpoch = $this->loginEpoch ;
    $this->loginExpires = time() + (60 * 60) ; // Expires in 60 minutes
    $this->loginCount++ ;
    $pageFile = $this->pathToScript ;
    if ($useKey) { // Using API KEY rather than login credentials
        $client = new Google_Client() ;
        $client->setApplicationName('gDrive stuff');
        $client->setDeveloperKey($this->apiKey) ;
        }
    else { // Standard login to Client services
        if (is_null($clientSecretFile))
            $clientSecretFile = $this->clientFileJSON ;
        if (is_null($clientSecretFile)) {
            $clientSecretFile = $this->GetYouTubeControlParameter("CLIENT_SECRET_FILE", $pageFile) ;
            $this->clientFileJSON = $clientSecretFile ;
            }
        if (is_null($scopes))
            $scopes = $this->scopes ;
        if (is_null($scopes)) {
            $scopes = $this->GetYouTubeControlParameter("SCOPES", $pageFile) ;
            $this->scopes = $scopes ;
            }
        if (is_string($scopes))
            if (strstr($scopes, ',') > 0)
                $scopes = explode(',', $scopes) ;
            else
                $scopes = [ $scopes ] ;
        if ( ! in_array('https://www.googleapis.com/auth/drive', $scopes))
            $scopes[] = 'https://www.googleapis.com/auth/drive' ; // Add this service
        $this->scopes = $scopes ;

        $client = $this->GetAuth($clientSecretFile, $scopes) ;
        if ($debug) {
            foreach ($client->getScopes() AS $scope)
                error_log('Requesed scope: ' . $scope) ;
            }
        }
    $service = new Google_Service_Drive($client) ;
    $this->service = $service ;
    $this->reLogInFile = $clientSecretFile ; // For automatic login before key expiration (hard limit 1 hour)
    $this->reLogInScopes = $scopes ;         // For automatic login before key expiration (hard limit 1 hour)
    $this->reLogInUseKey = $useKey ;         // For automatic login before key expiration (hard limit 1 hour)
    return $this->service ;
    }

Программный код на этом этапе довольно прост:

require_once('DriveRelated/DriveStuff.php') ;

$drv = new DriveStuff($lov, false) ; // True sets debug mode
$service = $drv->GetService(null
                            , [   'https://www.googleapis.com/auth/drive'
                                , 'https://www.googleapis.com/auth/drive.appdata'
                                , 'https://www.googleapis.com/auth/drive.file'
                                , 'https://www.googleapis.com/auth/drive.metadata'
                                , 'https://www.googleapis.com/auth/drive.metadata.readonly'
                                , 'https://www.googleapis.com/auth/drive.photos.readonly'
                                , 'https://www.googleapis.com/auth/drive.readonly'
                                  ]
                            , false, true) ;
$result = $drv->service->files->listFiles( [ 'pageSize' => 3000 ],
                                [
                                  'corpa' => 'user'
                                , 'includeFilesFromAllDrives' => true
                                , 'supportsAllDrives' => true
                                , 'spaces' => 'drive'
                                        ]) ;

... и файл журнала содержит:

[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.appdata
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.file
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.metadata
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.metadata.readonly
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.photos.readonly
[03-Aug-2023 20:28:03 America/New_York] Requesed scope: https://www.googleapis.com/auth/drive.readonly]
[03-Aug-2023 20:28:05 America/New_York] PHP Fatal error:  Uncaught Google\Service\Exception: {
  "error": {
    "code": 403,
    "message": "Request had insufficient authentication scopes.",
    "errors": [
      {
        "message": "Insufficient Permission",
        "domain": "global",
        "reason": "insufficientPermissions"
      }
    ],
    "status": "PERMISSION_DENIED",
    "details": [
      {
        "@type": "type.googleapis.com/google.rpc.ErrorInfo",
        "reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT",
        "domain": "googleapis.com",
        "metadata": {
          "method": "google.apps.drive.v3.DriveFiles.List",
          "service": "drive.googleapis.com"
        }
      }
    ]
  }
}
 in /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Http/REST.php:134
Stack trace:
#0 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Http/REST.php(107): Google\Http\REST::decodeHttpResponse()
#1 [internal function]: Google\Http\REST::doExecute()
#2 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Task/Runner.php(187): call_user_func_array()
#3 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Http/REST.php(66): Google\Task\Runner->run()
#4 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Client.php(922): Google\Http\REST::execute()
#5 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Service/Resource.php(238): Google\Client->execute()
#6 /www/cgi-bin/YouTubeRelated/vendor/google/apiclient-services/src/Drive/Resource/Files.php(258): Google\Service\Resource->call()
#7 /www/htdocs/gdrive/gdrive-test.php(30): Google\Service\Drive\Resource\Files->listFiles()
#8 {main}
  thrown in /www/cgi-bin/YouTubeRelated/vendor/google/apiclient/src/Http/REST.php on line 134

Между прочим, я попробовал несколько подмножеств областей (например, все области только для чтения, все, кроме только для чтения и т. д.), без каких-либо изменений в результате.

У меня хороший и довольно обширный опыт работы с YouTube API, поэтому я уверен, что это будет преодолено. Теперь я ищу недостающую часть, чтобы продолжить работу с Google Диском, выйдя за рамки этого начального этапа.

Обратите внимание, что слово «запрошено» написано с ошибкой, но это только косметическая проблема.

РЕДАКТИРОВАТЬ В соответствии с запросом, вот код авторизации:

/**
 * Get access code from Database or from Google 
 * @param null|string %clientSecreFile json file from GoogleDegeloper's Console project export
 * @param null|string|array $scopes the requested scope(s).  If not specified, these will e retrieved from preferences
 * @param string $appName value to place into client application name ($client->SetApplicationName())
 * @return mixed client object
*/
public function GetAuth($clientSecretFile
                        , $scopes
                        , $appName = 'Google Drive stuff'
                        ) {
    $this->clientSecretFile = $clientSecretFile ;
    if ($this->DEBUG)
        error_log("GetAuth() called") ;
    $client = new Google_Client();
    $client_json = json_decode(file_get_contents($clientSecretFile), true) ;

    // For Desktop apps, $client_json top will be 'installed'; for Weh apps, this is 'weh'....

    if (count($client_json) == 1)                       // Key may be 'web' or 'installed'
        $topKey = implode(array_keys($client_json)) ;
    else
        $topKey = 'installed' ;
    $clientId = $client_json[$topKey]['client_id'] ;
    $clientSecret = $client_json[$topKey]['client_secret'] ;
    $client->setApplicationName($appName);
    if ( is_string($scopes))
        $scopes = [ $scopes ] ;
    $client->setScopes($scopes);
    $authHistoyRow['authConfig'] = json_encode($client_json) ;
    $client->setAuthConfig($client_json) ;
    $accessType = 'offline' ;
    $client->setAccessType($accessType);
    $this->GetClientFromDB($clientId        // Handles inserttion of new Client information if necessary; returns as much information as we have otherwise
                        , $clientSecret
                        , $scope            // Values below here (including this value) are passed by reference, and are updated by GetClientFromDB()
                        , $accessToken      // Updated by GetClientFromDB()
                        , $tokenType        // Updated by GetClientFromDB()
                        , $refreshToken     // Updated by GetClientFromDB()
                        , $expires          // Updated by GetClientFromDB()
                        , $created          // Updated by GetClientFromDB()
                        , $rowId            // Updated by GetClientFromDB()
                            ) ;
    $authHistoryRow = [] ;
    $authHistoryRow['clientId'] = $clientId ;
    $authHistoryRow['clientSecret'] = $clientSecret ;
    $authHistoryRow['projectId'] = $client_json[$topKey]['project_id'] ;
    $authHistoryRow['authUri'] = $client_json[$topKey]['auth_uri'] ;
    $authHistoryRow['tokenUri'] = $client_json[$topKey]['token_uri'] ;
    $authHistoryRow['authProviderX509CertUrl'] = $client_json[$topKey]['auth_provider_x509_cert_url'] ;
    $authHistoryRow['appName'] = $appName ;
    $authHistoryRow['scopes'] = implode(',', $scopes) ;
    $authHistoryRow['accessType'] = $accessType ;
    if ((! $rowId) || $client->isAccessTokenExpired()) {
        $accessToken = null ;
        // Refresh the token...
        if ($rowId && $client->isAccessTokenExpired() && $refreshToken) {
            $authHistoryRow['refreshToken'] = $refreshToken ;
            $client->refreshToken($refreshToken) ;
            $accessToken = $client->getAccessToken() ;
            $authHistoryRow['accessToken'] = $accessToken ;
            $expires = $accessToken['expires_in'] ;
            $authHistoryRow['expires'] = $expires ;
            }
        if ((! $accessToken) ) { // Still not able to get authorization.  Talk to user.
            printf("Authorization expired at %s\n", date('Y-m-d H:i', $expires)) ;
            //$client->setRedirectUri('http://web.lovelady.com/google-api-php-client/examples/large-file-download.php') ;
            $authUrl = $client->createAuthUrl();
            printf("Open this link in your browser:\n%s\n", $authUrl);
            printf('Enter verification code: ');
            $authCode = trim(fgets(STDIN));
            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            $output = print_r($accessToken, true) ;
            file_put_contents("access_token.txt", $output) ;
            }
        $accessExpiresEpoch = time() + $accessToken['expires_in'] ;
        $accessCreatedEpoch = $accessToken['created'] ;
        $this->WriteClientToDB($clientId                    // UUpdate the database
                            , $clientSecret 
                            , $accessToken['scope']
                            , $accessToken['access_token']
                            , $accessToken['token_type']
                            , $accessToken['refresh_token']
                            , $accessExpiresEpoch
                            , $accessCreatedEpoch
                                ) ;
        }
    $client->setAccessToken($accessToken);
    $authHistoryRow['accessToken'] = $accessToken ;
    $this->addClientHistory($authHistoryRow) ;
    return $client ;
    }  // End of GetAuth()

В вашем сценарии показа 'https://www.googleapis.com/auth/drive.readonly]' неверно. Я думаю, что необходимо удалить ] последнего символа. Это связано с вашей текущей проблемой?

Tanaike 04.08.2023 03:13

@Tanaike = спасибо, хороший глаз. Я исправил это в сценарии и проверил. Увы, похоже, ничего не изменилось. Соответственно исправлю вопрос. Спасибо - это была хорошая находка.

Dennis 04.08.2023 05:15

Спасибо за ответ. Во-первых, я прошу прощения, что мой комментарий не был связан с вашей текущей проблемой. Как простая модификация, когда вы используете только область https://www.googleapis.com/auth/drive, какой результат вы получите? Если это не было связано с вашей текущей проблемой, о вашем сценарии, когда вы извлекаете текущие области из токена доступа, полученного в вашем сценарии, какие области вы получите? Я предположил, что причина вашей текущей проблемы может быть известна.

Tanaike 04.08.2023 05:26

появилось ли ваше приложение и снова запросило согласие пользователя, так как вы изменили области в своем коде? пожалуйста, отредактируйте свой вопрос и покажите нам изображение экрана согласия и области, которые он запрашивает

Linda Lawton - DaImTo 04.08.2023 08:29

Просто чтобы быть уверенным — вы на самом деле используете учетные данные для своего клиента API Google Диска, несмотря на метод, используемый для получения тех, кого называют GetYouTubeControlParameter, да?

CBroe 04.08.2023 08:41

@CBroe да, и я понимаю эту путаницу. Класс DriveStuff расширяет YouTubeStuff, который имеет недальновидный метод (упоминание youtube в его названии). Все, что он делает, это читает текстовый файл с именем (к сожалению) /usr/local/etc/YouTubeControl.ini), чтобы получить параметры, под которыми должно работать это приложение. Другие параметры управления, хранящиеся там, такие как сервер БД и тому подобное.

Dennis 04.08.2023 13:11

@LindaLawton-DaImTo Спасибо за ответ. (У меня была невестка по имени Линда Лейтон. Ваше имя навеяло хорошие воспоминания.) Обычно это пакетный процесс. Но я запускал его с веб-сервера, и всплывающих окон не было. Возможно, вы что-то здесь знаете, но я не помню, в чем разница, из-за которой появляется эта страница авторизации. Я изучу и обновлю вопрос.

Dennis 04.08.2023 13:16

@Tanaike - еще раз спасибо, и не нужно извиняться. Была хорошей находкой и, вероятно, никогда не сработает (без объяснения причин). К сожалению, нет никаких улучшений, если запросить просто область /drive. Это был мой первоначальный подход, пока я не скопировал области действия со страницы «Попробуйте», о которой я упоминал в вопросе.

Dennis 04.08.2023 13:21

@ Деннис, мне нужно увидеть остальную часть твоего кода. Где вы авторизуете это приложение, как вы храните токен доступа и токен обновления.

Linda Lawton - DaImTo 04.08.2023 13:54

@LindaLawton-DaImВ соответствии с просьбой вопрос был обновлен с помощью этого кода

Dennis 04.08.2023 23:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
0
10
62
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я думаю, у вас может быть непроверенное приложение, и поэтому у вас есть области, которые вы не можете использовать. если ваше приложение не проверено, вы можете использовать только неконфиденциальные области, а не конфиденциальные и ограниченные области.

Я считаю, что если вы будете следовать приведенному ниже руководству, оно должно работать с некоторыми оговорками. если вы не планируете использовать путь oauth для авторизации своего приложения.

https://developers.google.com/drive/api/guides/api-specific-auth

для @linda

если ваше общедоступное приложение использует области, разрешающие доступ к определенным пользовательским данным, оно должно пройти процесс проверки. Если вы видите непроверенное приложение на экране при тестировании своего приложения, вы должны отправить запрос на проверку, чтобы удалить его. Узнайте больше о непроверенных приложениях и получите ответы на часто задаваемые вопросы о проверке приложений в Справочном центре.

стол...

В столбце «Использование» в приведенной выше таблице указана чувствительность каждой области в соответствии со следующими определениями:

Рекомендуемые/неконфиденциальные. Эти области обеспечивают наименьшую область авторизации и требуют только базовой проверки приложения. Сведения об этом требовании см. в разделе Действия по подготовке к проверке.

Рекомендуемые/конфиденциальные. Эти области предоставляют доступ к определенным данным пользователя Google, авторизованным пользователем для вашего приложения. Это требует от вас пройти дополнительную проверку приложения. Сведения об этом требовании см. в разделе Действия для приложений, запрашивающих конфиденциальные области.

Ограниченный — эти области обеспечивают широкий доступ к данным пользователей Google и требуют от вас пройти процесс проверки ограниченной области. Сведения об этом требовании см. в разделе Службы API Google: политика в отношении пользовательских данных и дополнительные требования для конкретных областей действия API. Если вы храните данные с ограниченным объемом на серверах (или передаете), вам необходимо пройти оценку безопасности.

Если вашему приложению требуется доступ к каким-либо другим API Google, вы также можете добавить эти области. Дополнительные сведения об областях API Google см. в разделе Использование OAuth 2.0 для доступа к API Google.

Дополнительные сведения о конкретных областях OAuth 2.0 см. в разделе Области OAuth 2.0 для API Google.

Ах, спасибо, @AlexMac - эта ссылка разгадывает загадку, работает ли приложение когда-либо или нет. Спасибо за это!. Вы правильно оценили ситуацию (проект не проверен.) Все, что я делаю, это все для меня, и на моем собственном YouTube, и (сейчас) на Google Диске, поэтому проверка кажется излишней... но я, возможно, пойти по этому пути или забыть об этой идее. Последнее было бы грустным позором, поэтому я рассмотрю первое. В любом случае, уменьшение запрошенных скоупов (до drive.file, drive.resource, drive.appdata) проблему не решило. Может потребоваться повторная проверка, как сказала Линда выше. Глядя на это.

Dennis 04.08.2023 13:29

@ Деннис, просто используйте oauth и нечувствительные области. также, если вы считаете, что это правильно, не могли бы вы отметить это как ответ?

Alex Mac 05.08.2023 09:09

Спасибо, АлексМак. Это бесценная и труднодоступная ссылка.

Dennis 05.08.2023 11:29

@AlexMac, можете ли вы показать мне, где в документации по проверке говорится, что вы не можете использовать конфиденциальные и ограниченные области для приложений, которые не были проверены? Это странно, потому что у меня есть сервалы, которые не проверены и работают нормально. Они просто показывают экран непроверенного приложения.

Linda Lawton - DaImTo 05.08.2023 11:53

это в ссылке, предоставленной @linda в красном предупреждении. справа под столом.

Alex Mac 05.08.2023 11:59

Может я просто не вижу, но где написано, что нужно пройти верификацию? непроверенные приложения В проверке нет ничего, что мешало бы вам это сделать. Вы просто получаете непроверенный экран приложения. Там нет ничего, что говорит, что это не будет работать. Мои приложения по-прежнему работают нормально. Все еще не уверен, почему вы думаете, что он выдаст запрос с недостаточной областью проверки подлинности. ошибка либо это две разные вещи.

Linda Lawton - DaImTo 05.08.2023 12:06

он не использует oauth. почему это произошло 🤔 Вы просто получаете непроверенный экран приложения

Alex Mac 05.08.2023 12:07

Да, он использует Oauth2, вот как вы подключаетесь к API Google, вы запрашиваете токен доступа, используя Oauth2. Проверьте последнюю строку в вашем ответе, который вы только что обновили.

Linda Lawton - DaImTo 05.08.2023 12:09

извините, я оговорился, я имел в виду, что он не выполняет перенаправление с неявным uri .... woooahhh, что я не видел обновления. ммм, я бы предположил, что у него есть тип предоставления учетных данных клиента с учетными данными работника файловой службы json или apikey и секретом.

Alex Mac 05.08.2023 12:15

так он его чешет. ммм, тогда, @linda, ты, вероятно, более компетентна, чем я, чтобы ответить на этот вопрос. Я умею, однако у меня нет сертификата gcp или чего-то еще. Деннис, будь осторожен с этим конфигурационным файлом. если вы не ловите это должным образом, вы собираетесь поделиться им. у них есть пакеты для этого материала, которые делают его намного проще. это как 30 строк в моем приложении.

Alex Mac 05.08.2023 12:19

Я хотел бы предложить вам удалить эти поля из вашей базы данных.

$accessToken      // Updated by GetClientFromDB()
, $tokenType        // Updated by GetClientFromDB()
, $refreshToken     // Updated by GetClientFromDB()

Это должно заставить ваше приложение снова запросить авторизацию пользователя, и в это время ему должны быть показаны новые области, к которым ваше приложение запрашивает доступ, и предоставить доступ. Если ваше приложение работает правильно, оно сохранит эти новые значения в базе данных.

Проблема, с которой вы столкнулись, заключается в том, что вы уже запросили доступ пользователя и сохранили токен обновления с предыдущими предоставленными учетными данными. Поскольку ваше приложение уже имеет эти значения, оно использует их для запроса нового токена доступа, что приводит к

В запросе недостаточно областей проверки подлинности

Вы не можете просто изменить области в своем приложении без повторного запроса согласия пользователя. Удалив токен обновления, который вы сохранили для этого пользователя. Затем вы можете снова запросить авторизацию. На этот раз с увеличением масштабов. Затем вы должны сохранить новый токен обновления в своей базе данных, и он будет работать.

Другие вопросы по теме