forked from Github-Mirrors/canaille
Added a command to clean tokens and codes. Fixes #17
This commit is contained in:
parent
2fc6af0fc9
commit
d020cee00e
6 changed files with 104 additions and 6 deletions
|
@ -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!
|
||||||
|
|
|
@ -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
22
canaille/commands.py
Normal 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()
|
|
@ -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"]
|
||||||
|
|
|
@ -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
|
||||||
|
|
51
tests/test_command_clean.py
Normal file
51
tests/test_command_clean.py
Normal 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)
|
Loading…
Reference in a new issue