Code Monkey home page Code Monkey logo

sanic-session's Introduction

Sanic session management for humans

ReadTheDocs License: MIT PyPI version

sanic-session is session management extension for Sanic that integrates server-backed sessions with most convenient API.

sanic-session provides a number of session interfaces for you to store a client's session data. The interfaces available right now are:

  • Redis (supports both drivers aioredis and asyncio_redis)
  • Memcache (via aiomcache)
  • Mongodb (via sanic_motor and pymongo)
  • In-Memory (suitable for testing and development environments)

Installation

Install with pip (there is other options for different drivers, check documentation):

pip install sanic_session

Documentation

Documentation is available at sanic-session.readthedocs.io.

Also, make sure you read OWASP's Session Management Cheat Sheet for some really useful info on session management.

Example

A simple example uses the in-memory session interface.

from sanic import Sanic
from sanic.response import text
from sanic_session import Session, InMemorySessionInterface

app = Sanic(name="ExampleApp")
session = Session(app, interface=InMemorySessionInterface())

@app.route("/")
async def index(request):
    # interact with the session like a normal dict
    if not request.ctx.session.get('foo'):
        request.ctx.session['foo'] = 0

    request.ctx.session['foo'] += 1

    return text(str(request.ctx.session["foo"]))

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)

Examples of using redis and memcache backed sessions can be found in the documentation, under Using the Interfaces.

— ⭐️ —

sanic-session's People

Contributors

abdza avatar ahopkins avatar dependabot-preview[bot] avatar jonathan-s avatar kinware avatar laggardkernel avatar lixxu avatar marijngiesen avatar mastergenius avatar mikekeda avatar omarryhan avatar paladiamors avatar sharpbit avatar sim0nides avatar subyraman avatar theruziev avatar xen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sanic-session's Issues

No `session_interface` in main.py in testing documentation

https://github.com/xen/sanic_session/blob/master/docs/source/testing.rst

In the doc,

Use this session ID to retrieve the server-stored session data from the session_interface.

but there is no session_interface variable in main.py.

I think main.py should contains the following lines:

if os.environ.get('TESTING'):
    session_interface = InMemorySessionInterface()
else:
    session_interface = RedisSessionInterface(redis.get_redis_pool)

Session(app, interface = session_interface)

how to set expire for per session key

Session(app, interface=InMemorySessionInterface(expiry=3000))

@app.route("/session")
async def test(request):
    response = text(request['session']['foo'])
    request["session"]["foo"] = "bar"
    # how to set foo1's expiry is 300
    request["session"]["foo1"]["expiry"] = 300
    # how to set foo2's expiry is 600
    request["session"]["foo2"]["expiry"] = 600
    return response

"NoneType has no attribute cookies" with websockets

I think websocket requests in Sanic don't have responses (they're long-lived); so when I add a sanic-session session it tries to save cookies for the websocket requests as well, which gives the error above. It works better for me if I check for response being None in base.py _set_cookie_expiration.

Document mongodb support?

I saw the docs and was disappointed it wasn't supported but it seems like it is by reading the source code

is it a make sense idea to make one SessionInterface?

Only one interface to the end user and because they share many same arguments, like domain, expiry, httponly, cookie_name, prefix which can be reduced for some lines, like this:

# __init__.py
class SessionInterface:
    def __new__(cls, *args, **kwargs):
        backend = kwargs.pop('backend', 'memory')
        if backend == 'redis':
            interface_class = RedisSessionInterface
        elif backend == 'memcache':
            interface_class = MemcacheSessionInterface
        else:
            interface_class = InMemorySessionInterface

        return interface_class(*args, **kwargs)

class BaseSessionInterface:
    def __init__(self, domain=None, expiry=2592000,
                 httponly=True, cookie_name='session',
                 prefix='session:', **kwargs):
        self.expiry = expiry
        self.prefix = prefix
        self.cookie_name = cookie_name
        self.domain = domain
        self.httponly = httponly

class InMemorySessionInterface(BaseSessionInterface):
    def __init__(self, *args, **kwargs):
        super().__init__(**kwargs)
        self.session_store = ExpiringDict()

class MemcacheSessionInterface(BaseSessionInterface):
    def __init__(self, memcache_connection, *args, **kwargs):
        super().__init__(**kwargs)
        self.memcache_connection = memcache_connection
        # memcache has a maximum 30-day cache limit
        if self.expiry > 2592000:
            self.expiry = 0

class RedisSessionInterface(BaseSessionInterface):
    def __init__(self, redis_getter: Callable=None, *args, **kwargs):
        super().__init__(**kwargs)
        self.redis_getter = redis_getter

# memory
session = SessionInterface(**kwargs)
# redis
session = SessionInterface(redis_getter, backend='redis', **kwargs)
# memcache
session = SessionInterface(memcache_connection, backend='memcache', **kwargs)

AttributeError: 'NoneType' object has no attribute 'name'

I'm trying to use the mongodb version and this is my code:

app = Sanic(__name__)
Session(app, MongoDBSessionInterface(app, coll='rainboard_session', expiry=365 * 24 * 60 * 60, sessioncookie=True, secure=True))

But when starting the sanic server, i get this error:

Traceback (most recent call last):
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\app.py", line 1128, in run
    serve(**server_settings)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\server.py", line 891, in serve
    trigger_events(after_start, loop)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\server.py", line 693, in trigger_events
    loop.run_until_complete(result)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 583, in run_until_complete
    return future.result()
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_session\mongodb.py", line 123, in apply_session_indexes
    await _SessionModel.create_index("sid")
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_motor\__init__.py", line 359, in create_index
    coll = cls.get_collection(db)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_motor\__init__.py", line 176, in get_collection
    db = cls.__dbkey__ or cls.__app__.name
AttributeError: 'NoneType' object has no attribute 'name'
Traceback (most recent call last):
  File "app.py", line 82, in <module>
    app.run(port=app.config.PORT)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\app.py", line 1128, in run
    serve(**server_settings)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\server.py", line 891, in serve
    trigger_events(after_start, loop)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic\server.py", line 693, in trigger_events
    loop.run_until_complete(result)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\asyncio\base_events.py", line 583, in run_until_complete
    return future.result()
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_session\mongodb.py", line 123, in apply_session_indexes
    await _SessionModel.create_index("sid")
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_motor\__init__.py", line 359, in create_index
    coll = cls.get_collection(db)
  File "C:\Users\Jason\AppData\Local\Programs\Python\Python37\lib\site-packages\sanic_motor\__init__.py", line 176, in get_collection
    db = cls.__dbkey__ or cls.__app__.name
AttributeError: 'NoneType' object has no attribute 'name'

Not quite sure how to fix it or if the mongodb interface is even ready for use since I didnt see it on readthedocs

Cookie 'expires' property must be a datetime

Using the latest released version of Sanic (19.3.1) I get the following.

2019-03-24T19:32:58.955 ERROR app.py#966 Exception occurred in one of response middleware handlers
Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/sanic/app.py", line 958, in handle_request
    request, response
  File "/usr/lib/python3.6/site-packages/sanic/app.py", line 1238, in _run_response_middleware
    _response = await _response
  File "/usr/lib/python3.6/site-packages/ui/server.py", line 704, in save_session
    await session.save(request, response)
  File "/usr/lib/python3.6/site-packages/sanic_session/base.py", line 142, in save
    self._set_cookie_props(request, response)
  File "/usr/lib/python3.6/site-packages/sanic_session/base.py", line 50, in _set_cookie_props
    response.cookies[self.cookie_name]['expires'] = self._calculate_expires(self.expiry)
  File "/usr/lib/python3.6/site-packages/sanic/cookies.py", line 116, in __setitem__
    "Cookie 'expires' property must be a datetime"
TypeError: Cookie 'expires' property must be a datetime

sys:1: RuntimeWarning: coroutine 'Loop.create_server' was never awaited

While trying to run redis to create authentication, I encounter such exception:
sys:1: RuntimeWarning: coroutine 'Loop.create_server' was never awaited
Code:

@app.listener('before_server_start')
async def server_init(app, loop):
    app.redis = await aioredis.create_redis_pool(app.config['redis']) <- exception here KeyError: 'redis'
    session.init_app(app, interface=AIORedisSessionInterface(app.redis))

any plan to add init_app?

any plan to add init_app method so that no need to write add_session_to_request and save_session for every time use it?

redis session work with aioreids?

Hi, can "sanic_session" work with "aioredis" naturally, as the document demo with "asyncio_redis"?

If I don't misunderstand, in "redis_session_interface.py", we get redis connect as below:

redis_connection = await self.redis_getter()

this works fine with asyncio_redis, but in aioredis, when call get() with ConnectionsPool get a ContextManager, not a connection, so it will promote a error and cannot get a connection. You need to get connection with acquire()

Thanks for attention!

UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 2490: character maps to <undefined>

When installing from the master branch (pip install git+https://github.com/xen/sanic_session) (not replicable on pypi release, probably because README.md may have been changed after latest release), this error occurs:

Collecting git+https://github.com/xen/sanic_session
  Cloning https://github.com/xen/sanic_session to c:\users\jason\appdata\local\temp\pip-req-build-0tfgky2v
  Running command git clone -q https://github.com/xen/sanic_session 'C:\Users\Jason\AppData\Local\Temp\pip-req-build-0tfgky2v'
    ERROR: Command errored out with exit status 1:
     command: 'c:\users\jason\appdata\local\programs\python\python37\python.exe' -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\Jason\\AppData\\Local\\Temp\\pip-req-build-0tfgky2v\\setup.py'"'"'; __file__='"'"'C:\\Users\\Jason\\AppData\\Local\\Temp\\pip-req-build-0tfgky2v\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base 'C:\Users\Jason\AppData\Local\Temp\pip-pip-egg-info-n60qy5q4'
         cwd: C:\Users\Jason\AppData\Local\Temp\pip-req-build-0tfgky2v\
    Complete output (7 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "C:\Users\Jason\AppData\Local\Temp\pip-req-build-0tfgky2v\setup.py", line 4, in <module>
        long_description = fh.read()
      File "c:\users\jason\appdata\local\programs\python\python37\lib\encodings\cp1252.py", line 23, in decode
        return codecs.charmap_decode(input,self.errors,decoding_table)[0]
    UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 2490: character maps to <undefined>
    ----------------------------------------
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

Fixed in PR #71, just wanted to create this issue to track it

Session doesn't save

app

import os
import aioredis

from sanic_session import Session, AIORedisSessionInterface
from sanic import Sanic

app = Sanic(__name__)
session = Session(app)

@app.listener('before_server_start')
async def server_init(app, loop):
	app.redis = await aioredis.create_redis_pool(os.getenv("REDIS_URL"))
	session.init_app(app, interface=AIORedisSessionInterface(app.redis))
	print("Init")

if __name__ == '__main__':
	app.run(host="0.0.0.0", port=os.getenv("PORT", default=5000), debug=os.getenv("DEBUG_MODE", default=False))

routes

@bp.post("/")
async def save_data(request):
	request.ctx.session["data"] = request.json
	print(request.ctx.session.get("data"))
	return response.json({"message": "Done"}, status=200)


@bp.get("/")
async def get_data(request):
	return response.json(
		request.ctx.session.get("data")
	)

When I called get method, it return me null, but I write to the session not null
Help me please

Security tightening

Hey,

About a week ago, I contributed some code here. A while after, I wanted to add some features, some that would break backward compatibility and that would require me to rewrite a big portion of the tests. So I decided I will fork sanic_session and write my own lib.

While writing the library, I saw 2 minor security issues that I think should be addressed:

  1. Possibility for a user assigning an SID instead of strictly being generated server-side.

  2. It would be nice to provide an easy interface for refreshing an sid of a session_dict for authentication libraries to easily avoid session fixation attacks.
    (Unprivileged user having the same SID as a privileged user)

Before writing sanic_cookies I made some commits to my fork of sanic_session, however the changes weren't complete and not properly tested, so I wouldn't recommend blindly copying from them. I think what's better is that the 2 repos can somehow merge for a version 2.0? (this and sanic_cookies https://github.com/omarryhan/sanic-cookies)

Unfortunately, I'm very busy these days so I can't push quick fixes to sanic_session. I'll be more than happy to do code reviews if you want me to. I'll be even happier if someone can review my code @ sanic_cookies

Cheers :)

New release

Is there a planned date for a new release with all the latest changes? Currently deploying using git and the master branch, but would like to be able to deploy directly off of pypi... :)

Can't load session

Why do I follow the readme
Can't load session

Python error
ImportError: cannot import name 'Session'
have any idea?

Installed package
Package Version


aiofiles 0.3.2
AoikLiveReload 0.1.0
argh 0.26.2
httptools 0.0.11
Logbook 1.4.0
pathtools 0.1.2
pip 10.0.1
PyYAML 3.13
sanic 0.7.0
Sanic-Auth 0.2.0
sanic-session 0.1.5
setuptools 39.0.1
ujson 1.35
uvloop 0.11.0
watchdog 0.8.3
websockets 5.0.1

python version 3.7.0

instance extensions question

Setting variables on Sanic instances is deprecated and will be removed in version 21.9. You should change your Sanic instance to use instance.ctx.extensions instead.

file:sanic_session/init.py,line 26 and line 28 , change "app.extensions" to "app.ctx.extensions"

seems response header always has set-cookie, even use same session

use the aioredis demo:
https://sanic-session.readthedocs.io/en/latest/using_the_interfaces.html#redis-aioredis

and response header always has set-cookie, even use same session
// event comment out foo +=1

Is this normal?
// use set-cookie only when cookie has change ?

detail

version

  • sanic 21.6.2
  • sanic-session 0.8.0
  • python 3.9

chrome test

sanic_session_cookie__.png



curl test:

$ curl -sv 'http://localhost:8006/' --cookie 'session=ac97cd2a67e54328a6d06ba8e471cacd'

> GET / HTTP/1.1
> Host: localhost:8006
> User-Agent: curl/7.68.0
> Accept: */*
> Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd
> 

< HTTP/1.1 200 OK
< Set-Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd; Path=/; HttpOnly; expires=Thu, 03-Mar-2022 20:23:27 GMT; Max-Age=2592000
< content-length: 38
< connection: keep-alive
< content-type: text/plain; charset=utf-8
< 
foo: <SessionDict {'foo': {'val': 0}}>


$ curl -sv 'http://localhost:8006/' --cookie 'session=ac97cd2a67e54328a6d06ba8e471cacd'

> GET / HTTP/1.1
> Host: localhost:8006
> User-Agent: curl/7.68.0
> Accept: */*
> Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd
> 

< HTTP/1.1 200 OK
< Set-Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd; Path=/; HttpOnly; expires=Thu, 03-Mar-2022 20:23:29 GMT; Max-Age=2592000
< content-length: 38
< connection: keep-alive
< content-type: text/plain; charset=utf-8
< 
foo: <SessionDict {'foo': {'val': 0}}>


$ curl -sv 'http://localhost:8006/' --cookie 'session=ac97cd2a67e54328a6d06ba8e471cacd'

> GET / HTTP/1.1
> Host: localhost:8006
> User-Agent: curl/7.68.0
> Accept: */*
> Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd
> 

< HTTP/1.1 200 OK
< Set-Cookie: session=ac97cd2a67e54328a6d06ba8e471cacd; Path=/; HttpOnly; expires=Thu, 03-Mar-2022 20:23:30 GMT; Max-Age=2592000
< content-length: 38
< connection: keep-alive
< content-type: text/plain; charset=utf-8
< 
foo: <SessionDict {'foo': {'val': 0}}>

every request will got cookie with new expires value.


Unknown overflow error

I’ve been seeing sporadic Overflow errors but I can’t tell in what conditions that this is happening. The traceback does not tell me whole lot about what is happening either.

Would you be able to guess what might be happening — or alternatively, does it have to do with spf?

Sep 05 22:53:14 : 2020-09-05 22:53:14,534 ERROR sanic.error:985 | Exception occurred in one of response middleware handlers
Sep 05 22:53:14 : Traceback (most recent call last):
Sep 05 22:53:14 :   File "/path/to/lib/python3.6/site-packages/sanic/app.py", line 977, in handle_request
Sep 05 22:53:14 :     request, response, request_name=name
Sep 05 22:53:14 :   File "/path/to/lib/python3.6/site-packages/spf/framework.py", line 686, in _run_response_middleware_19_12
Sep 05 22:53:14 :     _response = await _response
Sep 05 22:53:14 :   File "/path/to/lib/python3.6/site-packages/sanic_session/__init__.py", line 41, in save_session
Sep 05 22:53:14 :     await self.interface.save(request, response)
Sep 05 22:53:14 :   File "/path/to/lib/python3.6/site-packages/sanic_session/base.py", line 156, in save
Sep 05 22:53:14 :     val = ujson.dumps(dict(req[self.session_name]))
Sep 05 22:53:14 : OverflowError: Maximum recursion level reached

don't work in IE/EDGE

Session wan't save in IE/EDGE, but in FIREFOX/CHROME everything works fine.
Tried on different mashines.
But custom cookies works fine.

So every page update creates new session on server.

Any help, please?

Alter example to show how to make session available in jinja2 template

I wrote this bit of code to make a variable called session available in jinja templates which contains the session dict. Use the jinja_render or jinja_response methods and pass in the template path instead of using jinja's get_template and render methods.

import os
from sanic.response import html
from jinja2 import Environment, FileSystemLoader

template_envirnoment = Environment(loader=FileSystemLoader(os.getcwd() + '/view/html'))
get_template = template_envirnoment.get_template

# using this method attaches the session to the template
def jinja_render(template_path, **kwargs):
  import inspect
  frame = inspect.currentframe()

  try:
    if inspect.getframeinfo(frame.f_back)[2] == 'jinja_response':
      session = frame.f_back.f_back.f_locals['request']['session']
    else:
      session = frame.f_back.f_locals['request']['session']

    return get_template(template_path).render(session=session, **kwargs)
  except Exception as e:
    return get_template(template_path).render(**kwargs)
  finally:
    # forgetting to delete the frame can cause reference cycles
    # https://docs.python.org/3/library/inspect.html#the-interpreter-stack
    del frame

def jinja_response(template_path, **kwargs):
  return html(jinja_render(template_path, **kwargs))

'NoneType' object has no attribute 'cookies'

Hello.

This is my simple code:

    response = file(join(dirname(__file__),'websocket.html'))
    if not request['session'].get('sessionid'):
        request['session']['sessionid'] = generator()

if i try print(request), i get this result:
{'session': <SessionDict {}>}

after set sessionid the result was this : {'session': <SessionDict {'sessionid': 'vjRsqwaMVOcP808OXTBDxR9UY9rnQjkfPF0f'}>}

But after this, i was get error with this traceback:

Traceback (most recent call last):
  File "/home/narnik/Программы/SanicProjects/venv/lib/python3.6/site-packages/sanic/app.py", line 495, in handle_request
    response)
  File "/usr/lib64/python3.6/asyncio/coroutines.py", line 109, in __next__
    return self.gen.send(None)
  File "/home/narnik/Программы/SanicProjects/venv/lib/python3.6/site-packages/sanic/app.py", line 621, in _run_response_middleware
    _response = await _response
  File "/usr/lib64/python3.6/asyncio/coroutines.py", line 109, in __next__
    return self.gen.send(None)
  File "main.py", line 27, in save_session
    await si.save(request, response)
  File "/usr/lib64/python3.6/asyncio/coroutines.py", line 109, in __next__
    return self.gen.send(None)
  File "/home/narnik/Программы/SanicProjects/venv/lib/python3.6/site-packages/sanic_session/in_memory_session_interface.py", line 83, in save
    self._set_cookie_expiration(request, response)
  File "/home/narnik/Программы/SanicProjects/venv/lib/python3.6/site-packages/sanic_session/base.py", line 29, in _set_cookie_expiration
    response.cookies[self.cookie_name] = request['session'].sid
AttributeError: 'NoneType' object has no attribute 'cookies'
2017-07-22 18:41:32 - (sanic)[ERROR]: NoneType: None

Redis always “TimeoutError: [Errno 110] Connection timed out”

Let it sit for a while to refresh the front-end page(about 15min),then redis timeout,but my redis-server set "timeout 0"。How should i deal with this problem?

#------------my code------------
from sanic import Sanic
import settings
import aioredis
from sanic_session import AIORedisSessionInterface, Session
......
app = Sanic("yunwei")

session = Session()
......
@app.before_server_start
async def before_server_start(app, loop):
app.ctx.redis_session = await aioredis.create_redis_pool(
(settings.REDIS_HOST, settings.REDIS_PORT), minsize=10, maxsize=20, loop=loop,
password=settings.REDIS_PASSWORD, db=settings.REDIS_SESSION_DB, encoding='utf-8'
)
session.init_app(app, interface=AIORedisSessionInterface(app.ctx.redis_session, expiry=settings.SESSION_EXPIRY))

@app.after_server_stop
async def after_server_stop(app, loop):
app.ctx.redis_session.close()
await app.ctx.redis_session.wait_closed()

#------------error message-----------
[2021-06-22 16:03:06 +0800] [18589] [ERROR] Exception occurred in one of response middleware handlers
Traceback (most recent call last):
File "/data/web/py39code/lib/python3.9/site-packages/sanic/request.py", line 183, in respond
response = await self.app._run_response_middleware(
File "/data/web/py39code/lib/python3.9/site-packages/sanic/app.py", line 1074, in _run_response_middleware
_response = await _response
File "/data/web/py39code/lib/python3.9/site-packages/sanic_session/init.py", line 41, in save_session
await self.interface.save(request, response)
File "/data/web/py39code/lib/python3.9/site-packages/sanic_session/base.py", line 150, in save
await self._delete_key(key)
File "/data/web/py39code/lib/python3.9/site-packages/sanic_session/aioredis.py", line 79, in _delete_key
await self.redis.delete(key)
File "/data/web/py39code/lib/python3.9/site-packages/aioredis/util.py", line 59, in wait_convert
result = await fut
File "/data/web/py39code/lib/python3.9/site-packages/aioredis/connection.py", line 186, in _read_data
obj = await self._reader.readobj()
File "/data/web/py39code/lib/python3.9/site-packages/aioredis/stream.py", line 102, in readobj
await self._wait_for_data('readobj')
File "/usr/local/python39/lib/python3.9/asyncio/streams.py", line 517, in _wait_for_data
await self._waiter
TimeoutError: [Errno 110] Connection timed out

Add an aiopg interface

Would you be interested in an aiopg interface?

I am planning to write one for my own use and can submit a pull request once I am done if you'd be interested!

Multiple session dicts (vs one) causes stale session data to get stored

With websockets, I commonly have multiple requests in flight, since the websocket one is long-lasting. I'm using sessions for auth, and I have this issue:

  • websocket connects, auth=False (session dict copy #1)
  • user POSTs to /login, sets auth=True, sessions saved with auth=True (session dict copy #2)
  • now websocket closes (this runs response middleware), session gets updated with auth=False because that's what's in the websocket's SessionDict copy #1
  • all future requests get auth=False instead of True

The reason for this seems to be that each request is using a clone of the SessionDict. If there was just one SessionDict per session key and all requests in that session got that dict, then when the user logged in, the single SessionDict would be updated to True, and then when the websocket closed down, it would just save auth=True as expected.

Is this expected behavior? I'd think it would be better if there was one SessionDict per key, and all requests with that key would share the same session dict so they'd see each other's changes while they're running.

I made a simple change to cache and reuse sessions in memory in base.py; that works OK for me. I'll add it to this issue to see if you like it.

Very random weird error when using session sanic

Hi,
I'm using the sanic_session + asyncio-redis to handle the sessions in my web applications. But sometimes it generates below error(attached below) and happens randomly. It seems the operation on cookies are causing this issue, but I have no clue on the root cause, is there anyone who also met the same issue? Expecting suggestions and ideas. Thank you!

raceback (most recent call last):
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic/app.py", line 580, in handle_request
8/27/2018 10:36:07 PM    response)
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/spf/framework.py", line 487, in _run_response_middleware
8/27/2018 10:36:07 PM    _response = await _response
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic_session/__init__.py", line 42, in save_session
8/27/2018 10:36:07 PM    await self.interface.save(request, response)
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic_session/base.py", line 129, in save
8/27/2018 10:36:07 PM    self._set_cookie_expiration(request, response)
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic_session/base.py", line 35, in _set_cookie_expiration
8/27/2018 10:36:07 PM    response.cookies[self.cookie_name] = request['session'].sid
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic/cookies.py", line 59, in __setitem__
8/27/2018 10:36:07 PM    self.headers[cookie_header] = cookie
8/27/2018 10:36:07 PM  File "/usr/local/lib/python3.6/site-packages/sanic/server.py", line 54, in __setitem__
8/27/2018 10:36:07 PM    return super().__setitem__(key.casefold(), value)
8/27/2018 10:36:07 PMAttributeError: 'MultiHeader' object has no attribute 'casefold'

bson serialization fails - utf-8

Traceback (most recent call last):
  File "/home/noah_howerton/.local/lib/python3.6/site-packages/sanic/app.py", line 640, in handle_request
    response)
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/noah_howerton/.local/lib/python3.6/site-packages/sanic/app.py", line 811, in _run_response_middleware
    _response = await _response
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/noah_howerton/.local/lib/python3.6/site-packages/sanic_session/__init__.py", line 42, in save_session
    await self.interface.save(request, response)
  File "/usr/lib/python3.6/asyncio/coroutines.py", line 110, in __next__
    return self.gen.send(None)
  File "/home/noah_howerton/.local/lib/python3.6/site-packages/sanic_session/base.py", line 127, in save
    val = ujson.dumps(dict(request['session']))
UnicodeEncodeError: 'utf-8' codec can't encode characters in position 2-3: surrogates not allowed

<SessionDict {'foo': 1, 'csrf': 'c5b6daf17f280d24712397a1fb1019e3e346f393', '_auth': {'uid': ObjectId('5c08f46a4a67770543e775b2'), 'name': '[email protected]'}}>

So it looks like this is failing with BSON "ObjectID" when trying to serialize to json.

I guess you can rely on the end user (me) to "fix" this by ensuring anything passed into the cookie is already easily serializable to json.

Another option is to use Pickle instead of ujson ... or maybe another json serializer that handles BSON better (you can write a real simple serializer that checks for ObjectId or use json_util in pymongo pkg)? I guess with cookies pickle puts you in kind of a pickle due to security issues (is that still a problem in 3.x though?)

Pickle is obv. going to be a lot more agnostic when handling python objects. I guess there's sort of a weak attempt to handle this sort of issue when you guys dump things into a "dict" but obv. that's not quite enough to strip out python objects that aren't going to easily serialize with ujson.

I guess maybe the first option (requiring end user to send already JSON serialized data into cookie) is probably the safest and most secure option. Though I'm not sure how I feel about that given you end up with a bunch of redundant layers of serialization code if you go this way.

Also, since you already have support for mongodb backend ... and any more complicated/customized session-object is likely going to have an ObjectId or two it might make sense to simply support BSON a little bit better rather than leave this for the end user to sort out :/

thx

cookie signature

Hello,

If I'm not mistaken, the cookie is not signed.
This is a security risk: someone could alter the browser's cookie sid and be identified as someone else by the server.

You should use itsdangerous to prevent someone from altering the cookie.
That would require asking for a secret(and an optional salt) when creating the Session.
cf sanic_cookiesession for an example

In any case, please add a big warning in the doc/readme while the signature is not implemented.

Unhashable type list

In RedisSessionInterface.save
specifically this line of code:
await redis_connection.delete([key])
throws an exception stating unhashable type list

I was using aioredis 1.0.0b1 and python 3.6.5
When I updated to aioredis 1.1.0
the exception is bit more descriptive:
Argument ['session:46d03770f1274b0f94823326e3b8d16a'] expected to be of bytearray, bytes, float, int, or str type

Is there any chance of updating the library to call
await redis_connection.delete(key) instead?

Transfer of project

@xen Are you willing to transfer this project to me and I will continue maintaining it? (Both the repo and the PyPI package.)

Middleware addition is broken on sanic 22.12.0+

In sanic 22.12.0 they changed the internal structure of app's middleware collections (request_middleware, response_middleware)

Look at this commit: sanic-org/sanic@2abe66b
Now they don't store pure middleware functions there but instead they use Middleware class for each of them.

It still works somehow but looks like middleware order is broken and other unpredictable bugs may occur.

Why don't you just use register_middleware method as suggested in docs? (https://sanic.dev/en/guide/basics/middleware.html#attaching-middleware)

better to have session.close() to cleanup when app stops

If you use redis or memcache as backend, a cleanup may be needed, like below code:

class BaseSessionInterface:
    async def close(self, *args, **kwargs):
        pass

class RedisSessionInterface(BaseSessionInterface):
    async def close(self, *args, **kwargs):
        redis_connection = await self.redis_getter()
        redis_connection.close()

class MemcacheSessionInterface(BaseSessionInterface):
    async def close(self, *args, **kwargs):
        memcache_connection = await self.memcache_connection
        await memcache_connection.close()

# then in app code:
@app.linstener('after_server_stop'):
    await session.close()

Make the usage of json implementation configurable

Currently, it is not possible to store objects inside a session containing UUID, datetimes, date, and time values. Substituting ujson for orjson would fix this. Maybe a configuration of the used json implementation would be the best solution.

AttributeError: 'Request' object has no attribute 'session'

I used the demo code provided:

from sanic import Sanic
from sanic.response import text
from sanic_session import Session, InMemorySessionInterface

app = Sanic()
session = Session(app, interface=InMemorySessionInterface())

@app.route("/")
async def index(request):
    # interact with the session like a normal dict
    if not request['session'].get('foo'):
        request['session']['foo'] = 0

    request['session']['foo'] += 1

    return text(request['session']['foo'])

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000, auto_reload=False)

And I get the following error:

Traceback (most recent call last):
  File "/home/justin/Coding/LearnReact/SanicBasics/pybin/lib/python3.8/site-packages/sanic/app.py", line 942, in handle_request
    response = await self._run_request_middleware(
  File "/home/justin/Coding/LearnReact/SanicBasics/pybin/lib/python3.8/site-packages/sanic/app.py", line 1304, in _run_request_middleware
    response = await response
  File "/home/justin/Coding/LearnReact/SanicBasics/pybin/lib/python3.8/site-packages/sanic_session/__init__.py", line 34, in add_session_to_request
    await self.interface.open(request)
  File "/home/justin/Coding/LearnReact/SanicBasics/pybin/lib/python3.8/site-packages/sanic_session/base.py", line 120, in open
    request[self.session_name] = session_dict
TypeError: 'Request' object does not support item assignment

It seems that this code is expecting a dict like object to set the session data.

Why does this piece of code work?

if not req[self.session_name]:
await self._delete_key(key)
if req[self.session_name].modified:
self._delete_cookie(request, response)
return

Hello,

I was reading your code and stumbled upon the linked code lines.

There I don't understand why line 167 doesn't throw an error.
Because if if not req[self.session_name] evaluates to True how can there be a modified field?

Best
Simon

Simplify the interface

The save and open method is basically all the same for all interfaces. So that code is repeated across all interfaces. So it would probably be better if each interface had private methods that implements the specific call to the store that is required.

That way you could delete quite a lot of code from the project, and it'd be a bit easier to start a new interface without thinking too much about the logic around.

AttributeError: 'str' object has no attribute 'sid'

Hello.

I made similar to what was specified in readme:
app.py:

from sanic_session import InMemorySessionInterface

session_interface = InMemorySessionInterface()

@app.middleware('request')
async def add_session_to_request(request):
    # before each request initialize a session
    # using the client's request
    await session_interface.open(request)

@app.middleware('response')
async def save_session(request, response):
    # after each request save the session,
    # pass the response to set client cookies
    await session_interface.save(request, response)

views.py:

@app.route('/gatekeeper')
def give_login_page(request):
	context = {
		'csrf_input': 'some',
		'static': link_to_static,
	}
	if 'shit' not in request['session']:
		print('was absent ______________')
		request['session'] = '888'    # <<<<<<<< fails on this line
	else:
		print(f"it's present, and == {request['session']} ________")
	return jinja.render('gatekeeper/login_page.html', request, **context)

but I receive such error:

File "/home/vlad/my_projects/web/times_new_novel_asyncio/venv/lib/python3.6/site-packages/sanic/app.py", line 727, in _run_response_middleware
_response = await _response
File "/home/vlad/my_projects/web/times_new_novel_asyncio/main/app.py", line 34, in save_session
await session_interface.save(request, response)
File "/home/vlad/my_projects/web/times_new_novel_asyncio/venv/lib/python3.6/site-packages/sanic_session/in_memory_session_interface.py", line 67, in save
key = self.prefix + request['session'].sid
AttributeError: 'str' object has no attribute 'sid'

I'll try to figure out the matter myself, but, seems, something is wrong.
Anyway I think, that developers might know, which errors happen often in their software =)

Best regards,
Vladislav

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.