|
8 | 8 | :license: BSD, see LICENSE for more details. |
9 | 9 | """ |
10 | 10 |
|
| 11 | +import os |
11 | 12 | import sys |
12 | 13 | import time |
13 | 14 | from datetime import datetime |
@@ -74,6 +75,10 @@ class SqlAlchemySession(ServerSideSession): |
74 | 75 | pass |
75 | 76 |
|
76 | 77 |
|
| 78 | +class GoogleCloudDataStoreSession(ServerSideSession): |
| 79 | + pass |
| 80 | + |
| 81 | + |
77 | 82 | class SessionInterface(FlaskSessionInterface): |
78 | 83 | def _generate_sid(self): |
79 | 84 | return str(uuid4()) |
@@ -800,3 +805,116 @@ def save_session(self, app, session, response): |
800 | 805 | path=path, |
801 | 806 | secure=secure, |
802 | 807 | ) |
| 808 | + |
| 809 | + |
| 810 | +class GoogleCloudDatastoreSessionInterface(SessionInterface): |
| 811 | + """Uses the Google cloud datastore as a session backend. |
| 812 | +
|
| 813 | + :param key_prefix: A prefix that is added to all store keys. |
| 814 | + :param use_signer: Whether to sign the session id cookie or not. |
| 815 | + :param permanent: Whether to use permanent session or not. |
| 816 | + """ |
| 817 | + |
| 818 | + serializer = pickle |
| 819 | + session_class = GoogleCloudDataStoreSession |
| 820 | + |
| 821 | + def __init__(self, gcloud_project, key_prefix, use_signer=False, permanent=True): |
| 822 | + self.gcloud_project = gcloud_project |
| 823 | + self.key_prefix = key_prefix |
| 824 | + self.use_signer = use_signer |
| 825 | + self.permanent = permanent |
| 826 | + |
| 827 | + def get_client(self): |
| 828 | + import requests |
| 829 | + from google.auth import compute_engine |
| 830 | + from google.cloud import datastore |
| 831 | + |
| 832 | + if os.environ.get("DATASTORE_EMULATOR_HOST"): |
| 833 | + return datastore.Client( |
| 834 | + _http=requests.Session, project="virustotal-avs-control" |
| 835 | + ) |
| 836 | + return datastore.Client(credentials=compute_engine.Credentials()) |
| 837 | + |
| 838 | + def open_session(self, app, request): |
| 839 | + ds_client = self.get_client() |
| 840 | + sid = request.cookies.get(app.config["SESSION_COOKIE_NAME"]) |
| 841 | + if not sid: |
| 842 | + sid = self._generate_sid() |
| 843 | + return self.session_class(sid=sid, permanent=self.permanent) |
| 844 | + if self.use_signer: |
| 845 | + signer = self._get_signer(app) |
| 846 | + if signer is None: |
| 847 | + return None |
| 848 | + try: |
| 849 | + sid_as_bytes = signer.unsign(sid) |
| 850 | + sid = sid_as_bytes.decode() |
| 851 | + except BadSignature: |
| 852 | + sid = self._generate_sid() |
| 853 | + return self.session_class(sid=sid, permanent=self.permanent) |
| 854 | + |
| 855 | + store_id = self.key_prefix + sid |
| 856 | + session_key = ds_client.key("session", store_id) |
| 857 | + saved_session = ds_client.get(session_key) |
| 858 | + if saved_session and saved_session["expiry"] <= pytz.utc.localize( |
| 859 | + datetime.now() |
| 860 | + ): |
| 861 | + ds_client.delete(session_key) |
| 862 | + saved_session = None |
| 863 | + if saved_session: |
| 864 | + try: |
| 865 | + value = saved_session["data"] |
| 866 | + data = self.serializer.loads(want_bytes(value)) |
| 867 | + return self.session_class(data, sid=sid) |
| 868 | + except: |
| 869 | + return self.session_class(sid=sid, permanent=self.permanent) |
| 870 | + return self.session_class(sid=sid, permanent=self.permanent) |
| 871 | + |
| 872 | + def save_session(self, app, session, response): |
| 873 | + from google.cloud import datastore |
| 874 | + |
| 875 | + ds_client = self.get_client() |
| 876 | + domain = self.get_cookie_domain(app) |
| 877 | + path = self.get_cookie_path(app) |
| 878 | + store_id = self.key_prefix + session.sid |
| 879 | + session_key = ds_client.key("session", store_id) |
| 880 | + saved_session = ds_client.get(session_key) |
| 881 | + if not session: |
| 882 | + if session.modified: |
| 883 | + if saved_session: |
| 884 | + ds_client.delete(session_key) |
| 885 | + response.delete_cookie( |
| 886 | + app.config["SESSION_COOKIE_NAME"], domain=domain, path=path |
| 887 | + ) |
| 888 | + return |
| 889 | + |
| 890 | + httponly = self.get_cookie_httponly(app) |
| 891 | + secure = self.get_cookie_secure(app) |
| 892 | + expires = self.get_expiration_time(app, session) |
| 893 | + value = self.serializer.dumps(dict(session)) |
| 894 | + if saved_session: |
| 895 | + if not expires: |
| 896 | + ds_client.delete(session_key) |
| 897 | + return |
| 898 | + saved_session["data"] = value |
| 899 | + saved_session["expiry"] = expires |
| 900 | + ds_client.put(saved_session) |
| 901 | + else: |
| 902 | + new_session = datastore.Entity( |
| 903 | + key=session_key, exclude_from_indexes=("data",) |
| 904 | + ) |
| 905 | + new_session["data"] = value |
| 906 | + new_session["expiry"] = expires |
| 907 | + ds_client.put(new_session) |
| 908 | + if self.use_signer: |
| 909 | + session_id = self._get_signer(app).sign(want_bytes(session.sid)) |
| 910 | + else: |
| 911 | + session_id = session.sid |
| 912 | + response.set_cookie( |
| 913 | + app.config["SESSION_COOKIE_NAME"], |
| 914 | + session_id, |
| 915 | + expires=expires, |
| 916 | + httponly=httponly, |
| 917 | + domain=domain, |
| 918 | + path=path, |
| 919 | + secure=secure, |
| 920 | + ) |
0 commit comments