Commit db1a1c78 authored by Pietro Albini's avatar Pietro Albini

Add actions logging

parent 565f65c7
Pipeline #87 passed with stage
in 0 seconds
...@@ -18,13 +18,14 @@ import os ...@@ -18,13 +18,14 @@ import os
import flask import flask
from . import actions
from . import auth from . import auth
from . import cache from . import cache
from . import db from . import db
from . import pages
from . import utils
from . import download from . import download
from . import navbar from . import navbar
from . import pages
from . import utils
from . import utils from . import utils
...@@ -55,12 +56,10 @@ def create_app(data_path): ...@@ -55,12 +56,10 @@ def create_app(data_path):
utils.prepare_app(app) utils.prepare_app(app)
app.download = download.Downloads(data_path) app.download = download.Downloads(data_path)
app.register_blueprint(
app.download.prepare_blueprint(app),
url_prefix="/download",
)
app.register_blueprint(auth.prepare_blueprint(app), url_prefix="/+auth")
app.register_blueprint(app.download.prepare_blueprint(app), url_prefix="/download")
app.register_blueprint(actions.prepare_blueprint(app), url_prefix="/+actions")
app.register_blueprint(auth.prepare_blueprint(app), url_prefix="/+auth")
app.register_blueprint(pages.prepare_blueprint(app)) app.register_blueprint(pages.prepare_blueprint(app))
nav = navbar.Navbar() nav = navbar.Navbar()
......
# Source code of the Ubuntu-it website
# Copyright (C) 2018 Pietro Albini <pietroalbini@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; witout even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
import flask
from uitwww import auth
ACTIONS_PER_PAGE = 5
class PageNotFoundError(BaseException):
pass
def log(message, session=None):
"""Log an action into the database"""
date = int(time.time())
if session is None:
session = flask.g.auth_id
flask.current_app.db.update(
"INSERT INTO actions (session, date, message) VALUES (?, ?, ?);",
session, date, message,
)
def list_actions(page=None, session=None):
"""List all the available actions"""
result = {
"actions": []
}
args = []
where = ""
if session is not None:
where = "WHERE a.session = ?"
args.append(session)
limit = ""
if page is not None:
page = page - 1
total = flask.current_app.db.query(
"SELECT COUNT(*) FROM actions %s;" % where, *args
)[0][0]
if page < 0 or page > total % ACTIONS_PER_PAGE:
raise PageNotFoundError
result["current_page"] = page + 1
result["has_prev"] = page != 0
result["has_next"] = page < total % ACTIONS_PER_PAGE
limit = "LIMIT ? OFFSET ?"
args += [ACTIONS_PER_PAGE, page * ACTIONS_PER_PAGE]
rows = flask.current_app.db.query(
"SELECT s.nickname, s.ip, s.id, a.date, a.message FROM actions AS a "
"INNER JOIN auth_sessions AS s ON (s.id = a.session) %s "
"ORDER BY date DESC %s;" % (where, limit), *args
)
for row in rows:
result["actions"].append({
"nickname": row[0],
"ip": row[1],
"session": row[2],
"date": row[3],
"message": row[4],
})
return result
def prepare_blueprint(app):
"""Prepare the auth blueprint"""
bp = flask.Blueprint("actions", __name__)
@bp.route("/")
@bp.route("/<int:page>")
@auth.permission("actions.show")
def show(page=1):
try:
actions = list_actions(page=page)
except PageNotFoundError:
return flask.abort(404)
return flask.render_template(
"actions/show.html",
actions=actions,
)
return bp
...@@ -95,7 +95,7 @@ class Sessions: ...@@ -95,7 +95,7 @@ class Sessions:
self.db.update( self.db.update(
"UPDATE auth_sessions SET expires_at = ? WHERE id != ? AND " "UPDATE auth_sessions SET expires_at = ? WHERE id != ? AND "
"expires_at IS NOT NULL AND expires_at >= ?;", "expires_at IS NOT NULL AND expires_at >= ?;",
now, id, now, now, except_id, now,
) )
def all(self): def all(self):
...@@ -205,6 +205,8 @@ def permission(perms): ...@@ -205,6 +205,8 @@ def permission(perms):
def prepare_blueprint(app): def prepare_blueprint(app):
"""Prepare the auth blueprint""" """Prepare the auth blueprint"""
from uitwww import actions
bp = flask.Blueprint("auth", __name__) bp = flask.Blueprint("auth", __name__)
oid = flask_openid.OpenID( oid = flask_openid.OpenID(
...@@ -246,6 +248,9 @@ def prepare_blueprint(app): ...@@ -246,6 +248,9 @@ def prepare_blueprint(app):
flask.session["auth"] = sessions.create(resp.nickname, teams) flask.session["auth"] = sessions.create(resp.nickname, teams)
flask.flash("Benvenuto %s!" % resp.nickname, "success") flask.flash("Benvenuto %s!" % resp.nickname, "success")
actions.log("Accesso effettuato", session=flask.session["auth"])
return flask.redirect(flask.url_for("pages.index")) return flask.redirect(flask.url_for("pages.index"))
@bp.route("/login") @bp.route("/login")
...@@ -284,6 +289,7 @@ def prepare_blueprint(app): ...@@ -284,6 +289,7 @@ def prepare_blueprint(app):
@bp.route("/sessions/+all/revoke") @bp.route("/sessions/+all/revoke")
@permission("auth.sessions.manage") @permission("auth.sessions.manage")
def sessions_revoke_all(): def sessions_revoke_all():
actions.log("Disabilitate tutte le altre sessioni")
sessions.disable_all(flask.g.auth_id) sessions.disable_all(flask.g.auth_id)
return flask.redirect(flask.url_for(".sessions_list")) return flask.redirect(flask.url_for(".sessions_list"))
...@@ -304,10 +310,15 @@ def prepare_blueprint(app): ...@@ -304,10 +310,15 @@ def prepare_blueprint(app):
continue continue
others.append(session) others.append(session)
user_actions = []
if app.permissions.check("actions.show"):
user_actions = actions.list_actions(session=id)
return flask.render_template( return flask.render_template(
"auth/session.html", "auth/session.html",
session=data, session=data,
others=others, others=others,
actions=user_actions,
) )
@bp.route("/sessions/<id>/revoke") @bp.route("/sessions/<id>/revoke")
...@@ -316,6 +327,10 @@ def prepare_blueprint(app): ...@@ -316,6 +327,10 @@ def prepare_blueprint(app):
"auth.sessions.own": lambda id: flask.g.auth_id == id, "auth.sessions.own": lambda id: flask.g.auth_id == id,
}) })
def sessions_revoke(id): def sessions_revoke(id):
actions.log(
'Disabilitata <a href="%s">una sessione</a>'
% flask.url_for("auth.sessions_show", id=id)
)
sessions.disable(id) sessions.disable(id)
return flask.redirect(flask.url_for(".sessions_list")) return flask.redirect(flask.url_for(".sessions_list"))
......
permissions: permissions:
- actions.show
- auth.logout - auth.logout
- auth.sessions.manage - auth.sessions.manage
- auth.sessions.own - auth.sessions.own
......
...@@ -89,4 +89,12 @@ MIGRATIONS = [ ...@@ -89,4 +89,12 @@ MIGRATIONS = [
("add_auth_sessions_created_at_column", """ ("add_auth_sessions_created_at_column", """
ALTER TABLE auth_sessions ADD COLUMN created_at INTEGER; ALTER TABLE auth_sessions ADD COLUMN created_at INTEGER;
"""), """),
("add_actions_table", """
CREATE TABLE actions (
session TEXT NOT NULL,
date INTEGER NOT NULL,
message TEXT NOT NULL
);
"""),
] ]
{# Source code of the Ubuntu-it website
# Copyright (C) 2018 Pietro Albini <pietroalbini@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; witout even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% macro render_actions(actions, own=False) %}
<div class="table">
<table>
<tr>
<th></th>
{% if not own %}
<th>Nome utente</th>
{% endif %}
<th>Data e ora</th>
{% if not own %}
<th>Indirizzo IP</th>
{% endif %}
</tr>
{% for action in actions.actions %}
<tr>
<td>{{ action.message|safe }}</td>
{% if not own %}
<td>{{ action.nickname }}</td>
{% endif %}
<td>{{ action["date"]|format_timestamp }}</td>
{% if not own %}
<td>
{% if (g.auth_name == action.nickname and permission("auth.sessions.own")) or permission("auth.sessions.manage") %}
<a href="{{ url_for("auth.sessions_show", id=action.session) }}">
{{ action.ip }}
</a>
{% else %}
{{ action.ip }}
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
<p class="text-center">
{% if actions.current_page %}
{% if actions.has_prev %}
<a href="{{ url_for(request.endpoint, page=actions.current_page - 1) }}">
Pagina precedente
</a>
-
{% endif %}
<b>Pagina {{ actions.current_page }}</b>
{% if actions.has_next %}
-
<a href="{{ url_for(request.endpoint, page=actions.current_page + 1) }}">
Pagina successiva
</a>
{% endif %}
{% endif %}
</p>
{% endmacro %}
{# Source code of the Ubuntu-it website
# Copyright (C) 2018 Pietro Albini <pietroalbini@ubuntu.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; witout even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#}
{% extends "layout.html" %}
{% from "actions/macros.html" import render_actions with context %}
{% block title %}Azioni recenti{% endblock %}
{% block content %}
<div class="page">
<div class="row">
<div class="col">
<h1>Azioni recenti</h1>
{{ render_actions(actions) }}
</div>
</div>
</div>
{% endblock %}
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#} #}
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "actions/macros.html" import render_actions with context %}
{% block title %}Dettagli sessione{% endblock %} {% block title %}Dettagli sessione{% endblock %}
...@@ -116,4 +117,15 @@ ...@@ -116,4 +117,15 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if permission("actions.show") and actions %}
<div class="page">
<div class="row">
<div class="col">
<h2>Azioni eseguite in questa sessione</h2>
{{ render_actions(actions, own=True) }}
</div>
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
...@@ -53,6 +53,11 @@ ...@@ -53,6 +53,11 @@
Sessioni attive: {{ g.auth_sessions_count }} Sessioni attive: {{ g.auth_sessions_count }}
</a></li> </a></li>
{% endif %} {% endif %}
{% if permission("actions.show") %}
<li><a href="{{ url_for("actions.show") }}">
Azioni recenti
</a></li>
{% endif %}
</ul> </ul>
<ul class="right"> <ul class="right">
{% if permission("auth.sessions.own") %} {% if permission("auth.sessions.own") %}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment