Migracja z usługi Użytkownicy App Engine do Cloud Identity Platform (moduł 21)

1. Przegląd

Seria codelabów Serverless Migration Station (samodzielne, praktyczne samouczki) i powiązane z nimi filmy mają na celu pomóc deweloperom usług bezserwerowych Google Cloud w modernizacji aplikacji poprzez przeprowadzenie ich przez co najmniej jedną migrację, głównie z usług starszego typu. Dzięki temu Twoje aplikacje będą bardziej przenośne, a Ty zyskasz więcej opcji i elastyczności, co umożliwi Ci integrację z szerszą gamą usług w chmurze i łatwiejsze przechodzenie na nowsze wersje języka. Chociaż początkowo skupialiśmy się na pierwszych użytkownikach usług w chmurze, głównie na deweloperach App Engine (środowisko standardowe), ta seria jest wystarczająco szeroka, aby obejmować inne platformy bezserwerowe, takie jak Cloud FunctionsCloud Run, lub inne, jeśli ma to zastosowanie.

Celem tych ćwiczeń z programowania jest pokazanie programistom aplikacji App Engine w Pythonie 2, jak przejść z interfejsu API/usługi App Engine Users na Cloud Identity Platform (GCIP). W przypadku dostępu do Datastore (głównie w module 2) następuje też niejawna migracja z App Engine NDB do Cloud NDB, a także uaktualnienie do Pythona 3.

Moduł 20 zawiera informacje o tym, jak dodać korzystanie z interfejsu Users API do przykładowej aplikacji z modułu 1. W tym module weźmiesz gotową aplikację z modułu 20 i przeniesiesz jej użycie na Cloud Identity Platform.

Dowiesz się, jak:

  • Zastąpienie usługi użytkowników App Engine Cloud Identity Platform
  • Zastąp App Engine NDB Cloud NDB (patrz też moduł 2).
  • Konfigurowanie różnych dostawców tożsamości do uwierzytelniania za pomocą Uwierzytelniania Firebase
  • Pobieranie informacji o uprawnieniach do projektu za pomocą interfejsu Cloud Resource Manager API
  • Pobieranie informacji o użytkownikach za pomocą pakietu Firebase Admin SDK
  • Przenoszenie przykładowej aplikacji do Pythona 3

Czego potrzebujesz

Ankieta

Jak zamierzasz korzystać z tego samouczka?

Tylko przeczytaj Przeczytaj i wykonaj ćwiczenia

Jak oceniasz swoje doświadczenie z Pythonem?

Początkujący Średnio zaawansowany Zaawansowany

Jak oceniasz korzystanie z usług Google Cloud?

Początkujący Średnio zaawansowany Zaawansowany

2. Tło

Usługa App Engine Users to system uwierzytelniania użytkowników przeznaczony do aplikacji App Engine. Udostępnia logowanie przez Google jako dostawcę tożsamości, wygodne linki do logowania i wylogowywania do użycia w aplikacjach oraz obsługuje koncepcję użytkowników z uprawnieniami administracyjnymi i funkcje dostępne tylko dla nich. Aby zwiększyć przenośność aplikacji, Google Cloud zaleca migrację z starszych usług pakietowych App Engine na samodzielne usługi Cloud, np. z usługi Użytkownicy na Cloud Identity Platform.

Identity Platform opiera się na Uwierzytelnianiu Firebase i dodaje szereg funkcji dla przedsiębiorstw, w tym uwierzytelnianie wielopoziomowe, obsługę logowania jednokrotnego OIDC i SAML, środowisko wielu najemców, gwarancję dostępności na poziomie 99, 95% i inne. Te różnice są również wyróżnione na stronie porównania usług Identity Platform i Uwierzytelnianie Firebase. Obie usługi mają znacznie więcej funkcji niż usługa Użytkownicy.

W tym module 21 znajdziesz ćwiczenia z programowania, które pokazują, jak przełączyć uwierzytelnianie użytkowników aplikacji z usługi Użytkownicy na funkcje Identity Platform, które najbardziej przypominają funkcje przedstawione w module 20. Moduł 21 zawiera też migrację z App Engine NDB do Cloud NDB w celu uzyskania dostępu do Datastore, powtarzając migrację z modułu 2.

Kod modułu 20 jest „reklamowany” jako przykładowa aplikacja w Pythonie 2, ale sam kod źródłowy jest zgodny z Pythonem 2 i 3. Nie zmienia się to nawet po przejściu na Identity Platform (i Cloud NDB) w module 21. Podczas uaktualniania do Pythona 3 możesz nadal korzystać z usługi Użytkownicy, ponieważ migracja do Identity Platform jest opcjonalna. Więcej informacji o dalszym korzystaniu z usług w pakiecie podczas przechodzenia na środowiska wykonawcze 2 generacji, takie jak Python 3, znajdziesz w samouczku dotyczącym modułu 17 i w filmie.

Ten samouczek obejmuje te kroki:

  1. Konfiguracja/przygotowanie
  2. Aktualizacja konfiguracji
  3. Modyfikowanie kodu aplikacji

3. Konfiguracja/przygotowanie

Z tej sekcji dowiesz się, jak:

  1. Konfigurowanie projektu w chmurze
  2. Pobieranie przykładowej aplikacji podstawowej
  3. (Ponowne) wdrażanie i weryfikowanie aplikacji podstawowej
  4. Włączanie nowych usług i interfejsów API Google Cloud

Dzięki tym czynnościom zaczniesz od działającego kodu, który jest gotowy do migracji do samodzielnych usług w chmurze.

1. Konfigurowanie projektu

Jeśli masz za sobą ćwiczenia z programowania w module 20, użyj tego samego projektu (i kodu). Możesz też utworzyć zupełnie nowy projekt lub użyć innego istniejącego projektu. Upewnij się, że projekt ma aktywne konto rozliczeniowe i włączoną aplikację App Engine. Znajdź identyfikator projektu i miej go pod ręką podczas tego samouczka. Używaj go zawsze, gdy napotkasz zmienną PROJ_ID.

2. Pobieranie przykładowej aplikacji podstawowej

Jednym z wymagań wstępnych jest działająca aplikacja App Engine z modułu 20, więc wykonaj ćwiczenia z programowania (zalecane; link powyżej) lub skopiuj kod modułu 20 z repozytorium. Niezależnie od tego, czy używasz własnego, czy naszego, od tego miejsca zaczniemy („START”). W tym Codelabs znajdziesz instrukcje migracji. Na końcu znajdziesz kod podobny do tego w folderze Repo modułu 21 („FINISH”).

Skopiuj folder repozytorium modułu 20. Powinny wyglądać podobnie do danych wyjściowych poniżej. Jeśli wykonano ćwiczenia z modułu 20, może być widoczny folder lib:

$ ls
README.md               appengine_config.py     templates
app.yaml                main.py                 requirements.txt

3. (Ponowne) wdrażanie i weryfikowanie aplikacji podstawowej

Aby wdrożyć aplikację Module 20, wykonaj te czynności:

  1. Usuń folder lib, jeśli istnieje, i uruchom polecenie pip install -t lib -r requirements.txt, aby go ponownie wypełnić. Jeśli masz zainstalowane zarówno Pythona 2, jak i 3, może być konieczne użycie polecenia pip2.
  2. Upewnij się, że narzędzie wiersza poleceń gcloud jest zainstalowane i zainicjowane oraz że znasz sposób jego użycia.
  3. Jeśli nie chcesz wpisywać PROJ_ID przy każdym poleceniu gcloud, najpierw ustaw projekt w chmurze za pomocą gcloud config set project PROJ_ID.
  4. Wdrażanie przykładowej aplikacji za pomocą gcloud app deploy
  5. Sprawdź, czy aplikacja działa zgodnie z oczekiwaniami i nie zawiera błędów. Jeśli masz ukończone ćwiczenie programistyczne z modułu 20, w górnej części aplikacji będą wyświetlane informacje o logowaniu użytkownika (adres e-mail, ewentualna „odznaka administratora” i przycisk logowania/wylogowywania) oraz ostatnie wizyty (jak na ilustracji poniżej).

907e64c19ef964f8.png

Gdy użytkownik zaloguje się jako zwykły użytkownik, wyświetli się jego adres e-mail, a przycisk „Zaloguj się” zmieni się na „Wyloguj się”:

ad7b59916b69a035.png

Gdy użytkownik zaloguje się jako administrator, jego adres e-mail będzie wyświetlany z dopiskiem „(administrator)”:

867bcb3334149e4.png

4. Włączanie nowych interfejsów API i usług Google Cloud

Wprowadzenie

Aplikacja Moduł 20 korzysta z interfejsów App Engine NDB i Users API, czyli usług pakietowych, które nie wymagają dodatkowej konfiguracji. Jednak samodzielne usługi Cloud wymagają konfiguracji, a zaktualizowana aplikacja będzie korzystać zarówno z Cloud Identity Platform, jak i z Cloud Datastore (za pomocą biblioteki klienta Cloud NDB). Ponadto konieczność określenia użytkowników administracyjnych App Engine wymaga użycia interfejsu Cloud Resource Manager API.

Koszt

  • App Engine i Cloud Datastore mają limity poziomu „Zawsze bezpłatny”, więc jeśli nie przekroczysz tych limitów, ukończenie tego samouczka nie powinno wiązać się z żadnymi opłatami. Więcej informacji znajdziesz też na stronie z cennikiem App Engine i stronie z cennikiem Cloud Datastore.
  • Korzystanie z platformy Identity Platform jest rozliczane w zależności od liczby aktywnych użytkowników miesięcznie (MAU) lub weryfikacji uwierzytelniania. W przypadku każdego modelu użytkowania dostępna jest wersja „bezpłatna”. Więcej informacji znajdziesz na stronie z cennikiem. Ponadto App Engine i Cloud Datastore wymagają rozliczeń, ale samo korzystanie z GCIP nie wymaga włączania rozliczeń, o ile nie przekroczysz dziennych limitów bez instrumentów. Warto o tym pamiętać w przypadku projektów w chmurze, które nie obejmują interfejsów API ani usług w chmurze wymagających rozliczeń.
  • Korzystanie z interfejsu Cloud Resource Manager API jest w większości przypadków bezpłatne zgodnie z informacjami na stronie z cenami.

Użytkownicy mogą włączać interfejsy Cloud API w konsoli Cloud lub w wierszu poleceń (za pomocą polecenia gcloud, które jest częścią pakietu SDK Cloud) w zależności od preferencji. Zacznijmy od interfejsów Cloud Datastore i Cloud Resource Manager API.

W konsoli Cloud

Otwórz stronę Biblioteka w Menedżerze interfejsów API (odpowiedniego projektu) w konsoli Cloud i wyszukaj interfejs API na pasku wyszukiwania. c7a740304e9d35b.png

Włącz te interfejsy API:

Znajdź i kliknij przycisk Włącz dla każdego interfejsu API osobno. Może pojawić się prośba o podanie informacji rozliczeniowych. Na przykład strona interfejsu Resource Manager API:

fc7bd8f4c49d12e5.png

Po włączeniu przycisku (zwykle po kilku sekundach) zmienia się on na Zarządzaj:

8eca12d6cc7b45b0.png

Włącz Cloud Datastore w ten sam sposób:

83811599b110e46b.png

W wierszu poleceń

Włączanie interfejsów API w konsoli jest wygodne, ale niektórzy wolą wiersz poleceń. Dodatkową zaletą jest możliwość włączenia dowolnej liczby interfejsów API jednocześnie. Wydaj to polecenie, aby włączyć interfejsy Cloud Datastore API i Cloud Resource Manager API, i poczekaj na zakończenie operacji, jak pokazano tutaj:

$ gcloud services enable cloudresourcemanager.googleapis.com datastore.googleapis.com
Operation "operations/acat.p2-aaa-bbb-ccc-ddd-eee-ffffff" finished successfully.

Może pojawić się prośba o podanie informacji rozliczeniowych.

„Adresy URL” każdego interfejsu API użytego w powyższym poleceniu to nazwy usług interfejsu API. Znajdziesz je u dołu strony biblioteki każdego interfejsu API. Jeśli chcesz włączyć inne interfejsy Cloud API dla swoich aplikacji, nazwy odpowiednich usług znajdziesz na stronach tych interfejsów API. To polecenie wyświetla nazwy wszystkich usług, które możesz włączyć:

gcloud services list --available --filter="name:googleapis.com".

Po wykonaniu powyższych czynności w konsoli Cloud lub wierszu poleceń nasz przykład będzie mieć dostęp do tych interfejsów API. Następne kroki to włączenie platformy Cloud Identity i wprowadzenie niezbędnych zmian w kodzie.

Włączanie i konfigurowanie platformy Identity Platform (tylko w konsoli Cloud)

Cloud Identity Platform to usługa Marketplace, ponieważ łączy się z zasobem poza Google Cloud lub od niego zależy, np. Uwierzytelnianie Firebase. Obecnie usługi Marketplace można włączyć tylko w konsoli Cloud. Wykonaj te czynności:

  1. Otwórz stronę Cloud Identity Platform w Cloud Marketplace i kliknij przycisk Włącz. W razie potrzeby przejdź na nową wersję Uwierzytelniania Firebase. Odblokuje to dodatkowe funkcje, takie jak te opisane wcześniej w sekcji Informacje ogólne. Oto strona Marketplace z wyróżnionym przyciskiem Włącz: 28475f1c9b29de69.png
  2. Po włączeniu Identity Platform możesz automatycznie przejść na stronę Dostawcy tożsamości. Jeśli nie, skorzystaj z tego linku. fc2d92d42a5d1dd7.png
  3. Włącz dostawcę uwierzytelniania Google. Jeśli nie skonfigurowano żadnych dostawców, kliknij Dodaj dostawcę i wybierz Google. Gdy wrócisz na ten ekran, pozycja Google powinna być włączona. W tym samouczku używamy tylko Google jako dostawcy uwierzytelniania, aby odzwierciedlić usługę App Engine usługa Użytkownicy jako uproszczoną usługę Logowanie przez Google. W swoich aplikacjach możesz włączyć dodatkowych dostawców uwierzytelniania.
  4. Po wybraniu i skonfigurowaniu Google oraz innych wybranych dostawców uwierzytelniania kliknij Szczegóły konfiguracji aplikacji, a następnie w wyświetlonym oknie skopiuj wartości apiKeyauthDomain w obiekcie config na karcie Sieć i zapisz je w bezpiecznym miejscu. Dlaczego nie skopiować wszystkiego? Fragment kodu w tym oknie jest zakodowany na stałe i nieaktualny, więc zapisz tylko najważniejsze części i użyj ich w naszym kodzie, który będzie częściej korzystać z uwierzytelniania Firebase. Po skopiowaniu wartości i zapisaniu ich w bezpiecznym miejscu kliknij przycisk Zamknij, aby zakończyć konfigurację. bbb09dcdd9be538e.png

4. Aktualizacja konfiguracji

Aktualizacje konfiguracji obejmują zarówno zmianę różnych plików konfiguracyjnych, jak i utworzenie odpowiednika App Engine, ale w ekosystemie Cloud Identity Platform.

appengine_config.py

  • Jeśli uaktualniasz Pythona do wersji 3, usuń appengine_config.py
  • Jeśli planujesz przejście na Identity Platform, ale pozostaniesz przy Pythonie 2, nie usuwaj tego pliku. Zaktualizujemy go później podczas przenoszenia do Pythona 2.

requirements.txt

W pliku requirements.txt modułu 20 wymieniono tylko Flask. W przypadku modułu 21 dodaj te pakiety:

Zawartość pliku requirements.txt powinna teraz wyglądać tak:

flask
google-auth
google-cloud-ndb
google-cloud-resource-manager
firebase-admin

app.yaml

  • Uaktualnienie do Pythona 3 oznacza uproszczenie pliku app.yaml. Usuń wszystko oprócz dyrektywy środowiska wykonawczego i ustaw ją na obecnie obsługiwaną wersję Pythona 3. W przykładzie używana jest obecnie wersja 3.10.
  • Jeśli nadal używasz Pythona 2, nie musisz jeszcze nic robić.

PRZED:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

Przykładowa aplikacja Moduł 20 nie ma modułów obsługi plików statycznych. Jeśli Twoje aplikacje to robią, pozostaw je bez zmian. Możesz usunąć wszystkie moduły obsługi skryptów lub pozostawić je jako odniesienie, o ile zmienisz ich uchwyty na auto, zgodnie z opisem w app.yaml przewodniku migracji. W związku z tymi zmianami zaktualizowany kod app.yaml dla języka Python 3 został uproszczony do postaci:

PO:

runtime: python310

Inne aktualizacje konfiguracji

Niezależnie od tego, czy pozostaniesz przy Pythonie 2, czy przejdziesz na Pythona 3, jeśli masz folder lib, usuń go.

5. Modyfikowanie kodu aplikacji

W tej sekcji znajdziesz aktualizacje głównego pliku aplikacji main.py, które zastępują użycie usługi Użytkownicy App Engine usługą Cloud Identity Platform. Po zaktualizowaniu głównej aplikacji zaktualizuj szablon internetowy templates/index.html.

Aktualizowanie importów i inicjowanie

Aby zaktualizować importy i zainicjować zasoby aplikacji, wykonaj te czynności:

  1. W przypadku importów zastąp App Engine NDB ciągiem Cloud NDB.
  2. Oprócz Cloud NDB zaimportuj też Cloud Resource Manager.
  3. Identity Platform jest oparta na Uwierzytelnianiu Firebase, więc zaimportuj pakiet Firebase Admin SDK.
  4. Interfejsy Cloud APIs wymagają użycia klienta API, więc zainicjuj go dla Cloud NDB tuż pod inicjowaniem Flask.

Pakiet Cloud Resource Manager jest tu importowany, ale użyjemy go na późniejszym etapie inicjowania aplikacji. Poniżej znajdziesz importy i inicjalizację z modułu 20 oraz wygląd sekcji po wprowadzeniu powyższych zmian:

PRZED:

from flask import Flask, render_template, request
from google.appengine.api import users
from google.appengine.ext import ndb

app = Flask(__name__)

PO:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

# initialize Flask and Cloud NDB API client
app = Flask(__name__)
ds_client = ndb.Client()

Pomoc dla użytkowników administracyjnych App Engine

Aby aplikacja rozpoznawała użytkowników z uprawnieniami administracyjnymi, musisz dodać do niej 2 komponenty:

  • _get_gae_admins() – zbiera zestaw użytkowników administracyjnych; wywoływana raz i zapisywana
  • is_admin() – sprawdza, czy zalogowany użytkownik jest administratorem; wywoływana przy każdym logowaniu użytkownika.

Funkcja użytkowa _get_gae_admins() wywołuje interfejs Resource Manager API, aby pobrać bieżącą zasadę zezwalającą Cloud IAM. Zasady zezwalające określają i wymuszają, jakie role są przyznawane poszczególnym podmiotom zabezpieczeń (użytkownikom, kontom usługi itp.). Konfiguracja obejmuje:

  • Pobieranie identyfikatora projektu w chmurze (PROJ_ID)
  • Tworzenie klienta interfejsu Resource Manager API (rm_client)
  • Tworzenie zestawu ról administratora App Engine (tylko do odczytu) (_TARGETS)

Menedżer zasobów wymaga identyfikatora projektu w Google Cloud, więc zaimportuj google.auth.default() i wywołaj tę funkcję, aby uzyskać identyfikator projektu. To wywołanie zawiera parametr, który wygląda jak adres URL, ale jest zakresem uprawnień OAuth2. Podczas uruchamiania aplikacji w chmurze, np. na maszynie wirtualnej Compute Engine lub w aplikacji App Engine, udostępniane jest domyślne konto usługi, które ma szerokie uprawnienia. Zgodnie ze sprawdzoną metodą przyznawania jak najmniejszych uprawnień zalecamy utworzenie własnych kont usługi zarządzanych przez użytkownika.

W przypadku wywołań interfejsu API najlepiej dodatkowo ograniczyć zakres aplikacji do minimum niezbędnego do prawidłowego działania. Wywołanie interfejsu Resource Manager API, które wykonamy, to get_iam_policy(). Aby działać, wymaga ono jednego z tych zakresów:

  • https://www.googleapis.com/auth/cloud-platform
  • https://www.googleapis.com/auth/cloud-platform.read-only
  • https://www.googleapis.com/auth/cloudplatformprojects
  • https://www.googleapis.com/auth/cloudplatformprojects.readonly

Przykładowa aplikacja potrzebuje tylko dostępu do odczytu do zasady zezwalającej. Nie modyfikuje ona zasad ani nie wymaga dostępu do całego projektu. Oznacza to, że aplikacja nie potrzebuje żadnego z pierwszych 3 uprawnień. Ostatni z nich jest wymagany i to właśnie jego wdrażamy w przykładowej aplikacji.

Główna część funkcji tworzy pusty zbiór użytkowników z uprawnieniami administracyjnymi (admins), pobiera allow_policy za pomocą get_iam_policy() i przechodzi przez wszystkie jego powiązania, szukając w szczególności ról administratora App Engine:

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin

W przypadku każdej znalezionej roli docelowej zbiera informacje o tym, którzy użytkownicy do niej należą, i dodaje ich do ogólnego zbioru użytkowników z uprawnieniami administracyjnymi. Zwraca wszystkich znalezionych i zapisanych w pamięci podręcznej administratorów jako stałą (_ADMINS) na czas istnienia tej instancji App Engine. Za chwilę zobaczymy to połączenie.

Dodaj do pliku main.py definicję funkcji _get_gae_admins() tuż pod utworzeniem instancji klienta interfejsu Cloud NDB API (ds_client):

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    _, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
    rm_client = resourcemanager.ProjectsClient()
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
    for b in allow_policy.bindings:     # bindings in IAM allow-policy
        if b.role in _TARGETS:          # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b.members)
    return admins

Gdy użytkownicy zalogują się w aplikacji, nastąpią te działania:

  1. Po zalogowaniu się użytkownika w Firebase w szablonie internetowym przeprowadzane jest szybkie sprawdzenie.
  2. Gdy stan uwierzytelniania w szablonie ulegnie zmianie, do /is_admin zostanie wysłane wywołanie w stylu Ajax fetch(), którego modułem obsługi jest następna funkcja is_admin().
  3. Token identyfikatora Firebase jest przekazywany w treści żądania POST do funkcji is_admin(), która pobiera go z nagłówków i wywołuje pakiet Firebase Admin SDK, aby go zweryfikować. Jeśli jest to prawidłowy użytkownik, wyodrębnij jego adres e-mail i sprawdź, czy jest on administratorem.
  4. Wynik logiczny jest następnie zwracany do szablonu jako kod 200.

Dodaj is_admin() do main.py tuż po _get_gae_admins():

@app.route('/is_admin', methods=['POST'])
def is_admin():
    'check if user (via their Firebase ID token) is GAE admin (POST) handler'
    id_token = request.headers.get('Authorization')
    email = auth.verify_id_token(id_token).get('email')
    return {'admin': email in _ADMINS}, 200

Aby odtworzyć funkcje dostępne w usłudze Użytkownicy, a w szczególności jej funkcję is_current_user_admin(), wymagany jest cały kod z obu funkcji. To wywołanie funkcji w module 20 wykonało całą ciężką pracę, w przeciwieństwie do modułu 21, w którym wdrażamy rozwiązanie zastępcze. Dobra wiadomość jest taka, że aplikacja nie jest już zależna od usługi dostępnej tylko w App Engine, co oznacza, że możesz przenieść aplikacje do Cloud Run lub innych usług. Możesz też zmienić definicję „administratora” w przypadku własnych aplikacji, przełączając się na wybrane role w _TARGETS. Usługa Użytkownicy jest natomiast na stałe powiązana z rolami administratora App Engine.

Inicjowanie Firebase Auth i buforowanie użytkowników administracyjnych App Engine

Mogliśmy zainicjować Firebase Auth na górze, w pobliżu miejsca, w którym inicjowana jest aplikacja Flask i tworzony jest klient API Cloud NDB, ale nie było takiej potrzeby, dopóki nie został zdefiniowany cały kod administratora. Podobnie, gdy zdefiniujesz _get_gae_admins(), wywołaj ją, aby zapisać w pamięci podręcznej listę użytkowników z uprawnieniami administracyjnymi.

Dodaj te wiersze tuż pod treścią funkcji is_admin():

# initialize Firebase and fetch set of App Engine admins
initialize_app()
_ADMINS = _get_gae_admins()

Aktualizacje modelu danych

Model danych Visit nie ulegnie zmianie. Dostęp do Datastore wymaga jawnego użycia menedżera kontekstu klienta Cloud NDB API, ds_client.context(). W kodzie oznacza to, że wywołania Datastore należy umieścić w blokach store_visit()fetch_visits() w blokach with w Pythonie. Ta aktualizacja jest identyczna z modułem 2. Wprowadź zmiany w ten sposób:

PRZED:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    return Visit.query().order(-Visit.timestamp).fetch(limit)

PO:

class Visit(ndb.Model):
    'Visit entity registers visitor IP address & timestamp'
    visitor   = ndb.StringProperty()
    timestamp = ndb.DateTimeProperty(auto_now_add=True)

def store_visit(remote_addr, user_agent):
    'create new Visit entity in Datastore'
    with ds_client.context():
        Visit(visitor='{}: {}'.format(remote_addr, user_agent)).put()

def fetch_visits(limit):
    'get most recent visits'
    with ds_client.context():
        return Visit.query().order(-Visit.timestamp).fetch(limit)

Przenoszenie logiki logowania użytkownika do szablonu internetowego

Usługa App Engine Users działa po stronie serwera, a Uwierzytelnianie Firebase i Cloud Identity Platform działają głównie po stronie klienta. W rezultacie większość kodu zarządzania użytkownikami w aplikacji z modułu 20 zostaje przeniesiona do szablonu internetowego z modułu 21.

main.py kontekst internetowy przekazuje do szablonu 5 podstawowych elementów danych. Pierwsze 4 z nich są powiązane z zarządzaniem użytkownikami i różnią się w zależności od tego, czy użytkownik jest zalogowany:

  • who – adres e-mail użytkownika, jeśli jest zalogowany, lub użytkownik w przeciwnym razie.
  • admin – plakietka (administrator), jeśli zalogowany użytkownik jest administratorem;
  • sign – wyświetla przycisk Zaloguj się lub Wyloguj się.
  • link – linki logowania i wylogowywania po kliknięciu przycisku
  • visits – najnowsze wizyty

PRZED:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)

    # put together users context for web template
    user = users.get_current_user()
    context = {  # logged in
        'who':   user.nickname(),
        'admin': '(admin)' if users.is_current_user_admin() else '',
        'sign':  'Logout',
        'link':  '/_ah/logout?continue=%s://%s/' % (
                      request.environ['wsgi.url_scheme'],
                      request.environ['HTTP_HOST'],
                  ),  # alternative to users.create_logout_url()
    } if user else {  # not logged in
        'who':   'user',
        'admin': '',
        'sign':  'Login',
        'link':  users.create_login_url('/'),
    }

    # add visits to context and render template
    context['visits'] = visits  # display whether logged in or not
    return render_template('index.html', **context)

Całe zarządzanie użytkownikami przenosimy do szablonu internetowego, więc zostają nam tylko wizyty. Główny moduł obsługi wraca do tego, co mieliśmy w aplikacji z modułu 1:

PO:

@app.route('/')
def root():
    'main application (GET) handler'
    store_visit(request.remote_addr, request.user_agent)
    visits = fetch_visits(10)
    return render_template('index.html', visits=visits)

Aktualizowanie szablonu internetowego

Jak wszystkie zmiany z poprzedniej sekcji wyglądają w szablonie? Głównie przenosimy zarządzanie użytkownikami z aplikacji do usługi Firebase Auth działającej w szablonie i częściowo przenosimy cały ten kod do JavaScriptu. main.py znacznie się zmniejszył, więc spodziewamy się podobnego wzrostu w przypadku templates/index.html.

PRZED:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>
</head>
<body>
<p>
Welcome, {{ who }} <code>{{ admin }}</code>
<button id="logbtn">{{ sign }}</button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
document.getElementById("logbtn").onclick = () => {
    window.location.href = '{{ link }}';
};
</script>
</body>
</html>

Zastąp cały szablon internetowy treścią poniżej:

PO:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

Treść HTML zawiera wiele komponentów, więc omówimy je po kolei.

Importy z Firebase

Po tytule strony, ale w nagłówku dokumentu HTML, zaimportuj potrzebne komponenty Firebase. Komponenty Firebase są teraz podzielone na kilka modułów, co zwiększa wydajność. Kod inicjujący Firebase jest importowany z głównego modułu aplikacji w Firebase, a funkcje zarządzające uwierzytelnianiem Firebase, Google jako dostawcą uwierzytelniania, logowaniem i wylogowywaniem oraz zmianą stanu uwierzytelniania (wywołanie zwrotne) są importowane z modułu Uwierzytelnianie Firebase:

<!doctype html>
<html>
<head>
<title>VisitMe Example</title>

<script type="module">
// import Firebase module attributes
import {
        initializeApp
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-app.js";
import {
        GoogleAuthProvider,
        getAuth,
        onAuthStateChanged,
        signInWithPopup,
        signOut
} from "https://www.gstatic.com/firebasejs/9.10.0/firebase-auth.js";

Konfiguracja Firebase

Wcześniej, w części tego samouczka dotyczącej konfiguracji Identity Platform, zapisaliśmy wartości apiKeyauthDomain z okna Szczegóły konfiguracji aplikacji. Dodaj te wartości do zmiennej firebaseConfig w następnej sekcji. W komentarzach znajdziesz link do bardziej szczegółowych instrukcji:

// Firebase config:
// 1a. Go to: console.cloud.google.com/customer-identity/providers
// 1b. May be prompted to enable GCIP and upgrade from Firebase
// 2. Click: "Application Setup Details" button
// 3. Copy: 'apiKey' and 'authDomain' from 'config' variable
var firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "YOUR_AUTH_DOMAIN",
};

Inicjowanie Firebase

W następnej sekcji zainicjujesz Firebase za pomocą tych informacji o konfiguracji.

// initialize Firebase app & auth components
initializeApp(firebaseConfig);
var auth = getAuth();
var provider = new GoogleAuthProvider();
//provider.setCustomParameters({prompt: 'select_account'});

Ustawia to możliwość używania Google jako dostawcy uwierzytelniania i udostępnia zakomentowaną opcję wyświetlania selektora konta nawet wtedy, gdy w sesji przeglądarki zarejestrowane jest tylko jedno konto Google. Innymi słowy, gdy masz kilka kont, wyświetla się „selektor konta”: a38369389b7c4c7e.png. Jeśli jednak w sesji jest tylko 1 użytkownik, proces logowania kończy się automatycznie bez interakcji ze strony użytkownika. (Wyskakujące okienko pojawi się, a potem zniknie). Możesz wymusić wyświetlenie okna wyboru konta w przypadku jednego użytkownika (zamiast natychmiastowego logowania się w aplikacji), odkomentowując wiersz parametru niestandardowego. Jeśli ta opcja jest włączona, nawet w przypadku logowania pojedynczego użytkownika wyświetla się selektor kont: b75624cb68d94557.png

Funkcje logowania i wylogowywania

Kolejne wiersze kodu tworzą funkcje kliknięć przycisku logowania lub wylogowania:

// define login and logout button functions
function login() {
    signInWithPopup(auth, provider);
};

function logout() {
    signOut(auth);
};

Działania związane z logowaniem i wylogowywaniem

Ostatnią ważną sekcją w tym bloku <script> jest funkcja wywoływana przy każdej zmianie autoryzacji (logowanie lub wylogowywanie).

// check if admin & switch to logout button on login; reset everything on logout
onAuthStateChanged(auth, async (user) => {
    if (user && user != null) {
        var email = user.email;
        who.innerHTML = email;
        logbtn.onclick = logout;
        logbtn.innerHTML = "Logout";
        var idToken = await user.getIdToken();
        var rsp = await fetch("/is_admin", {
                method: "POST",
                headers: {Authorization: idToken}
        });
        var data = await rsp.json();
        if (data.admin) {
            admin.style.display = "inline";
        }
    } else {
        who.innerHTML = "user";
        admin.style.display = "none";
        logbtn.onclick = login;
        logbtn.innerHTML = "Login";
    }
});
</script>
</head>

Kod w module 20, który określa, czy wysłać kontekst szablonu „użytkownik zalogowany” czy „użytkownik wylogowany”, jest przenoszony tutaj. Warunek u góry daje w wyniku true, jeśli użytkownik zalogował się prawidłowo, co powoduje wykonanie tych działań:

  1. Adres e-mail użytkownika zostanie ustawiony jako wyświetlany.
  2. Przycisk Zaloguj się zmieni się na Wyloguj się.
  3. Wywoływane jest wywołanie w stylu Ajax do /is_admin, aby określić, czy wyświetlić plakietkę użytkownika administracyjnego (admin).

Gdy użytkownik się wyloguje, wykonywane jest klauzula else, która resetuje wszystkie informacje o użytkowniku:

  1. Nazwa użytkownika ustawiona na user
  2. Usunięto plakietkę dowolnego administratora
  3. Przycisk Wyloguj został zmieniony z powrotem na Zaloguj.

Zmienne szablonu

Po zakończeniu sekcji nagłówka główna część zaczyna się od zmiennych szablonu, które są zastępowane elementami HTML zmieniającymi się w zależności od potrzeb:

  1. Wyświetlana nazwa użytkownika
  2. (admin) plakietka administratora (w razie potrzeby);
  3. Przycisk Zaloguj się lub Wyloguj się
<body>
<p>
Welcome, <span id="who"></span> <span id="admin"><code>(admin)</code></span>
<button id="logbtn"></button>
</p><hr>

Najnowsze wizyty i zmienne elementów HTML

Kod ostatnich wizyt nie ulega zmianie, a ostatni blok <script> ustawia zmienne dla elementów HTML, które zmieniają się w przypadku logowania i wylogowywania (są one wymienione powyżej):

<h1>VisitMe example</h1>
<h3>Last 10 visits</h3>
<ul>
{% for visit in visits %}
    <li>{{ visit.timestamp.ctime() }} from {{ visit.visitor }}</li>
{% endfor %}
</ul>

<script>
var who    = document.getElementById("who");
var admin  = document.getElementById("admin");
var logbtn = document.getElementById("logbtn");
</script>
</body>
</html>

To już wszystkie zmiany, które należy wprowadzić w aplikacji i szablonie internetowym, aby przejść z interfejsów API App Engine NDB i Users na interfejsy API Cloud NDB i Identity Platform oraz zaktualizować wersję Pythona do wersji 3. Gratulujemy dotarcia do nowej aplikacji przykładowej z modułu 21. Nasza wersja jest dostępna do sprawdzenia w folderze repozytorium modułu 21b.

Kolejna część tego przewodnika jest opcjonalna (*) i przeznaczona tylko dla użytkowników, których aplikacje muszą pozostać w Pythonie 2. Zawiera ona instrukcje, które pozwolą Ci uzyskać działającą aplikację z modułu 21 w Pythonie 2.

6. *Wersja Pythona 2

Ta sekcja opcjonalna jest przeznaczona dla deweloperów, którzy przeprowadzają migrację do Identity Platform, ale muszą nadal korzystać ze środowiska wykonawczego Python 2. Jeśli nie jest to dla Ciebie problem, pomiń tę sekcję.

Aby utworzyć działającą wersję aplikacji z modułu 21 w Pythonie 2, potrzebujesz:

  1. Wymagania środowiska wykonawczego: pliki konfiguracyjne obsługujące język Python 2 i wymagane zmiany w głównej aplikacji, które pozwolą uniknąć niezgodności z językiem Python 3.
  2. Niewielka zmiana w bibliotece: Python 2 został wycofany, zanim do biblioteki klienta Resource Manager dodano niektóre wymagane funkcje. W rezultacie potrzebujesz alternatywnego sposobu dostępu do tej funkcji.

Wykonajmy teraz te czynności, zaczynając od konfiguracji.

Przywracanie pliku appengine_config.py

Wcześniej w tym samouczku poprosiliśmy Cię o usunięcie appengine_config.py, ponieważ nie jest on używany przez środowisko wykonawcze App Engine w Pythonie 3. W przypadku Pythona 2 nie tylko musi on zostać zachowany, ale też moduł 20 appengine_config.py musi zostać zaktualizowany, aby obsługiwać wbudowane biblioteki innych firm, czyli grpciosetuptools. Te pakiety są wymagane, gdy aplikacja App Engine korzysta z bibliotek klienta Cloud, takich jak Cloud NDB i Cloud Resource Manager.

Za chwilę dodasz te pakiety do app.yaml, ale aby aplikacja mogła z nich korzystać, musisz wywołać funkcję pkg_resources.working_set.add_entry()setuptools. Umożliwia to skopiowanym (spakowanym samodzielnie lub dostarczonym przez dostawcę) bibliotekom innych firm zainstalowanym w folderze lib komunikowanie się z bibliotekami wbudowanymi.

Aby wprowadzić te zmiany, zaktualizuj plik appengine_config.py w ten sposób:

PRZED:

from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)

Sam ten kod nie wystarczy do obsługi setuptoolsgrpcio. Potrzebnych jest jeszcze kilka wierszy, więc zaktualizuj plik appengine_config.py, aby wyglądał tak:

PO:

import pkg_resources
from google.appengine.ext import vendor

# Set PATH to your libraries folder.
PATH = 'lib'
# Add libraries installed in the PATH folder.
vendor.add(PATH)
# Add libraries to pkg_resources working set to find the distribution.
pkg_resources.working_set.add_entry(PATH)

Więcej informacji o zmianach wymaganych do obsługi bibliotek klienta Cloud znajdziesz w dokumentacji dotyczącej migracji usług pakietowych.

app.yaml

Podobnie jak w przypadku appengine_config.py, plik app.yaml musi zostać przywrócony do wersji obsługującej Pythona 2. Zacznijmy od oryginalnego modułu 20 app.yaml:

PRZED:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

Oprócz setuptoolsgrpcio, o których wspomnieliśmy wcześniej, istnieje zależność (niezwiązana bezpośrednio z migracją Identity Platform) wymagająca użycia biblioteki klienta Cloud Storage, a ona potrzebuje innego wbudowanego pakietu innej firmy, ssl. Dodaj wszystkie 3 pakiety w nowej sekcji libraries, wybierając „najnowsze” dostępne wersje tych pakietów, aby app.yaml:

PO:

runtime: python27
threadsafe: yes
api_version: 1

handlers:
- url: /.*
  script: main.app

libraries:
- name: grpcio
  version: latest
- name: setuptools
  version: latest
- name: ssl
  version: latest

requirements.txt

W module 21 dodaliśmy do środowiska Python 3 requirements.txt Google Auth, Cloud NDB, Cloud Resource Managerpakiet Firebase Admin SDK. Sytuacja w przypadku Pythona 2 jest bardziej złożona:

  • Interfejs Resource Manager API udostępnia funkcję allow-policy potrzebną w aplikacji przykładowej. Niestety ta funkcja nie była jeszcze dostępna w ostatecznej wersji biblioteki klienta Cloud Resource Manager w języku Python 2. (Jest dostępna tylko w wersji Python 3).
  • W związku z tym wymagany jest alternatywny sposób dostępu do tej funkcji z poziomu interfejsu API. Rozwiązaniem jest użycie biblioteki klienta interfejsów API Google niższego poziomu do komunikacji z interfejsem API. Aby przełączyć się na tę bibliotekę klienta, zastąp google-cloud-resource-manager pakietem google-api-python-client niższego poziomu.
  • Python 2 został wycofany, dlatego wykres zależności obsługujący moduł 21 wymaga zablokowania niektórych pakietów w określonych wersjach. Niektóre pakiety muszą być wywoływane nawet wtedy, gdy nie są określone w app.yaml w Pythonie 3.

PRZED:

flask

Zacznij od modułu 20 requirements.txt i zaktualizuj go w ten sposób, aby uzyskać działającą aplikację modułu 21:

PO:

grpcio==1.0.0
protobuf<3.18.0
six>=1.13.0
flask
google-gax<0.13.0
google-api-core==1.31.1
google-api-python-client<=1.11.0
google-auth<2.0dev
google-cloud-datastore==1.15.3
google-cloud-firestore==1.9.0
google-cloud-ndb
google-cloud-pubsub==1.7.0
firebase-admin

Numery pakietu i wersji będą aktualizowane w repozytorium w miarę zmian zależności, ale w momencie pisania tego artykułu app.yaml wystarcza do działania aplikacji.

Inne aktualizacje konfiguracji

Jeśli nie usuniesz lib folderu z poprzedniej części tego laboratorium, zrób to teraz. W przypadku nowo zaktualizowanego narzędzia requirements.txt wydaj to znane polecenie, aby zainstalować te wymagania w lib:

pip install -t lib -r requirements.txt  # or pip2

Jeśli na komputerze deweloperskim masz zainstalowane zarówno Pythona 2, jak i 3, zamiast pip może być konieczne użycie pip2.

Modyfikowanie kodu aplikacji

Na szczęście większość wymaganych zmian znajduje się w plikach konfiguracyjnych. Jedyna zmiana, jakiej wymaga kod aplikacji, to niewielka aktualizacja, która umożliwia dostęp do interfejsu API za pomocą biblioteki klienta interfejsu Google API niższego poziomu zamiast biblioteki klienta usługi Resource Manager. Szablon internetowy templates/index.html nie wymaga aktualizacji.

Aktualizowanie importów i inicjowanie

Zastąp bibliotekę klienta Resource Manager (google.cloud.resourcemanager) biblioteką klienta interfejsów API Google (googleapiclient.discovery), jak pokazano poniżej:

PRZED:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb, resourcemanager
from firebase_admin import auth, initialize_app

PO:

from flask import Flask, render_template, request
from google.auth import default
from google.cloud import ndb
from googleapiclient import discovery
from firebase_admin import auth, initialize_app

Pomoc dla użytkowników administracyjnych App Engine

Aby można było korzystać z biblioteki klienta niższego poziomu, w _get_gae_admins() trzeba wprowadzić kilka zmian. Najpierw omówimy zmiany, a potem podamy cały kod do aktualizacji.

Kod w Pythonie 2 wymaga użycia zarówno danych logowania, jak i identyfikatora projektu zwróconego przez interfejs google.auth.default(). Dane logowania nie są używane w języku Python 3, więc zostały przypisane do ogólnej zmiennej fikcyjnej podkreślenia ( _). Ponieważ jest to wymagane w przypadku wersji w języku Python 2, zmień podkreślenie na CREDS. Zamiast tworzyć klienta interfejsu Resource Manager API, utworzysz punkt końcowy usługi interfejsu API, który jest podobny do klienta interfejsu API, więc zachowamy tę samą nazwę zmiennej (rm_client). Różnica polega na tym, że utworzenie instancji punktu końcowego usługi wymaga podania danych logowania (CREDS).

Te zmiany zostały uwzględnione w poniższym kodzie:

PRZED:

_, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloudplatformprojects.readonly'])
rm_client = resourcemanager.ProjectsClient()

PO:

CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
        ['https://www.googleapis.com/auth/cloud-platform'])
rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)

Kolejna różnica polega na tym, że biblioteka klienta Resource Manager zwraca obiekty allow-policy, które używają notacji z kropkami, a biblioteka klienta niższego poziomu zwraca słowniki Pythona, w których używane są nawiasy kwadratowe ( [ ]). Na przykład w bibliotece klienta Resource Manager używa się binding.role, a w bibliotece niższego poziomu – binding['role']. Pierwsza z nich używa też nazw „underscore_separated”, a biblioteka niższego poziomu preferuje nazwy „CamelCased” i nieco inny sposób przekazywania parametrów interfejsu API.

Różnice w użyciu są widoczne poniżej:

PRZED:

allow_policy = rm_client.get_iam_policy(resource='projects/%s' % PROJ_ID)
for b in allow_policy.bindings:     # bindings in IAM allow-policy
    if b.role in _TARGETS:          # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b.members)

PO:

allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
for b in allow_policy['bindings']:  # bindings in IAM allow-policy
    if b['role'] in _TARGETS:       # only look at GAE admin roles
        admins.update(user.split(':', 1).pop() for user in b['members'])

Łącząc wszystkie te zmiany, zastąp kod w Pythonie 3 _get_gae_admins() jego odpowiednikiem w Pythonie 2:

def _get_gae_admins():
    'return set of App Engine admins'
    # setup constants for calling Cloud Resource Manager API
    CREDS, PROJ_ID = default(  # Application Default Credentials and project ID
            ['https://www.googleapis.com/auth/cloud-platform'])
    rm_client = discovery.build('cloudresourcemanager', 'v1', credentials=CREDS)
    _TARGETS = frozenset((     # App Engine admin roles
            'roles/viewer',
            'roles/editor',
            'roles/owner',
            'roles/appengine.appAdmin',
    ))

    # collate users who are members of at least one GAE admin role (_TARGETS)
    admins = set()                      # set of all App Engine admins
    allow_policy = rm_client.projects().getIamPolicy(resource=PROJ_ID).execute()
    for b in allow_policy['bindings']:  # bindings in IAM allow-policy
        if b['role'] in _TARGETS:       # only look at GAE admin roles
            admins.update(user.split(':', 1).pop() for user in b['members'])
    return admins

Funkcja is_admin() nie wymaga aktualizacji, ponieważ korzysta z funkcji _get_gae_admins(), która została już zaktualizowana.

To już wszystkie zmiany, które trzeba wprowadzić, aby przenieść aplikację z modułu 21 w języku Python 3 do języka Python 2. Gratulacje! Dotarłeś(-aś) do zaktualizowanej aplikacji przykładowej z modułu 21. Cały kod znajdziesz w folderze repozytorium modułu 21a.

7. Podsumowanie i czyszczenie

Ostatnie kroki w tym samouczku to sprawdzenie, czy podmioty (użytkownicy lub konta usługi) uruchamiające tę aplikację mają odpowiednie uprawnienia, a następnie wdrożenie aplikacji, aby potwierdzić, że działa zgodnie z przeznaczeniem, a zmiany są odzwierciedlone w danych wyjściowych.

Możliwość odczytywania zezwalającej zasady uprawnień

Wcześniej przedstawiliśmy 4 role, które są wymagane, aby zostać uznanym za użytkownika z uprawnieniami administratora App Engine. Teraz jest jeszcze 1 rola, z którą warto się zapoznać:

  • roles/viewer
  • roles/editor
  • roles/owner
  • roles/appengine.appAdmin
  • roles/resourcemanager.projectIamAdmin (w przypadku podmiotów zabezpieczeń uzyskujących dostęp do zasady zezwalającej uprawnień)

Rola roles/resourcemanager.projectIamAdmin umożliwia podmiotom określenie, czy użytkownik jest członkiem którejkolwiek z ról administratora App Engine. Bez członkostwa w grupie roles/resourcemanager.projectIamAdmin wywołania interfejsu Cloud Resource Manager API w celu uzyskania zasady zezwalającej zakończą się niepowodzeniem.

Nie musisz podejmować żadnych działań, ponieważ aplikacja będzie działać na domyślnym koncie usługi App Engine, które automatycznie otrzymuje członkostwo w tej roli. Nawet jeśli w fazie rozwoju używasz domyślnego konta usługi, zdecydowanie zalecamy utworzenie i używanie konta usługi zarządzanego przez użytkownika z minimalnymi uprawnieniami wymaganymi do prawidłowego działania aplikacji. Aby przyznać takiemu kontu usługi członkostwo, uruchom to polecenie:

$ gcloud projects add-iam-policy-binding PROJ_ID --member="serviceAccount:USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com" --role=roles/resourcemanager.projectIamAdmin

PROJ_ID to identyfikator projektu w chmurze, a USR_MGD_SVC_ACCT@PROJ_ID.iam.gserviceaccount.com to konto usługi zarządzane przez użytkownika, które tworzysz na potrzeby aplikacji. To polecenie zwraca zaktualizowane zasady uprawnień projektu, w którym możesz sprawdzić, czy konto usługi należy do roles/resourcemanager.projectIamAdmin. Więcej informacji znajdziesz w dokumentacji. Powtórzmy: w tym laboratorium nie musisz wydawać tego polecenia, ale zachowaj je jako odniesienie do modernizacji własnych aplikacji.

Wdrażanie i weryfikowanie aplikacji

Prześlij aplikację do chmury za pomocą standardowego polecenia gcloud app deploy. Po wdrożeniu powinna być widoczna funkcjonalność niemal identyczna z aplikacją z modułu 20, z tym że usługa Użytkownicy App Engine została zastąpiona przez Cloud Identity Platform (i Firebase Auth) do zarządzania użytkownikami:

3a83ae745121d70.png

W porównaniu z modułem 20 zauważysz jedną różnicę: kliknięcie opcji Zaloguj się powoduje wyświetlenie wyskakującego okienka zamiast przekierowania. Możesz to zobaczyć na niektórych zrzutach ekranu poniżej. Podobnie jak w przypadku modułu 20, działanie różni się nieco w zależności od tego, ile kont Google zostało zarejestrowanych w przeglądarce.

Jeśli w przeglądarce nie ma zarejestrowanych użytkowników lub jest tylko jeden, który nie zalogował się jeszcze na konto, pojawi się ogólne okienko logowania w Google:

8437f5f3d489a942.png

Jeśli użytkownik jest zarejestrowany w przeglądarce, ale loguje się w innym miejscu, nie pojawia się żadne okno (lub pojawia się i natychmiast znika), a aplikacja przechodzi w stan zalogowania (wyświetla adres e-mail użytkownika i przycisk Wyloguj się).

Niektórzy deweloperzy mogą chcieć udostępnić selektor kont, nawet jeśli aplikacja jest przeznaczona dla jednego użytkownika:

b75624cb68d94557.png

Aby to zrobić, odkomentuj w szablonie internetowym wiersz provider.setCustomParameters({prompt: 'select_account'});, jak opisano wcześniej.

Jeśli jest wielu użytkowników, pojawi się okno wyboru konta (patrz poniżej). Jeśli użytkownik nie jest jeszcze zalogowany, pojawi się odpowiedni komunikat. Jeśli użytkownik jest już zalogowany, wyskakujące okienko zniknie, a aplikacja przejdzie w stan zalogowania.

c454455b6020d5e4.png

Stan zalogowania w module 21 wygląda identycznie jak interfejs użytkownika w module 20:

49ebe4dcc1eff11f.png

To samo dotyczy sytuacji, gdy zaloguje się użytkownik z uprawnieniami administracyjnymi:

44302f35b39856eb.png

W przeciwieństwie do modułu 21 moduł 20 zawsze uzyskuje dostęp do logiki treści szablonu internetowego z aplikacji (kod po stronie serwera). Wadą modułu 20 jest to, że jedna wizyta jest rejestrowana, gdy użytkownik końcowy po raz pierwszy uruchamia aplikację, a druga – gdy się loguje.

W module 21 logika logowania odbywa się tylko w szablonie internetowym (kod po stronie klienta). Nie ma potrzeby wykonywania wywołania po stronie serwera, aby określić, jakie treści mają być wyświetlane. Jedynym wywołaniem serwera jest sprawdzenie, czy użytkownik jest administratorem, po zalogowaniu się użytkownika. Oznacza to, że logowanie i wylogowywanie nie rejestruje dodatkowych wizyt, więc lista ostatnich wizyt pozostaje stała w przypadku działań związanych z zarządzaniem użytkownikami. Zwróć uwagę, że na zrzutach ekranu powyżej widać ten sam zestaw 4 wizyt w przypadku wielu użytkowników.

Na zrzutach ekranu modułu 20 na początku tych zajęć pokazano „błąd podwójnej wizyty”. Dla każdego logowania i wylogowania wyświetlane są osobne dzienniki wizyt. Sprawdź sygnatury czasowe ostatniej wizyty dla każdego zrzutu ekranu, aby zobaczyć kolejność chronologiczną.

Czyszczenie danych

Ogólne

Jeśli na razie nie chcesz już korzystać z usługi, zalecamy wyłączenie aplikacji App Engine, aby uniknąć naliczania opłat. Jeśli jednak chcesz przeprowadzić więcej testów lub eksperymentów, platforma App Engine ma bezpłatny limit, więc dopóki nie przekroczysz tego poziomu wykorzystania, nie powinny być naliczane żadne opłaty. Dotyczy to obliczeń, ale mogą też wystąpić opłaty za odpowiednie usługi App Engine, więc więcej informacji znajdziesz na stronie z cennikiem. Jeśli migracja obejmuje inne usługi w chmurze, są one rozliczane oddzielnie. W każdym przypadku, jeśli to konieczne, zapoznaj się z sekcją „Specyficzne dla tego laboratorium” poniżej.

Wdrożenie na bezserwerowej platformie obliczeniowej Google Cloud, takiej jak App Engine, wiąże się z niewielkimi kosztami kompilacji i przechowywania. Cloud Build ma własny bezpłatny limit, podobnie jak Cloud Storage. Przechowywanie tego obrazu wykorzystuje część tego limitu. Możesz jednak mieszkać w regionie, w którym nie ma takiego bezpłatnego pakietu, więc kontroluj wykorzystanie miejsca na dane, aby zminimalizować potencjalne koszty. Sprawdź te „foldery” Cloud Storage:

  • console.cloud.google.com/storage/browser/LOC.artifacts.PROJECT_ID.appspot.com/containers/images
  • console.cloud.google.com/storage/browser/staging.PROJECT_ID.appspot.com
  • Linki do pamięci masowej powyżej zależą od PROJECT_IDLOC, np. „us”, jeśli aplikacja jest hostowana w Stanach Zjednoczonych.

Jeśli nie zamierzasz kontynuować pracy z tą aplikacją ani innymi powiązanymi z nią samouczkami dotyczącymi migracji i chcesz wszystko całkowicie usunąć, wyłącz projekt.

Dotyczy tych ćwiczeń z programowania

Usługi wymienione poniżej są dostępne tylko w tym laboratorium. Więcej informacji znajdziesz w dokumentacji poszczególnych usług:

  • Usługa App Engine Datastore jest udostępniana przez Cloud Datastore (Cloud Firestore w trybie Datastore), która również ma bezpłatny poziom. Więcej informacji znajdziesz na stronie z cennikiem.
  • Korzystanie z platformy Identity Platform w ramach Cloud Identity jest w pewnym stopniu „bezpłatne”, w zależności od tego, z których usług korzystasz. Więcej informacji znajdziesz na stronie z cennikiem.
  • Korzystanie z interfejsu Cloud Resource Manager API jest w większości przypadków bezpłatne zgodnie z informacjami na stronie z cenami.

Dalsze kroki

Oprócz tego samouczka możesz też zapoznać się z innymi modułami migracji, które skupiają się na przejściu z starszych usług w pakiecie:

App Engine nie jest już jedyną platformą bezserwerową w Google Cloud. Jeśli masz małą aplikację App Engine lub aplikację o ograniczonej funkcjonalności i chcesz przekształcić ją w samodzielny mikroserwis albo podzielić aplikację monolityczną na wiele komponentów wielokrotnego użytku, warto rozważyć przejście na Cloud Functions. Jeśli konteneryzacja stała się częścią procesu tworzenia aplikacji, zwłaszcza jeśli obejmuje potok CI/CD (tryb ciągłej integracji/tryb ciągłego dostarczania lub wdrażanie), rozważ migrację do Cloud Run. Te scenariusze są omówione w tych modułach:

  • Migracja z App Engine do Cloud Functions: patrz moduł 11
  • Migracja z App Engine do Cloud Run: w module 4 dowiesz się, jak skonteneryzować aplikację za pomocą Dockera, a w module 5 – jak to zrobić bez kontenerów, wiedzy o Dockerze ani Dockerfiles

Przejście na inną platformę bezserwerową jest opcjonalne. Zanim wprowadzisz jakiekolwiek zmiany, zalecamy rozważenie najlepszych opcji dla Twoich aplikacji i przypadków użycia.

Niezależnie od tego, który moduł migracji wybierzesz, wszystkie materiały dotyczące Serverless Migration Station (ćwiczenia z programowania, filmy, kod źródłowy [jeśli jest dostępny]) znajdziesz w repozytorium open source. W repozytorium README znajdziesz też wskazówki dotyczące migracji, które warto rozważyć, oraz odpowiednią „kolejność” modułów migracji.

8. Dodatkowe materiały

Poniżej znajdziesz dodatkowe materiały dla programistów, którzy chcą dowiedzieć się więcej o tym lub podobnych modułach migracji. Poniżej możesz przesłać opinię o tych treściach, znaleźć linki do kodu i różne dokumenty, które mogą Ci się przydać.

Problemy z Codelabs lub opinie na ich temat

Jeśli zauważysz jakieś problemy z tym kursem, najpierw poszukaj rozwiązania, a dopiero potem zgłoś problem. Linki do wyszukiwania i tworzenia nowych problemów:

Materiały dotyczące migracji

Linki do folderów repozytorium dla modułu 20 (START) i modułu 21 (FINISH) znajdziesz w tabeli poniżej.

Ćwiczenia z programowania

Python 2

Python 3

Moduł 20

kod

(n/a)

Moduł 21 (to ćwiczenie)

kod

kod

Odsyłacze online

Poniżej znajdziesz zasoby przydatne w tym samouczku:

Cloud Identity Platform i Cloud Marketplace

Cloud Resource Manager, Cloud IAM, Firebase Admin SDK

Użytkownicy App Engine, App Engine NDB, Cloud NDB, Cloud Datastore

Materiały dotyczące innych modułów migracji

Migracja App Engine

Platforma App Engine

Pakiet SDK Cloud

Inne informacje o chmurze

Filmy

Licencja

To zadanie jest licencjonowane na podstawie ogólnej licencji Creative Commons Attribution 2.0.