Source code for ucsschool.lib.internetrules

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Management Console
#  module: Internet Rules Module
#
# Copyright 2012-2025 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.

import re

from six import string_types

import univention.config_registry
from univention.management.console.config import ucr
from univention.management.console.log import MODULE

# regular expression to match UCR variables for filter properties
_regFilterNames = re.compile(
    r"^proxy/filter/setting(?P<userPrefix>-user)?/(?P<name>[^/]*)/(?P<property>[^/]*)"
    r"(/(?P<listType>[^/]*)/(?P<index>[^/]*))?$"
)

WHITELIST, BLACKLIST = range(2)
_filterTypes = {
    "blacklist-pass": BLACKLIST,
    "whitelist-block": WHITELIST,
}
_filterTypesInv = {_i[1]: _i[0] for _i in _filterTypes.items()}
_listTypes = {
    "blacklisted": BLACKLIST,
    "whitelisted": WHITELIST,
}
_listTypesInv = {_i[1]: _i[0] for _i in _listTypes.items()}


[docs] class Rule(object): def __init__(self, name, type=WHITELIST, priority=0, wlan=False, domains=[], userRule=False): self.name = name self.type = type self.priority = priority self.wlan = wlan self.domains = domains # proxy/filter/setting/* or proxy/filter/setting-user/* ? self.userRule = userRule def __str__(self): return "<rule:%s type:%s priority:%s>" % (self.name, self.type, self.priority) def __repr__(self): return self.__str__() def _getIndexedDomains(self, type): return [i for i in sorted(self._domains) if i[0] >= 0 and i[2] == type] def _getAppendedDomains(self, type): return [i for i in self._domains if i[0] < 0 and i[2] == type] def _getDomains(self, type): return [i for i in self._getIndexedDomains(type) + self._getAppendedDomains(type)] @property def domains(self): """ Return list of all domains, the order respects the indeces. Show only the entries that match the current filter type. """ return [i[1] for i in self._getDomains(self.type)] @domains.setter def domains(self, domains): """domains can be a list of strings or a list of index-string-type tuples.""" self._domains = [] for i in domains: if isinstance(i, string_types): self._domains.append((-1, i, self.type)) else: self._domains.append(i)
[docs] def addDomain(self, domain, idx=-1, listType=None): """add a new domain with an optional fixed index and list type""" if listType not in _listTypesInv: listType = self.type self._domains.append((idx, domain, listType))
[docs] def save(self): """ Save the current rule as UCR variables. If the rule already exists, only the changed properties will be saved. In case the rules are similar, no changes will be done. """ # load original rule orgRule = load(self.name) # prepare for saving filter properties vars = [] rmVars = [] prefix = "proxy/filter/setting/%s" % self.name if self.userRule: # this is a user rule which has a different prefix prefix = "proxy/filter/setting-user/%s" % self.name if not orgRule or orgRule.type != self.type: vars.append("%s/filtertype=%s" % (prefix, _filterTypesInv[self.type])) if not orgRule or orgRule.priority != self.priority: vars.append("%s/priority=%s" % (prefix, self.priority)) if not orgRule or orgRule.wlan != self.wlan: wlan = "true" if not self.wlan: wlan = "false" vars.append("%s/wlan=%s" % (prefix, wlan)) # iterate over all blacklist and whitelist entries for itype in _listTypes.values(): # saving domains is a bit more tricky as we need to take care of the indeces # ... get the original list of domains with indeces and sorted orgDomains = [] if orgRule: orgDomains = orgRule._getIndexedDomains(itype) + orgRule._getAppendedDomains(itype) # prepare list of current domains with indeces domains = [i[1] for i in self._getDomains(itype)] domains = [(i + 1, domains[i], itype) for i in range(len(domains))] # find the entries that need to be changed/added domainPrefix = "%s/domain/%s" % (prefix, _listTypesInv[itype]) iorg = 0 inew = 0 while inew < len(domains): if iorg >= len(orgDomains) or orgDomains[iorg] != domains[inew]: vars.append("%s/%s=%s" % (domainPrefix, domains[inew][0], domains[inew][1])) # increment iterators if ( iorg < len(orgDomains) and orgDomains[iorg][0] <= domains[inew][0] and orgDomains[iorg][0] >= 0 ): iorg += 1 inew += 1 # collect entries that need to be removed while iorg < len(orgDomains): rmVars.append("%s/%s" % (domainPrefix, orgDomains[iorg][0])) iorg += 1 # write changes if vars: univention.config_registry.handler_set(vars) if rmVars: univention.config_registry.handler_unset(rmVars)
[docs] def findUCRVariables(filterName=None, userRule=False): """ Returns a dict of all UCR variables or all variables matching the specified rule name. """ # refresh internal UCR cache ucr.load() # iterate over all UCR variables vars = {} for k, v in ucr.items(): imatch = _regFilterNames.match(k) if imatch: # we found a filter variable iname = imatch.group("name") if not iname or (filterName is not None and filterName != iname): # empty name or name does not match the specified filter continue # see whether this is a user specific rule or a general rule if userRule != bool(imatch.group("userPrefix")): continue # bingo, we got a match :) vars[k] = v # return all matched variables return vars
[docs] def remove(name, userRule=False): """Removes the UCR variables corresponding to the specified rule.""" if not name: return False rmVars = findUCRVariables(name, userRule).keys() if rmVars: univention.config_registry.handler_unset(rmVars) return True return False
[docs] def load(name, userRule=False): """Wrapper for list(name).""" return list(name, userRule)
[docs] def list(filterName=None, userRule=False): """ Returns a list of all existing rules. If name is given, returns only the rule matching the specified name or None. userRule specifies whether all common rules (=False) or only user-specific rules (=True) are listed. If filterName is specified, only rule matching this name is returned as single object (not as list!). """ # iterate over all UCR variables rules = {} for k, v in findUCRVariables(filterName, userRule).items(): imatch = _regFilterNames.match(k) if not imatch: # should not happen continue # get filter name iname = imatch.group("name") # get the rule from our cache irule = rules.get(iname, Rule(iname)) # update the rule with the given property # NOTE: URL black-/whitelists are not supported anymore, only domain lists iproperty = imatch.group("property") if iproperty == "filtertype": if v not in _filterTypes: irule.type = WHITELIST MODULE.error( 'Unknown filtertype "%s" for rule "%s", using whitelist as default.' % (v, irule) ) else: irule.type = _filterTypes[v] elif iproperty == "priority": try: irule.priority = int(v) except ValueError: irule.priority = 5 MODULE.error( 'Could not parse priority "%s" for rule "%s", using default value "5".' % (v, irule) ) elif iproperty == "wlan": irule.wlan = ucr.is_true(k) elif iproperty == "domain": # get the index idx = -1 try: idx = int(imatch.group("index")) except ValueError: pass # get list type (blacklisted or whitelisted) listType = _listTypes.get(imatch.group("listType")) # add domain to list of domains irule.addDomain(v, idx, listType) # save the rule back to our cache rules[iname] = irule if filterName: # handle case for filtered search if not rules: # no match return None # return single element return next(r for r in rules.values()) return [r for r in rules.values()]
[docs] def getGroupRuleName(groupNames): """ Return the name of the filter rule for the specified group name. Usage: `getGroupRuleName([<groupName>, ...]) -> { <groupName>:<ruleName>, ... }` or: `getGroupRuleName(<groupName) -> <ruleName>` """ ucr.load() if not isinstance(groupNames, type([])): return ucr.get("proxy/filter/groupdefault/%s" % groupNames) return {iname: ucr.get("proxy/filter/groupdefault/%s" % iname) for iname in groupNames}
[docs] def unsetGroupRuleName(groupNames): """ Unset the default rule for the given group name. Usage: `setGroupRuleName(<groupName>)` or: `setGroupRuleName([<groupName>, ... ])` """ vars = [] if not isinstance(groupNames, type([])): vars.append("proxy/filter/groupdefault/%s" % groupNames) else: vars = ["proxy/filter/groupdefault/%s" % iname for iname in groupNames] univention.config_registry.handler_unset(vars)
[docs] def setGroupRuleName(*args): """ Set the default rule for the given group name. Usage: `setGroupRuleName(<groupName>, <ruleName>)` or: `setGroupRuleName({ <groupName>: <ruleName>, ... })` """ vars = [] if len(args) > 1: vars = ["proxy/filter/groupdefault/%s=%s" % (args[0], args[1])] else: vars = ["proxy/filter/groupdefault/%s=%s" % (iname, irule) for iname, irule in args[0].items()] univention.config_registry.handler_set(vars)