dialogflow-cx/vpc-sc-auth-server/server/session.py (49 lines of code) (raw):
# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Stored session management.
Based on https://github.com/GoogleCloudPlatform/emblem/blob/main/website/middleware/session.py
Create and read data for a stored session.
Examples:
from middleware import session
session_id = session.create({"name": "J. Doe", "email": "jdoe@example.com"})
info = session.read(session_id)
info["country"] = "US"
Implementation:
The session data will be stored in Google Cloud Storage. A bucket must already
exist for this use, and the name of the bucket provided in the environment
variable SESSION_BUCKET. The website should be using a service
account that has object read and write permission in the bucket. Access to
the bucket and its contents should be tightly controlled.
No other objects should be in this bucket as they might conflict with
the objects managed by this module.
The code that calls these methods is responsible for managing
a cookie with the session ID in the user's web browser.
"""
import io
import json
import logging
import os
import google.api_core.exceptions
import google.auth
import google.cloud.storage as storage # pylint: disable=consider-using-from-import
from aes_cipher import AESCipher
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from flask import Response
log = logging.getLogger("session")
credentials, project = google.auth.default()
NOT_FOUND_ERROR_MESSAGE = "Exception: google.api_core.exceptions.NotFound"
class NoBucketError(Exception):
"""Exception to throw when environment does not have SESSION_BUCKET set."""
message = "Environment variable SESSION_BUCKET is required"
def __init__(self):
"""Initialize NoBucketError with default message"""
def get_session_bucket():
"""# Initialize data that's not specific to an http request."""
session_bucket_name = os.environ.get("SESSION_BUCKET")
if session_bucket_name is None:
log.error("Could not initialize session module.")
raise NoBucketError()
return storage.Client().bucket(session_bucket_name)
def create(session_data, session_id=None, public_pem=None):
"""Create a stored session containing the provided data.
Args:
session_data: the data to be saved for this session. It can be any data
type that can be serialized and then deserialized with the Python
standard json module.
Returns:
A string containing an identifier for the created session, or None if
the session store was not created.
"""
bucket = get_session_bucket()
aes_cipher = AESCipher()
rsa_cipher = PKCS1_OAEP.new(key=RSA.import_key(public_pem))
aes_key_ciphertext = rsa_cipher.encrypt(aes_cipher.key)
ciphertext = aes_cipher.encrypt(json.dumps(session_data))
blob = storage.blob.Blob(f"{session_id}.key", bucket)
stream = io.BytesIO(aes_key_ciphertext)
blob.upload_from_file(stream)
blob = storage.blob.Blob(f"{session_id}.aes", bucket)
stream = io.BytesIO(ciphertext)
blob.upload_from_file(stream)
return session_id
def read(session_id):
"""Return the data previously saved for the specified session id.
Args:
session_id (str): the unique identifier of the session store.
Returns:
The stored session data if it exists and can be retrieved,
None otherwise.
"""
bucket = get_session_bucket()
try:
blob = storage.blob.Blob(f"{session_id}.key", bucket)
key_bytes = blob.download_as_bytes()
blob = storage.blob.Blob(f"{session_id}.aes", bucket)
session_data_bytes = blob.download_as_bytes()
return {
"key": io.BytesIO(key_bytes),
"session_data": io.BytesIO(session_data_bytes),
}
except google.api_core.exceptions.NotFound:
return {"error": Response(status=401, response=NOT_FOUND_ERROR_MESSAGE)}