CZ-NIC / pyoidc

A complete OpenID Connect implementation in Python
Other
714 stars 258 forks source link

persistent Session DB storage #435

Closed taskoma closed 5 years ago

taskoma commented 6 years ago

Good day, for now SessionDB in-memory solution, this mean that after application restart all "authorized" users flush and need again pass authorization.

What is right way to support persistent storage? Or maybe there are any "ready to use" solutions?

decentral1se commented 6 years ago

Hi @taskoma, you need to adapt the SessionDB to use a data store of your choice underneath. This is really unfortunate to not have inside the library and there is some talk between maintainers about providing a pre-built persistent sessionDB store. We didn't get there yet.

I've written a Redis one, so can provide portable code for that - which data store will you choose?

taskoma commented 6 years ago

redis is good choice, if you can share you code it will help a lot :)

decentral1se commented 6 years ago

OK, let's see now, here's what I had:

class RedisSessionDB(dict):
    """Dict interface for Redis session management."""
    def __init__(self, connection):
        self.connection = connection

    def keys(self):
        return self.connection.keys()

    def flushall(self):
        self.connection.flushall()

    def __getitem__(self, key):
        value = self.connection.get(key)
        if value is None:
            raise KeyError(key)
        utf8ified = str(value, "utf-8")
        return json.loads(utf8ified)

    def __setitem__(self, key, value):
        value = json.dumps(value)
        self.connection.set(key, value)

    def __contains__(self, item):
        return self.connection.exists(item)

    def __delitem__(self, key):
        self.connection.delete(key)

Let me know if that works!

taskoma commented 6 years ago

@lwm thanks a lot, may be you can share example of using you code? can't catch how to switch from class SessionDB(object):

decentral1se commented 6 years ago

No worries, it looks like I did:

    connection = Redis(host=host, port=port, db=db_num)
    redis_db = RedisSessionDB(connection)
    session_db = SessionDB("", db=redis_db)
    Provider(
        issuer,
        session_db,  # passed in here!
        client_db,
        auth_broker,
        user_info_store,
        authz_handler,
        verification_function,
    )
taskoma commented 6 years ago

@lwm

2017-12-01 18:24:22 application     ERROR    <oic.utils.sdb.AuthnEvent object at 0x7f0f110a1c90> is not JSON serializable
Traceback (most recent call last):
  File "./server.py", line 538, in application
    return callback(environ, start_response)
  File "./server.py", line 415, in authorization
    self.oas.authorization_endpoint, logger=logger)
  File "/opt/py2staging/local/lib/python2.7/site-packages/oic-0.9.5.0-py2.7.egg/oic/utils/http_util.py", line 526, in wsgi_wrapper
    args = func(**kwargs)
  File "/opt/py2staging/local/lib/python2.7/site-packages/oic-0.9.5.0-py2.7.egg/oic/oic/provider.py", line 827, in authorization_endpoint
    sid = self.setup_session(areq, authnres["authn_event"], cinfo)
  File "/opt/py2staging/local/lib/python2.7/site-packages/oic-0.9.5.0-py2.7.egg/oic/oic/provider.py", line 692, in setup_session
    sid = self.sdb.create_authz_session(authn_event, areq, oidreq=oidc_req)
  File "/opt/py2staging/local/lib/python2.7/site-packages/oic-0.9.5.0-py2.7.egg/oic/utils/sdb.py", line 635, in create_authz_session
    self._db[sid] = _dic
  File "./server.py", line 95, in __setitem__
    value = json.dumps(value)
  File "/usr/lib64/python2.7/json/__init__.py", line 244, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib64/python2.7/json/encoder.py", line 207, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib64/python2.7/json/encoder.py", line 270, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib64/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: <oic.utils.sdb.AuthnEvent object at 0x7f0f110a1c90> is not JSON serializable

this is result of outdated source? I use source base that fetched before " [#387]: Refactored the oic.utils.sdb.SessionDB constructor API " did it make influence and rootcause?

decentral1se commented 6 years ago

this is result of outdated source?

Perhaps, although we have made frequent releases. You can try latest HEAD as well.

If you provide more information, perhaps raise another issue with the problem.

schlenk commented 6 years ago

Yes, AuthnEvent wasn't json serializable in older versions. Update.

taskoma commented 6 years ago

@lwm, @schlenk thanks thats mean quite a lot of custom changes I need migrate to new version, heh (normal configuration for OP, telegraf metrics support, etc :) )

taskoma commented 6 years ago

@lwm finally I migrated to current version of codebase but anyway something go wrong

1) I see items in redis with code, access_token, refresh_token after user initialization 2) if not restart application all going well: I can receive user info and update access_token by refresh_token

in logs for user_info:

2017-12-02 21:23:40 _parse_request  DEBUG    Found 0 verify keys
2017-12-02 21:23:40 _do_user_info   DEBUG    access_token type: 'T'
2017-12-02 21:23:40 __getitem__     INFO     d689fdd47251980b2da6df1677e3d62e9b647196fa090e0e54c3cb76

3) but after application restart I catch error "Invalid token" for same request as to point 2

in logs

2017-12-02 21:26:56 _parse_request  DEBUG    Found 0 verify keys

api response:

{
    "error_description": "Invalid Token",
    "error": "invalid_token"
}

and response for new acess_token

{
    "error_description": "Refresh token is expired",
    "error": "invalid_request"
}
taskoma commented 6 years ago

answers 1) about userinfo: need set same secret / password during sessionDb initialization

2) about refresh_token: refresh_tokens stored separated, so need use same schema as to sessiondb and redis