(captive portal) work in progress rewrite captive portal feature using new framework and standards... lots of things to do here, but we've started.

This commit is contained in:
Ad Schellevis 2015-09-22 16:32:57 +02:00
parent b496dc52d1
commit 44edcd0cde
7 changed files with 555 additions and 0 deletions

View File

@ -0,0 +1,66 @@
<?php
/**
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\CaptivePortal\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Core\Backend;
/**
* Class ServiceController
* @package OPNsense\CaptivePortal
*/
class ServiceController extends ApiControllerBase
{
/**
* reconfigure captive portal
*/
public function reconfigureAction()
{
if ($this->request->isPost()) {
// close session for long running action
$this->sessionClose();
$backend = new Backend();
// the ipfw rules need to know about all the zones, so we need to reload ipfw for the portal to work
$backend->configdRun("template reload OPNsense.IPFW");
$bckresult = trim($backend->configdRun("ipfw reload"));
if ($bckresult == "OK") {
// TODO: implement portal webservers restart/reconfigure
$status = "ok";
} else {
$status = "error reloading captive portal (".$bckresult.")";
}
return array("status" => $status);
} else {
return array("status" => "failed");
}
}
}

View File

@ -0,0 +1,224 @@
<?php
/**
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\CaptivePortal\Api;
use \OPNsense\Base\ApiControllerBase;
use \OPNsense\Core\Config;
use \OPNsense\CaptivePortal\CaptivePortal;
use \OPNsense\Base\UIModelGrid;
/**
* Class SettingsController Handles settings related API actions for Captive Portal
* @package OPNsense\TrafficShaper
*/
class SettingsController extends ApiControllerBase
{
/**
* validate and save model after update or insertion.
* Use the reference node and tag to rename validation output for a specific node to a new offset, which makes
* it easier to reference specific uuids without having to use them in the frontend descriptions.
* @param $mdlShaper
* @param $node reference node, to use as relative offset
* @param $reference reference for validation output, used to rename the validation output keys
* @return array result / validation output
*/
private function save($mdlShaper, $node = null, $reference = null)
{
$result = array("result"=>"failed","validations" => array());
// perform validation
$valMsgs = $mdlShaper->performValidation();
foreach ($valMsgs as $field => $msg) {
// replace absolute path to attribute for relative one at uuid.
if ($node != null) {
$fieldnm = str_replace($node->__reference, $reference, $msg->getField());
$result["validations"][$fieldnm] = $msg->getMessage();
} else {
$result["validations"][$msg->getField()] = $msg->getMessage();
}
}
// serialize model to config and save when there are no validation errors
if (count($result['validations']) == 0) {
// save config if validated correctly
$mdlShaper->serializeToConfig();
Config::getInstance()->save();
$result = array("result" => "saved");
}
return $result;
}
/**
* retrieve zone settings or return defaults
* @param $uuid item unique id
* @return array
*/
public function getZoneAction($uuid = null)
{
$mdlCP = new CaptivePortal();
if ($uuid != null) {
$node = $mdlCP->getNodeByReference('zones.zone.'.$uuid);
if ($node != null) {
// return node
return array("zone" => $node->getNodes());
}
} else {
// generate new node, but don't save to disc
$node = $mdlCP->zones->zone->add() ;
return array("zone" => $node->getNodes());
}
return array();
}
/**
* update zone with given properties
* @param $uuid item unique id
* @return array
*/
public function setZoneAction($uuid)
{
if ($this->request->isPost() && $this->request->hasPost("zone")) {
$mdlCP = new CaptivePortal();
if ($uuid != null) {
$node = $mdlCP->getNodeByReference('zones.zone.'.$uuid);
if ($node != null) {
$node->setNodes($this->request->getPost("zone"));
return $this->save($mdlCP, $node, "zone");
}
}
}
return array("result"=>"failed");
}
/**
* add new zone and set with attributes from post
* @return array
*/
public function addZoneAction()
{
$result = array("result"=>"failed");
if ($this->request->isPost() && $this->request->hasPost("zone")) {
$mdlCP = new CaptivePortal();
$node = $mdlCP->zones->zone->Add();
$node->setNodes($this->request->getPost("zone"));
return $this->save($mdlCP, $node, "zone");
}
return $result;
}
/**
* delete zone by uuid
* @param $uuid item unique id
* @return array status
*/
public function delZoneAction($uuid)
{
$result = array("result"=>"failed");
if ($this->request->isPost()) {
$mdlCP = new CaptivePortal();
if ($uuid != null) {
if ($mdlCP->zones->zone->del($uuid)) {
// if item is removed, serialize to config and save
$mdlCP->serializeToConfig();
Config::getInstance()->save();
$result['result'] = 'deleted';
} else {
$result['result'] = 'not found';
}
}
}
return $result;
}
/**
* toggle zone by uuid (enable/disable)
* @param $uuid item unique id
* @param $enabled desired state enabled(1)/disabled(1), leave empty for toggle
* @return array status
*/
public function toggleZoneAction($uuid, $enabled = null)
{
$result = array("result" => "failed");
if ($this->request->isPost()) {
$mdlCP = new CaptivePortal();
if ($uuid != null) {
$node = $mdlCP->getNodeByReference('zones.zone.' . $uuid);
if ($node != null) {
if ($enabled == "0" || $enabled == "1") {
$node->enabled = (string)$enabled;
} elseif ((string)$node->enabled == "1") {
$node->enabled = "0";
} else {
$node->enabled = "1";
}
$result['result'] = $node->enabled;
// if item has toggled, serialize to config and save
$mdlCP->serializeToConfig();
Config::getInstance()->save();
}
}
}
return $result;
}
/**
* search captive portal zones
* @return array
*/
public function searchZonesAction()
{
if ($this->request->isPost()) {
$this->sessionClose();
// fetch query parameters
$itemsPerPage = $this->request->getPost('rowCount', 'int', 9999);
$currentPage = $this->request->getPost('current', 'int', 1);
$sortBy = array("number");
$sortDescending = false;
if ($this->request->hasPost('sort') && is_array($this->request->getPost("sort"))) {
$sortBy = array_keys($this->request->getPost("sort"));
if ($this->request->getPost("sort")[$sortBy[0]] == "desc") {
$sortDescending = true;
}
}
$searchPhrase = $this->request->getPost('searchPhrase', 'string', '');
// create model and fetch query resuls
$fields = array("enabled", "description", "zoneid");
$mdlCP = new CaptivePortal();
$grid = new UIModelGrid($mdlCP->zones->zone);
return $grid->fetch($fields, $itemsPerPage, $currentPage, $sortBy, $sortDescending, $searchPhrase);
} else {
return array();
}
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\CaptivePortal;
/**
* Class IndexController
* @package OPNsense\CaptivePortal
*/
class IndexController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->title = "Captive Portal";
// link rule dialog
$this->view->formDialogZone = $this->getForm("dialogZone");
// choose template
$this->view->pick('OPNsense/CaptivePortal/index');
}
}

View File

@ -0,0 +1,28 @@
<form>
<field>
<id>zone.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>enable this zone</help>
</field>
<field>
<id>zone.zoneid</id>
<label>zone#</label>
<type>info</type>
<help>internal number used for this zone</help>
</field>
<field>
<id>zone.interfaces</id>
<label>interfaces</label>
<type>select_multiple</type>
<style>tokenize</style>
<help><![CDATA[Select the interface(s) to enable for captive portal.]]></help>
<hint>Type or select interface.</hint>
</field>
<field>
<id>zone.description</id>
<label>description</label>
<type>text</type>
<help>Description to identify this zone.</help>
</field>
</form>

View File

@ -0,0 +1,39 @@
<?php
/**
* Copyright (C) 2015 Deciso B.V.
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
namespace OPNsense\CaptivePortal;
use OPNsense\Base\BaseModel;
/**
* Class CaptivePortal
* @package OPNsense\CaptivePortal
*/
class CaptivePortal extends BaseModel
{
}

View File

@ -0,0 +1,36 @@
<model>
<mount>//OPNsense/captiveportal</mount>
<description>
Captive portal application model
</description>
<items>
<zones>
<zone type="ArrayField">
<enabled type="BooleanField">
<default>1</default>
<Required>Y</Required>
</enabled>
<zoneid type="AutoNumberField">
<MinimumValue>0</MinimumValue>
<MaximumValue>19</MaximumValue>
<ValidationMessage>Maximum number of zones reached</ValidationMessage>
<Required>Y</Required>
</zoneid>
<interfaces type="InterfaceField">
<Required>Y</Required>
<multiple>Y</multiple>
<default>lan</default>
<filters>
<enable>/^(?!0).*$/</enable>
<ipaddr>/^((?!dhcp).)*$/</ipaddr>
</filters>
</interfaces>
<description type="TextField">
<Required>Y</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.,_\x{00A0}-\x{FFFF}]){1,255}$/u</mask>
<ValidationMessage>Description should be a string between 1 and 255 characters</ValidationMessage>
</description>
</zone>
</zones>
</items>
</model>

View File

@ -0,0 +1,117 @@
{#
OPNsense® is Copyright © 2014 2015 by Deciso B.V.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
#}
<script type="text/javascript">
$( document ).ready(function() {
/*************************************************************************************************************
* link grid actions
*************************************************************************************************************/
$("#grid-zones").UIBootgrid(
{ search:'/api/captiveportal/settings/searchZones',
get:'/api/captiveportal/settings/getZone/',
set:'/api/captiveportal/settings/setZone/',
add:'/api/captiveportal/settings/addZone/',
del:'/api/captiveportal/settings/delZone/',
toggle:'/api/captiveportal/settings/toggleZone/'
}
);
/*************************************************************************************************************
* Commands
*************************************************************************************************************/
/**
* Reconfigure
*/
$("#reconfigureAct").click(function(){
$("#reconfigureAct_progress").addClass("fa fa-spinner fa-pulse");
ajaxCall(url="/api/captiveportal/service/reconfigure", sendData={}, callback=function(data,status) {
// when done, disable progress animation.
$("#reconfigureAct_progress").removeClass("fa fa-spinner fa-pulse");
if (status != "success" || data['status'] != 'ok') {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_WARNING,
title: "{{ lang._('Error reconfiguring captiveportal') }}",
message: data['status'],
draggable: true
});
}
});
});
});
</script>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs">
<li class="active"><a data-toggle="tab" href="#zones">{{ lang._('Zones') }}</a></li>
<li><a data-toggle="tab" href="#general">{{ lang._('General') }}</a></li>
</ul>
<div class="tab-content content-box tab-content">
<div id="zones" class="tab-pane fade in active">
<!-- tab page "zones" -->
<table id="grid-zones" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="DialogZone">
<thead>
<tr>
<th data-column-id="enabled" data-width="6em" data-type="string" data-formatter="rowtoggle">{{ lang._('Enabled') }}</th>
<th data-column-id="zoneid" data-type="number" data-visible="false">{{ lang._('Zoneid') }}</th>
<th data-column-id="description" data-type="string">{{ lang._('Description') }}</th>
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
<th data-column-id="uuid" data-type="string" data-identifier="true" data-visible="false">{{ lang._('ID') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button data-action="deleteSelected" type="button" class="btn btn-xs btn-default"><span class="fa fa-trash-o"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div id="general" class="tab-pane fade in">
</div>
<div class="col-md-12">
<hr/>
<button class="btn btn-primary" id="reconfigureAct" type="button"><b>{{ lang._('Apply') }}</b><i id="reconfigureAct_progress" class=""></i></button>
</div>
</div>
{# include dialogs #}
{{ partial("layout_partials/base_dialog",['fields':formDialogZone,'id':'DialogZone','label':'Edit zone'])}}