Commit 716aa1c4 authored by rgaudin's avatar rgaudin
Browse files

new version of captive portal

parent 6ece3c4a
......@@ -41,6 +41,8 @@ php_version: "7.3"
# Apps path
venvs_root: /home/{{ username }}/venvs
captive_root: /var/www/captiveportal
captive_env: "{{ venvs_root }}/captive"
aflatoun_root: "{{ data_path }}/aflatoun"
aflatoun_env: "{{ venvs_root }}/aflatoun"
edupi_root: "/home/{{ username }}/edupi"
......
hotspot-portal
====
## i18n
``` sh
# update message catalog
pybabel extract -F portal/babel.cfg -o portal/locale/messages.pot portal/
# add new locale (once per locale)
pybabel init -i portal/locale/messages.pot -d portal/locale -l fr
# update locales
pybabel update -i portal/locale/messages.pot -d portal/locale
# compile locales
pybabel compile -d portal/locale
```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu
import sys
import pathlib
sys.path.append(str(pathlib.Path(__file__).parent.resolve()))
from portal.web import app
if __name__ == "__main__":
app.run(host="0.0.0.0", port=80)
else:
application = app
[python: **.py]
[jinja2: **/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu
import os
import pathlib
import datetime
import peewee
from portal.utils import REGISTRATION_TIMEOUT, is_active
portal_db = peewee.SqliteDatabase(
str(pathlib.Path(os.getenv("TMP_DIR", "/tmp")).joinpath("hotspot-portal.db"))
)
class User(peewee.Model):
class Meta:
database = portal_db
# ident-related fields
hw_addr = peewee.CharField(primary_key=True)
ip_addr = peewee.IPField()
# metadata
platform = peewee.CharField(null=True)
system = peewee.CharField(null=True)
system_version = peewee.FloatField(null=True)
browser = peewee.CharField(null=True)
browser_version = peewee.FloatField(null=True)
language = peewee.CharField(null=True)
# registration-related fields
last_seen_on = peewee.DateTimeField(default=datetime.datetime.now)
registered_on = peewee.DateTimeField(null=True)
@property
def is_registered(self):
""" """
if not self.registered_on:
return False
now = datetime.datetime.now()
return (
now > self.registered_on
and (now - self.registered_on).total_seconds() < REGISTRATION_TIMEOUT
)
@property
def is_being_registered(self):
""" has started but not completed registration process """
if not self.registered_on:
return False
now = datetime.datetime.now()
return self.registered_on > now
@property
def is_active(self):
return is_active(self.ip_addr)
@property
def is_apple(self):
return self.system in ("macos", "iphone", "ipad")
@property
def is_recent_android(self):
if self.system == "Android" and self.system_version >= 7:
return True
if self.platform == "linux" and self.browser == "chrome":
return True
return False
def register(self, delay=0):
self.registered_on = datetime.datetime.now() + datetime.timedelta(seconds=delay)
self.save()
@classmethod
def create_or_update(cls, hw_addr, ip_addr, extras):
now = datetime.datetime.now()
data = {"ip_addr": ip_addr, "last_seen_on": now}
user, created = cls.get_or_create(hw_addr=hw_addr, defaults=data)
extras.update(data)
for key, value in extras.items():
if hasattr(user, key):
setattr(user, key, value)
user.save()
return user
portal_db.create_tables([User])
# French translations for PROJECT.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-10-07 10:52+0000\n"
"PO-Revision-Date: 2019-10-07 09:38+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: fr\n"
"Language-Team: fr <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: portal/utils.py:138
msgid "Online"
msgstr "connecté"
#: portal/utils.py:138
msgid "Offline"
msgstr "hors-ligne"
#: portal/templates/portal.html:3
#, python-format
msgid "Entering “<em>%(hotspot_name)s</em>”"
msgstr "Connection à «<em>%(hotspot_name)s</em>»"
#: portal/templates/portal.html:6
#, python-format
msgid "<em>%(project_name)s</em> is a free-to-use, content-enabled box."
msgstr "<em>%(project_name)s</em> est une ressource de contenus libres d'accès"
#: portal/templates/portal.html:7
msgid "Please, keep the following in mind:"
msgstr "Merci de vous rappeler des points suivants :"
#: portal/templates/portal.html:8
#, python-format
msgid ""
"Access to this box's content is done <strong>solely via "
"<em>http://%(fqdn)s/</em></strong>."
msgstr ""
"L'accès à ces contenus ne se fait <strong>qu'à travers "
"<em>http://%(fqdn)s/</em></strong>."
#: portal/templates/portal.html:8
msgid "Remember-it !"
msgstr "Souvenez-vous en !"
#: portal/templates/portal.html:9
#, python-format
msgid ""
"This box may also provide access to Internet (currently "
"%(colored_status)s)"
msgstr ""
"Cette ressource pourrait fournir un accès"
" à Internet (actuellement %(colored_status)s)."
#: portal/templates/portal.html:10
msgid ""
"You will then be prompted with this page <strong>after every "
"15mn</strong> of inactivity."
msgstr ""
"Cette page vous sera alors présenté <strong>après 15mn</strong> "
"d'inactivité."
#: portal/templates/portal.html:11
msgid ""
"When browsing a secure website, your browser might display a <em>Security"
" Warning</em> instead of displaying this page. Just open a new page onto "
"a non-secure website to register again."
msgstr ""
"Surfant sur un site sécurisé, le navigateur pourrait vous afficher un "
"<em>avertissement de sécurité</em> au lieu d'afficher cette page. Ouvrez-"
"en simplement une nouvelle sur un site non-sécurisé et enregistrez-vous à"
" nouveau."
#: portal/templates/portal.html:15
msgid "Action Required!"
msgstr "Action requise !"
#: portal/templates/portal.html:16
msgid ""
"After you click the button bellow, <strong>you need to <u>manually</u> go"
" to</strong>:"
msgstr ""
"Après avoir cliqué sur le bouton ci-dessous, <strong>vous devrez "
"<u>manuellement</u> aller à</strong> :"
#: portal/templates/portal.html:17
msgid "Copy to clipboard"
msgstr "Copier l'adresse"
#: portal/templates/portal.html:18
msgid ""
"Open your Web browser copy-paste or type this address there then click "
"bellow."
msgstr ""
"Ouvrez votre navigateur Web et collez ou saisissez cette adresse ; puis "
"cliquez ci-dessous."
#: portal/templates/portal.html:20
msgid "Agree to register and close"
msgstr "OK pour continuer"
#: portal/templates/registered.html:3
#, python-format
msgid ""
"Registered to “<em>%(hotspot_name)s</em>” (<a "
"href=\"http://%(fqdn)s/\">%(fqdn)s</a>)"
msgstr ""
"Enregistré sur «<em>%(hotspot_name)s</em>» (<a "
"href=\"http://%(fqdn)s/\">%(fqdn)s</a>)"
#: portal/templates/registered.html:6
msgid "You're all set!"
msgstr "C'est tout bon !"
#: portal/templates/registered.html:8
#, python-format
msgid ""
"Now open your web browser and go to <button class=\"clipboard button "
"button-sm neutral\" data-clipboard-"
"text=\"http://%(fqdn)s/\">%(fqdn)s</button> as instructed earlier."
msgstr ""
"Maintenant, ouvrez votre navigateur et allez à <button class=\"clipboard "
"button button-sm neutral\" data-clipboard-"
"text=\"http://%(fqdn)s/\">%(fqdn)s</button> en suivant les instructions "
"précédentes."
#: portal/templates/registered.html:11
#, python-format
msgid "You're now ready to access <em>%(project_name)s's content</em>."
msgstr ""
"Vous êtes désormais prêt à accèder aux <em>contenus de "
"%(project_name)s</em>."
#: portal/templates/registered.html:12
#, python-format
msgid "Go to %(fqdn)s"
msgstr "Aller à %(fqdn)s"
# Translations template for PROJECT.
# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2019-10-07 10:52+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.7.0\n"
#: portal/utils.py:138
msgid "Online"
msgstr ""
#: portal/utils.py:138
msgid "Offline"
msgstr ""
#: portal/templates/portal.html:3
#, python-format
msgid "Entering “<em>%(hotspot_name)s</em>”"
msgstr ""
#: portal/templates/portal.html:6
#, python-format
msgid "<em>%(project_name)s</em> is a free-to-use, content-enabled box."
msgstr ""
#: portal/templates/portal.html:7
msgid "Please, keep the following in mind:"
msgstr ""
#: portal/templates/portal.html:8
#, python-format
msgid ""
"Access to this box's content is done <strong>solely via "
"<em>http://%(fqdn)s/</em></strong>."
msgstr ""
#: portal/templates/portal.html:8
msgid "Remember-it !"
msgstr ""
#: portal/templates/portal.html:9
#, python-format
msgid ""
"This box may also provide access to Internet (currently "
"%(colored_status)s)"
msgstr ""
#: portal/templates/portal.html:10
msgid ""
"You will then be prompted with this page <strong>after every "
"15mn</strong> of inactivity."
msgstr ""
#: portal/templates/portal.html:11
msgid ""
"When browsing a secure website, your browser might display a <em>Security"
" Warning</em> instead of displaying this page. Just open a new page onto "
"a non-secure website to register again."
msgstr ""
#: portal/templates/portal.html:15
msgid "Action Required!"
msgstr ""
#: portal/templates/portal.html:16
msgid ""
"After you click the button bellow, <strong>you need to <u>manually</u> go"
" to</strong>:"
msgstr ""
#: portal/templates/portal.html:17
msgid "Copy to clipboard"
msgstr ""
#: portal/templates/portal.html:18
msgid ""
"Open your Web browser copy-paste or type this address there then click "
"bellow."
msgstr ""
#: portal/templates/portal.html:20
msgid "Agree to register and close"
msgstr ""
#: portal/templates/registered.html:3
#, python-format
msgid ""
"Registered to “<em>%(hotspot_name)s</em>” (<a "
"href=\"http://%(fqdn)s/\">%(fqdn)s</a>)"
msgstr ""
#: portal/templates/registered.html:6
msgid "You're all set!"
msgstr ""
#: portal/templates/registered.html:8
#, python-format
msgid ""
"Now open your web browser and go to <button class=\"clipboard button "
"button-sm neutral\" data-clipboard-"
"text=\"http://%(fqdn)s/\">%(fqdn)s</button> as instructed earlier."
msgstr ""
#: portal/templates/registered.html:11
#, python-format
msgid "You're now ready to access <em>%(project_name)s's content</em>."
msgstr ""
#: portal/templates/registered.html:12
#, python-format
msgid "Go to %(fqdn)s"
msgstr ""
<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>{{ hotspot_name }}</title>
<link rel="icon" href="/hotspot-static/branding/favicon.png">
<link rel="stylesheet" href="/hotspot-static/font-awesome/css/font-awesome.min.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" type="text/css" href="/hotspot-static/purecss/pure-css-grids-old-ie-min.css">
<![endif]-->
<!--[if gt IE 8]><!-->
<link rel="stylesheet" type="text/css" href="/hotspot-static/purecss/pure-css-grids-min.css">
<!--<![endif]-->
<link rel="stylesheet" type="text/css" href="/hotspot-static/main.css" />
<link rel="stylesheet" type="text/css" href="/hotspot-static/branding/style.css" />
<script src="/hotspot-static/clipboard.min.js"></script>
</head>
<body class="">
<header>
<section>
<a href="/">
<h1>
<img src="/hotspot-static/branding/logo.png"></img>
</h1>
</a>
<span class="boxid">{{ project_name }}!</span>
</section>
</header>
<ul class="messages">
{% block intro %}{% endblock %}
</ul>
<div class="content">
{% block content %}{% endblock %}
</div>
<footer>
<div class="row">
<div class="col third i18n_switch"></div>
<div class="col half"></div>
</div>
</footer>
<script type="text/javascript">
(function(exports, d) {
function domReady(fn, context) {
function onReady(event) {
d.removeEventListener("DOMContentLoaded", onReady);
fn.call(context || exports, event);
}
function onReadyIe(event) {
if (d.readyState === "complete") {
d.detachEvent("onreadystatechange", onReadyIe);
fn.call(context || exports, event);
}
}
d.addEventListener && d.addEventListener("DOMContentLoaded", onReady) ||
d.attachEvent && d.attachEvent("onreadystatechange", onReadyIe);
}
exports.domReady = domReady;
})(window, document);
</script>
<script type="text/javascript">domReady(function(event) {
new ClipboardJS('.clipboard');
{% block js %}{% endblock %}
});</script>
</body>
</html>
{% extends "base.html" %}
{% block intro %}<li class="info">{% trans %}Entering “<em>{{ hotspot_name }}</em>”{% endtrans %} (<a href="http://{{ fqdn }}/">{{ fqdn }}</a>)</li>{% endblock %}
{% block content %}
<h2>{% trans %}<em>{{ project_name }}</em> is a free-to-use, content-enabled box.{% endtrans %}</h2>
<p class="underline">{% trans %}Please, keep the following in mind:{% endtrans %}</p>
<ul><li>{% trans %}Access to this box's content is done <strong>solely via <em>http://{{ fqdn }}/</em></strong>.{% endtrans %} <span class="underline">{% trans %}Remember-it !{% endtrans %}</li>
<li>{% trans colored_status=internet_status|colored_status|safe %}This box may also provide access to Internet (currently {{ colored_status }}){% endtrans %}</li>
<li>{% trans %}You will then be prompted with this page <strong>after every 15mn</strong> of inactivity.{% endtrans %}</li>
<li>{% trans %}When browsing a secure website, your browser might display a <em>Security Warning</em> instead of displaying this page. Just open a new page onto a non-secure website to register again.{% endtrans %}</li>
</ul>
<hr />
{% if action_required %}
<h2>{% trans %}Action Required!{% endtrans %}</h2>
<p>{% trans %}After you click the button bellow, <strong>you need to <u>manually</u> go to</strong>:{% endtrans %}</p>
<p><code>http://{{ fqdn }}/</code> <button class="clipboard button neutral" data-clipboard-text="http://{{ fqdn }}/">{% trans %}Copy to clipboard{% endtrans %}</button></p>
<p>{% trans %}Open your Web browser copy-paste or type this address there then click bellow.{% endtrans %}</p>
{% endif %}
<p><a class="button" href="/hotspot-register">{% trans %}Agree to register and close{% endtrans %}</a></p>
{% endblock %}
{% extends "base.html" %}
{% block intro %}<li class="success">{% trans %}Registered to “<em>{{ hotspot_name }}</em>” (<a href="http://{{ fqdn }}/">{{ fqdn }}</a>){% endtrans %}</li>{% endblock %}
{% block content %}
<h2>{% trans %}You're all set!{% endtrans %}</h2>
{% if action_required %}
<p>{% trans %}Now open your web browser and go to <button class="clipboard button button-sm neutral" data-clipboard-text="http://{{ fqdn }}/">{{ fqdn }}</button> as instructed earlier.{% endtrans %}</p>
{% else %}
<p>{% trans %}You're now ready to access <em>{{ project_name }}'s content</em>.{% endtrans %}</p>
<p><a class="button" href="http://{{ fqdn }}" target="_system">{% trans %}Go to {{ fqdn }}{% endtrans %}</a></p>
{% endif %}{% endblock %}
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# vim: ai ts=4 sts=4 et sw=4 nu
import re
import subprocess
from flask_babel import gettext
# seconds to expire registration after
# synced with cron job launching clean_iptables.sh
REGISTRATION_TIMEOUT = 15 * 60
APPLE_HOSTS = [
"captive.apple.com",
"appleiphonecell.com",
"*.apple.com.edgekey.net",
"gsp1.apple.com",
"apple.com",
"www.apple.com",
]
MICROSOFT_HOSTS = [
"ipv6.msftncsi.com",
"detectportal.firefox.com",
"ipv6.msftncsi.com.edgesuite.net",
"www.msftncsi.com",
"www.msftncsi.com.edgesuite.net",
"www.msftconnecttest.com",
"www.msn.com",
"teredo.ipv6.microsoft.com",
"teredo.ipv6.microsoft.com.nsatc.net",
"ctldl.windowsupdate.com",
]
GOOGLE_HOSTS = [
"clients3.google.com",
"mtalk.google.com",
"alt7-mtalk.google.com",
"alt6-mtalk.google.com",
"connectivitycheck.android.com",
"connectivitycheck.gstatic.com",
"developers.google.cn",
]
LINUX_HOSTS = ["connectivity-check.ubuntu.com", "nmcheck.gnome.org"]
FIREFOX_HOSTS = ["detectportal.firefox.com"]
def has_internet():
try:
with open("/tmp/has_internet", "r") as f:
return f.read().strip() == "yes"
except Exception:
return False