Я пытаюсь разработать приложение, которое обеспечит синхронизацию папок между папкой файловой системы 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()
@Tanaike = спасибо, хороший глаз. Я исправил это в сценарии и проверил. Увы, похоже, ничего не изменилось. Соответственно исправлю вопрос. Спасибо - это была хорошая находка.
Спасибо за ответ. Во-первых, я прошу прощения, что мой комментарий не был связан с вашей текущей проблемой. Как простая модификация, когда вы используете только область https://www.googleapis.com/auth/drive, какой результат вы получите? Если это не было связано с вашей текущей проблемой, о вашем сценарии, когда вы извлекаете текущие области из токена доступа, полученного в вашем сценарии, какие области вы получите? Я предположил, что причина вашей текущей проблемы может быть известна.
появилось ли ваше приложение и снова запросило согласие пользователя, так как вы изменили области в своем коде? пожалуйста, отредактируйте свой вопрос и покажите нам изображение экрана согласия и области, которые он запрашивает
Просто чтобы быть уверенным — вы на самом деле используете учетные данные для своего клиента API Google Диска, несмотря на метод, используемый для получения тех, кого называют GetYouTubeControlParameter, да?
@CBroe да, и я понимаю эту путаницу. Класс DriveStuff расширяет YouTubeStuff, который имеет недальновидный метод (упоминание youtube в его названии). Все, что он делает, это читает текстовый файл с именем (к сожалению) /usr/local/etc/YouTubeControl.ini), чтобы получить параметры, под которыми должно работать это приложение. Другие параметры управления, хранящиеся там, такие как сервер БД и тому подобное.
@LindaLawton-DaImTo Спасибо за ответ. (У меня была невестка по имени Линда Лейтон. Ваше имя навеяло хорошие воспоминания.) Обычно это пакетный процесс. Но я запускал его с веб-сервера, и всплывающих окон не было. Возможно, вы что-то здесь знаете, но я не помню, в чем разница, из-за которой появляется эта страница авторизации. Я изучу и обновлю вопрос.
@Tanaike - еще раз спасибо, и не нужно извиняться. Была хорошей находкой и, вероятно, никогда не сработает (без объяснения причин). К сожалению, нет никаких улучшений, если запросить просто область /drive. Это был мой первоначальный подход, пока я не скопировал области действия со страницы «Попробуйте», о которой я упоминал в вопросе.
@ Деннис, мне нужно увидеть остальную часть твоего кода. Где вы авторизуете это приложение, как вы храните токен доступа и токен обновления.
@LindaLawton-DaImВ соответствии с просьбой вопрос был обновлен с помощью этого кода






Я думаю, у вас может быть непроверенное приложение, и поэтому у вас есть области, которые вы не можете использовать. если ваше приложение не проверено, вы можете использовать только неконфиденциальные области, а не конфиденциальные и ограниченные области.
Я считаю, что если вы будете следовать приведенному ниже руководству, оно должно работать с некоторыми оговорками. если вы не планируете использовать путь 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) проблему не решило. Может потребоваться повторная проверка, как сказала Линда выше. Глядя на это.
@ Деннис, просто используйте oauth и нечувствительные области. также, если вы считаете, что это правильно, не могли бы вы отметить это как ответ?
Спасибо, АлексМак. Это бесценная и труднодоступная ссылка.
@AlexMac, можете ли вы показать мне, где в документации по проверке говорится, что вы не можете использовать конфиденциальные и ограниченные области для приложений, которые не были проверены? Это странно, потому что у меня есть сервалы, которые не проверены и работают нормально. Они просто показывают экран непроверенного приложения.
это в ссылке, предоставленной @linda в красном предупреждении. справа под столом.
Может я просто не вижу, но где написано, что нужно пройти верификацию? непроверенные приложения В проверке нет ничего, что мешало бы вам это сделать. Вы просто получаете непроверенный экран приложения. Там нет ничего, что говорит, что это не будет работать. Мои приложения по-прежнему работают нормально. Все еще не уверен, почему вы думаете, что он выдаст запрос с недостаточной областью проверки подлинности. ошибка либо это две разные вещи.
он не использует oauth. почему это произошло 🤔 Вы просто получаете непроверенный экран приложения
Да, он использует Oauth2, вот как вы подключаетесь к API Google, вы запрашиваете токен доступа, используя Oauth2. Проверьте последнюю строку в вашем ответе, который вы только что обновили.
извините, я оговорился, я имел в виду, что он не выполняет перенаправление с неявным uri .... woooahhh, что я не видел обновления. ммм, я бы предположил, что у него есть тип предоставления учетных данных клиента с учетными данными работника файловой службы json или apikey и секретом.
так он его чешет. ммм, тогда, @linda, ты, вероятно, более компетентна, чем я, чтобы ответить на этот вопрос. Я умею, однако у меня нет сертификата gcp или чего-то еще. Деннис, будь осторожен с этим конфигурационным файлом. если вы не ловите это должным образом, вы собираетесь поделиться им. у них есть пакеты для этого материала, которые делают его намного проще. это как 30 строк в моем приложении.
Я хотел бы предложить вам удалить эти поля из вашей базы данных.
$accessToken // Updated by GetClientFromDB()
, $tokenType // Updated by GetClientFromDB()
, $refreshToken // Updated by GetClientFromDB()
Это должно заставить ваше приложение снова запросить авторизацию пользователя, и в это время ему должны быть показаны новые области, к которым ваше приложение запрашивает доступ, и предоставить доступ. Если ваше приложение работает правильно, оно сохранит эти новые значения в базе данных.
Проблема, с которой вы столкнулись, заключается в том, что вы уже запросили доступ пользователя и сохранили токен обновления с предыдущими предоставленными учетными данными. Поскольку ваше приложение уже имеет эти значения, оно использует их для запроса нового токена доступа, что приводит к
В запросе недостаточно областей проверки подлинности
Вы не можете просто изменить области в своем приложении без повторного запроса согласия пользователя. Удалив токен обновления, который вы сохранили для этого пользователя. Затем вы можете снова запросить авторизацию. На этот раз с увеличением масштабов. Затем вы должны сохранить новый токен обновления в своей базе данных, и он будет работать.
В вашем сценарии показа
'https://www.googleapis.com/auth/drive.readonly]'неверно. Я думаю, что необходимо удалить]последнего символа. Это связано с вашей текущей проблемой?