Added a command to clean tokens and codes. Fixes #17

This commit is contained in:
Éloi Rivard 2020-10-23 11:31:16 +02:00
parent 2fc6af0fc9
commit d020cee00e
6 changed files with 104 additions and 6 deletions

View file

@ -46,6 +46,15 @@ pip install gunicorn
gunicorn "canaille:create_app()" gunicorn "canaille:create_app()"
``` ```
## Recurrent jobs
You might want to clean up your database to avoid it growing too much. You can regularly delete
expired tokens and authorization codes with:
```
flask clean
```
## Contribute ## Contribute
Contributions are welcome! Contributions are welcome!

View file

@ -7,6 +7,7 @@ import canaille.admin.tokens
import canaille.admin.authorizations import canaille.admin.authorizations
import canaille.admin.clients import canaille.admin.clients
import canaille.consents import canaille.consents
import canaille.commands
import canaille.oauth import canaille.oauth
import canaille.account import canaille.account
import canaille.tokens import canaille.tokens
@ -106,6 +107,16 @@ def setup_ldap_tree(app):
conn.unbind_s() conn.unbind_s()
def setup_ldap(app):
g.ldap = ldap.initialize(app.config["LDAP"]["URI"])
g.ldap.simple_bind_s(app.config["LDAP"]["BIND_DN"], app.config["LDAP"]["BIND_PW"])
def teardown_ldap(app):
if "ldap" in g:
g.ldap.unbind_s()
def setup_app(app): def setup_app(app):
if SENTRY and app.config.get("SENTRY_DSN"): if SENTRY and app.config.get("SENTRY_DSN"):
sentry_sdk.init(dsn=app.config["SENTRY_DSN"], integrations=[FlaskIntegration()]) sentry_sdk.init(dsn=app.config["SENTRY_DSN"], integrations=[FlaskIntegration()])
@ -123,6 +134,7 @@ def setup_app(app):
setup_ldap_tree(app) setup_ldap_tree(app)
app.register_blueprint(canaille.account.bp) app.register_blueprint(canaille.account.bp)
app.register_blueprint(canaille.oauth.bp, url_prefix="/oauth") app.register_blueprint(canaille.oauth.bp, url_prefix="/oauth")
app.register_blueprint(canaille.commands.bp)
app.register_blueprint(canaille.consents.bp, url_prefix="/consent") app.register_blueprint(canaille.consents.bp, url_prefix="/consent")
app.register_blueprint(canaille.tokens.bp, url_prefix="/token") app.register_blueprint(canaille.tokens.bp, url_prefix="/token")
app.register_blueprint(canaille.well_known.bp, url_prefix="/.well-known") app.register_blueprint(canaille.well_known.bp, url_prefix="/.well-known")
@ -153,15 +165,11 @@ def setup_app(app):
@app.before_request @app.before_request
def before_request(): def before_request():
g.ldap = ldap.initialize(app.config["LDAP"]["URI"]) setup_ldap(app)
g.ldap.simple_bind_s(
app.config["LDAP"]["BIND_DN"], app.config["LDAP"]["BIND_PW"]
)
@app.after_request @app.after_request
def after_request(response): def after_request(response):
if "ldap" in g: teardown_ldap(app)
g.ldap.unbind_s()
return response return response
@app.context_processor @app.context_processor

22
canaille/commands.py Normal file
View file

@ -0,0 +1,22 @@
from flask import Blueprint, current_app
from canaille.models import AuthorizationCode, Token
bp = Blueprint("commands", __name__)
@bp.cli.command("clean")
def clean():
from canaille import setup_ldap
setup_ldap(current_app)
for t in Token.filter():
if t.is_expired():
t.delete()
for a in AuthorizationCode.filter():
if a.is_expired():
a.delete()
teardown_ldap()

View file

@ -234,6 +234,13 @@ class Token(LDAPObject, TokenMixin):
return self.expire_date >= datetime.datetime.now() return self.expire_date >= datetime.datetime.now()
def is_expired(self):
return (
datetime.datetime.strptime(self.oauthIssueDate, "%Y%m%d%H%M%SZ")
+ datetime.timedelta(seconds=int(self.oauthTokenLifetime))
< datetime.datetime.now()
)
class Consent(LDAPObject): class Consent(LDAPObject):
object_class = ["oauthConsent"] object_class = ["oauthConsent"]

View file

@ -26,6 +26,7 @@ include_package_data = true
python_requires = >= 3.6 python_requires = >= 3.6
install_requires = install_requires =
authlib authlib
click
email_validator email_validator
flask flask
flask-babel flask-babel

View file

@ -0,0 +1,51 @@
import datetime
from canaille.commands import clean
from canaille.models import AuthorizationCode, Token
from werkzeug.security import gen_salt
def test_clean_command(testclient, slapd_connection, client, user):
AuthorizationCode.ocs_by_name(slapd_connection)
code = AuthorizationCode(
oauthCode="my-code",
oauthClient=client.dn,
oauthSubject=user.dn,
oauthRedirectURI="https://foo.bar/callback",
oauthResponseType="code",
oauthScope="openid profile",
oauthNonce="nonce",
oauthAuthorizationDate=(
datetime.datetime.now() - datetime.timedelta(days=1)
).strftime("%Y%m%d%H%M%SZ"),
oauthAuthorizationLifetime="3600",
oauthCodeChallenge="challenge",
oauthCodeChallengeMethod="method",
oauthRevokation="",
)
code.save(slapd_connection)
Token.ocs_by_name(slapd_connection)
token = Token(
oauthAccessToken="my-token",
oauthClient=client.dn,
oauthSubject=user.dn,
oauthTokenType=None,
oauthRefreshToken=gen_salt(48),
oauthScope="openid profile",
oauthIssueDate=(datetime.datetime.now() - datetime.timedelta(days=1)).strftime(
"%Y%m%d%H%M%SZ"
),
oauthTokenLifetime=str(3600),
)
token.save(slapd_connection)
assert AuthorizationCode.get("my-code", conn=slapd_connection)
assert Token.get("my-token", conn=slapd_connection)
assert code.is_expired()
assert token.is_expired()
runner = testclient.app.test_cli_runner()
res = runner.invoke(clean)
assert not AuthorizationCode.get("my-code", conn=slapd_connection)
assert not Token.get("my-token", conn=slapd_connection)