Source code for ucsschool.lib.internetrules

#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Univention Management Console
#  module: Internet Rules Module
#
# Copyright 2012-2021 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 dict([(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)