Skip to content

Commit

Permalink
Services: Captive Portal - code cleanup in session handling and prese…
Browse files Browse the repository at this point in the history
…ntation.
  • Loading branch information
AdSchellevis committed May 22, 2024
1 parent 00e3d6d commit 78845fc
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 270 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,7 @@ public function logonAction($zoneid = 0)
(string)$cpZone->zoneid,
$userName,
$clientIp,
$authServerName,
'json'
$authServerName
]
);
$CPsession = json_decode($CPsession, true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

/**
* Copyright (C) 2015 Deciso B.V.
* Copyright (C) 2015-2024 Deciso B.V.
*
* All rights reserved.
*
Expand Down Expand Up @@ -50,27 +50,37 @@ public function listAction($zoneid = 0)
$mdlCP = new CaptivePortal();
$cpZone = $mdlCP->getByZoneID($zoneid);
if ($cpZone != null) {
$backend = new Backend();
$allClientsRaw = $backend->configdpRun(
"captiveportal list_clients",
array($cpZone->zoneid, 'json')
);
$allClients = json_decode($allClientsRaw ?? '', true);

return $allClients;
$allClientsRaw = (new Backend())->configdpRun("captiveportal list_clients", [$cpZone->zoneid]);
return json_decode($allClientsRaw ?? '', true);
} else {
// illegal zone, return empty response
return array();
return [];
}
}

/**
* search through connected clients
*/
public function searchAction()
{
$this->sessionClose();
$selected_zones = $this->request->get('selected_zones');
$records = json_decode((new Backend())->configdRun("captiveportal list_clients") ?? '', true);

$response = $this->searchRecordsetBase($records, null, 'userName', function ($key) use ($selected_zones) {
return empty($selected_zones) || in_array($key['zoneid'], $selected_zones);
});

return $response;
}

/**
* return list of available zones
* @return array available zones
*/
public function zonesAction()
{
$response = array();
$response = [];
$mdlCP = new CaptivePortal();
foreach ($mdlCP->zones->zone->iterateItems() as $zone) {
$response[(string)$zone->zoneid] = (string)$zone->description;
Expand All @@ -81,25 +91,24 @@ public function zonesAction()

/**
* disconnect a client
* @param string|int $zoneid zoneid
* @param string|int $zoneid zoneid (deprecated)
* @return array|mixed
*/
public function disconnectAction($zoneid = 0)
public function disconnectAction($zoneid = '')
{
if ($this->request->isPost() && $this->request->hasPost('sessionId')) {
$backend = new Backend();
$statusRAW = $backend->configdpRun(
$statusRAW = (new Backend())->configdpRun(
"captiveportal disconnect",
array($zoneid, $this->request->getPost('sessionId'), 'json')
[$this->request->getPost('sessionId')]
);
$status = json_decode($statusRAW, true);
$status = json_decode($statusRAW ?? '', true);
if ($status != null) {
return $status;
} else {
return array("status" => "Illegal response");
return ["status" => "Illegal response"];
}
}
return array();
return [];
}

/**
Expand All @@ -109,7 +118,7 @@ public function disconnectAction($zoneid = 0)
*/
public function connectAction($zoneid = 0)
{
$response = array();
$response = [];

if ($this->request->isPost()) {
// Get details from POST request
Expand All @@ -136,13 +145,12 @@ public function connectAction($zoneid = 0)
$backend = new Backend();
$CPsession = $backend->configdpRun(
"captiveportal allow",
array(
[
(string)$cpZone->zoneid,
$userName,
$clientIp,
'API',
'json'
)
'API'
]
);

// Only return session if configd returned a valid json response
Expand Down
166 changes: 63 additions & 103 deletions src/opnsense/mvc/app/views/OPNsense/CaptivePortal/clients.volt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{#

OPNsense® is Copyright © 20142015 by Deciso B.V.
OPNsense® is Copyright © 20142024 by Deciso B.V.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
Expand Down Expand Up @@ -30,115 +30,75 @@ POSSIBILITY OF SUCH DAMAGE.
<script>

$( document ).ready(function() {
/**
* update zone list
*/
function updateZones() {
ajaxGet("/api/captiveportal/session/zones/", {}, function(data, status) {
if (status == "success") {
$('#cp-zones').html("");
$.each(data, function(key, value) {
$('#cp-zones').append($("<option></option>").attr("value", key).text(value));
});
$('.selectpicker').selectpicker('refresh');
// link on change event
$('#cp-zones').on('change', function(){
loadSessions();
});
// initial load sessions
loadSessions();
}
});
}

/**
* load sessions for selected zone, hook events
*/
function loadSessions() {
var zoneid = $('#cp-zones').find("option:selected").val();
var gridopt = {
ajax: false,
selection: true,
multiSelect: true,
formatters: {
"commands": function (column, row) {
return '<button type="button" class="btn btn-xs btn-default command-disconnect bootgrid-tooltip" title="{{ lang._('Disconnect') }}" data-row-id="' + row.sessionId + '"><span class="fa fa-trash-o fa-fw"></span></button>';
}
}
};
if ($("#grid-clients").hasClass('bootgrid-table')) {
$("#grid-clients").bootgrid('clear');
} else {
let grid_clients = $("#grid-clients").bootgrid(gridopt).on("loaded.rs.jquery.bootgrid", function(){
// hook disconnect button
grid_clients.find(".command-disconnect").on("click", function(e) {
var zoneid = $('#cp-zones').find("option:selected").val();
var sessionId=$(this).data("row-id");
stdDialogConfirm('{{ lang._('Confirm disconnect') }}',
'{{ lang._('Do you want to disconnect the selected client?') }}',
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function () {
ajaxCall("/api/captiveportal/session/disconnect/" + zoneid + '/',
{'sessionId': sessionId}, function(data,status){
// reload grid after delete
loadSessions();
});
});
});
$(this).find(".bootgrid-tooltip").each(function (index) {
$(this).tooltip();
});
ajaxGet("/api/captiveportal/session/zones/", {}, function(data, status) {
if (status == "success") {
$('#zone-selection').empty();
$.each(data, function(key, value) {
$('#zone-selection').append($("<option></option>").attr("value", key).text(value));
});
$('.selectpicker').selectpicker('refresh');
}
ajaxGet("/api/captiveportal/session/list/"+zoneid+"/", {}, function(data, status) {
if (status == "success") {
// format records (our bootgrid doesn't like null and expects moment for datetime)
let table = [];
for (var i = 0; i < data.length; i++) {
let record = {};
$.each(data[i], function(key, value) {
record[key] = value !== null ? value : "";
});

$("#zone-selection").on("changed.bs.select", function (e) {
$("#grid-clients").bootgrid('reload');
});
$("#grid-clients").UIBootgrid({
search:'/api/captiveportal/session/search/',
datakey: 'sessionId',
commands: {
disconnect: {
title: "{{ lang._('Disconnect') }}",
method: function() {
let sessid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : '';
stdDialogConfirm(
"{{ lang._('Confirm disconnect') }}",
"{{ lang._('Do you want to disconnect the selected client?') }}",
"{{ lang._('Yes') }}",
"{{ lang._('Cancel') }}",
function () {
ajaxCall("/api/captiveportal/session/disconnect",{'sessionId': sessid}, function(data,status){
$("#grid-clients").bootgrid('reload');
});
});
table.push(record);
}
$("#grid-clients").bootgrid('append', table);
// hide actionBar on mobile
$('.actionBar').addClass('hidden-xs hidden-sm');
},
classname: 'fa fa-trash-o fa-fw',
sequence: 1,
}
},
options: {
selection: false,
multiSelect: false,
useRequestHandlerOnGet: true,
requestHandler: function(request) {
request['selected_zones'] = $("#zone-selection").val();
return request;
}
});
}
}
});

// init with first selected zone
updateZones();
$("#zone-selection-wrapper").detach().prependTo('#grid-clients-header > .row > .actionBar > .actions');
});
</script>

<div class="content-box">
<div class="content-box-main">
<div class="table-responsive">
<div class="col-sm-12">
<div class="pull-right">
<select id="cp-zones" class="selectpicker" data-width="200px"></select>
<hr/>
</div>
</div>
<div>
<table id="grid-clients" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="sessionId" data-type="string" data-identifier="true" data-visible="false">{{ lang._('Session') }}</th>
<th data-column-id="userName" data-type="string">{{ lang._('Username') }}</th>
<th data-column-id="macAddress" data-type="string" data-css-class="hidden-xs hidden-sm" data-header-css-class="hidden-xs hidden-sm">{{ lang._('MAC address') }}</th>
<th data-column-id="ipAddress" data-type="string" data-css-class="hidden-xs hidden-sm" data-header-css-class="hidden-xs hidden-sm">{{ lang._('IP address') }}</th>
<th data-column-id="startTime" data-type="datetime">{{ lang._('Connected since') }}</th>
<th data-column-id="commands" data-searchable="false" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
</tfoot>
</table>
</div>
</div>
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs"></ul>
<div class="tab-content content-box col-xs-12 __mb">
<div class="btn-group" id="zone-selection-wrapper">
<select class="selectpicker" multiple="multiple" data-live-search="true" id="zone-selection" data-width="auto" title="{{ lang._('All Zones') }}">
</select>
</div>
<table id="grid-clients" class="table table-condensed table-hover table-striped table-responsive">
<thead>
<tr>
<th data-column-id="sessionId" data-type="string" data-identifier="true" data-visible="false">{{ lang._('Session') }}</th>
<th data-column-id="userName" data-type="string">{{ lang._('Username') }}</th>
<th data-column-id="macAddress" data-type="string" data-css-class="hidden-xs hidden-sm" data-header-css-class="hidden-xs hidden-sm">{{ lang._('MAC address') }}</th>
<th data-column-id="ipAddress" data-type="string" data-css-class="hidden-xs hidden-sm" data-header-css-class="hidden-xs hidden-sm">{{ lang._('IP address') }}</th>
<th data-column-id="startTime" data-type="datetime">{{ lang._('Connected since') }}</th>
<th data-column-id="commands" data-searchable="false" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
60 changes: 20 additions & 40 deletions src/opnsense/scripts/OPNsense/CaptivePortal/allow.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/local/bin/python3

"""
Copyright (c) 2015-2019 Ad Schellevis <ad@opnsense.org>
Copyright (c) 2015-2024 Ad Schellevis <ad@opnsense.org>
All rights reserved.
Redistribution and use in source and binary forms, with or without
Expand All @@ -28,48 +28,28 @@
--------------------------------------------------------------------------------------
allow user/host to captive portal
"""
import argparse
import sys
import ujson
from lib.db import DB
from lib.arp import ARP
from lib.ipfw import IPFW

# parse input parameters
parameters = {'username': '', 'ip_address': None, 'zoneid': None, 'authenticated_via': None, 'output_type': 'plain'}
current_param = None
for param in sys.argv[1:]:
if len(param) > 1 and param[0] == '/':
current_param = param[1:].lower()
elif current_param is not None:
if current_param in parameters:
parameters[current_param] = param.strip()
current_param = None

# create new session
if parameters['ip_address'] is not None and parameters['zoneid'] is not None:
cpDB = DB()
cpIPFW = IPFW()
arp_entry = ARP().get_by_ipaddress(parameters['ip_address'])
if arp_entry is not None:
mac_address = arp_entry['mac']
else:
mac_address = None

response = cpDB.add_client(zoneid=parameters['zoneid'],
authenticated_via=parameters['authenticated_via'],
username=parameters['username'],
ip_address=parameters['ip_address'],
mac_address=mac_address
)
cpIPFW.add_to_table(table_number=parameters['zoneid'], address=parameters['ip_address'])
response['clientState'] = 'AUTHORIZED'
else:
response = {'clientState': 'UNKNOWN'}


# output result as plain text or json
if parameters['output_type'] != 'json':
for item in response:
print ('%20s %s' % (item, response[item]))
else:
print(ujson.dumps(response))
parser = argparse.ArgumentParser()
parser.add_argument('-username', help='username', type=str, required=True)
parser.add_argument('-zoneid', help='zone number to allow this user in', type=str, required=True)
parser.add_argument('-authenticated_via', help='authentication source', type=str)
parser.add_argument('-ip_address', help='source ip address', type=str)
args = parser.parse_args()

arp_entry = ARP().get_by_ipaddress(args.ip_address)
response = DB().add_client(
zoneid=args.zoneid,
authenticated_via=args.authenticated_via,
username=args.username,
ip_address=args.ip_address,
mac_address=arp_entry['mac'] if arp_entry is not None else None
)
IPFW().add_to_table(table_number=args.zoneid, address=args.ip_address)
response['clientState'] = 'AUTHORIZED'
print(ujson.dumps(response))
Loading

0 comments on commit 78845fc

Please sign in to comment.