"Fossies" - the Fresh Open Source Software Archive

Member "LinOTP-release-2.11/linotpd/src/linotp/lib/config/parsing.py" (12 Nov 2019, 7335 Bytes) of package /linux/misc/LinOTP-release-2.11.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "parsing.py" see the Fossies "Dox" file reference documentation.

    1 # -*- coding: utf-8 -*-
    2 #
    3 #    LinOTP - the open source solution for two factor authentication
    4 #    Copyright (C) 2010 - 2019 KeyIdentity GmbH
    5 #
    6 #    This file is part of LinOTP server.
    7 #
    8 #    This program is free software: you can redistribute it and/or
    9 #    modify it under the terms of the GNU Affero General Public
   10 #    License, version 3, as published by the Free Software Foundation.
   11 #
   12 #    This program is distributed in the hope that it will be useful,
   13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
   14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   15 #    GNU Affero General Public License for more details.
   16 #
   17 #    You should have received a copy of the
   18 #               GNU Affero General Public License
   19 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
   20 #
   21 #
   22 #    E-mail: linotp@keyidentity.com
   23 #    Contact: www.linotp.org
   24 #    Support: www.keyidentity.com
   25 #
   26 """
   27     This module provides an interface to parse linotp config
   28     key-value pairs into a structured ConfigTree
   29 """
   30 
   31 import json
   32 
   33 from collections import defaultdict
   34 
   35 # -------------------------------------------------------------------------- --
   36 
   37 
   38 class ConfigNotRecognized(Exception):
   39 
   40     """
   41     This exception should be raised by config parser functions, when the
   42     parser isn't responsible for the supplied type of config entry.
   43 
   44     :param key: The config key that was supplied to the parser
   45     :param message: Custom message (optional, generic message on default)
   46     """
   47 
   48     def __init__(self, key, message=None):
   49 
   50         if message is None:
   51             message = 'Unrecognized config key: %s' % key
   52         Exception.__init__(self, message)
   53         self.key = key
   54 
   55 
   56 # -------------------------------------------------------------------------- --
   57 
   58 def parse_system_config(composite_key, value):
   59 
   60     """
   61     Parses system config entries
   62 
   63     ::warning: does a very generic match. should be added last to the
   64         internal config tree parser list
   65     """
   66 
   67     if not composite_key.startswith('linotp.'):
   68         raise ConfigNotRecognized(composite_key)
   69 
   70     return 'system_config', {composite_key: value}
   71 
   72 # -------------------------------------------------------------------------- --
   73 
   74 
   75 def parse_deprecated_enc(composite_key, value):
   76 
   77     """
   78     Parses soon to be deprecated 'enclinotp' config entries
   79 
   80     ::warning: does a very generic match. should be added last to the
   81         internal config tree parser list
   82     """
   83 
   84     # XXX LEGACY DEPRECATED
   85 
   86     if not composite_key.startswith('enclinotp.'):
   87         raise ConfigNotRecognized(composite_key)
   88 
   89     return 'deprecated_enc', {composite_key: value}
   90 
   91 # -------------------------------------------------------------------------- --
   92 
   93 
   94 class ConfigTree(dict):
   95 
   96     """
   97     A dictionary-like object, that processes config key-value pairs with
   98     a series of parsers.
   99 
  100     Usage:
  101 
  102     >>> tree = ConfigTree
  103     >>> tree.add_parser('resolvers', parse_resolver)
  104     >>> for key, value in config_dict.items():
  105     >>>     tree.consume_entry(key, value)
  106     """
  107 
  108     # the list of parsers get initialized on startup
  109     # by the add_parser method.
  110 
  111     _parsers = [('globals', parse_system_config),
  112                 ('deprecated', parse_deprecated_enc)]
  113 
  114     def __init__(self):
  115 
  116         # initialize config tree subspaces according to
  117         # parser definitions
  118 
  119         for target, __ in self._parsers:
  120             self[target] = defaultdict(dict)
  121 
  122     @classmethod
  123     def add_parser(cls, target, func):
  124 
  125         """
  126         Adds a parser function for the config tree.
  127 
  128         :param target: A string identifier for the first set of child
  129             nodes of this tree (e.g. 'resolvers', 'realms', etc)
  130             Multiple parsers for the same string identifier can
  131             exist.
  132 
  133         :param func: A parser function that asks for the composite
  134             key of the config entry and the value and returns a
  135             tuple (object_id, attr_updates) where object_id is a
  136             unique identifier inside the target scope (such as a
  137             resolver name) and attr_updates is a dictionary
  138             consisting of key-value-pairs where each keys is
  139             an attribute name and each value its value.
  140 
  141         .. warning:: The order in which the parsers are added
  142             is relevant. Later parsers have a higher priority
  143         """
  144 
  145         # the following is a hack.
  146 
  147         # we are facing the problem, that we need information
  148         # from different modules (such as the list of available
  149         # resolver types) in order to define the parsing function
  150         # for the module. we can't simply import this data in here
  151         # because it produces circular dependencies (most of the
  152         # modules use functions from the config module).
  153 
  154         # because of this, with this commit, we change this method
  155         # into a class method and delegate the calls to add_parser
  156         # to the different modules. add_parser will now be called
  157         # in the respective modules with the parser function defined
  158         # there as well.
  159 
  160         # however, we have 2 basic config types ('globals' and 'deprecated'),
  161         # that have no distinction criteria to the other config entries and
  162         # rely on the other parsers being processed first
  163 
  164         # because we cannot possibly control import order (at least not
  165         # at our current stage of insanity) we need to make sure that
  166         # the parsers for 'globals' and 'deprecated' always come last.
  167 
  168         # this problem is 'solved' by adding both 'globals' and
  169         # 'deprecated' parsers at the class level (see above) and
  170         # by PREPENDING every other parser that gets added from
  171         # outside, effectively reversing the order of priority.
  172         # (from later ^= lower_prio to later ^= higher prio)
  173 
  174         cls._parsers.insert(0, (target, func))
  175 
  176     def consume_entry(self, composite_key, value):
  177 
  178         """
  179         Integrates a config pair of a composite key and value
  180         into the tree.
  181 
  182         :param composite_key: A composite key from the config
  183             (such as 'linotp.Policy.mypolicy.scope')
  184 
  185         :param value: The associated value
  186 
  187         :raises ConfigNotRecognized: If none of the defined
  188             parsers recognized the config pair
  189         """
  190 
  191         for target, parser_func in self._parsers:
  192 
  193             try:
  194 
  195                 object_id, attr_updates = parser_func(composite_key, value)
  196                 self[target][object_id].update(attr_updates)
  197                 break
  198 
  199             except ConfigNotRecognized:
  200                 continue
  201 
  202         else:
  203 
  204             raise ConfigNotRecognized(composite_key)
  205 
  206     def pretty(self):
  207 
  208         """ Returns a pretty print of the tree """
  209 
  210         return json.dumps(self, indent=4)
  211 
  212 
  213 # -------------------------------------------------------------------------- --
  214 
  215 
  216 def parse_config(config_dict):
  217 
  218     """
  219     Translates a flat config_dict into a hierarchical ConfigTree
  220 
  221     :param config_dict: The config dictionary retrieved from
  222         the low-level typing API
  223 
  224     :return: ConfigTree object
  225     """
  226 
  227     tree = ConfigTree()
  228 
  229     # ---------------------------------------------------------------------- --
  230 
  231     for composite_key, value in config_dict.items():
  232         tree.consume_entry(composite_key, value)
  233 
  234     return tree