captive.whump.shanti-portal/portal.py

201 lines
5.3 KiB
Python
Raw Normal View History

# Captiveportal web application using Bottle.py
import json
from pprint import pprint
2016-04-15 20:12:17 +00:00
from uuid import UUID
from importlib import import_module
2016-04-16 14:50:35 +00:00
2016-04-16 16:48:39 +00:00
# Until pyvenv-3.4 is fixed on centos 7 support python 2.
2016-04-16 14:50:35 +00:00
try:
from configparser import RawConfigParser
except ImportError:
from ConfigParser import RawConfigParser
from logging import Formatter, getLogger, DEBUG, INFO, WARN
2016-04-05 14:40:37 +00:00
from logging.handlers import SysLogHandler, RotatingFileHandler
from redis import Redis
from rq import Queue
2016-04-15 20:12:17 +00:00
from bottle import Bottle, default_app
from bottle import request, response, template, static_file
2016-04-05 14:40:37 +00:00
config = RawConfigParser()
config.readfp(open('./portal.cfg'))
config.read(['/etc/captiveportal/portal.cfg', './portal_local.cfg'])
# Plugins configuration is separate so plugins can be disabled by having
# their section removed/commented from the config file.
plugin_config = RawConfigParser()
plugin_config.readfp(open('./plugins.cfg'))
plugin_config.read(['/etc/captiveportal/plugins.cfg'])
2016-04-05 14:40:37 +00:00
# Setup logging
logFormatter = Formatter(config.get('logging', 'log_format'))
2016-04-05 14:40:37 +00:00
l = getLogger('captiveportal')
if config.get('logging', 'log_handler') == 'syslog':
syslog_address = config.get('logging', 'syslog_address')
if syslog_address.startswith('/'):
logHandler = SysLogHandler(
2016-04-05 14:40:37 +00:00
address=syslog_address,
facility=SysLogHandler.LOG_LOCAL0
)
else:
logHandler = SysLogHandler(
2016-04-05 14:40:37 +00:00
address=(
config.get('logging', 'syslog_address'),
config.getint('logging', 'syslog_port')
),
facility=SysLogHandler.LOG_LOCAL0
)
else:
logHandler = RotatingFileHandler(
2016-04-05 14:40:37 +00:00
config.get('logging', 'log_file'),
maxBytes=config.getint('logging', 'log_max_bytes'),
backupCount=config.getint('logging', 'log_max_copies')
)
logHandler.setFormatter(logFormatter)
l.addHandler(logHandler)
2016-04-05 14:40:37 +00:00
if config.get('logging', 'log_debug'):
l.setLevel(DEBUG)
else:
l.setLevel(WARN)
# Redis Queue
R = Redis(
host=config.get('portal', 'redis_host'),
port=config.getint('portal', 'redis_port')
)
2016-04-15 20:12:17 +00:00
# Custom UUID route filter for bottle.py
def uuid_filter(config):
2016-04-16 14:50:35 +00:00
# Should catch UUIDv4 type strings
2016-04-15 20:12:17 +00:00
regexp = r'[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}'
2016-04-05 14:40:37 +00:00
2016-04-15 20:12:17 +00:00
def to_python(match):
return UUID(match, version=4)
2016-04-15 20:12:17 +00:00
def to_url(uuid):
return str(uuid)
2016-04-05 14:40:37 +00:00
2016-04-15 20:12:17 +00:00
return regexp, to_python, to_url
2016-04-05 14:40:37 +00:00
# Add plugins to job queue
def dispatch_plugins():
Q = Queue(connection=R)
jobs = []
for plugin in plugin_config.sections():
l.debug('Loading plugin {plugin}'.format(
plugin=plugin
2016-04-05 14:40:37 +00:00
))
arg = {}
2016-04-05 14:40:37 +00:00
# Import some values from WSGI environ
arg['environ'] = {}
for key in request.environ:
value = request.environ.get(key)
if isinstance(value, (int, str, float, dict, set, tuple)):
arg['environ'][key] = value
2016-04-05 14:40:37 +00:00
2016-04-16 17:09:22 +00:00
# Import all the plugin configuration values as OrderedDict
config_sections = plugin_config._sections
2016-04-17 09:21:37 +00:00
arg['config'] = dict(config_sections[plugin])
# Is plugin enabled?
if not plugin_config.getboolean(plugin, 'enabled'):
2016-04-17 09:23:50 +00:00
l.debug('{plugin}: Not enabled, skipping'.format(
plugin=plugin
))
2016-04-17 09:21:37 +00:00
continue
2016-04-16 17:09:22 +00:00
2016-04-16 16:48:39 +00:00
# Import the plugin
try:
plugin_module = import_module('plugins.'+plugin)
except Exception as e:
l.warn('{plugin}: failed import: {error}'.format(
plugin=plugin,
error=str(e)
))
continue
# Run plugin.run()
2016-04-05 14:40:37 +00:00
try:
plugin_job = Q.enqueue(
plugin_module.run,
2016-04-16 14:50:35 +00:00
arg,
ttl=config.getint('portal', 'plugin_ttl')
2016-04-05 14:40:37 +00:00
)
except Exception as e:
2016-04-16 16:48:39 +00:00
l.warn('{plugin}: {error}'.format(
error=str(e),
plugin=plugin
2016-04-05 14:40:37 +00:00
))
continue
2016-04-15 20:12:17 +00:00
jobs.append(plugin_job.id)
return jobs
2016-04-16 14:50:35 +00:00
# Define app so we can add a custom filter to app.router
2016-04-15 20:12:17 +00:00
app = Bottle()
app.router.add_filter('uuid', uuid_filter)
@app.route('/')
def portalindex():
2016-04-16 14:50:35 +00:00
return template(
'portalindex',
plugin_ttl=config.get('portal', 'plugin_ttl')
)
2016-04-15 20:12:17 +00:00
@app.route('/static/<path:path>')
def server_static(path):
2016-04-16 14:50:35 +00:00
return static_file(path, root=config.get('portal', 'static_dir'))
2016-04-15 20:12:17 +00:00
@app.route('/job/<job_id:uuid>')
def job_status(job_id):
Q = Queue(connection=R)
job = Q.fetch_job(str(job_id))
2016-04-16 14:50:35 +00:00
response.content_type = 'application/json'
if job is None:
response.status = 404
2016-04-15 20:12:17 +00:00
return json.dumps({'error': 'Job not found'})
# Get data on the job to return to the client
job_data = {
'id': job.id,
'is_failed': job.is_failed,
'is_finished': job.is_finished,
'is_queued': job.is_queued,
2016-04-16 14:50:35 +00:00
'is_started': job.is_started,
'result': job.result
2016-04-15 20:12:17 +00:00
}
return json.dumps(job_data)
@app.route('/approve', method='POST')
def approve_client():
response.content_type = 'application/json'
jobs = dispatch_plugins()
return json.dumps(jobs)
2016-04-05 14:40:37 +00:00
if __name__ == '__main__':
2016-04-15 20:12:17 +00:00
app.run(
2016-04-05 14:40:37 +00:00
host=config.get('portal', 'listen_host'),
port=config.getint('portal', 'listen_port')
)
debug(config.getbool('portal', 'debug'))
else:
2016-04-15 20:12:17 +00:00
application = app