Commit 0636d2d9 authored by Pietro Albini's avatar Pietro Albini

Add session expiration

parent 4360efbc
# Source code of the Ubuntu-it website # Source code of the Ubuntu-it website
# Copyright (C) 2015-2016 Pietro Albini <pietroalbini@ubuntu.com> # Copyright (C) 2015-2018 Pietro Albini <pietroalbini@ubuntu.com>
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU Affero General Public License as published
...@@ -25,6 +25,7 @@ from . import pages ...@@ -25,6 +25,7 @@ from . import pages
from . import utils from . import utils
from . import download from . import download
from . import navbar from . import navbar
from . import utils
def create_app(data_path): def create_app(data_path):
...@@ -51,6 +52,8 @@ def create_app(data_path): ...@@ -51,6 +52,8 @@ def create_app(data_path):
app.config["CACHE_PATH"] = os.path.join(data_path, "cache") app.config["CACHE_PATH"] = os.path.join(data_path, "cache")
cache.install_cache(app) cache.install_cache(app)
utils.prepare_app(app)
app.download = download.Downloads(data_path) app.download = download.Downloads(data_path)
app.register_blueprint( app.register_blueprint(
app.download.prepare_blueprint(app), app.download.prepare_blueprint(app),
......
...@@ -20,11 +20,15 @@ import uuid ...@@ -20,11 +20,15 @@ import uuid
import flask import flask
import flask_openid import flask_openid
import pkg_resources import pkg_resources
import time
import yaml import yaml
from uitwww.third_party import openid_teams from uitwww.third_party import openid_teams
SESSION_EXPIRES_AFTER = 86400
class SessionError(BaseException): class SessionError(BaseException):
pass pass
...@@ -40,20 +44,35 @@ class Sessions: ...@@ -40,20 +44,35 @@ class Sessions:
ip = flask.request.remote_addr ip = flask.request.remote_addr
self.db.update( self.db.update(
"INSERT INTO auth_sessions (id, nickname, teams, ip) " "INSERT INTO auth_sessions (id, nickname, teams, ip, expires_at) "
"VALUES (?, ?, ?, ?)", id, nickname, ",".join(teams), ip, "VALUES (?, ?, ?, ?, ?)", id, nickname, ",".join(teams), ip,
int(time.time()) + SESSION_EXPIRES_AFTER,
) )
return id return id
def check(self, id): def check(self, id):
"""Check if a session is valid and return its data""" """Check if a session is valid and return its data"""
data = self.db.query("SELECT nickname, teams, ip FROM auth_sessions WHERE id = ?", id) data = self.db.query(
"SELECT nickname, teams, ip, expires_at FROM auth_sessions "
"WHERE id = ?", id,
)
if not data: if not data:
raise SessionError("La tua sessione è scaduta, accedi di nuovo.") raise SessionError("La tua sessione è scaduta, accedi di nuovo.")
nickname = data[0][0] nickname = data[0][0]
teams = data[0][1].split(",") teams = data[0][1].split(",")
ip = data[0][2] ip = data[0][2]
expires_at = data[0][3]
if expires_at is None or expires_at < time.time():
self.delete(id)
raise SessionError("La tua sessione è scaduta, accedi di nuovo.")
else:
# Bump the session every time a new page is visited
self.db.update(
"UPDATE auth_sessions SET expires_at = ? WHERE id = ?",
int(time.time()) + SESSION_EXPIRES_AFTER, id,
)
if ip != flask.request.remote_addr: if ip != flask.request.remote_addr:
raise SessionError("Questa sessione è valida solo su un'altra rete, accedi di nuovo.") raise SessionError("Questa sessione è valida solo su un'altra rete, accedi di nuovo.")
...@@ -73,31 +92,49 @@ class Sessions: ...@@ -73,31 +92,49 @@ class Sessions:
def all(self): def all(self):
"""Return all the sessions""" """Return all the sessions"""
rows = self.db.query("SELECT id, nickname, teams, ip FROM auth_sessions;") rows = self.db.query("SELECT id, nickname, teams, ip, expires_at FROM auth_sessions;")
now = time.time()
result = [] result = []
for row in rows: for row in rows:
# Delete expired sessions
if row[0] is None or row[4] < now:
self.delete(row[0])
continue
result.append({ result.append({
"id": row[0], "id": row[0],
"nickname": row[1], "nickname": row[1],
"teams": row[2].split(","), "teams": row[2].split(","),
"ip": row[3], "ip": row[3],
"expires_at": row[4],
}) })
return result return result
def get(self, id): def get(self, id):
"""Return details about a session""" """Return details about a session"""
row = self.db.query("SELECT nickname, teams, ip FROM auth_sessions WHERE id = ?;", id) row = self.db.query("SELECT nickname, teams, ip, expires_at FROM auth_sessions WHERE id = ?;", id)
if row: if row:
# Delete expired sessions
if row[0][3] is None or row[0][3] < time.time():
self.delete(id)
return
return { return {
"id": id, "id": id,
"nickname": row[0][0], "nickname": row[0][0],
"teams": row[0][1].split(","), "teams": row[0][1].split(","),
"ip": row[0][2], "ip": row[0][2],
"expires_at": row[0][3],
} }
def count(self): def count(self):
"""Return the number of active sessions""" """Return the number of active sessions"""
return self.db.query("SELECT COUNT(*) FROM auth_sessions;")[0][0] now = time.time()
return self.db.query(
"SELECT COUNT(*) FROM auth_sessions "
"WHERE expires_at IS NOT NULL AND expires_at >= ?;", now,
)[0][0]
class Permissions: class Permissions:
......
...@@ -81,4 +81,8 @@ MIGRATIONS = [ ...@@ -81,4 +81,8 @@ MIGRATIONS = [
ip TEXT NOT NULL ip TEXT NOT NULL
); );
"""), """),
("add_auth_sessions_expires_at_column", """
ALTER TABLE auth_sessions ADD COLUMN expires_at INTEGER;
"""),
] ]
...@@ -35,6 +35,10 @@ ...@@ -35,6 +35,10 @@
<td>Indirizzo IP</td> <td>Indirizzo IP</td>
<td>{{ session.ip }}</td> <td>{{ session.ip }}</td>
</tr> </tr>
<tr>
<td>Scadenza</td>
<td>{{ session.expires_at|format_timestamp }}</td>
</tr>
<tr> <tr>
<td>Team</td> <td>Team</td>
<td> <td>
...@@ -64,12 +68,14 @@ ...@@ -64,12 +68,14 @@
<table> <table>
<tr> <tr>
<th>Indirizzo IP</th> <th>Indirizzo IP</th>
<th>Scadenza</th>
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
{% for session in others %} {% for session in others %}
<tr> <tr>
<td>{{ session.ip }}</td> <td>{{ session.ip }}</td>
<td>{{ session.expires_at|format_timestamp }}
<td> <td>
<a href="{{ url_for(".sessions_show", id=session.id) }}"> <a href="{{ url_for(".sessions_show", id=session.id) }}">
Dettagli Dettagli
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
<tr> <tr>
<th>Nome utente</th> <th>Nome utente</th>
<th>Indirizzo IP</th> <th>Indirizzo IP</th>
<th>Scadenza</th>
<th></th> <th></th>
<th></th> <th></th>
</tr> </tr>
...@@ -47,6 +48,7 @@ ...@@ -47,6 +48,7 @@
<tr> <tr>
<td>{{ session.nickname }}</td> <td>{{ session.nickname }}</td>
<td>{{ session.ip }}</td> <td>{{ session.ip }}</td>
<td>{{ session.expires_at|format_timestamp }}</td>
<td> <td>
<a href="{{ url_for(".sessions_show", id=session.id) }}"> <a href="{{ url_for(".sessions_show", id=session.id) }}">
Dettagli Dettagli
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import random import random
import string import string
...@@ -100,3 +101,13 @@ class ReverseProxied: ...@@ -100,3 +101,13 @@ class ReverseProxied:
if scheme: if scheme:
environ['wsgi.url_scheme'] = scheme environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response) return self.app(environ, start_response)
def prepare_app(app):
"""Add some utilities to the app"""
@app.template_filter("format_timestamp")
def format_timestamp(timestamp):
return datetime.datetime.fromtimestamp(
int(timestamp)
).strftime('%d/%m/%Y %H:%M:%S')
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