Подтвердить что ты не робот

Google Endpoints API + расширение Chrome возвращает None для endpoints.get_current_user(). User_id()

Я разрабатываю приложение Google App Engine, написанное на Python, и использующее Endpoints API. В связи с этим я пишу расширение Chrome для взаимодействия с API конечных точек. У меня возникло множество проблем с API-интерфейсом Endpoints и авторизацией. В настоящее время здесь моя настройка:

API конечных точек (Python)

from google.appengine.ext import endpoints
from protorpc import message_types
from protorpc import remote

ALLOWED_CLIENT_IDS = ['client_id_from_google_api_console',
                      endpoints.API_EXPLORER_CLIENT_ID]

@endpoints.api(name='my_api',version='v1', description='My API',
               allowed_client_ids=ALLOWED_CLIENT_IDS)
class MyApi(remote.Service):

    @endpoints.method(message_types.VoidMessage, DeviceListResponse,
                      name='user.device.list', path='user/device/list', 
                      http_method='GET')
    def user_device_list(self, request):
        current_user = endpoints.get_current_user()
        if current_user is None:
            raise endpoints.UnauthorizedException('You must authenticate first.')
        if current_user.user_id() is None:
            raise endpoints.NotFoundException("Your user id was not found.")

        return DeviceListResponse(devices=[]) #Hypothetically return devices

api_service = endpoints.api_server([MyApi], restricted=False)

Консоль API Google

Истоки JS включают: хром-расширения://chrome_app_id

ХРОМНОЕ РАСШИРЕНИЕ (JS)

var apiRoot = "https://my_app_id.appspot.com/_ah/api";
var clientID = "client_id_from_google_api_console";
var oauthScopes = ["https://www.googleapis.com/auth/userinfo.email"];
var responseType = "token id_token";

//Helper method to log to the console
function l(o){console.log(o);}

function oauthSignin(mode) {
    gapi.auth.authorize({client_id: clientID, scope: oauthScopes,
    immediate: mode, response_type: responseType}, function() {
        var request = gapi.client.oauth2.userinfo.get();
        request.execute(function(resp) {
            authenticated = !resp.code;
            if(authenticated) {
                var token = gapi.auth.getToken();
                token.access_token = token.id_token;
                gapi.auth.setToken(token);
                l("Successfully authenticated. Loading device list");
                gapi.client.my_api.user.device.list({}).execute(function(resp) {
                    if(resp.code) {
                        l("Response from device list: " + resp.message);
                    }
                    l(resp);
                });
            }
        });
    });
}


//This get called when the page and js library has loaded.
function jsClientLoad() {
    l("JS Client Libary loaded. Now loading my_api and oauth2 APIs.");
    var apisToLoad;
    var callback = function() {
        if (--apisToLoad == 0) {
            l("APIs have loaded.")
            oauthSignin(true);
        } else {
            l("Waiting for " + apisToLoad + " API" + (apisToLoad>1?"s":"") + " to load.");
        }
    }

    apisToLoad = 2; // must match number of calls to gapi.client.load()
    gapi.client.load('my_api', 'v1', callback, apiRoot);
    gapi.client.load('oauth2', 'v2', callback);
}

Результат

Теперь, когда я показал основной кусок моего кода (заметьте, мне пришлось немного изменить его, чтобы иметь смысл, не загружая весь код), если я перейду в Google API Explorer и запустил этот метод, я получаю 200 ответ. Если я запустил его в Chrome Extension, я получаю код 404 с сообщением "Идентификатор пользователя не найден".

4b9b3361

Ответ 1

Непонятно, почему/когда это когда-либо приводит к 200; это не должно. Как упоминалось в Функция User.getUserId() в конечной точке Cloud api возвращает null для объекта пользователя, который не является нулевым, это известная проблема.

TL;DR;

В результате endpoints.get_current_user() будет заполнен user_id никогда. Обходной путь существует: путем хранения пользовательского объекта в хранилище данных и последующего его получения (с помощью нового get, если вы используете ndb), значение user_id() будет заполнено.

Вам следует настоятельно рассмотреть использование идентификатора профиля Google, связанного с учетной записью, вместо идентификатора пользователя App Engine.

История/Объяснение:

endpoints предназначен для использования как с токенами-носителями, так и с идентификационными маркерами (для Android). Идентификаторы ID - это особый тип JWT (веб-токен JSON), подписанный в сочетании с криптографией устройства. В результате разбор пользователя из этих токенов может определять информацию, закодированную в этом токене (см. Оконечные точки оконечного устройства oauth2 для получения дополнительной информации об этом).

Поскольку эти маркеры отчеканены общим поставщиком Google Auth (OAuth 2.0) за пределами App Engine, идентификатор пользователя App Engine не известен/не используется этой службой. В результате, никогда можно заполнить user_id(), когда для подписи запроса используется токен ID.

При использовании стандартного маркера Bearer (что будет хорошо для вашего приложения Chrome) используется AppApp метод. Это делает RPC для уровня общего уровня приложений, который предоставляет услугу, которая может получить текущего пользователя от токена. В этом случае установлен user_id() возвращаемого пользователя WILL, однако значение пользователя не поддерживается для endpoints.get_current_user, только адрес электронной почты и авторизация домена.

Другое обходное решение:

Вызов oauth.get_current_user() только дорогой IF, он делает RPC. Метод _maybe_call_get_oauth_user сохраняет значение из последнего вызова, поэтому второй раз вызов oauth.get_current_user() не будет накладывать на сеть/скорость, кроме нескольких наносекунд, для поиска значения из Python dict.

Это важно, потому что endpoints.get_current_user() использует вызов oauth.get_current_user() для определения пользователя маркера Bearer, поэтому, если вы хотите его снова вызвать, вы можете беспокоиться об этой производительности.

Если вы знаете, что никогда не будете использовать идентификационные маркеры ID или можете легко определить эти ситуации, вы можете изменить свой код, чтобы просто вызвать оба:

endpoints_user = endpoints.get_current_user()
if endpoints_user is None:
    raise endpoints.UnauthorizedException(...)

oauth_user = oauth.get_current_user(known_scope)
if oauth_user is None or oauth_user.user_id() is None:
    # This should never happen
    raise endpoints.NotFoundException(...)

ПРИМЕЧАНИЕ. Мы все равно должны называть endpoints.get_current_user(), потому что он всегда гарантирует, что наш токен чеканится только для одной из определенных областей, которые мы разрешили, и для одного из конкретных идентификаторов клиента мы попросили поговорить с нашим приложением.

ПРИМЕЧАНИЕ. Значение known_scope будет зависеть от того, какая из возможных областей совпадает с маркером. Ваш список областей будет зациклирован в одном из вспомогательных методах, и если это удастся, окончательная область соответствия будет сохранена как os.getenv('OAUTH_LAST_SCOPE'). Я бы настоятельно рекомендовал использовать это значение для known_scope.

Лучшая альтернатива:

Как уже упоминалось, идентификатор пользователя App Engine просто не может быть подразумевается из токена ID (в текущий момент), однако идентификатор профиля Google может использоваться вместо идентификатора пользователя App Engine. (Этот идентификатор часто рассматривается как идентификатор Google+, хотя это согласуется во многих сервисах.)

Чтобы убедиться, что это значение связано с вашими токенами OR, убедитесь, что вы также запрашиваете одну из областей, не относящихся к userinfo.email, связанных с userinfo API:

  • https://www.googleapis.com/auth/plus.login
  • https://www.googleapis.com/auth/plus.me
  • https://www.googleapis.com/auth/userinfo.email
  • https://www.googleapis.com/auth/userinfo.profile

(Этот список областей, существующих на момент написания этой статьи 20 мая 2013 года.)

Аналогично тому, как с идентификатором пользователя App Engine в случае токена-носителя, этот идентификатор профиля Google отбрасывается endpoints.get_current_user(), НО, он доступен для обоих видов токенов.

get_google_plus_user_id() метод, который является частью appengine-picturesque-pythonобразец исправляет один из вспомогательных методов endpoints.get_current_user() для хранения этих данных и позволяет использовать это значение, не повторяя дорогостоящие сетевые вызовы, используемые для проверки маркера носителя или идентификатора из запроса.

Ответ 2

На всякий случай кто-то здесь с 1.8.6 и по-прежнему пытается использовать auth_util.py для возврата идентификатора профиля Google. endpoints.token_id теперь имеет два метода в зависимости от того, находится ли пользователь на сервере разработки или нет.

Когда на серверах Google поток возвращает oauth_user и не попадает в конечную точку tokeninfo. Поэтому никакая информация токена не сохраняется в auth_util.py. Тем не менее, на dev-сервере он попадает в конечную точку tokeninfo, поэтому работает так, как ожидалось.

Для меня самый простой способ решить это был просто для патча обезьяны endpoints.token_id._is_local_dev и установить, что всегда быть правдой.

Ответ 3

Я только что столкнулся с этой головной болью. Однако после некоторого тестирования кажется, что с 1.9.2 user.user_id() возвращает None на локальном сервере разработки. Однако при развертывании он вернет значение. Не уверен, что это идентификатор App Engine или идентификатор Google+. В любом случае, чтобы узнать?

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