Skip to content

Commit

Permalink
feat(elasticsearch): added elasticsearch session interface
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherpickering committed Aug 17, 2022
1 parent c589d82 commit e5dc958
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 13 deletions.
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[run]
branch = True

[report]
show_missing = True
skip_covered = True
omit =
test_session.py
15 changes: 15 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ jobs:
uses: supercharge/[email protected]
with:
redis-version: 6
- name: Configure sysctl limits for elasticsearch
run: |
sudo swapoff -a
sudo sysctl -w vm.swappiness=1
sudo sysctl -w fs.file-max=262144
sudo sysctl -w vm.max_map_count=262144
- name: Start elasticsearch
uses: getong/[email protected]
with:
elasticsearch version: 8.3.3
host port: 9200
container port: 9200
host node port: 9300
node port: 9300
discovery type: single-node
- name: install deps
run: python -m pip install tox poetry tox-poetry
- name: test
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.python-version
node_modules/
# Byte-compiled / optim
# Byte-compiled / optimized / DLL files
.mypy_cache
.DS_Store

ized / DLL files
__pycache__/
*.py[cod]

Expand Down
6 changes: 5 additions & 1 deletion .releaserc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
["@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md"
}
],
"@semantic-release/npm",
["@semantic-release/exec", {
"prepareCmd" : "poetry version ${nextRelease.version}"
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ Uses SQLAlchemy as a session backend. ([Flask-SQLAlchemy](https://pythonhosted.o
- SESSION_SQLALCHEMY
- SESSION_SQLALCHEMY_TABLE

### `ElasticsearchSessionInterface`

Uses elasticsearch as a session backend. ([elasticsearch](https://elasticsearch-py.readthedocs.io/en/v8.3.3/) required)

- SESSION_ELASTICSEARCH
- SESSION_ELASTICSEARCH_HOST
- SESSION_ELASTICSEARCH_INDEX

## Credits

This project is a fork of [flask-session](https://github.com/fengsp/flask-session), created by [Shipeng Feng](https://github.com/fengsp).
13 changes: 13 additions & 0 deletions flask_session/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os

from .sessions import (
ElasticsearchSessionInterface,
FileSystemSessionInterface,
MemcachedSessionInterface,
MongoDBSessionInterface,
Expand Down Expand Up @@ -89,6 +90,9 @@ def _get_interface(self, app):
config.setdefault("SESSION_MONGODB_DB", "flask_session")
config.setdefault("SESSION_MONGODB_COLLECT", "sessions")
config.setdefault("SESSION_MONGODB_TZ_AWARE", False)
config.setdefault("SESSION_ELASTICSEARCH", None)
config.setdefault("SESSION_ELASTICSEARCH_HOST", "http://localhost:9200")
config.setdefault("SESSION_ELASTICSEARCH_INDEX", "sessions")
config.setdefault("SESSION_SQLALCHEMY", None)
config.setdefault("SESSION_SQLALCHEMY_TABLE", "sessions")
config.setdefault("SESSION_SQLALCHEMY_SEQUENCE", None)
Expand Down Expand Up @@ -137,6 +141,15 @@ def _get_interface(self, app):
config["SESSION_PERMANENT"],
config["SESSION_SQLALCHEMY_SEQUENCE"],
)
elif config["SESSION_TYPE"] == "elasticsearch":
session_interface = ElasticsearchSessionInterface(
config["SESSION_ELASTICSEARCH"],
config["SESSION_ELASTICSEARCH_HOST"],
config["SESSION_ELASTICSEARCH_INDEX"],
config["SESSION_KEY_PREFIX"],
config["SESSION_USE_SIGNER"],
config["SESSION_PERMANENT"],
)
else:
session_interface = NullSessionInterface()

Expand Down
120 changes: 111 additions & 9 deletions flask_session/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class MongoDBSession(ServerSideSession):
pass


class ElasticsearchSession(ServerSideSession):
pass


class SqlAlchemySession(ServerSideSession):
pass

Expand Down Expand Up @@ -221,12 +225,6 @@ def __init__(self, app, client, key_prefix, use_signer=False, permanent=True):

def _get_preferred_memcache_client(self, app):
server = "127.0.0.1:11211"
# try:
# import pylibmc
# except ImportError:
# pass
# else:
# return pylibmc.Client(servers)

try:
import pymemcache
Expand Down Expand Up @@ -587,13 +585,13 @@ class Session(self.db.Model):
__tablename__ = table

if sequence:
id = self.db.Column(
id = self.db.Column( # noqa: A003, VNE003, A001
self.db.Integer, self.db.Sequence(sequence), primary_key=True
)
else:
id = self.db.Column(
id = self.db.Column( # noqa: A003, VNE003, A001
self.db.Integer, primary_key=True
) # noqa: A003, VNE003
)

session_id = self.db.Column(self.db.String(255), unique=True)
data = self.db.Column(self.db.LargeBinary)
Expand Down Expand Up @@ -698,3 +696,107 @@ def save_session(self, app, session, response):
secure=secure,
**conditional_cookie_kwargs,
)


class ElasticsearchSessionInterface(SessionInterface):
"""A Session interface that uses Elasticsearch as backend.
.. versionadded:: 0.X
:param client: A ``elasticsearch.Elasticsearch`` instance.
:param host: The elasticsearch host url you want to use.
:param index: The elasticsearch index you want to use.
:param key_prefix: A prefix that is added to all MongoDB store keys.
:param use_signer: Whether to sign the session id cookie or not.
:param permanent: Whether to use permanent session or not.
"""

serializer = None
session_class = ElasticsearchSession

def __init__(
self, client, host, index, key_prefix, use_signer=False, permanent=True
):
if client is None:
from elasticsearch import Elasticsearch

client = Elasticsearch(host)

self.client = client
self.index = index
try: # noqa: SIM105
self.client.indices.create(index=self.index)
except:
pass
self.key_prefix = key_prefix
self.use_signer = use_signer
self.permanent = permanent

def open_session(self, app, request):
sid = request.cookies.get(app.config["SESSION_COOKIE_NAME"])
if not sid:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)
if self.use_signer:
signer = self._get_signer(app)

if signer is None:
return None

try:
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self._generate_sid()
return self.session_class(sid=sid, permanent=self.permanent)

store_id = self.key_prefix + sid
document = self.client.get(index=self.index, id=store_id, ignore=404)
if document["found"]:
expiration = document["_source"]["expiration"]

expiration = datetime.strptime(expiration, "%Y-%m-%dT%H:%M:%S.%f%z")
if expiration <= datetime.utcnow().replace(tzinfo=pytz.UTC):
# Delete expired session
self.client.delete(index=self.index, id=store_id)
document = None
if document is not None:
try:
value = document["_source"]["val"]
return self.session_class(value, sid=sid)
except:
return self.session_class(sid=sid, permanent=self.permanent)
return self.session_class(sid=sid, permanent=self.permanent)

def save_session(self, app, session, response):
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
store_id = self.key_prefix + session.sid
if not session:
if session.modified:
self.client.delete(index=self.index, id=store_id)
response.delete_cookie(
app.config["SESSION_COOKIE_NAME"], domain=domain, path=path
)
return

httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
value = dict(session)
self.client.index(
index=self.index,
id=store_id,
body={"id": store_id, "val": value, "expiration": expires},
)
if self.use_signer:
session_id = self._get_signer(app).sign(want_bytes(session.sid))
else:
session_id = session.sid
response.set_cookie(
app.config["SESSION_COOKIE_NAME"],
session_id,
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
)
81 changes: 80 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e5dc958

Please sign in to comment.