2020import cachecontrol
2121import requests
2222import six
23+ from google .auth import credentials
24+ from google .auth import exceptions
25+ from google .auth import iam
2326from google .auth import jwt
2427from google .auth import transport
2528import google .oauth2 .id_token
26-
27- from firebase_admin import credentials
29+ import google .oauth2 .service_account
2830
2931
3032# ID token constants
4648 'acr' , 'amr' , 'at_hash' , 'aud' , 'auth_time' , 'azp' , 'cnf' , 'c_hash' ,
4749 'exp' , 'firebase' , 'iat' , 'iss' , 'jti' , 'nbf' , 'nonce' , 'sub'
4850])
51+ METADATA_SERVICE_URL = ('http://metadata/computeMetadata/v1/instance/service-accounts/'
52+ 'default/email' )
4953
5054# Error codes
5155COOKIE_CREATE_ERROR = 'COOKIE_CREATE_ERROR'
56+ TOKEN_SIGN_ERROR = 'TOKEN_SIGN_ERROR'
5257
5358
5459class ApiCallError (Exception ):
@@ -60,20 +65,81 @@ def __init__(self, code, message, error=None):
6065 self .detail = error
6166
6267
68+ class _SigningProvider (object ):
69+ """Stores a reference to a google.auth.crypto.Signer."""
70+
71+ def __init__ (self , signer , signer_email ):
72+ self ._signer = signer
73+ self ._signer_email = signer_email
74+
75+ @property
76+ def signer (self ):
77+ return self ._signer
78+
79+ @property
80+ def signer_email (self ):
81+ return self ._signer_email
82+
83+ @classmethod
84+ def from_credential (cls , google_cred ):
85+ return _SigningProvider (google_cred .signer , google_cred .signer_email )
86+
87+ @classmethod
88+ def from_iam (cls , request , google_cred , service_account ):
89+ signer = iam .Signer (request , google_cred , service_account )
90+ return _SigningProvider (signer , service_account )
91+
92+
6393class TokenGenerator (object ):
6494 """Generates custom tokens and session cookies."""
6595
6696 def __init__ (self , app , client ):
67- self ._app = app
68- self ._client = client
97+ self .app = app
98+ self .client = client
99+ self .request = transport .requests .Request ()
100+ self ._signing_provider = None
101+
102+ def _init_signing_provider (self ):
103+ """Initializes a signing provider by following the go/firebase-admin-sign protocol."""
104+ # If the SDK was initialized with a service account, use it to sign bytes.
105+ google_cred = self .app .credential .get_credential ()
106+ if isinstance (google_cred , google .oauth2 .service_account .Credentials ):
107+ return _SigningProvider .from_credential (google_cred )
108+
109+ # If the SDK was initialized with a service account email, use it with the IAM service
110+ # to sign bytes.
111+ service_account = self .app .options .get ('serviceAccountId' )
112+ if service_account :
113+ return _SigningProvider .from_iam (self .request , google_cred , service_account )
114+
115+ # If the SDK was initialized with some other credential type that supports signing
116+ # (e.g. GAE credentials), use it to sign bytes.
117+ if isinstance (google_cred , credentials .Signing ):
118+ return _SigningProvider .from_credential (google_cred )
119+
120+ # Attempt to discover a service account email from the local Metadata service. Use it
121+ # with the IAM service to sign bytes.
122+ resp = self .request (url = METADATA_SERVICE_URL , headers = {'Metadata-Flavor' : 'Google' })
123+ service_account = resp .data .decode ()
124+ return _SigningProvider .from_iam (self .request , google_cred , service_account )
125+
126+ @property
127+ def signing_provider (self ):
128+ """Initializes and returns the SigningProvider instance to be used."""
129+ if not self ._signing_provider :
130+ try :
131+ self ._signing_provider = self ._init_signing_provider ()
132+ except Exception as error :
133+ url = 'https://firebase.google.com/docs/auth/admin/create-custom-tokens'
134+ raise ValueError (
135+ 'Failed to determine service account: {0}. Make sure to initialize the SDK '
136+ 'with service account credentials or specify a service account ID with '
137+ 'iam.serviceAccounts.signBlob permission. Please refer to {1} for more '
138+ 'details on creating custom tokens.' .format (error , url ))
139+ return self ._signing_provider
69140
70141 def create_custom_token (self , uid , developer_claims = None ):
71142 """Builds and signs a Firebase custom auth token."""
72- if not isinstance (self ._app .credential , credentials .Certificate ):
73- raise ValueError (
74- 'Must initialize Firebase App with a certificate credential '
75- 'to call create_custom_token().' )
76-
77143 if developer_claims is not None :
78144 if not isinstance (developer_claims , dict ):
79145 raise ValueError ('developer_claims must be a dictionary' )
@@ -93,10 +159,11 @@ def create_custom_token(self, uid, developer_claims=None):
93159 if not uid or not isinstance (uid , six .string_types ) or len (uid ) > 128 :
94160 raise ValueError ('uid must be a string between 1 and 128 characters.' )
95161
162+ signing_provider = self .signing_provider
96163 now = int (time .time ())
97164 payload = {
98- 'iss' : self . _app . credential . service_account_email ,
99- 'sub' : self . _app . credential . service_account_email ,
165+ 'iss' : signing_provider . signer_email ,
166+ 'sub' : signing_provider . signer_email ,
100167 'aud' : FIREBASE_AUDIENCE ,
101168 'uid' : uid ,
102169 'iat' : now ,
@@ -105,7 +172,12 @@ def create_custom_token(self, uid, developer_claims=None):
105172
106173 if developer_claims is not None :
107174 payload ['claims' ] = developer_claims
108- return jwt .encode (self ._app .credential .signer , payload )
175+ try :
176+ return jwt .encode (signing_provider .signer , payload )
177+ except exceptions .TransportError as error :
178+ msg = 'Failed to sign custom token. {0}' .format (error )
179+ raise ApiCallError (TOKEN_SIGN_ERROR , msg , error )
180+
109181
110182 def create_session_cookie (self , id_token , expires_in ):
111183 """Creates a session cookie from the provided ID token."""
@@ -131,7 +203,7 @@ def create_session_cookie(self, id_token, expires_in):
131203 'validDuration' : expires_in ,
132204 }
133205 try :
134- response = self ._client .request ('post' , 'createSessionCookie' , json = payload )
206+ response = self .client .request ('post' , 'createSessionCookie' , json = payload )
135207 except requests .exceptions .RequestException as error :
136208 self ._handle_http_error (COOKIE_CREATE_ERROR , 'Failed to create session cookie' , error )
137209 else :
0 commit comments