def create_custom_token(uid, valid_minutes=60): """ Create a secure token for the given id. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id that will be used by Firebase's security rules to prevent unauthorized access. In this case, the uid will be the channel id which is a combination of a user id and a game id. """ now = int(time.time()) # Encode the required claims # per https://firebase.google.com/docs/auth/server/create-custom-tokens payload = base64.b64encode(json.dumps({ 'iss': _CLIENT_EMAIL, 'sub': _CLIENT_EMAIL, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, # The field that will be used in Firebase rule enforcement 'iat': now, 'exp': now + (valid_minutes * 60), })) # Add standard header to identify this as a JWT header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) to_sign = '{}.{}'.format(header, payload) # Sign the jwt using the built in app_identity service return '{}.{}'.format(to_sign, base64.b64encode(app_identity.sign_blob(to_sign, deadline = _TIMEOUT)[1]))
def create_custom_token(uid, valid_minutes=60): """Create a secure token for the given id. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id (uid) that will be used by Firebase's security rules to prevent unauthorized access. In this case, the uid will be the channel id which is a combination of user_id and game_key """ # use the app_identity service from google.appengine.api to get the # project's service account email automatically client_email = app_identity.get_service_account_name() now = int(time.time()) # encode the required claims # per https://firebase.google.com/docs/auth/server/create-custom-tokens payload = base64.b64encode( json.dumps({ 'iss': client_email, 'sub': client_email, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, # the important parameter, as it will be the channel id 'iat': now, 'exp': now + (valid_minutes * 60), })) # add standard header to identify this as a JWT header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) to_sign = '{}.{}'.format(header, payload) # Sign the jwt using the built in app_identity service return '{}.{}'.format(to_sign, base64.b64encode(app_identity.sign_blob(to_sign)[1]))
def prepare_upload(bucket_name, path, expiry=DEFAULT_URL_VALID_SECONDS): """Prepare a signed GCS upload.""" expiration_time = (datetime.datetime.utcnow() + datetime.timedelta(seconds=expiry)) conditions = [ { 'key': path }, { 'bucket': bucket_name }, ['content-length-range', 0, MAX_UPLOAD_SIZE], ['starts-with', '$x-goog-meta-filename', ''], ] policy = base64.b64encode( json.dumps({ 'expiration': expiration_time.isoformat() + 'Z', 'conditions': conditions, })) local_server = environment.get_value('LOCAL_GCS_SERVER_HOST') if local_server: url = local_server signature = 'SIGNATURE' service_account_name = 'service_account' else: url = STORAGE_URL % bucket_name signature = base64.b64encode(app_identity.sign_blob(policy)[1]) service_account_name = app_identity.get_service_account_name() return GcsUpload(url, bucket_name, path, service_account_name, policy, signature)
def _get_signature_bytes(credentials, string_to_sign): """Uses crypto attributes of credentials to sign a string/bytes. :type credentials: :class:`client.SignedJwtAssertionCredentials`, :class:`service_account._ServiceAccountCredentials`, :class:`_GAECreds` :param credentials: The credentials used for signing text (typically involves the creation of an RSA key). :type string_to_sign: string :param string_to_sign: The string to be signed by the credentials. :rtype: bytes :returns: Signed bytes produced by the credentials. """ if isinstance(credentials, _GAECreds): _, signed_bytes = app_identity.sign_blob(string_to_sign) return signed_bytes else: pem_key = _get_pem_key(credentials) # Sign the string with the RSA key. signer = PKCS1_v1_5.new(pem_key) if not isinstance(string_to_sign, six.binary_type): string_to_sign = string_to_sign.encode('utf-8') signature_hash = SHA256.new(string_to_sign) return signer.sign(signature_hash)
def get_signed_url(bucket_name, path, method='GET', expiry=DEFAULT_URL_VALID_SECONDS): """Return a signed url.""" timestamp = _get_expiration_time(expiry) blob = '%s\n\n\n%d\n/%s/%s' % (method, timestamp, bucket_name, path) local_server = environment.get_value('LOCAL_GCS_SERVER_HOST') if local_server: url = local_server + '/' + bucket_name signed_blob = 'SIGNATURE' service_account_name = 'service_account' else: url = STORAGE_URL % bucket_name signed_blob = app_identity.sign_blob(str(blob))[1] service_account_name = app_identity.get_service_account_name() params = { 'GoogleAccessId': service_account_name, 'Expires': timestamp, 'Signature': base64.b64encode(signed_blob), } return str(url + '/' + path + '?' + urllib.urlencode(params))
def generate_jwt(): """Generates a signed JSON Web Token using the Google App Engine default service account.""" now = int(time.time()) header_json = json.dumps({ "typ": "JWT", "alg": "RS256"}) payload_json = json.dumps({ "iat": now, # expires after one hour. "exp": now + 3600, # iss is the service account email. "iss": SERVICE_ACCOUNT_EMAIL, # target_audience is the URL of the target service. "target_audience": TARGET_AUD, # aud must be Google token endpoints URL. "aud": "https://www.googleapis.com/oauth2/v4/token" }) header_and_payload = '{}.{}'.format( base64.urlsafe_b64encode(header_json), base64.urlsafe_b64encode(payload_json)) (key_name, signature) = app_identity.sign_blob(header_and_payload) signed_jwt = '{}.{}'.format( header_and_payload, base64.urlsafe_b64encode(signature)) return signed_jwt
def GenerateStorageSignedUrl(self, request): """Generates signed url for Cloud Storage.""" GetEndpointsAuthUser() if not request.filename: raise endpoints.BadRequestException( 'Missing request field "filename".') if not request.owner: raise endpoints.BadRequestException( 'Missing request field "owner".') expires = '%sZ' % (datetime.utcnow() + timedelta(hours=1)).isoformat()[:19] policy = base64.b64encode( json.dumps({ 'expiration': expires, 'conditions': [ ['eq', '$bucket', GCS_BUCKET], ['eq', '$key', request.filename], ['eq', '$x-goog-meta-owner', request.owner], ], })) signature = base64.b64encode(app_identity.sign_blob(policy)[1]) return StorageSignedUrlResponse( form_action=GCS_API_URL % GCS_BUCKET, bucket=GCS_BUCKET, policy=policy, signature=signature, google_access_id=app_identity.get_service_account_name(), filename=request.filename)
def sign_url(bucket_object, expires_after_seconds=300): method = 'GET' gcs_filename = urllib.quote('/%s%s' % (settings.FILE_BUCKET, bucket_object)) content_md5, content_type = None, None expiration = datetime.datetime.utcnow() + timedelta(seconds=expires_after_seconds) expiration = int(time.mktime(expiration.timetuple())) # Generate the string to sign. signature_string = '\n'.join([ method, content_md5 or '', content_type or '', str(expiration), gcs_filename]) _, signature_bytes = app_identity.sign_blob(str(signature_string)) signature = base64.b64encode(signature_bytes) # Set the right query parameters. query_params = {'GoogleAccessId': app_identity.get_service_account_name(), 'Expires': str(expiration), 'Signature': signature} # Return the download URL. return '{endpoint}{resource}?{querystring}'.format(endpoint=GCS_ACCESS_ENDPOINT, resource=gcs_filename, querystring=urllib.urlencode(query_params))
def SignUrl(gcs_filename): expiration_dt = _Now() + ATTACHMENT_TTL expiration = int(time.mktime(expiration_dt.timetuple())) signature_string = '\n'.join([ 'GET', '', # Optional MD5, which we don't have. '', # Optional content-type, which only applies to uploads. str(expiration), gcs_filename]).encode('utf-8') try: signature_bytes = app_identity.sign_blob(signature_string)[1] query_params = {'GoogleAccessId': app_identity.get_service_account_name(), 'Expires': str(expiration), 'Signature': base64.b64encode(signature_bytes)} result = 'https://storage.googleapis.com{resource}?{querystring}' if IS_DEV_APPSERVER: result = '/_ah/gcs{resource}?{querystring}' return result.format( resource=gcs_filename, querystring=urllib.urlencode(query_params)) except Exception as e: logging.exception(e) return '/missing-gcs-url'
def GenerateStorageSignedUrl(self, request): """Generates signed url for Cloud Storage.""" GetEndpointsAuthUser() if not request.filename: raise endpoints.BadRequestException('Missing request field "filename".') if not request.owner: raise endpoints.BadRequestException('Missing request field "owner".') expires = '%sZ' % (datetime.utcnow() + timedelta(hours=1)).isoformat()[:19] policy = base64.b64encode(json.dumps({ 'expiration': expires, 'conditions': [ ['eq', '$bucket', GCS_BUCKET], ['eq', '$key', request.filename], ['eq', '$x-goog-meta-owner', request.owner], ], })) signature = base64.b64encode(app_identity.sign_blob(policy)[1]) return StorageSignedUrlResponse( form_action=GCS_API_URL % GCS_BUCKET, bucket=GCS_BUCKET, policy=policy, signature=signature, google_access_id=app_identity.get_service_account_name(), filename=request.filename )
def generate_jwt(): """Generates a signed JSON Web Token using the Google App Engine default service account.""" now = int(time.time()) header_json = json.dumps({ "typ": "JWT", "alg": "RS256"}) payload_json = json.dumps({ "iat": now, # expires after one hour. "exp": now + 3600, # iss is the service account email. "iss": SERVICE_ACCOUNT_EMAIL, # target_audience is the URL of the target service. "target_audience": TARGET_AUD, # aud must be Google token endpoints URL. "aud": "https://www.googleapis.com/oauth2/v4/token" }) headerAndPayload = '{}.{}'.format( base64.urlsafe_b64encode(header_json), base64.urlsafe_b64encode(payload_json)) (key_name, signature) = app_identity.sign_blob(headerAndPayload) signed_jwt = '{}.{}'.format( headerAndPayload, base64.urlsafe_b64encode(signature)) return signed_jwt
def generate_jwt(): """Generates a signed JSON Web Token using the Google App Engine default service account.""" now = int(time.time()) header_json = json.dumps({ "typ": "JWT", "alg": "RS256"}) payload_json = json.dumps({ 'iat': now, # expires after one hour. "exp": now + 3600, # iss is the Google App Engine default service account email. 'iss': DEFAULT_SERVICE_ACCOUNT, 'sub': DEFAULT_SERVICE_ACCOUNT, # aud must match 'audience' in the security configuration in your # swagger spec.It can be any string. 'aud': 'echo.endpoints.sample.google.com', "email": DEFAULT_SERVICE_ACCOUNT }) headerAndPayload = '{}.{}'.format( base64.urlsafe_b64encode(header_json), base64.urlsafe_b64encode(payload_json)) (key_name, signature) = app_identity.sign_blob(headerAndPayload) signed_jwt = '{}.{}'.format( headerAndPayload, base64.urlsafe_b64encode(signature)) return signed_jwt
def create_custom_token(uid, is_premium_account): service_account_email = app_identity.get_service_account_name() payload = { 'iss': service_account_email, 'sub': service_account_email, 'aud': 'https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit', 'uid': uid, 'claims': { 'premium_account': is_premium_account } } exp = datetime.timedelta(minutes=60) token = jwt.generate_jwt(payload, None, 'RS256', exp) header, body, secret = token.split('.') # 返ってくるヘッダーは 'alg': 'none' になっているので RS256 に変更 header = jws.utils.encode({ 'typ': 'JWT', 'alg': 'RS256' }).decode('utf-8') # app_identity.sign_blob を使ってサインを作成 sign = base64.urlsafe_b64encode(app_identity.sign_blob(str(header + '.' + body))[1]).strip('=') return header + '.' + body + '.' + sign
def create_custom_token(uid, valid_minutes=60): """Create a secure token for the given id. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id (uid) that will be used by Firebase's security rules to prevent unauthorized access. In this case, the uid will be the channel id which is a combination of user_id and game_key """ header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) client_email = app_identity.get_service_account_name() now = int(time.time()) payload = base64.b64encode( json.dumps({ 'iss': client_email, 'sub': client_email, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, 'iat': now, 'exp': now + (valid_minutes * 60), })) to_sign = '{}.{}'.format(header, payload) # Sign the jwt return '{}.{}'.format(to_sign, base64.b64encode(app_identity.sign_blob(to_sign)[1]))
def _get_signature_bytes(credentials, string_to_sign): """Uses crypto attributes of credentials to sign a string/bytes. :type credentials: :class:`service_account.ServiceAccountCredentials`, :class:`_GAECreds` :param credentials: The credentials used for signing text (typically involves the creation of a PKey). :type string_to_sign: string :param string_to_sign: The string to be signed by the credentials. :rtype: bytes :returns: Signed bytes produced by the credentials. :raises: `EnvironmentError` if `crypto` did not import successfully. """ if isinstance(credentials, _GAECreds): _, signed_bytes = app_identity.sign_blob(string_to_sign) return signed_bytes else: # Sign the string with the PKey. pkey = _get_pem_key(credentials) if not isinstance(string_to_sign, six.binary_type): string_to_sign = string_to_sign.encode('utf-8') if crypto is None: raise EnvironmentError( 'pyOpenSSL must be installed to sign content using a ' 'private key') return crypto.sign(pkey, string_to_sign, 'SHA256')
def create_custom_token(uid, valid_minutes=60): """Create a secure token for the given id. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id (uid) that will be used by Firebase's security rules to prevent unauthorized access. In this case, the uid will be the channel id which is a combination of user_id and game_key """ # use the app_identity service from google.appengine.api to get the # project's service account email automatically client_email = app_identity.get_service_account_name() now = int(time.time()) # encode the required claims # per https://firebase.google.com/docs/auth/server/create-custom-tokens payload = base64.b64encode(json.dumps({ 'iss': client_email, 'sub': client_email, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, # the important parameter, as it will be the channel id 'iat': now, 'exp': now + (valid_minutes * 60), })) # add standard header to identify this as a JWT header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) to_sign = '{}.{}'.format(header, payload) # Sign the jwt using the built in app_identity service return '{}.{}'.format(to_sign, base64.b64encode( app_identity.sign_blob(to_sign)[1]))
def sign_url(bucket_object, expires_after_seconds=6, bucket=default_bucket): """ cloudstorage signed url to download cloudstorage object without login Docs : https://cloud.google.com/storage/docs/access-control?hl=bg#Signed-URLs API : https://cloud.google.com/storage/docs/reference-methods?hl=bg#getobject """ method = 'GET' gcs_filename = urllib.quote('/%s/%s' % (bucket, bucket_object)) content_md5, content_type = None, None # expiration : number of seconds since epoch expiration_dt = datetime.utcnow() + timedelta(seconds=expires_after_seconds) expiration = int(time.mktime(expiration_dt.timetuple())) # Generate the string to sign. signature_string = '\n'.join([ method, content_md5 or '', content_type or '', str(expiration), gcs_filename]) signature_bytes = app_identity.sign_blob(signature_string)[1] # Set the right query parameters. we use a gae service account for the id query_params = {'GoogleAccessId': google_access_id, 'Expires': str(expiration), 'Signature': base64.b64encode(signature_bytes)} # Return the built URL. result = '{endpoint}{resource}?{querystring}'.format(endpoint=GCS_API_ACCESS_ENDPOINT, resource=gcs_filename, querystring=urllib.urlencode(query_params)) return result
def generate_jwt(): """Generates a signed JSON Web Token using the Google App Engine default service account.""" now = int(time.time()) header_json = json.dumps({ "typ": "JWT", "alg": "RS256"}) payload_json = json.dumps({ "iat": now, # expires after one hour. "exp": now + 3600, # iss is the Google App Engine default service account email. "iss": DEFAUTL_SERVICE_ACCOUNT, # scope must match 'audience' for google_id_token in the security # configuration in your swagger spec. "scope": TARGET_AUD, # aud must be Google token endpoints URL. "aud": "https://www.googleapis.com/oauth2/v4/token" }) headerAndPayload = '{}.{}'.format( base64.urlsafe_b64encode(header_json), base64.urlsafe_b64encode(payload_json)) (key_name, signature) = app_identity.sign_blob(headerAndPayload) signed_jwt = '{}.{}'.format( headerAndPayload, base64.urlsafe_b64encode(signature)) return signed_jwt
def generate_jwt(): """Generates a signed JSON Web Token using the Google App Engine default service account.""" now = int(time.time()) header_json = json.dumps({"typ": "JWT", "alg": "RS256"}) payload_json = json.dumps({ "iat": now, # expires after one hour. "exp": now + 3600, # iss is the Google App Engine default service account email. "iss": DEFAUTL_SERVICE_ACCOUNT, # scope must match 'audience' for google_id_token in the security # configuration in your swagger spec. "scope": TARGET_AUD, # aud must be Google token endpoints URL. "aud": "https://www.googleapis.com/oauth2/v4/token" }) headerAndPayload = '{}.{}'.format(base64.urlsafe_b64encode(header_json), base64.urlsafe_b64encode(payload_json)) (key_name, signature) = app_identity.sign_blob(headerAndPayload) signed_jwt = '{}.{}'.format(headerAndPayload, base64.urlsafe_b64encode(signature)) return signed_jwt
def gcs_upload(acl='bucket-owner-read'): """ return GCS upload form context more info : https://cloud.google.com/storage/docs/xml-api/post-object """ user_id = users.get_current_user().email().lower() google_access_id = app_identity.get_service_account_name() success_redirect = webapp2.uri_for('gcs_upload_ok', _full=True) # GCS signed upload url expires expiration_dt = datetime.now() + timedelta(seconds=60) # The security json policy document that describes what can and cannot be uploaded in the form policy_string = """ {"expiration": "%s", "conditions": [ ["starts-with", "$key", ""], {"acl": "%s"}, {"success_action_redirect": "%s"}, {"success_action_status": "201"}, {"x-goog-meta-user-id": "%s"}, ]}""" % (expiration_dt.replace(microsecond=0).isoformat() + 'Z', acl, success_redirect, user_id) # sign the policy document policy = base64.b64encode(policy_string) _, signature_bytes = app_identity.sign_blob(policy) signature = base64.b64encode(signature_bytes) logging.debug('GCS upload policy : ' + policy_string) return dict(form_bucket=default_bucket, form_access_id=google_access_id, form_policy=policy, form_signature=signature, form_succes_redirect=success_redirect, form_user_id=user_id, form_folders=bucket_folders)
def get_url(path, ttl=15): """Returns a signed URL for accessing a resource in the provided path. Args: path - path to the resource ttl - signed URL expiry time in minutes Returns: Signed URL to the resource """ expiry = int(round(time.time() + ttl * 60)) bucket = app_identity.get_default_gcs_bucket_name() cpath = '/' + bucket + '/' + path data = [] data.append('GET') # Method data.append('') # MD5 digest value data.append('') # Content-type data.append(str(expiry)) # Expiry date data.append(cpath) # Path to the resource data_str = "\n".join(data) print(type(data_str)) signing_key_name, signature = app_identity.sign_blob(str(data_str)) url = 'https://storage.googleapis.com' url += cpath url += '?GoogleAccessId=' + app_identity.get_service_account_name() url += '&Expires=' + str(expiry) url += '&Signature=' + urllib.quote_plus(base64.b64encode(signature)) return url
def key_id(self): """Optional[str]: The key ID used to identify this private key. .. note:: This makes a request to the App Identity service. """ key_id, _ = app_identity.sign_blob(b'') return key_id
def get(self): message = 'Hello, world!' signing_key_name, signature = app_identity.sign_blob(message) verified = verify_signed_by_app(message, signature) self.response.content_type = 'text/plain' self.response.write('Message: {}\n'.format(message)) self.response.write( 'Signature: {}\n'.format(base64.b64encode(signature))) self.response.write('Verified: {}\n'.format(verified))
def get(self): message = 'Hello, world!' signing_key_name, signature = app_identity.sign_blob(message) verified = verify_signed_by_app(message, signature) self.response.content_type = 'text/plain' self.response.write('Message: {}\n'.format(message)) self.response.write('Signature: {}\n'.format( base64.b64encode(signature))) self.response.write('Verified: {}\n'.format(verified))
def sign(message): """Signs a message. Args: message (Union[str, bytes]): The message to be signed. Returns: bytes: The signature of the message. """ message = _helpers.to_bytes(message) return app_identity.sign_blob(message)
def sign_blob(blob, deadline=None): """Signs a blob using current service's private key. Just an alias for GAE app_identity.sign_blob function for symmetry with 'check_signature'. Note that |blob| can be at most 8KB. Returns: Tuple (name of a key used, RSA+SHA256 signature). """ # app_identity.sign_blob is producing RSA+SHA256 signature. Sadly, it isn't # documented anywhere. But it should be relatively stable since this API is # used by OAuth2 libraries (and so changing signature method may break a lot # of stuff). return app_identity.sign_blob(blob, deadline)
def sign_blob(self, blob): """Cryptographically sign a blob (of bytes). Implements abstract method :meth:`oauth2client.client.AssertionCredentials.sign_blob`. Args: blob: bytes, Message to be signed. Returns: tuple, A pair of the private key ID used to sign the blob and the signed contents. """ return app_identity.sign_blob(blob)
def sign_blob(blob): """Signs a blob using current service's private key. Uses GAE app_identity.sign_blob function. It has a limit of 8KB on a size of a blob, so |blob| is hashed first (with sha512). So final signature is RSA+SHA256(sha512(blob)). Returns: Tuple (name of a key used, signature). """ # app_identity.sign_blob is producing RSA+SHA256 signature. Sadly, it isn't # documented anywhere. But it should be relatively stable since this API is # used by OAuth2 libraries (and so changing signature method may break a lot # of stuff). return app_identity.sign_blob(hashlib.sha512(blob).digest())
def get(self): schema_ver = int(self.request.get('schema')) since = self.request.get('since') since = "0001-01-01 00:00:00" if since is None or since == "" else since since = datetime.datetime.strptime(since, "%Y-%m-%d %H:%M:%S") dbversion = models.DBUpdate.query(models.DBUpdate.schema_version == schema_ver, models.DBUpdate.source_time <= since).order(-models.DBUpdate.source_time).get() expiry = int(time.time()) + 30 obj = dbversion.delta_gs_object_name obj = obj[3:] if obj[:4] == "/gs/" else obj string_to_sign = "GET\n\n\n%d\n%s" % (expiry, obj) signature = app_identity.sign_blob(str(string_to_sign))[1] query_params = {'GoogleAccessId': app_identity.get_service_account_name(), 'Expires': str(expiry), 'Signature': base64.b64encode(signature)} url = '%s%s?%s' % (api_url, obj, urllib.urlencode(query_params)) return self.redirect(str(url))
def create_custom_token(uid, claims, mobile=False): """Create a secure token for the given ids. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id (uid) and a session id (sid) that will be used by Firebase's security rules to prevent unauthorized access. Args: uid (str): a unique id (between 1-36 characters long) claims (dict): Additional claims mobile (bool): if the mobile service account should be used instead of default service account """ if mobile: credentials = json.loads(get_server_settings().mobileFirebaseCredentials) client_email = credentials['client_email'] else: # use the app_identity service from google.appengine.api to get the # project's service account email automatically client_email = app_identity.get_service_account_name() now = int(time.time()) payload = { 'iss': client_email, 'sub': client_email, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, 'iat': now, 'exp': now + 3600, 'claims': claims } if mobile: return jwt.encode(payload, credentials['private_key'], algorithm=Algorithms.RS256) else: if DEBUG: from google.appengine.api.app_identity.app_identity_stub import APP_SERVICE_ACCOUNT_NAME if client_email == APP_SERVICE_ACCOUNT_NAME: raise Exception('Cannot create firebase token with default development service account.' ' Set the GOOGLE_APPLICATION_CREDENTIALS environment variable with as value the path ' 'to a json file containing the credentials for a service account.' ' See https://developers.google.com/identity/protocols/application-default-credentials') # encode the required claims # per https://firebase.google.com/docs/auth/server/create-custom-tokens # uid and sid will be used as channel ids, sid is added to *claims* header = b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) encoded_payload = b64encode(json.dumps(payload)) to_sign = '%s.%s' % (header, encoded_payload) return '{}.{}'.format(to_sign, b64encode(app_identity.sign_blob(to_sign)[1]))
def sign_url(self, object_name, url_lifetime): """ Generates Cloud Storage signed URL to download Google Cloud Storage object without sign in. See: https://cloud.google.com/storage/docs/access-control/signed-urls This only works on a real App Engine app, not in a dev app server. Args: object_name (str): The name of the object which is signed. url_lifetime (datetime.timedelta): Lifetime of the signed URL. The server rejects any requests received after this time from now. """ if utils.is_dev_app_server(): # Not working on a dev app server because it doesn't support # app_identity.sign_blob(). An alternative implementation would # be needed to make it work on a dev app server. raise Exception( 'sign_url only works on a real App Engine app, not on a dev ' 'app server.') method = 'GET' expiration_time = utils.get_utcnow() + url_lifetime expiration_sec = int(time.mktime(expiration_time.timetuple())) path = '/%s/%s' % (self.bucket_name, object_name) # These are unused in our use case. content_md5 = '' content_type = '' signed_text = '\n'.join([ method, content_md5, content_type, str(expiration_sec), path, ]) (_, signature) = app_identity.sign_blob(signed_text.encode('utf-8')) query_params = { 'GoogleAccessId': app_identity.get_service_account_name(), 'Expires': str(expiration_sec), 'Signature': base64.b64encode(signature), } return 'https://storage.googleapis.com%s?%s' % ( path, urllib.urlencode(query_params))
def sign_url(self, object_name, url_lifetime): """ Generates Cloud Storage signed URL to download Google Cloud Storage object without sign in. See: https://cloud.google.com/storage/docs/access-control/signed-urls This only works on a real App Engine app, not in a dev app server. Args: object_name (str): The name of the object which is signed. url_lifetime (datetime.timedelta): Lifetime of the signed URL. The server rejects any requests received after this time from now. """ if utils.is_dev_app_server(): # Not working on a dev app server because it doesn't support # app_identity.sign_blob(). An alternative implementation would # be needed to make it work on a dev app server. raise Exception( 'sign_url only works on a real App Engine app, not on a dev ' 'app server.') method = 'GET' expiration_time = utils.get_utcnow() + url_lifetime expiration_sec = int(time.mktime(expiration_time.timetuple())) path = '/%s/%s' % (self.bucket_name, object_name) # These are unused in our use case. content_md5 = '' content_type = '' signed_text = '\n'.join([ method, content_md5, content_type, str(expiration_sec), path, ]) (_, signature) = app_identity.sign_blob(signed_text.encode('utf-8')) query_params = { 'GoogleAccessId': app_identity.get_service_account_name(), 'Expires': str(expiration_sec), 'Signature': base64.b64encode(signature), } return 'https://storage.googleapis.com%s?%s' % (path, urllib.urlencode(query_params))
def sign_jwt(aud): """Produces a JWT signed with app's service account key.""" now = int(utils.time_time()) issuer = utils.get_service_account_name() claims = { 'email': issuer, 'exp': now + 3600, 'iat': now, 'iss': issuer, 'sub': issuer, } if aud: claims['aud'] = aud claims_b64 = b64.encode(utils.encode_to_json(claims)) payload = '.'.join((_jwt_header_b64, claims_b64)) # TODO(vadimsh): Use sign_jwt RPC to get JWT header with 'kid' populated. _, sig = app_identity.sign_blob(payload) return '.'.join((payload, b64.encode(sig)))
def auth_check(): credentials, project = google.auth.default() key_name, signature = app_identity.sign_blob(b'abc') scope = 'https://www.googleapis.com/auth/userinfo.email' token, expiry = app_identity.get_access_token(scope) return code_block( '>>> import google.auth', '>>> credentials, project = google.auth.default()', '>>> credentials', repr(credentials), '>>> project', repr(project), '>>> credentials.__dict__', repr(credentials.__dict__), '>>> from google.appengine.api import app_identity', '>>> app_identity', repr(app_identity), # ALSO: get_access_token_uncached # (scopes, service_account_id=None) '>>> scope = \'https://www.googleapis.com/auth/userinfo.email\'', '>>> token, expiry = app_identity.get_access_token(scope)', '>>> token', repr(token[:6] + b'...'), '>>> expiry', repr(expiry), '>>> app_identity.get_application_id()', repr(app_identity.get_application_id()), '>>> app_identity.get_default_gcs_bucket_name()', repr(app_identity.get_default_gcs_bucket_name()), '>>> app_identity.get_default_version_hostname()', repr(app_identity.get_default_version_hostname()), '>>> app_identity.get_public_certificates()', repr(app_identity.get_public_certificates()), '>>> app_identity.get_service_account_name()', repr(app_identity.get_service_account_name()), '>>> key_name, signature = app_identity.sign_blob(b\'abc\')', '>>> key_name', repr(key_name), '>>> signature', repr(signature[:16] + b'...'), )
def create_custom_token(uid, sid, valid_minutes=60): """Create a secure token for the given ids. This method is used to create secure custom JWT tokens to be passed to clients. It takes a unique id (uid) and a session id (sid) that will be used by Firebase's security rules to prevent unauthorized access. Args: uid (str): a unique id (between 1-36 characters long) """ # use the app_identity service from google.appengine.api to get the # project's service account email automatically client_email = app_identity.get_service_account_name() if DEBUG: from google.appengine.api.app_identity.app_identity_stub import APP_SERVICE_ACCOUNT_NAME if client_email == APP_SERVICE_ACCOUNT_NAME: raise Exception('Cannot create firebase token with default development service account.' ' Set the GOOGLE_APPLICATION_CREDENTIALS environment variable with as value the path to a ' 'json file containing the credentials for a service account.' ' See https://developers.google.com/identity/protocols/application-default-credentials') now = int(time.time()) # encode the required claims # per https://firebase.google.com/docs/auth/server/create-custom-tokens # uid and sid will be used as channel ids, sid is added to *claims* payload = base64.b64encode(json.dumps({ 'iss': client_email, 'sub': client_email, 'aud': _IDENTITY_ENDPOINT, 'uid': uid, 'iat': now, 'exp': now + (valid_minutes * 60), 'claims': { 'sid': sid } })) # add standard header to identify this as a JWT header = base64.b64encode(json.dumps({'typ': 'JWT', 'alg': 'RS256'})) to_sign = '{}.{}'.format(header, payload) # Sign the jwt using the built in app_identity service return '{}.{}'.format(to_sign, base64.b64encode(app_identity.sign_blob(to_sign)[1]))
def sign_gcs_url(gcs_filename, expires_after_seconds=6): """ cloudstorage signed url to download cloudstorage object without login Docs : https://cloud.google.com/storage/docs/access-control?hl=bg#Signed-URLs API : https://cloud.google.com/storage/docs/reference-methods?hl=bg#getobject """ GCS_API_ACCESS_ENDPOINT = 'https://storage.googleapis.com' google_access_id = app_identity.get_service_account_name() method = 'GET' # TODO: decide whether to support content_md5 and content_type as params content_md5, content_type = None, None # expiration : number of seconds since epoch expiration_dt = datetime.utcnow() + timedelta( seconds=expires_after_seconds) expiration = int(time.mktime(expiration_dt.timetuple())) # Generate the string to sign. signature_string = '\n'.join([ method, content_md5 or '', content_type or '', str(expiration), gcs_filename ]) signature_bytes = app_identity.sign_blob(str(signature_string))[1] # Set the right query parameters. we use a gae service account for the id query_params = { 'GoogleAccessId': google_access_id, 'Expires': str(expiration), 'Signature': base64.b64encode(signature_bytes) } # Return the built URL. result = '{endpoint}{resource}?{querystring}'.format( endpoint=GCS_API_ACCESS_ENDPOINT, resource=gcs_filename, querystring=urllib.urlencode(query_params)) return str(result)
def _Base64Sign(self, plaintext): """Signs and returns a base64-encoded SHA256 digest.""" _, signature_bytes = app_identity.sign_blob(plaintext) return base64.b64encode(signature_bytes)
def generate_gcs_v4_signed_url(bucket_name, object_name, http_method, expiration, query_parameters=None, headers=None): """ Generate a signed URL for managing GCS objects using the Cloud Storage V4 signing process. Code below heavily borrowed from here: https://cloud.google.com/storage/docs/access-control/signing-urls-manually """ if expiration > 604800: print('Expiration Time can\'t be longer than 604800 seconds (7 days).') expiration = 604800 escaped_object_name = quote(object_name, safe='') canonical_uri = '/{}/{}'.format(bucket_name, escaped_object_name) datetime_now = datetime.utcnow() request_timestamp = datetime_now.strftime('%Y%m%dT%H%M%SZ') datestamp = datetime_now.strftime('%Y%m%d') client_email = app_identity.get_service_account_name() credential_scope = '{}/auto/storage/goog4_request'.format(datestamp) credential = '{}/{}'.format(client_email, credential_scope) if headers is None: headers = dict() headers['host'] = 'storage.googleapis.com' canonical_headers = '' ordered_headers = collections.OrderedDict(sorted(headers.items())) for k, v in ordered_headers.items(): lower_k = str(k).lower() strip_v = str(v).lower() canonical_headers += '{}:{}\n'.format(lower_k, strip_v) signed_headers = '' for k, _ in ordered_headers.items(): lower_k = str(k).lower() signed_headers += '{};'.format(lower_k) signed_headers = signed_headers[:-1] # remove trailing '&' if query_parameters is None: query_parameters = dict() query_parameters['X-Goog-Algorithm'] = 'GOOG4-RSA-SHA256' query_parameters['X-Goog-Credential'] = credential query_parameters['X-Goog-Date'] = request_timestamp query_parameters['X-Goog-Expires'] = expiration query_parameters['X-Goog-SignedHeaders'] = signed_headers canonical_query_string = '' ordered_query_parameters = collections.OrderedDict( sorted(query_parameters.items())) for k, v in ordered_query_parameters.items(): encoded_k = quote(str(k), safe='') encoded_v = quote(str(v), safe='') canonical_query_string += '{}={}&'.format(encoded_k, encoded_v) canonical_query_string = canonical_query_string[:-1] # remove trailing '&' canonical_request = '\n'.join([ http_method, canonical_uri, canonical_query_string, canonical_headers, signed_headers, 'UNSIGNED-PAYLOAD' ]) canonical_request_hash = hashlib.sha256( canonical_request.encode()).hexdigest() string_to_sign = '\n'.join([ 'GOOG4-RSA-SHA256', request_timestamp, credential_scope, canonical_request_hash ]) signing_key_name, signature = app_identity.sign_blob(string_to_sign) signature = binascii.hexlify(signature).decode() host_name = 'https://storage.googleapis.com' signed_url = '{}{}?{}&X-Goog-Signature={}'.format(host_name, canonical_uri, canonical_query_string, signature) return signed_url
def get(self): encoded_blob = self.request.get('blob').encode('utf-8') blob = base64.urlsafe_b64decode(encoded_blob) key_name, signature = app_identity.sign_blob(blob) json.dump({'key_name': key_name, 'signature': base64.b64encode(signature)}, self.response)
def test_sign_blob(): cleartext = 'Curiouser and curiouser!' key_name, signature = app_identity.sign_blob(cleartext) assert key_name assert signature
def sign_bytes(self, message): return app_identity.sign_blob(message)
def sign(self, message): message = _helpers.to_bytes(message) _, signature = app_identity.sign_blob(message) return signature