Skip to content

Commit 78845fc

Browse files
committed
Services: Captive Portal - code cleanup in session handling and presentation.
1 parent 00e3d6d commit 78845fc

File tree

9 files changed

+162
-270
lines changed

9 files changed

+162
-270
lines changed

src/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/Api/AccessController.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,7 @@ public function logonAction($zoneid = 0)
190190
(string)$cpZone->zoneid,
191191
$userName,
192192
$clientIp,
193-
$authServerName,
194-
'json'
193+
$authServerName
195194
]
196195
);
197196
$CPsession = json_decode($CPsession, true);

src/opnsense/mvc/app/controllers/OPNsense/CaptivePortal/Api/SessionController.php

+32-24
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
/**
4-
* Copyright (C) 2015 Deciso B.V.
4+
* Copyright (C) 2015-2024 Deciso B.V.
55
*
66
* All rights reserved.
77
*
@@ -50,27 +50,37 @@ public function listAction($zoneid = 0)
5050
$mdlCP = new CaptivePortal();
5151
$cpZone = $mdlCP->getByZoneID($zoneid);
5252
if ($cpZone != null) {
53-
$backend = new Backend();
54-
$allClientsRaw = $backend->configdpRun(
55-
"captiveportal list_clients",
56-
array($cpZone->zoneid, 'json')
57-
);
58-
$allClients = json_decode($allClientsRaw ?? '', true);
59-
60-
return $allClients;
53+
$allClientsRaw = (new Backend())->configdpRun("captiveportal list_clients", [$cpZone->zoneid]);
54+
return json_decode($allClientsRaw ?? '', true);
6155
} else {
6256
// illegal zone, return empty response
63-
return array();
57+
return [];
6458
}
6559
}
6660

61+
/**
62+
* search through connected clients
63+
*/
64+
public function searchAction()
65+
{
66+
$this->sessionClose();
67+
$selected_zones = $this->request->get('selected_zones');
68+
$records = json_decode((new Backend())->configdRun("captiveportal list_clients") ?? '', true);
69+
70+
$response = $this->searchRecordsetBase($records, null, 'userName', function ($key) use ($selected_zones) {
71+
return empty($selected_zones) || in_array($key['zoneid'], $selected_zones);
72+
});
73+
74+
return $response;
75+
}
76+
6777
/**
6878
* return list of available zones
6979
* @return array available zones
7080
*/
7181
public function zonesAction()
7282
{
73-
$response = array();
83+
$response = [];
7484
$mdlCP = new CaptivePortal();
7585
foreach ($mdlCP->zones->zone->iterateItems() as $zone) {
7686
$response[(string)$zone->zoneid] = (string)$zone->description;
@@ -81,25 +91,24 @@ public function zonesAction()
8191

8292
/**
8393
* disconnect a client
84-
* @param string|int $zoneid zoneid
94+
* @param string|int $zoneid zoneid (deprecated)
8595
* @return array|mixed
8696
*/
87-
public function disconnectAction($zoneid = 0)
97+
public function disconnectAction($zoneid = '')
8898
{
8999
if ($this->request->isPost() && $this->request->hasPost('sessionId')) {
90-
$backend = new Backend();
91-
$statusRAW = $backend->configdpRun(
100+
$statusRAW = (new Backend())->configdpRun(
92101
"captiveportal disconnect",
93-
array($zoneid, $this->request->getPost('sessionId'), 'json')
102+
[$this->request->getPost('sessionId')]
94103
);
95-
$status = json_decode($statusRAW, true);
104+
$status = json_decode($statusRAW ?? '', true);
96105
if ($status != null) {
97106
return $status;
98107
} else {
99-
return array("status" => "Illegal response");
108+
return ["status" => "Illegal response"];
100109
}
101110
}
102-
return array();
111+
return [];
103112
}
104113

105114
/**
@@ -109,7 +118,7 @@ public function disconnectAction($zoneid = 0)
109118
*/
110119
public function connectAction($zoneid = 0)
111120
{
112-
$response = array();
121+
$response = [];
113122

114123
if ($this->request->isPost()) {
115124
// Get details from POST request
@@ -136,13 +145,12 @@ public function connectAction($zoneid = 0)
136145
$backend = new Backend();
137146
$CPsession = $backend->configdpRun(
138147
"captiveportal allow",
139-
array(
148+
[
140149
(string)$cpZone->zoneid,
141150
$userName,
142151
$clientIp,
143-
'API',
144-
'json'
145-
)
152+
'API'
153+
]
146154
);
147155

148156
// Only return session if configd returned a valid json response
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{#
22

3-
OPNsense® is Copyright © 20142015 by Deciso B.V.
3+
OPNsense® is Copyright © 20142024 by Deciso B.V.
44
All rights reserved.
55

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

3232
$( document ).ready(function() {
33-
/**
34-
* update zone list
35-
*/
36-
function updateZones() {
37-
ajaxGet("/api/captiveportal/session/zones/", {}, function(data, status) {
38-
if (status == "success") {
39-
$('#cp-zones').html("");
40-
$.each(data, function(key, value) {
41-
$('#cp-zones').append($("<option></option>").attr("value", key).text(value));
42-
});
43-
$('.selectpicker').selectpicker('refresh');
44-
// link on change event
45-
$('#cp-zones').on('change', function(){
46-
loadSessions();
47-
});
48-
// initial load sessions
49-
loadSessions();
50-
}
51-
});
52-
}
53-
54-
/**
55-
* load sessions for selected zone, hook events
56-
*/
57-
function loadSessions() {
58-
var zoneid = $('#cp-zones').find("option:selected").val();
59-
var gridopt = {
60-
ajax: false,
61-
selection: true,
62-
multiSelect: true,
63-
formatters: {
64-
"commands": function (column, row) {
65-
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>';
66-
}
67-
}
68-
};
69-
if ($("#grid-clients").hasClass('bootgrid-table')) {
70-
$("#grid-clients").bootgrid('clear');
71-
} else {
72-
let grid_clients = $("#grid-clients").bootgrid(gridopt).on("loaded.rs.jquery.bootgrid", function(){
73-
// hook disconnect button
74-
grid_clients.find(".command-disconnect").on("click", function(e) {
75-
var zoneid = $('#cp-zones').find("option:selected").val();
76-
var sessionId=$(this).data("row-id");
77-
stdDialogConfirm('{{ lang._('Confirm disconnect') }}',
78-
'{{ lang._('Do you want to disconnect the selected client?') }}',
79-
'{{ lang._('Yes') }}', '{{ lang._('Cancel') }}', function () {
80-
ajaxCall("/api/captiveportal/session/disconnect/" + zoneid + '/',
81-
{'sessionId': sessionId}, function(data,status){
82-
// reload grid after delete
83-
loadSessions();
84-
});
85-
});
86-
});
87-
$(this).find(".bootgrid-tooltip").each(function (index) {
88-
$(this).tooltip();
89-
});
33+
ajaxGet("/api/captiveportal/session/zones/", {}, function(data, status) {
34+
if (status == "success") {
35+
$('#zone-selection').empty();
36+
$.each(data, function(key, value) {
37+
$('#zone-selection').append($("<option></option>").attr("value", key).text(value));
9038
});
39+
$('.selectpicker').selectpicker('refresh');
9140
}
92-
ajaxGet("/api/captiveportal/session/list/"+zoneid+"/", {}, function(data, status) {
93-
if (status == "success") {
94-
// format records (our bootgrid doesn't like null and expects moment for datetime)
95-
let table = [];
96-
for (var i = 0; i < data.length; i++) {
97-
let record = {};
98-
$.each(data[i], function(key, value) {
99-
record[key] = value !== null ? value : "";
41+
});
42+
43+
$("#zone-selection").on("changed.bs.select", function (e) {
44+
$("#grid-clients").bootgrid('reload');
45+
});
46+
$("#grid-clients").UIBootgrid({
47+
search:'/api/captiveportal/session/search/',
48+
datakey: 'sessionId',
49+
commands: {
50+
disconnect: {
51+
title: "{{ lang._('Disconnect') }}",
52+
method: function() {
53+
let sessid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : '';
54+
stdDialogConfirm(
55+
"{{ lang._('Confirm disconnect') }}",
56+
"{{ lang._('Do you want to disconnect the selected client?') }}",
57+
"{{ lang._('Yes') }}",
58+
"{{ lang._('Cancel') }}",
59+
function () {
60+
ajaxCall("/api/captiveportal/session/disconnect",{'sessionId': sessid}, function(data,status){
61+
$("#grid-clients").bootgrid('reload');
62+
});
10063
});
101-
table.push(record);
102-
}
103-
$("#grid-clients").bootgrid('append', table);
104-
// hide actionBar on mobile
105-
$('.actionBar').addClass('hidden-xs hidden-sm');
64+
},
65+
classname: 'fa fa-trash-o fa-fw',
66+
sequence: 1,
67+
}
68+
},
69+
options: {
70+
selection: false,
71+
multiSelect: false,
72+
useRequestHandlerOnGet: true,
73+
requestHandler: function(request) {
74+
request['selected_zones'] = $("#zone-selection").val();
75+
return request;
10676
}
107-
});
108-
}
77+
}
78+
});
10979

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

115-
<div class="content-box">
116-
<div class="content-box-main">
117-
<div class="table-responsive">
118-
<div class="col-sm-12">
119-
<div class="pull-right">
120-
<select id="cp-zones" class="selectpicker" data-width="200px"></select>
121-
<hr/>
122-
</div>
123-
</div>
124-
<div>
125-
<table id="grid-clients" class="table table-condensed table-hover table-striped table-responsive">
126-
<thead>
127-
<tr>
128-
<th data-column-id="sessionId" data-type="string" data-identifier="true" data-visible="false">{{ lang._('Session') }}</th>
129-
<th data-column-id="userName" data-type="string">{{ lang._('Username') }}</th>
130-
<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>
131-
<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>
132-
<th data-column-id="startTime" data-type="datetime">{{ lang._('Connected since') }}</th>
133-
<th data-column-id="commands" data-searchable="false" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
134-
</tr>
135-
</thead>
136-
<tbody>
137-
</tbody>
138-
<tfoot>
139-
</tfoot>
140-
</table>
141-
</div>
142-
</div>
84+
<ul class="nav nav-tabs" data-tabs="tabs" id="maintabs"></ul>
85+
<div class="tab-content content-box col-xs-12 __mb">
86+
<div class="btn-group" id="zone-selection-wrapper">
87+
<select class="selectpicker" multiple="multiple" data-live-search="true" id="zone-selection" data-width="auto" title="{{ lang._('All Zones') }}">
88+
</select>
14389
</div>
90+
<table id="grid-clients" class="table table-condensed table-hover table-striped table-responsive">
91+
<thead>
92+
<tr>
93+
<th data-column-id="sessionId" data-type="string" data-identifier="true" data-visible="false">{{ lang._('Session') }}</th>
94+
<th data-column-id="userName" data-type="string">{{ lang._('Username') }}</th>
95+
<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>
96+
<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>
97+
<th data-column-id="startTime" data-type="datetime">{{ lang._('Connected since') }}</th>
98+
<th data-column-id="commands" data-searchable="false" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
99+
</tr>
100+
</thead>
101+
<tbody>
102+
</tbody>
103+
</table>
144104
</div>
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/local/bin/python3
22

33
"""
4-
Copyright (c) 2015-2019 Ad Schellevis <[email protected]>
4+
Copyright (c) 2015-2024 Ad Schellevis <[email protected]>
55
All rights reserved.
66
77
Redistribution and use in source and binary forms, with or without
@@ -28,48 +28,28 @@
2828
--------------------------------------------------------------------------------------
2929
allow user/host to captive portal
3030
"""
31+
import argparse
3132
import sys
3233
import ujson
3334
from lib.db import DB
3435
from lib.arp import ARP
3536
from lib.ipfw import IPFW
3637

37-
# parse input parameters
38-
parameters = {'username': '', 'ip_address': None, 'zoneid': None, 'authenticated_via': None, 'output_type': 'plain'}
39-
current_param = None
40-
for param in sys.argv[1:]:
41-
if len(param) > 1 and param[0] == '/':
42-
current_param = param[1:].lower()
43-
elif current_param is not None:
44-
if current_param in parameters:
45-
parameters[current_param] = param.strip()
46-
current_param = None
47-
48-
# create new session
49-
if parameters['ip_address'] is not None and parameters['zoneid'] is not None:
50-
cpDB = DB()
51-
cpIPFW = IPFW()
52-
arp_entry = ARP().get_by_ipaddress(parameters['ip_address'])
53-
if arp_entry is not None:
54-
mac_address = arp_entry['mac']
55-
else:
56-
mac_address = None
57-
58-
response = cpDB.add_client(zoneid=parameters['zoneid'],
59-
authenticated_via=parameters['authenticated_via'],
60-
username=parameters['username'],
61-
ip_address=parameters['ip_address'],
62-
mac_address=mac_address
63-
)
64-
cpIPFW.add_to_table(table_number=parameters['zoneid'], address=parameters['ip_address'])
65-
response['clientState'] = 'AUTHORIZED'
66-
else:
67-
response = {'clientState': 'UNKNOWN'}
68-
69-
70-
# output result as plain text or json
71-
if parameters['output_type'] != 'json':
72-
for item in response:
73-
print ('%20s %s' % (item, response[item]))
74-
else:
75-
print(ujson.dumps(response))
38+
parser = argparse.ArgumentParser()
39+
parser.add_argument('-username', help='username', type=str, required=True)
40+
parser.add_argument('-zoneid', help='zone number to allow this user in', type=str, required=True)
41+
parser.add_argument('-authenticated_via', help='authentication source', type=str)
42+
parser.add_argument('-ip_address', help='source ip address', type=str)
43+
args = parser.parse_args()
44+
45+
arp_entry = ARP().get_by_ipaddress(args.ip_address)
46+
response = DB().add_client(
47+
zoneid=args.zoneid,
48+
authenticated_via=args.authenticated_via,
49+
username=args.username,
50+
ip_address=args.ip_address,
51+
mac_address=arp_entry['mac'] if arp_entry is not None else None
52+
)
53+
IPFW().add_to_table(table_number=args.zoneid, address=args.ip_address)
54+
response['clientState'] = 'AUTHORIZED'
55+
print(ujson.dumps(response))

0 commit comments

Comments
 (0)