Merge branch 'master' of ssh+git://github.com/stemid/captiveportal into rs
This commit is contained in:
commit
1d2ec580e3
|
@ -11,6 +11,10 @@ This is a commonly seen setup in public Wifi networks or hotspots.
|
||||||
|
|
||||||
This app was specifically written for such a hotspot and as such requires a lot of other configuration around it. This is an ongoing [documentation project here](https://wiki.sydit.se/teknik:guider:networking:captive_portal_med_iptables).
|
This app was specifically written for such a hotspot and as such requires a lot of other configuration around it. This is an ongoing [documentation project here](https://wiki.sydit.se/teknik:guider:networking:captive_portal_med_iptables).
|
||||||
|
|
||||||
|
## More documentation
|
||||||
|
|
||||||
|
I've moved all examples from the [aforementioned wiki-page](https://wiki.sydit.se/teknik:guider:networking:captive_portal_med_iptables) to the docs/examples directory.
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
|
|
||||||
Plugins are executed when the user clicks through the captive portal form, whether they submit data or just approve an EULA these plugins are executed.
|
Plugins are executed when the user clicks through the captive portal form, whether they submit data or just approve an EULA these plugins are executed.
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# IPtables example
|
||||||
|
|
||||||
|
The example is written for Ansible so it contains Jinja2 brackets for things like input NIC, output NIC and other items important for a captive portal firewall configuration.
|
||||||
|
|
||||||
|
Server configurations vary so it's not applicable to any situation but it might help as guidance and it's well commented.
|
||||||
|
|
||||||
|
# Script examples
|
||||||
|
|
||||||
|
They're also written as Ansible templates because I copy them straight from my Ansible playbooks for deploying the captive portal. But it matters less for them, no important values to keep track of, everything is argument input.
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Captiveportal iptables wrapper script
|
||||||
|
#iptables_mac = iptables -t mangle -I internet 1 -m mac --mac-source {mac_address} -j RETURN
|
||||||
|
|
||||||
|
# First argument must be IP-address of client
|
||||||
|
test -n "$1" || exit 1
|
||||||
|
|
||||||
|
client_ip="$1"
|
||||||
|
ipt=/sbin/iptables
|
||||||
|
|
||||||
|
# Enable client traffic in internet chain by jumping over the mark
|
||||||
|
$ipt -t mangle -I internet 1 -p tcp --source "$client_ip" -j RETURN &>/dev/null && \
|
||||||
|
$ipt -t mangle -I internet 1 -p udp --source "$client_ip" -j RETURN &>/dev/null
|
||||||
|
iptables_rc=$?
|
||||||
|
|
||||||
|
# Delete conntrack info for client IP
|
||||||
|
/usr/local/sbin/rmtrack.sh "$client_ip" &>/dev/null
|
||||||
|
rmtrack_rc=$?
|
||||||
|
|
||||||
|
if [[ $iptables_rc == 0 && $rmtrack_rc == 0 ]]; then
|
||||||
|
# Success
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Error: iptables[$iptables_rc], rmtrack[$rmtrack_rc]" 1&>2
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -0,0 +1,98 @@
|
||||||
|
# {{ ansible_managed }}
|
||||||
|
#
|
||||||
|
# These rules are for the Captive Portal project.
|
||||||
|
# by Stefan Midjich - 2016/03
|
||||||
|
|
||||||
|
# Routing of traffic requires: sysctl net.ipv4.ip_forward = 1
|
||||||
|
#
|
||||||
|
# {{captiveportal_conf.input_nic}} is LAN and used as default route on LAN clients.
|
||||||
|
# {{captiveportal_conf.output_nic}} is WAN.
|
||||||
|
# {{captiveportal_conf.webportal_ip}} is the same as IP on {{captiveportal_conf.input_nic}}
|
||||||
|
|
||||||
|
# Mangle table allows the marking of traffic. If you use -j RETURN before
|
||||||
|
# -j MARK you jump out of the internet chain and your traffic is not marked.
|
||||||
|
*mangle
|
||||||
|
:PREROUTING ACCEPT
|
||||||
|
:INPUT ACCEPT
|
||||||
|
:OUTPUT ACCEPT
|
||||||
|
:POSTROUTING ACCEPT
|
||||||
|
|
||||||
|
# Create custom chain in mangle table called "internet"
|
||||||
|
:internet - [0:0]
|
||||||
|
|
||||||
|
# Run all traffic from {{captiveportal_conf.input_nic}} through the internet chain
|
||||||
|
-A PREROUTING -i {{captiveportal_conf.input_nic}} -j internet
|
||||||
|
|
||||||
|
# Example to allow authorized clients in by MAC address
|
||||||
|
#-A internet -m mac --mac-source "xx:xx:xx:xx:56:eb" -j RETURN
|
||||||
|
# Live example: -I internet 1 -m mac --mac-source "xx:xx:xx:xx:56:eb" -j RETURN
|
||||||
|
# inserts at the top of the rules before the mark rule.
|
||||||
|
#
|
||||||
|
# iptables -t mangle -I internet -m tcp -p tcp --source 1.2.3.4 -j RETURN
|
||||||
|
# iptables -t mangle -I internet -m udp -p udp --source 1.2.3.4 -j RETURN
|
||||||
|
|
||||||
|
# For MGMT SSH traffic return out of internet chain so it's not marked
|
||||||
|
-A internet -p tcp -d {{captiveportal_conf.webportal_ip}} --dport ssh -j RETURN
|
||||||
|
|
||||||
|
# Bypass NTP also
|
||||||
|
#-A internet -p udp --dport ntp -j RETURN
|
||||||
|
|
||||||
|
# Mark all other traffic in the internet chain with 99. Any traffic after
|
||||||
|
# this rule is marked and blocked.
|
||||||
|
-A internet -j MARK --set-mark 99
|
||||||
|
|
||||||
|
COMMIT
|
||||||
|
|
||||||
|
# NAT rules that redirect traffic and allow the portal server to act as gateway.
|
||||||
|
*nat
|
||||||
|
:PREROUTING ACCEPT
|
||||||
|
:INPUT ACCEPT
|
||||||
|
:OUTPUT ACCEPT
|
||||||
|
:POSTROUTING ACCEPT
|
||||||
|
|
||||||
|
# Redirect all marked HTTP traffic to the webportal IP
|
||||||
|
-A PREROUTING -m mark --mark 99 -p tcp --dport http -j DNAT --to-destination {{captiveportal_conf.webportal_ip}}
|
||||||
|
-A PREROUTING -m mark --mark 99 -p tcp --dport https -j DNAT --to-destination {{captiveportal_conf.webportal_ip}}
|
||||||
|
|
||||||
|
# Redirect all marked DNS traffic to the webportal IP
|
||||||
|
-A PREROUTING -m mark --mark 99 -p udp --dport domain -j DNAT --to-destination {{captiveportal_conf.webportal_ip}}
|
||||||
|
-A PREROUTING -m mark --mark 99 -p tcp --dport domain -j DNAT --to-destination {{captiveportal_conf.webportal_ip}}
|
||||||
|
|
||||||
|
# Redirect all ICMP to the webportal IP
|
||||||
|
-A PREROUTING -m mark --mark 99 -p icmp -j DNAT --to-destination {{captiveportal_conf.webportal_ip}}
|
||||||
|
|
||||||
|
# Redirect all unmarked DNS traffic to upstream DNS servers
|
||||||
|
{% for server in captiveportal_conf.upstream_dns %}
|
||||||
|
-A PREROUTING -p udp --dport domain -j DNAT --to-destination {{server}}
|
||||||
|
-A PREROUTING -p tcp --dport domain -j DNAT --to-destination {{server}}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
# Route any traffic out through the output NIC to act as gateway
|
||||||
|
-A POSTROUTING -o {{captiveportal_conf.output_nic}} -j MASQUERADE
|
||||||
|
|
||||||
|
COMMIT
|
||||||
|
|
||||||
|
# Filter rules that determine access to the portal server.
|
||||||
|
*filter
|
||||||
|
:INPUT ACCEPT
|
||||||
|
:FORWARD ACCEPT
|
||||||
|
:OUTPUT ACCEPT
|
||||||
|
|
||||||
|
# Enable stateful connections
|
||||||
|
-I OUTPUT -o {{captiveportal_conf.output_nic}} -d 0.0.0.0/0 -j ACCEPT
|
||||||
|
-I INPUT -i {{captiveportal_conf.input_nic}} -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||||
|
|
||||||
|
# Accept HTTP traffic both to the server and forwarded
|
||||||
|
-A INPUT -p tcp --dport http -j ACCEPT
|
||||||
|
-A FORWARD -p tcp --dport http -j ACCEPT
|
||||||
|
|
||||||
|
# Accept DNS traffic to self
|
||||||
|
-A INPUT -p udp --dport domain -j ACCEPT
|
||||||
|
-A INPUT -p tcp --dport domain -j ACCEPT
|
||||||
|
|
||||||
|
# Drop all other traffic marked 99
|
||||||
|
-A FORWARD -m mark --mark 99 -j DROP
|
||||||
|
-A INPUT -m mark --mark 99 -j DROP
|
||||||
|
|
||||||
|
COMMIT
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Conntracking keeps track of active connections so even if a user
|
||||||
|
# authenticates with a captive portal and new firewall rules are
|
||||||
|
# created it will take a while before the client takes these new
|
||||||
|
# routes. So conntrack -D can expedite that process.
|
||||||
|
|
||||||
|
test -n "$1" || exit 1
|
||||||
|
client_ip=$1
|
||||||
|
|
||||||
|
conntrack_cmd=/sbin/conntrack
|
||||||
|
|
||||||
|
# Deletes all conntracking entries for connections originating from
|
||||||
|
# to webportal server IP so that hopefully new connections can be
|
||||||
|
# initiated directly to destination.
|
||||||
|
$conntrack_cmd -D --orig-src $client_ip --orig-dst {{captiveportal_conf.webportal_ip}}
|
|
@ -13,13 +13,11 @@ mandatory = True
|
||||||
enabled = False
|
enabled = False
|
||||||
debug = True
|
debug = True
|
||||||
|
|
||||||
# If you know you won't be able to get the clients HW address then use this.
|
|
||||||
only_ip = True
|
|
||||||
|
|
||||||
# Command templates for arping and iptables.
|
# Command templates for arping and iptables.
|
||||||
# Arping might block so make sure you use a timeout and limit the number of
|
# Arping might block so make sure you use a timeout and limit the number of
|
||||||
# packets it sends.
|
# packets it sends.
|
||||||
arping = -f -c 1 -w 30 -I eth0 {ip_address}
|
arping = -f -c 1 -w 30 -I eth0 {ip_address}
|
||||||
|
|
||||||
iptables_mac = -t mangle -I internet 1 -m mac --mac-source {mac_address} -j RETURN
|
# This is a command to run to create iptables rules. Two arguments are
|
||||||
iptables_ip = -t mangle -I internet 1 -m tcp -p tcp --source {ip_address} -j RETURN
|
# passed and replace these two placeholders.
|
||||||
|
iptables_cmd = /usr/local/sbin/cp_iptables.sh "{ip_address}" "{mac_address}"
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
# Add an iptables rule
|
# Add an iptables rule
|
||||||
|
# This actually runs a command, so you can either define an iptables
|
||||||
|
# command or a script. See the plugins.cfg for the options that are
|
||||||
|
# replaced into the command line.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
|
@ -13,9 +16,11 @@ except ImportError:
|
||||||
from portal import logHandler, logFormatter
|
from portal import logHandler, logFormatter
|
||||||
|
|
||||||
# Try to import arping for mac_from_ip()
|
# Try to import arping for mac_from_ip()
|
||||||
|
use_arping = True
|
||||||
try:
|
try:
|
||||||
from sh import arping
|
from sh import arping
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
use_arping = False
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# By default run iptables through sudo, so the worker process must run with
|
# By default run iptables through sudo, so the worker process must run with
|
||||||
|
@ -38,6 +43,7 @@ def run(arg):
|
||||||
l.setLevel(DEBUG)
|
l.setLevel(DEBUG)
|
||||||
l.debug('debug logging enabled')
|
l.debug('debug logging enabled')
|
||||||
|
|
||||||
|
# Get client IP from webapp
|
||||||
client_ip = environ.get(
|
client_ip = environ.get(
|
||||||
'HTTP_X_FORWARDED_FOR',
|
'HTTP_X_FORWARDED_FOR',
|
||||||
environ.get('REMOTE_ADDR')
|
environ.get('REMOTE_ADDR')
|
||||||
|
@ -46,7 +52,7 @@ def run(arg):
|
||||||
error_msg = None
|
error_msg = None
|
||||||
iptables_failed = False
|
iptables_failed = False
|
||||||
|
|
||||||
# Verify IP
|
# Verify client IP
|
||||||
try:
|
try:
|
||||||
socket.inet_aton(client_ip)
|
socket.inet_aton(client_ip)
|
||||||
except socket.error:
|
except socket.error:
|
||||||
|
@ -56,100 +62,52 @@ def run(arg):
|
||||||
'failed': True
|
'failed': True
|
||||||
}
|
}
|
||||||
|
|
||||||
# Attempt to get client HW address first.
|
# Attempt to get client HW address with arping
|
||||||
try:
|
if use_arping:
|
||||||
client_mac = mac_from_ip(
|
try:
|
||||||
l,
|
client_mac = mac_from_ip(
|
||||||
config.get('iptables', 'arping'),
|
l,
|
||||||
client_ip
|
config.get('iptables', 'arping'),
|
||||||
)
|
client_ip
|
||||||
except Exception as e:
|
)
|
||||||
l.warn('Failed to get client HW address: {error}'.format(
|
except Exception as e:
|
||||||
error=str(e)
|
l.warn('Failed to get client HW address: {error}'.format(
|
||||||
))
|
error=str(e)
|
||||||
error_msg = str(e)
|
))
|
||||||
pass
|
error_msg = str(e)
|
||||||
|
pass
|
||||||
|
|
||||||
# If HW address was found, use it now.
|
if client_ip:
|
||||||
if client_mac:
|
iptables_cmd = config.get('iptables', 'iptables_cmd').format(
|
||||||
l.debug('Found client HW address: {hw}'.format(
|
ip_address=client_ip,
|
||||||
hw=client_mac
|
|
||||||
))
|
|
||||||
|
|
||||||
# Create tuple out of iptables command
|
|
||||||
iptables_mac = config.get('iptables', 'iptables_mac').format(
|
|
||||||
mac_address=client_mac
|
mac_address=client_mac
|
||||||
)
|
)
|
||||||
iptables_mac = tuple(iptables_mac.split(' '))
|
|
||||||
|
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
error = BytesIO()
|
error = BytesIO()
|
||||||
try:
|
try:
|
||||||
rc = sudo.iptables(iptables_mac, _out=output, _err=error)
|
# The two arguments must not contain spaces of course.
|
||||||
|
rc = sudo(tuple(iptables_cmd.split(' ')), _out=output, _err=error)
|
||||||
if rc.exit_code == 0:
|
|
||||||
l.debug('Created iptables MAC rule successfully')
|
|
||||||
return {
|
|
||||||
'error': error_msg,
|
|
||||||
'failed': False
|
|
||||||
}
|
|
||||||
except ErrorReturnCode:
|
except ErrorReturnCode:
|
||||||
error.seek(0)
|
error.seek(0)
|
||||||
error_msg = error.read()
|
error_msg = error.read()
|
||||||
l.warn('{cmd}: exited badly: {error}'.format(
|
l.warn('{cmd}: exited badly: {error}'.format(
|
||||||
cmd=('iptables', iptables_mac),
|
cmd=('iptables', iptables_cmd),
|
||||||
error=error_msg
|
error=error_msg
|
||||||
))
|
))
|
||||||
iptables_failed = True
|
iptables_failed = True
|
||||||
pass
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
l.warn('{cmd}: failed: {error}'.format(
|
l.warn('{cmd}: failed: {error}'.format(
|
||||||
cmd=('iptables', iptables_mac),
|
cmd=('iptables', iptables_cmd),
|
||||||
error=str(e)
|
error=str(e)
|
||||||
))
|
))
|
||||||
error_msg = str(e)
|
error_msg = str(e)
|
||||||
iptables_failed = True
|
iptables_failed = True
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Fallback on IP if HW address fails
|
if rc.exit_code == 0:
|
||||||
if client_ip:
|
l.debug('Created iptables IP rule successfully')
|
||||||
l.debug('Using client IP: {ip}'.format(
|
|
||||||
ip=client_ip
|
|
||||||
))
|
|
||||||
|
|
||||||
iptables_ip = config.get('iptables', 'iptables_ip').format(
|
|
||||||
ip_address=client_ip
|
|
||||||
)
|
|
||||||
iptables_ip = tuple(iptables_ip.split(' '))
|
|
||||||
|
|
||||||
output = BytesIO()
|
|
||||||
error = BytesIO()
|
|
||||||
try:
|
|
||||||
rc = sudo.iptables(iptables_ip, _out=output, _err=error)
|
|
||||||
|
|
||||||
if rc.exit_code == 0:
|
|
||||||
l.debug('Created iptables IP rule successfully')
|
|
||||||
return {
|
|
||||||
'error': error_msg,
|
|
||||||
'failed': False
|
|
||||||
}
|
|
||||||
except ErrorReturnCode:
|
|
||||||
error.seek(0)
|
|
||||||
error_msg = error.read()
|
|
||||||
l.warn('{cmd}: exited badly: {error}'.format(
|
|
||||||
cmd=('iptables', iptables_ip),
|
|
||||||
error=error_msg
|
|
||||||
))
|
|
||||||
iptables_failed = True
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
l.warn('{cmd}: failed: {error}'.format(
|
|
||||||
cmd=('iptables', iptables_ip),
|
|
||||||
error=str(e)
|
|
||||||
))
|
|
||||||
error_msg = str(e)
|
|
||||||
iptables_failed = True
|
|
||||||
pass
|
|
||||||
|
|
||||||
# If all else fails, error! This will be shown to end users.
|
# If all else fails, error! This will be shown to end users.
|
||||||
return {
|
return {
|
||||||
|
@ -183,3 +141,4 @@ def mac_from_ip(l, arping_args, ip):
|
||||||
if line.startswith(line_start):
|
if line.startswith(line_start):
|
||||||
m = re.search('(([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2}))', line)
|
m = re.search('(([0-9A-Fa-f]{2}[:]){5}([0-9A-Fa-f]{2}))', line)
|
||||||
if m: return m.group(0)
|
if m: return m.group(0)
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@ debug=True
|
||||||
redis_host=127.0.0.1
|
redis_host=127.0.0.1
|
||||||
redis_port=6379
|
redis_port=6379
|
||||||
|
|
||||||
plugin_ttl=120
|
plugin_timeout=180
|
||||||
|
|
||||||
|
# Can specify an alternate webpage for the portal
|
||||||
index_page=portalindex
|
index_page=portalindex
|
||||||
|
|
||||||
[logging]
|
[logging]
|
||||||
|
|
22
portal.py
22
portal.py
|
@ -1,7 +1,7 @@
|
||||||
# Captiveportal web application using Bottle.py
|
# Captiveportal web application using Bottle.py
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from pprint import pprint
|
from pprint import pprint as pp
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
|
||||||
|
@ -128,12 +128,17 @@ def dispatch_plugins():
|
||||||
))
|
))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Let plugin run for 30 more seconds than the defined plugin_timeout
|
||||||
|
# because that value is also used by the JS code to poll the job
|
||||||
|
# status so we don't want rq to kill the job before JS has timed out.
|
||||||
|
plugin_timeout = config.getint('portal', 'plugin_timeout')+30
|
||||||
|
|
||||||
# Run plugin.run()
|
# Run plugin.run()
|
||||||
try:
|
try:
|
||||||
plugin_job = Q.enqueue(
|
plugin_job = Q.enqueue(
|
||||||
plugin_module.run,
|
plugin_module.run,
|
||||||
arg,
|
arg,
|
||||||
ttl=config.getint('portal', 'plugin_ttl')
|
timeout=plugin_timeout
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
l.warn('{plugin}: {error}'.format(
|
l.warn('{plugin}: {error}'.format(
|
||||||
|
@ -166,7 +171,7 @@ app.router.add_filter('uuid', uuid_filter)
|
||||||
def portalindex():
|
def portalindex():
|
||||||
return template(
|
return template(
|
||||||
config.get('portal', 'index_page'),
|
config.get('portal', 'index_page'),
|
||||||
plugin_ttl=config.get('portal', 'plugin_ttl')
|
plugin_timeout=config.getint('portal', 'plugin_timeout')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,13 +208,22 @@ def job_status(job_id):
|
||||||
'meta': job.meta
|
'meta': job.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return json.dumps(job_data)
|
return json.dumps(job_data)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/approve', method='POST')
|
@app.route('/approve', method='POST')
|
||||||
def approve_client():
|
def approve_client():
|
||||||
response.content_type = 'application/json'
|
response.content_type = 'application/json'
|
||||||
jobs = dispatch_plugins()
|
try:
|
||||||
|
jobs = dispatch_plugins()
|
||||||
|
except Exception as e:
|
||||||
|
response.status = 500
|
||||||
|
jobs = {
|
||||||
|
'result': {
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return json.dumps(jobs)
|
return json.dumps(jobs)
|
||||||
|
|
||||||
|
|
|
@ -18,3 +18,7 @@
|
||||||
color: #D8000C;
|
color: #D8000C;
|
||||||
background-color: #FFBABA;
|
background-color: #FFBABA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#error-box {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,32 +1,62 @@
|
||||||
// Captive portal Javascript
|
// Captive portal Javascript
|
||||||
// by Stefan Midjich
|
// by Stefan Midjich @ Cygate AB
|
||||||
//
|
|
||||||
//
|
//
|
||||||
|
|
||||||
var debug = true;
|
var debug = true;
|
||||||
|
|
||||||
|
function getUrlParameter(sParam, default_value) {
|
||||||
|
var sPageURL = decodeURIComponent(window.location.search.substring(1)),
|
||||||
|
sURLVariables = sPageURL.split('&'),
|
||||||
|
sParameterName,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < sURLVariables.length; i++) {
|
||||||
|
sParameterName = sURLVariables[i].split('=');
|
||||||
|
|
||||||
|
if (sParameterName[0] === sParam) {
|
||||||
|
return sParameterName[1] === undefined ? true : sParameterName[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// This function ensures the user gets redirect to the correct destination once
|
// This function ensures the user gets redirect to the correct destination once
|
||||||
// all jobs have succeeded in the portal software.
|
// all jobs have succeeded in the portal software.
|
||||||
function do_success() {
|
function do_success() {
|
||||||
console.log('success: '+window.location);
|
var url = getUrlParameter('url', 'www.google.com');
|
||||||
|
|
||||||
// Do something like refresh the window or go to another URL.
|
// If url does not start with http the window.location redirect
|
||||||
window.location = window.href;
|
// won't work. So prefix http to url.
|
||||||
location.reload(true);
|
if (!url.startsWith('http')) {
|
||||||
|
url = 'http://'+url;
|
||||||
|
}
|
||||||
|
console.log('success: '+url);
|
||||||
|
$('#error-box').html('<p>If you\'re not automatically redirected open your browser and try any website manually.</p>');
|
||||||
|
$('#error-box').show();
|
||||||
|
$('#statusDiv').html('');
|
||||||
|
$('#approveButton').prop('disabled', false);
|
||||||
|
|
||||||
|
// Redirect user to the url paramter.
|
||||||
|
window.location = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Show an error to the user
|
// Show an error to the user
|
||||||
function do_error(message) {
|
function do_error(message) {
|
||||||
console.log('failure: '+message);
|
$('#approveButton').prop('disabled', false);
|
||||||
|
$('#statusDiv').html('');
|
||||||
|
|
||||||
$('#error-box').show();
|
$('#error-box').show();
|
||||||
$('#form-row').hide();
|
$('#error-box').html('<p>Failed. Reload page and try again or contact support.</p> ');
|
||||||
$('#error-box').append('<p>Failed. Reload page and try again or contact support.</p> ');
|
|
||||||
if (message) {
|
if (message) {
|
||||||
|
console.log('server: '+message);
|
||||||
$('#error-box').append('<p>System response: '+message+'</p>');
|
$('#error-box').append('<p>System response: '+message+'</p>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Poll the returned jobs and ensure they all succeed
|
// Poll the returned jobs and ensure they all succeed
|
||||||
function poll_jobs(data) {
|
function poll_jobs(data) {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
|
@ -45,7 +75,7 @@ function poll_jobs(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
promises.push(new Promise(function(resolve, reject) {
|
promises.push(new Promise(function(resolve, reject) {
|
||||||
var maxRun = plugin_ttl/2;
|
var maxRun = plugin_timeout/2;
|
||||||
var timesRun = 0;
|
var timesRun = 0;
|
||||||
|
|
||||||
// Timer function that polls the API for job results
|
// Timer function that polls the API for job results
|
||||||
|
@ -61,14 +91,14 @@ function poll_jobs(data) {
|
||||||
|
|
||||||
console.log(job_result);
|
console.log(job_result);
|
||||||
if(job_result.is_finished) {
|
if(job_result.is_finished) {
|
||||||
console.log('Resolving job: ', job_result.id);
|
console.log('Resolving job: ', job_result);
|
||||||
resolve(job_result);
|
resolve(job_result);
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
return(true);
|
return(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(job_result.is_failed) {
|
if(job_result.is_failed) {
|
||||||
console.log('Job failed: ', job_result.id);
|
console.log('Job failed: ', job_result);
|
||||||
reject(job_result);
|
reject(job_result);
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
return(false);
|
return(false);
|
||||||
|
@ -95,47 +125,46 @@ function poll_jobs(data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run .all() on promises array until all promises resolve
|
// Run .all() on promises array until all promises resolve
|
||||||
|
// This is resolve() above.
|
||||||
Promise.all(promises).then(function(result) {
|
Promise.all(promises).then(function(result) {
|
||||||
var success = true;
|
var success = true;
|
||||||
|
|
||||||
for(var i=0;i<result.length;i++) {
|
for(var i=0;i<result.length;i++) {
|
||||||
console.log('Job result: ', result[i]);
|
|
||||||
var r = result[i].result;
|
var r = result[i].result;
|
||||||
var m = result[i].meta;
|
var meta = result[i].meta;
|
||||||
if (r.failed && m.mandatory) {
|
if (meta.mandatory) {
|
||||||
do_error(r.error);
|
if (result[i].is_finished && result[i].is_failed) {
|
||||||
success = false;
|
do_error(r.error);
|
||||||
break;
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
do_success();
|
// This is for Steve...
|
||||||
|
// Apple devices don't poll their captiveportal URL,
|
||||||
|
// so this is for them. Android devices will do their
|
||||||
|
// own polling and close the wifi-portal before this.
|
||||||
|
setTimeout(do_success, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is reject() above.
|
||||||
}, function(reason) {
|
}, function(reason) {
|
||||||
do_error(reason);
|
do_error(reason);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('#error-box').hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Submit the form
|
// Submit the form
|
||||||
$('#approveForm').submit(function (event) {
|
$('#approveForm').submit(function (event) {
|
||||||
var api_url = '/approve';
|
var api_url = '/approve';
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
$('#error-box').hide();
|
||||||
|
$('#approveButton').prop('disabled', true);
|
||||||
|
$('#statusDiv').html('<img src="/static/images/radio.svg" alt="Loading, please wait..." />');
|
||||||
|
|
||||||
// Had some issues trying to set a background image on the button, so I'm
|
|
||||||
// just replacing it.
|
|
||||||
if ($('#approveCheckbox').is(':checked')) {
|
if ($('#approveCheckbox').is(':checked')) {
|
||||||
$('#approveButton').prop('disabled', true);
|
|
||||||
$('#approveButton').val('');
|
|
||||||
$('#approveButton').addClass('button-loading');
|
|
||||||
|
|
||||||
$('#approveButtonDiv').replaceWith('<img src="/static/images/radio.svg" alt="Loading, please wait..." />');
|
|
||||||
|
|
||||||
var ajaxReq = $.post(api_url);
|
var ajaxReq = $.post(api_url);
|
||||||
ajaxReq.done(poll_jobs);
|
ajaxReq.done(poll_jobs);
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -9,8 +9,6 @@
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="/static/css/normalize.css">
|
<link rel="stylesheet" href="/static/css/normalize.css">
|
||||||
<link rel="stylesheet" href="/static/css/skeleton.css">
|
<link rel="stylesheet" href="/static/css/skeleton.css">
|
||||||
<link rel="stylesheet" href="/static/css/captiveportal.css">
|
<link rel="stylesheet" href="/static/css/captiveportal.css">
|
||||||
|
@ -56,6 +54,8 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="error-box" class="five columns msgbox msgbox-error">
|
<div id="error-box" class="five columns msgbox msgbox-error">
|
||||||
</div>
|
</div>
|
||||||
|
<div id="statusDiv">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="form-row" class="row">
|
<div id="form-row" class="row">
|
||||||
|
@ -66,7 +66,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="approveButtonDiv" class="one column u-pull-left">
|
<div id="approveButtonDiv" class="one column u-pull-left">
|
||||||
<label>
|
<label>
|
||||||
<input id="approveButton" class="button-primary" value="Approve" type="submit">
|
<button class="button-primary" id="approveButton" type="submit">Approve</button>
|
||||||
|
<!--<input id="approveButton" class="button-primary" value="Approve" type="submit">-->
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,9 +76,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var plugin_ttl = {{plugin_ttl}};
|
var plugin_timeout = {{plugin_timeout}};
|
||||||
</script>
|
</script>
|
||||||
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
|
<script src="/static/js/jquery-1.12.2.min.js"></script>
|
||||||
<script src="/static/js/captiveportal.js"></script>
|
<script src="/static/js/captiveportal.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue