mirror of
https://github.com/netbirdio/plugins.git
synced 2026-05-22 18:44:07 -07:00
Add feature to build rules based on users, groups on Proxy/Squid (#337)
This commit is contained in:
committed by
Franco Fichtner
parent
31dd6f6d51
commit
5469067606
@@ -0,0 +1,6 @@
|
||||
rm -f /usr/local/etc/squid/pre-auth/ProxyUserACL.conf
|
||||
rm -f /usr/local/etc/squid/groupACL_*.txt
|
||||
rm -f /usr/local/etc/squid/userACL_*.txt
|
||||
if [ -f /var/run/squid/squid.pid ]; then
|
||||
service squid reload
|
||||
fi
|
||||
@@ -0,0 +1,5 @@
|
||||
/usr/local/opnsense/scripts/OPNsense/ProxyUserACL/reconfigure.php
|
||||
if [ -f /var/run/squid/squid.pid ]; then
|
||||
service squid reload
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
PLUGIN_NAME= web-proxy-useracl
|
||||
PLUGIN_VERSION= 0.0.1
|
||||
PLUGIN_COMMENT= Group & User Squid ACL
|
||||
PLUGIN_DEPENDS=
|
||||
PLUGIN_MAINTAINER= kekek2@ya.ru
|
||||
PLUGIN_WWW= http://smart-soft.ru
|
||||
PLUGIN_DEVEL= yes
|
||||
|
||||
.include "../../Mk/plugins.mk"
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2017 Smart-Soft
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
function proxy_useracl_configure()
|
||||
{
|
||||
return [
|
||||
'webproxy' => ['proxy_useracl_squid_hook:2'],
|
||||
];
|
||||
}
|
||||
|
||||
function proxy_useracl_squid_hook()
|
||||
{
|
||||
configd_run('template reload OPNsense/ProxyUserACL');
|
||||
configd_run('proxyuseracl reconfigure');
|
||||
}
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2017 Smart-Soft
|
||||
*
|
||||
* 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\ProxyUserACL\Api;
|
||||
|
||||
use \OPNsense\Base\ApiMutableModelControllerBase;
|
||||
use \OPNsense\Core\Config;
|
||||
use \OPNsense\Base\UIModelGrid;
|
||||
use \OPNsense\Auth\AuthenticationFactory;
|
||||
use \OPNsense\Proxy\Proxy;
|
||||
|
||||
/**
|
||||
* Class SettingsController Handles settings related API actions for the ProxyUserACL
|
||||
* @package OPNsense\ProxySSO
|
||||
*/
|
||||
class SettingsController extends ApiMutableModelControllerBase
|
||||
{
|
||||
static protected $internalModelName = 'proxyuseracl';
|
||||
static protected $internalModelClass = '\OPNsense\ProxyUserACL\ProxyUserACL';
|
||||
|
||||
/**
|
||||
*
|
||||
* search ACL
|
||||
* @return array
|
||||
*/
|
||||
public function searchACLAction()
|
||||
{
|
||||
$this->sessionClose();
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
foreach ($mdlProxyUserACL->general->ACLs->ACL->getNodes() as $uuid => $acl) {
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$uuid}->Domains = $this->decode($mdlProxyUserACL->general->ACLs->ACL->{$uuid}->Domains);
|
||||
}
|
||||
$grid = new UIModelGrid($mdlProxyUserACL->general->ACLs->ACL);
|
||||
return $grid->fetchBindRequest($this->request, array("Group", "Name", "Domains", "Black", "Priority", "uuid"),
|
||||
"Priority");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* add ACL
|
||||
* @return array
|
||||
*/
|
||||
public function addACLAction()
|
||||
{
|
||||
$result = array("result" => "failed");
|
||||
if ($this->request->isPost() && $this->request->hasPost("ACL")) {
|
||||
|
||||
$result = array("result" => "failed", "validations" => array());
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
$post = $this->request->getPost("ACL");
|
||||
$post["Hex"] = $this->strToHex($post["Name"]);
|
||||
|
||||
$count = count($mdlProxyUserACL->general->ACLs->ACL->getNodes());
|
||||
if ($post["Priority"] > $count) {
|
||||
$post["Priority"] = $count;
|
||||
}
|
||||
foreach ($mdlProxyUserACL->general->ACLs->ACL->sortedBy("Priority", true) as $acl) {
|
||||
$key = $acl->getAttributes()["uuid"];
|
||||
$priority = (string)$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority;
|
||||
if ($priority < $post["Priority"]) {
|
||||
break;
|
||||
}
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority = (string)($priority + 1);
|
||||
}
|
||||
$node = $mdlProxyUserACL->general->ACLs->ACL->Add();
|
||||
$post["Domains"] = \OPNsense\Proxy\Api\SettingsController::encode($post["Domains"]);
|
||||
$node->setNodes($post);
|
||||
$find = $this->checkName($post["Name"], $post["Group"]);
|
||||
if ($find !== true) {
|
||||
$result["validations"]["ACL.Name"] = $find;
|
||||
}
|
||||
$valMsgs = $mdlProxyUserACL->performValidation();
|
||||
|
||||
foreach ($valMsgs as $field => $msg) {
|
||||
$fieldnm = str_replace($node->__reference, "ACL", $msg->getField());
|
||||
$result["validations"][$fieldnm] = $msg->getMessage();
|
||||
}
|
||||
|
||||
if (count($result['validations']) <= 0) {
|
||||
|
||||
// save config if validated correctly
|
||||
$mdlProxyUserACL->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
return array("result" => "saved");
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* get ACL
|
||||
* @return array
|
||||
*/
|
||||
public function getACLAction($uuid = null)
|
||||
{
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
if ($uuid == null) {
|
||||
// generate new node, but don't save to disc
|
||||
$node = $mdlProxyUserACL->general->ACLs->ACL->add();
|
||||
return array("ACL" => $node->getNodes());
|
||||
}
|
||||
|
||||
$node = $mdlProxyUserACL->getNodeByReference('general.ACLs.ACL.' . $uuid);
|
||||
if ($node != null) {
|
||||
// return node
|
||||
$node->Domains = $this->decode((string)$node->Domains);
|
||||
return array("ACL" => $node->getNodes());
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* set ACL
|
||||
* @return array
|
||||
*/
|
||||
public function setACLAction($uuid)
|
||||
{
|
||||
$result = array("result" => "failed");
|
||||
if ($this->request->isPost() && $this->request->hasPost("ACL")) {
|
||||
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
if ($uuid != null) {
|
||||
|
||||
$node = $mdlProxyUserACL->getNodeByReference('general.ACLs.ACL.' . $uuid);
|
||||
if ($node != null) {
|
||||
$result = array("result" => "failed", "validations" => array());
|
||||
$ACLInfo = $this->request->getPost("ACL");
|
||||
$ACLInfo["Hex"] = $this->strToHex($ACLInfo["Name"]);
|
||||
$ACLInfo["Domains"] = \OPNsense\Proxy\Api\SettingsController::encode($ACLInfo["Domains"]);
|
||||
$old_priority = (string)$node->Priority;
|
||||
$new_priority = $ACLInfo["Priority"];
|
||||
|
||||
if ($new_priority < $old_priority) {
|
||||
if ($new_priority < 0) {
|
||||
$new_priority = 0;
|
||||
}
|
||||
|
||||
foreach ($mdlProxyUserACL->general->ACLs->ACL->sortedBy("Priority", true) as $acl) {
|
||||
$key = $acl->getAttributes()["uuid"];
|
||||
$priority = (string)$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority;
|
||||
if ($priority < $new_priority) {
|
||||
break;
|
||||
}
|
||||
if ($priority >= $old_priority) {
|
||||
continue;
|
||||
}
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority = (string)($priority + 1);
|
||||
}
|
||||
} elseif (($new_priority > $old_priority)) {
|
||||
$count = count($mdlProxyUserACL->general->ACLs->ACL->getNodes());
|
||||
if ($new_priority >= $count) {
|
||||
$new_priority = $count - 1;
|
||||
$ACLInfo["Priority"] = $new_priority;
|
||||
}
|
||||
foreach ($mdlProxyUserACL->general->ACLs->ACL->sortedBy("Priority") as $acl) {
|
||||
$key = $acl->getAttributes()["uuid"];
|
||||
$priority = (string)$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority;
|
||||
if ($priority > $new_priority) {
|
||||
break;
|
||||
}
|
||||
if ($priority <= $old_priority) {
|
||||
continue;
|
||||
}
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority = (string)($priority - 1);
|
||||
}
|
||||
}
|
||||
$node->setNodes($ACLInfo);
|
||||
$find = $this->checkName($ACLInfo["Name"], $ACLInfo["Group"]);
|
||||
if ($find !== true) {
|
||||
$result["validations"]["ACL.Name"] = $find;
|
||||
}
|
||||
$valMsgs = $mdlProxyUserACL->performValidation();
|
||||
foreach ($valMsgs as $field => $msg) {
|
||||
$fieldnm = str_replace($node->__reference, "ACL", $msg->getField());
|
||||
$result["validations"][$fieldnm] = $msg->getMessage();
|
||||
}
|
||||
|
||||
if (count($result['validations']) > 0) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// save config if validated correctly
|
||||
$mdlProxyUserACL->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
return array("result" => "saved");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* del ACL
|
||||
* @return array
|
||||
*/
|
||||
public function delACLAction($uuid)
|
||||
{
|
||||
$result = array("result" => "failed");
|
||||
if ($this->request->isPost() && $uuid != null) {
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
if ($mdlProxyUserACL->general->ACLs->ACL->del($uuid)) {
|
||||
// if item is removed, serialize to config and save
|
||||
$this->repackPriority();
|
||||
$mdlProxyUserACL->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
$result['result'] = 'deleted';
|
||||
} else {
|
||||
$result['result'] = 'not found';
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Change ACL priority
|
||||
* @param $uuid item unique id
|
||||
* @return array
|
||||
*/
|
||||
public function updownACLAction($uuid)
|
||||
{
|
||||
$result = array("result" => "failed");
|
||||
if ($this->request->isPost() && $uuid != null && $this->request->hasPost("command")) {
|
||||
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
$count = $this->repackPriority();
|
||||
$nodes = $mdlProxyUserACL->general->ACLs->ACL->getNodes();
|
||||
$acl = $nodes[$uuid];
|
||||
$priority = $acl["Priority"];
|
||||
switch ($this->request->getPost("command")) {
|
||||
case "up":
|
||||
$new_priority = $priority - 1;
|
||||
if ($new_priority < 0) {
|
||||
return array("result" => "success");
|
||||
}
|
||||
break;
|
||||
|
||||
case "down":
|
||||
$new_priority = $priority + 1;
|
||||
if ($new_priority >= $count) {
|
||||
return array("result" => "success");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return array("result" => "failed");
|
||||
}
|
||||
foreach ($nodes as $key => $node) {
|
||||
if ($node["Priority"] == $new_priority) {
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority = (string)$priority;
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$uuid}->Priority = (string)$new_priority;
|
||||
$mdlProxyUserACL->serializeToConfig();
|
||||
Config::getInstance()->save();
|
||||
return array('result' => 'success');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function checkName($user, $search)
|
||||
{
|
||||
$authFactory = new AuthenticationFactory();
|
||||
$servers = $authFactory->listServers();
|
||||
|
||||
foreach (explode(',', (new Proxy())->forward->authentication->method) as $method) {
|
||||
if ($method == "") {
|
||||
return gettext("No authentication method selected");
|
||||
}
|
||||
$server = $servers[$method];
|
||||
switch ($server["type"]) {
|
||||
case "ldap":
|
||||
if (!isset($server["ldap_binddn"])) {
|
||||
return gettext("LDAP user name is not specified");
|
||||
}
|
||||
|
||||
if (!isset($server["ldap_bindpw"])) {
|
||||
return gettext("LDAP user password is not specified");
|
||||
}
|
||||
|
||||
$ldapBindURL = strstr($server['ldap_urltype'], "Standard") ? "ldap://" : "ldaps://";
|
||||
$ldapBindURL .= strpos($server['host'], "::") !== false ? "[{$server['host']}]" : $server['host'];
|
||||
$ldapBindURL .= !empty($server['ldap_port']) ? ":{$server['ldap_port']}" : "";
|
||||
$ldap_auth_server = $authFactory->get($server["name"]);
|
||||
if ($ldap_auth_server->connect($ldapBindURL, $server["ldap_binddn"],
|
||||
$server["ldap_bindpw"]) == false) {
|
||||
return gettext("Error connecting to LDAP server");
|
||||
}
|
||||
|
||||
try {
|
||||
$users = $ldap_auth_server->searchUsers($user, $server["ldap_attr_user"]);
|
||||
} catch (\Exception $e) {
|
||||
break;
|
||||
}
|
||||
if ($users !== false && count($users) > 0) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case "local":
|
||||
foreach (Config::getInstance()->object()->system->{"$search"} as $item) {
|
||||
if ($user == (string)$item->name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sprintf(gettext('The %s %s does not exist'), $search, $user);
|
||||
}
|
||||
|
||||
private function repackPriority()
|
||||
{
|
||||
$mdlProxyUserACL = $this->getModel();
|
||||
$count = 0;
|
||||
foreach ($mdlProxyUserACL->general->ACLs->ACL->sortedBy("Priority") as $node) {
|
||||
$key = $node->getAttributes()["uuid"];
|
||||
$mdlProxyUserACL->general->ACLs->ACL->{$key}->Priority = (string)$count++;
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function strToHex($string)
|
||||
{
|
||||
$hex = '';
|
||||
for ($i = 0; $i < strlen($string); $i++) {
|
||||
$hex .= dechex(ord($string[$i]));
|
||||
}
|
||||
return $hex;
|
||||
}
|
||||
|
||||
private function decode($domains)
|
||||
{
|
||||
$result = array();
|
||||
foreach (explode(",", $domains) as $domain) {
|
||||
if ($domain != "") {
|
||||
$result[] = idn_to_utf8($domain);
|
||||
}
|
||||
}
|
||||
return implode(",", $result);
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2017 Smart-Soft
|
||||
*
|
||||
* 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\ProxyUserACL;
|
||||
|
||||
class IndexController extends \OPNsense\Base\IndexController
|
||||
{
|
||||
public function indexAction()
|
||||
{
|
||||
// set page title, used by the standard template in layouts/default.volt.
|
||||
$this->view->title = gettext("Group and User ACL settings");
|
||||
// pick the template to serve to our users.
|
||||
$this->view->pick('OPNsense/ProxyUserACL/index');
|
||||
$this->view->formDialogACL = $this->getForm("dialogACL");
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<form>
|
||||
<field>
|
||||
<id>ACL.Name</id>
|
||||
<label>Name</label>
|
||||
<type>text</type>
|
||||
<help>Enter a name of user/group. Group name is case sensitive.</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ACL.Priority</id>
|
||||
<label>Priority</label>
|
||||
<type>text</type>
|
||||
<help>Rule priority</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ACL.Group</id>
|
||||
<label>Group/User</label>
|
||||
<type>dropdown</type>
|
||||
<help>Group or User ACL</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ACL.Black</id>
|
||||
<label>Black/White</label>
|
||||
<type>dropdown</type>
|
||||
<help>Black or White list</help>
|
||||
</field>
|
||||
<field>
|
||||
<id>ACL.Domains</id>
|
||||
<label>Domains</label>
|
||||
<type>select_multiple</type>
|
||||
<style>tokenize</style>
|
||||
<allownew>true</allownew>
|
||||
<help><![CDATA[Destination domains.<br/>
|
||||
You may use a regular expression, use a comma or press Enter for new item.<br/>
|
||||
<div class="alert alert-info">
|
||||
<b>Examples:</b><br/>
|
||||
<b class="text-primary">mydomain.com</b> -> matches on <b>*.mydomain.com</b><br/>
|
||||
<b class="text-primary">^https?:\/\/([a-zA-Z]+)\.mydomain\.</b> -> matches on <b>http(s)://textONLY.mydomain.*</b><br/>
|
||||
<b class="text-primary">\.gif$</b> -> matches on <b>\*.gif</b> but not on <b class="text-danger">\*.gif\test</b><br/>
|
||||
<b class="text-primary">\[0-9]+\.gif$</b> -> matches on <b>\123.gif</b> but not on <b class="text-danger">\test.gif</b><br/>
|
||||
</div>
|
||||
<div class="text-info"><b>TIP: </b>You can also paste a comma separated list into this field.</div>]]></help>
|
||||
<hint>Regular expressions are allowed.</hint>
|
||||
</field>
|
||||
</form>
|
||||
@@ -0,0 +1,8 @@
|
||||
<menu>
|
||||
<Services>
|
||||
<WebProxy>
|
||||
<ProxyUserACL VisibleName="Group and User ACL" cssClass="fa fa-shield fa-fw" order="30" url="/ui/proxyuseracl/"/>
|
||||
</WebProxy>
|
||||
</Services>
|
||||
</menu>
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2017 Smart-Soft
|
||||
*
|
||||
* 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\ProxyUserACL;
|
||||
|
||||
use OPNsense\Base\BaseModel;
|
||||
|
||||
class ProxyUserACL extends BaseModel
|
||||
{
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<model>
|
||||
<mount>//OPNsense/ProxyUserACL</mount>
|
||||
<version>1.0.0</version>
|
||||
<description>
|
||||
Group and User ACL settings
|
||||
</description>
|
||||
<items>
|
||||
<general>
|
||||
<ACLs>
|
||||
<ACL type="ArrayField">
|
||||
<Name type="TextField">
|
||||
<Required>Y</Required>
|
||||
</Name>
|
||||
<Hex type="TextField">
|
||||
<Required>Y</Required>
|
||||
</Hex>
|
||||
<Domains type="CSVListField">
|
||||
<Required>N</Required>
|
||||
</Domains>
|
||||
<Black type="OptionField">
|
||||
<Required>Y</Required>
|
||||
<default>Black</default>
|
||||
<OptionValues>
|
||||
<deny>Black</deny>
|
||||
<allow>White</allow>
|
||||
</OptionValues>
|
||||
</Black>
|
||||
<Priority type="IntegerField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
</Priority>
|
||||
<Group type="OptionField">
|
||||
<Required>Y</Required>
|
||||
<default>group</default>
|
||||
<OptionValues>
|
||||
<group>Group</group>
|
||||
<user>User</user>
|
||||
</OptionValues>
|
||||
</Group>
|
||||
</ACL>
|
||||
</ACLs>
|
||||
</general>
|
||||
</items>
|
||||
</model>
|
||||
@@ -0,0 +1,155 @@
|
||||
{#
|
||||
Copyright (C) 2017 Smart-Soft
|
||||
|
||||
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 () {
|
||||
grid = $("#grid-acl").UIBootgrid(
|
||||
{
|
||||
'search': '/api/proxyuseracl/settings/searchACL',
|
||||
'get': '/api/proxyuseracl/settings/getACL/',
|
||||
'set': '/api/proxyuseracl/settings/setACL/',
|
||||
'add': '/api/proxyuseracl/settings/addACL/',
|
||||
'del': '/api/proxyuseracl/settings/delACL/',
|
||||
'toggle': '/api/proxyuseracl/settings/toggleACL/',
|
||||
options: {
|
||||
formatters: {
|
||||
"commands": function (column, row) {
|
||||
return "<button type=\"button\" class=\"btn btn-xs btn-default command-edit\" data-row-id=\"" + row.uuid + "\"><span class=\"fa fa-pencil\"></span></button> " +
|
||||
"<button type=\"button\" class=\"btn btn-xs btn-default command-copy\" data-row-id=\"" + row.uuid + "\"><span class=\"fa fa-clone\"></span></button>" +
|
||||
"<button type=\"button\" class=\"btn btn-xs btn-default command-delete\" data-row-id=\"" + row.uuid + "\"><span class=\"fa fa-trash-o\"></span></button>";
|
||||
},
|
||||
"rowtoggle": function (column, row) {
|
||||
if (parseInt(row[column.id], 2) == 1) {
|
||||
return "<span style=\"cursor: pointer;\" class=\"fa fa-check-square-o command-toggle\" data-value=\"1\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
} else {
|
||||
return "<span style=\"cursor: pointer;\" class=\"fa fa-square-o command-toggle\" data-value=\"0\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
}
|
||||
},
|
||||
"boolean": function (column, row) {
|
||||
if (parseInt(row[column.id], 2) == 1) {
|
||||
return "<span class=\"fa fa-check\" data-value=\"1\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
} else {
|
||||
return "<span class=\"fa fa-times\" data-value=\"0\" data-row-id=\"" + row.uuid + "\"></span>";
|
||||
}
|
||||
},
|
||||
"updown": function (column, row) {
|
||||
return "<button type=\"button\" class=\"btn btn-xs btn-default command-updown\" data-row-id=\"" + row.uuid + "\" data-command=\"up\"><span class=\"fa fa-arrow-up\"></span></button> " +
|
||||
"<button type=\"button\" class=\"btn btn-xs btn-default command-updown\" data-row-id=\"" + row.uuid + "\" data-command=\"down\"><span class=\"fa fa-arrow-down\"></span></button>";
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
grid.on("loaded.rs.jquery.bootgrid", function () {
|
||||
grid.find(".command-updown").on("click", function () {
|
||||
ajaxCall(url = "/api/proxyuseracl/settings/updownACL/" + $(this).data("row-id"), sendData = {"command": $(this).data("command")}, callback = function () {
|
||||
$("#grid-acl").bootgrid("reload");
|
||||
});
|
||||
}).end();
|
||||
|
||||
grid.find("*[data-action=add]").click(function () {
|
||||
$("#btn_DialogACL_save_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
$("#btn_DialogACL_save").click(function () {
|
||||
$("#btn_DialogACL_save_progress").addClass("fa fa-spinner fa-pulse");
|
||||
var old_handleFormValidation = window.handleFormValidation;
|
||||
window.handleFormValidation = function (parent, validationErrors) {
|
||||
$("#btn_DialogACL_save_progress").removeClass("fa fa-spinner fa-pulse");
|
||||
window.handleFormValidation = old_handleFormValidation;
|
||||
handleFormValidation(parent, validationErrors);
|
||||
}
|
||||
});
|
||||
}).end();
|
||||
});
|
||||
|
||||
$("#reconfigureAct").click(function () {
|
||||
$("#reconfigureAct_progress").addClass("fa fa-spinner fa-pulse");
|
||||
ajaxCall(url = "/api/proxy/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: "Error reconfiguring proxy",
|
||||
message: data['status'],
|
||||
draggable: true
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="acl">
|
||||
<table id="acl-content">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<table id="grid-acl" class="table table-condensed table-hover table-striped table-responsive"
|
||||
data-editDialog="DialogACL">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="Priority" data-width="10em" data-type="string" data-sortable="false"
|
||||
data-visible="true">{{ lang._('Number') }}</th>
|
||||
<th data-column-id="Group" data-width="10em" data-type="string"
|
||||
data-sortable="false">{{ lang._('Group') }}</th>
|
||||
<th data-column-id="Black" data-width="10em" data-type="string"
|
||||
data-sortable="false">{{ lang._('Black') }}</th>
|
||||
<th data-column-id="Name" data-type="string"
|
||||
data-sortable="false">{{ lang._('Name') }}</th>
|
||||
<th data-column-id="Domains" data-type="string" data-sortable="false"
|
||||
data-visible="true">{{ lang._('Domains') }}</th>
|
||||
<th data-column-id="updown" data-width="7em" data-formatter="updown"
|
||||
data-sortable="false">{{ lang._('Priority') }}</th>
|
||||
<th data-column-id="commands" data-width="7em" data-formatter="commands"
|
||||
data-sortable="false">{{ lang._('Commands') }}</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>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="reconfigureAct" type="button"><b>{{ lang._('Apply') }}</b><i
|
||||
id="reconfigureAct_progress" class=""></i></button>
|
||||
|
||||
{{ partial("layout_partials/base_dialog",['fields':formDialogACL,'id':'DialogACL','label':lang._('Edit user/group white and black lists')]) }}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Copyright (C) 2017 Smart-Soft
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
require_once('script/load_phalcon.php');
|
||||
|
||||
use \OPNsense\ProxyUserACL\ProxyUserACL;
|
||||
use \OPNsense\Core\Config;
|
||||
|
||||
$mdlProxyUserACL = new ProxyUserACL();
|
||||
$domain = strtoupper((string) Config::getInstance()->object()->system->domain);
|
||||
|
||||
array_map('unlink', glob("/usr/local/etc/squid/ACL_*.txt"));
|
||||
foreach ($mdlProxyUserACL->getNodeByReference('general.ACLs.ACL')->getNodes() as $acl) {
|
||||
file_put_contents("/usr/local/etc/squid/ACL_" . $acl["Priority"] . ".txt", $acl["Name"] . "\n" . ($acl["Group"]["user"]["selected"] == "1" ? $acl["Name"] . "@" . $domain . "\n" : ""));
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[reconfigure]
|
||||
command:/usr/local/opnsense/scripts/OPNsense/ProxyUserACL/reconfigure.php
|
||||
parameters:
|
||||
type:script
|
||||
message:reconfigure proxy
|
||||
@@ -0,0 +1 @@
|
||||
ProxyUserACL.conf:/usr/local/etc/squid/auth/ProxyUserACL.conf
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
{% set ldap = [] %}
|
||||
{% set local = [] %}
|
||||
{% if helpers.exists('OPNsense.proxy.forward.authentication.method') and OPNsense.proxy.forward.authentication.method != '' %}
|
||||
{% for method in OPNsense.proxy.forward.authentication.method.split(",") %}
|
||||
{% if method == "Local Database" %}
|
||||
{% if local.append("1") %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% for server in helpers.toList('system.authserver') %}
|
||||
{% if server.type == 'ldap' and server.name == method %}
|
||||
{% if ldap.append(server) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if helpers.exists('OPNsense.ProxyUserACL.general.ACLs.ACL') %}
|
||||
{% for ACL in helpers.toList('OPNsense.ProxyUserACL.general.ACLs.ACL') %}
|
||||
{% if ACL.Group == "group" %}
|
||||
{% if ldap|length == 1 %}
|
||||
{% if helpers.exists('OPNsense.ProxySSO.EnableSSO') and OPNsense.ProxySSO.EnableSSO == '1' %}
|
||||
external_acl_type ext_group_ldap_{{ ACL.Priority}} ttl=300 negative_ttl=60 %LOGIN /usr/local/libexec/squid/ext_kerberos_ldap_group_acl -a -t {{ ACL.Hex }} -D {{ system.domain|upper }}
|
||||
acl group_ldap_{{ACL.Priority}} external ext_group_ldap_{{ ACL.Priority }}
|
||||
{% else %}
|
||||
{% for authcn in ldap[0].ldap_authcn.split(";") %}
|
||||
{% if ldap[0].ldap_attr_user == 'cn' %}
|
||||
external_acl_type ext_ldap_{{ ACL.Priority }}_{{ loop.index }} ttl=300 negative_ttl=60 %LOGIN /usr/local/libexec/squid/ext_ldap_group_acl -R -b "{{ldap[0].ldap_basedn}}" -f "(&(cn=%a)(memberUid=%u))" -D "{{ldap[0].ldap_binddn}}" -w "{{ldap[0].ldap_bindpw}}" -p "{{ldap[0].ldap_port}}" "{{ldap[0].host}}"
|
||||
{% else %}
|
||||
external_acl_type ext_ldap_{{ ACL.Priority }}_{{ loop.index }} ttl=300 negative_ttl=60 %LOGIN /usr/local/libexec/squid/ext_ldap_group_acl -R -b "{{ldap[0].ldap_basedn}}" -f "(&({{ldap[0].ldap_attr_user}}=%u)(memberOf=cn=%a,{{authcn}}))" -D "{{ldap[0].ldap_binddn}}" -w "{{ldap[0].ldap_bindpw}}" -p "{{ldap[0].ldap_port}}" "{{ldap[0].host}}"
|
||||
{% endif %}
|
||||
acl group_ldap_{{ACL.Priority}}_{{ loop.index }} external ext_ldap_{{ ACL.Priority }}_{{ loop.index }} "/usr/local/etc/squid/ACL_{{ ACL.Priority }}.txt"
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if local|length == 1 %}
|
||||
external_acl_type ext_group_local_{{ ACL.Priority }} ttl=300 negative_ttl=60 %LOGIN /usr/local/libexec/squid/ext_unix_group_acl -p
|
||||
acl group_local_{{ACL.Priority}} external ext_group_local_{{ ACL.Priority }} "/usr/local/etc/squid/ACL_{{ ACL.Priority }}.txt"
|
||||
{% endif %}
|
||||
{% else %}
|
||||
acl user_{{ACL.Priority}} proxy_auth "/usr/local/etc/squid/ACL_{{ ACL.Priority }}.txt"
|
||||
{% endif %}
|
||||
{% if ldap|length == 1 or local|length == 1 %}
|
||||
{% for element in ACL.Domains.split(",") %}
|
||||
{% if '^' in element or '\\' in element or '$' in element or '[' in element %}
|
||||
acl domains_{{ACL.Priority}} url_regex {{element}}
|
||||
{% else %}
|
||||
acl domains_{{ACL.Priority}} url_regex {{element|replace(".","\.")}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if helpers.exists('OPNsense.ProxyUserACL.general.ACLs.ACL') and (ldap|length == 1 or local|length == 1) %}
|
||||
{% for priority in range(0,helpers.toList('OPNsense.ProxyUserACL.general.ACLs.ACL')|length) %}
|
||||
{% for ACL in helpers.toList('OPNsense.ProxyUserACL.general.ACLs.ACL') %}
|
||||
{% if ACL.Priority == priority|string %}
|
||||
{% if ACL.Group == "group" %}
|
||||
{% if ldap|length == 1 %}
|
||||
{% if helpers.exists('OPNsense.proxy.forward.icap.enable') and OPNsense.proxy.forward.icap.enable == '1' %}
|
||||
adaptation_access response_mod {{ACL.Black}} group_ldap_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
adaptation_access request_mod {{ACL.Black}} group_ldap_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% endif %}
|
||||
{% if helpers.exists('OPNsense.ProxySSO.EnableSSO') and OPNsense.ProxySSO.EnableSSO == '1' %}
|
||||
http_access {{ACL.Black}} group_ldap_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% else %}
|
||||
{% for authcn in ldap[0].ldap_authcn.split(";") %}
|
||||
http_access {{ACL.Black}} group_ldap_{{ACL.Priority}}_{{ loop.index }} domains_{{ACL.Priority}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if local|length == 1 %}
|
||||
{% if helpers.exists('OPNsense.proxy.forward.icap.enable') and OPNsense.proxy.forward.icap.enable == '1' %}
|
||||
adaptation_access response_mod {{ACL.Black}} group_local_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
adaptation_access request_mod {{ACL.Black}} group_local_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% endif %}
|
||||
http_access {{ACL.Black}} group_local_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if helpers.exists('OPNsense.proxy.forward.icap.enable') and OPNsense.proxy.forward.icap.enable == '1' %}
|
||||
adaptation_access response_mod {{ACL.Black}} user_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
adaptation_access request_mod {{ACL.Black}} user_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% endif %}
|
||||
http_access {{ACL.Black}} user_{{ACL.Priority}} domains_{{ACL.Priority}}
|
||||
{% endif %}
|
||||
{% break %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user