"Fossies" - the Fresh Open Source Software Archive

Member "LinOTP-release-2.11/linotpd/src/linotp/lib/config/db_api.py" (12 Nov 2019, 13556 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 "db_api.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 '''handle all configuration items with aspekts like persitance and
   27    syncronysation and provides this to all requests
   28 '''
   29 
   30 
   31 import logging
   32 
   33 from linotp.lib.crypto import decryptPassword
   34 from linotp.lib.crypto import encryptPassword
   35 from linotp.lib.error import ConfigAdminError
   36 from linotp.model import Config
   37 
   38 from pylons import tmpl_context as c
   39 
   40 from linotp.lib.text_utils import UTF8_MAX_BYTES
   41 from linotp.lib.text_utils import simple_slice
   42 from linotp.lib.text_utils import utf8_slice
   43 
   44 from linotp.lib.crypto.encrypted_data import EncryptedData
   45 
   46 from linotp.lib.config.util import expand_here
   47 
   48 import linotp.model.meta
   49 
   50 Session = linotp.model.meta.Session
   51 
   52 
   53 #
   54 # MAX_VALUE_LEN defines the max len before we split the config entries into
   55 #  continuous config entries blocks.
   56 #
   57 
   58 MAX_VALUE_LEN = 2000 - UTF8_MAX_BYTES
   59 
   60 
   61 log = logging.getLogger(__name__)
   62 
   63 ###############################################################################
   64 #     private interface
   65 ###############################################################################
   66 
   67 
   68 def _storeConfigDB(key, val, typ=None, desc=None):
   69     """
   70     insert or update the entry with  key, value, type and
   71     description in the config DB
   72 
   73     """
   74     value = val
   75 
   76     if not key.startswith("linotp."):
   77         key = u"linotp." + key
   78 
   79     if isinstance(key, str):
   80         key = u'' + key
   81 
   82     log.debug('Changing config entry %r in database: New value is %r',
   83               key, val)
   84 
   85     # ---------------------------------------------------------------------- --
   86 
   87     # in case of an encrypted entry where the typ is 'encrypted_data', we store
   88     # the encrypted value which is retreived by the str value of the
   89     # EncrypteData object
   90 
   91     # other types like datetime or int are simply stored
   92 
   93     if not (isinstance(value, str) or isinstance(value, unicode)):
   94         return _storeConfigEntryDB(key, value, typ=typ, desc=desc)
   95 
   96     # ---------------------------------------------------------------------- --
   97 
   98     # if there are chunked entries for this key we delete them to prevent
   99     # dangling, not updated entries
  100 
  101     _delete_continous_entry_db(key)
  102 
  103     # ---------------------------------------------------------------------- --
  104 
  105     # the split algorithm depends on the value data -
  106     # in case of only ascii, we can use the much faster simple algorithm
  107     # in case of unicode characters we have to take the much slower one
  108 
  109     # for 'utf8_slice' the number of chunks is oriented toward the max length
  110     # defined by utf8 in bytes + the clipping of 6 bytes each. But as this
  111     # could vary, we could not calculate the number of chunks and thus use
  112     # an iterator to split the value into chunks
  113 
  114     chunks = []
  115     if len(value) < len(value.encode('utf-8')):
  116         text_slice = utf8_slice
  117     else:
  118         text_slice = simple_slice
  119 
  120     for cont_value in text_slice(value, MAX_VALUE_LEN):
  121         chunks.append(cont_value)
  122 
  123     # ---------------------------------------------------------------------- --
  124 
  125     # finally store either single entry or multiple chunks
  126 
  127     if len(chunks) == 1:
  128         return _storeConfigEntryDB(key, value, typ=typ, desc=desc)
  129 
  130     return _store_continous_entry_db(chunks, key, val, typ, desc)
  131 
  132 
  133 def _delete_continous_entry_db(key):
  134     """
  135     delete all chunk entries of a key
  136 
  137     in case of an update of an continous entry, the new set of entries might
  138     be smaller than the old one. So if we try to store the continous entry, we
  139     first have to remove all chunks
  140 
  141     :param key: the key prefix of the chunks
  142     """
  143 
  144     search_key = u"%s__[%%:%%]" % (key)
  145     continous_entries = Session.query(Config).filter(
  146                                       Config.Key.like(search_key))
  147 
  148     for continous_entry in continous_entries:
  149         Session.delete(continous_entry)
  150 
  151 
  152 def _store_continous_entry_db(chunks, key, val, typ, desc):
  153     """
  154     store continous entries -
  155     for strings or unicode, we support continued entries
  156 
  157     the continuous type is a split over multiple entries:
  158 
  159     normal Config Entry:
  160     +-----------------+---------------------------+-------+-------------+
  161     | key             | value                     |  type | description |
  162     +-----------------+---------------------------+-------+-------------+
  163     | small_val       | <small chunk of data>     | 'text' | 'my cert'  |
  164     +-----------------+---------------------------+-------+-------------+
  165 
  166     continous Config Entry:
  167     +-------------------+---------------------------+--------+-------------+
  168     | key               | value                     |  type  | description |
  169     +-------------------+---------------------------+--------+-------------+
  170     | long_value        | <big chunk > part 0       | 'C'    | '0:3'       |
  171     +-------------------+---------------------------+--------+-------------+
  172     | long_value__[1:3] | <big chunk > part 1       | 'C'    | '1:3'       |
  173     +----------------..-+---------------------------+--------+-------------+
  174     | long_value__[2:3] | <big chunk > part 2       | 'C'    | '2:3'       |
  175     +-------------------+---------------------------+--------+-------------+
  176     | long_value__[3:3] | <big chunk > part 3       | 'text' | 'my cert'   |
  177     +-------------------+---------------------------+--------+-------------+
  178 
  179     handling of key, type and description in chunked entries:
  180 
  181     key: every entry will have an enumerated key but the first one which
  182          has the original one.
  183 
  184     type: for all the entries the type is 'C', but the last one which
  185           contains the original type.
  186 
  187     descr: for all entries the description contains the enumeration like 0:3,
  188            which to be read as 'this is part 0 of 3 chunks'. the last entry
  189            contains the the original description
  190 
  191     :params key: the key
  192     :params val: the value
  193     :params val: the value
  194     :params desc: the desctiption
  195     :params chunks: the array of the split up entries
  196     """
  197 
  198     number_of_chunks = len(chunks)
  199 
  200     for i, cont_value in enumerate(chunks):
  201 
  202         cont_typ = u"C"
  203         cont_desc = u"%d:%d" % (i, number_of_chunks - 1)
  204         cont_key = u"%s__[%d:%d]" % (key, i, number_of_chunks - 1)
  205 
  206         # first one will contain the correct key with type 'C' continuous
  207         if i == 0:
  208             cont_key = key
  209 
  210         # the last one will contain the correct type and description
  211         elif i == number_of_chunks - 1:
  212             cont_typ = typ
  213             cont_desc = desc
  214 
  215         res = _storeConfigEntryDB(cont_key, cont_value,
  216                                   typ=cont_typ,
  217                                   desc=cont_desc)
  218 
  219     return res
  220 
  221 
  222 def _storeConfigEntryDB(key, value, typ=None, desc=None):
  223     """
  224     lowest level for storing database entries in the config table
  225     """
  226 
  227     confEntries = Session.query(Config).filter(Config.Key == unicode(key))
  228     theConf = None
  229 
  230     # update
  231     if confEntries.count() == 1:
  232         theConf = confEntries[0]
  233         theConf.Value = unicode(value)
  234         theConf.Type = typ
  235         theConf.Description = desc
  236 
  237     # insert
  238     elif confEntries.count() == 0:
  239         theConf = Config(
  240                         Key=unicode(key),
  241                         Value=unicode(value),
  242                         Type=unicode(typ),
  243                         Description=unicode(desc)
  244                         )
  245     if theConf is not None:
  246         Session.add(theConf)
  247 
  248     return 101
  249 
  250 
  251 def _removeConfigDB(key):
  252     """
  253     remove entry from config table
  254 
  255     :param key: the name of the entry
  256     :return: number of deleted entries
  257     """
  258     log.debug('removing config entry %r from database table' % key)
  259 
  260     if (not key.startswith("linotp.")):
  261         if not key.startswith('enclinotp.'):
  262             key = u"linotp." + key
  263 
  264     if isinstance(key, str):
  265         key = u'' + key
  266 
  267     confEntries = Session.query(Config).filter(
  268                                         Config.Key == unicode(key)).all()
  269 
  270     if not confEntries:
  271         return 0
  272 
  273     theConf = confEntries[0]
  274 
  275     to_be_deleted = []
  276     to_be_deleted.append(theConf)
  277 
  278     # if entry is a contious type, delete all of this kind
  279     if theConf.Type == u'C' and theConf.Description[:len('0:')] == '0:':
  280         _start, end = theConf.Description.split(':')
  281         search_key = u"%s__[%%:%s]" % (key, end)
  282         cont_entries = Session.query(Config).filter(
  283                                      Config.Key.like(search_key)).all()
  284 
  285         to_be_deleted.extend(cont_entries)
  286 
  287     try:
  288         for entry in to_be_deleted:
  289             # Session.add(theConf)
  290             Session.delete(entry)
  291 
  292     except Exception as e:
  293         raise ConfigAdminError("remove Config failed for %r: %r"
  294                                % (key, e), id=1133)
  295 
  296     return len(to_be_deleted)
  297 
  298 
  299 def _retrieveConfigDB(Key):
  300 
  301     # prepend "linotp." if required
  302     key = Key
  303     if (not key.startswith("linotp.")):
  304         if (not key.startswith("enclinotp.")):
  305             key = u"linotp." + Key
  306 
  307     if isinstance(key, str):
  308         key = u'' + key
  309 
  310     myVal = None
  311 
  312     entries = Session.query(Config).filter(Config.Key == key).all()
  313 
  314     if not entries:
  315         return None
  316 
  317     theConf = entries[0]
  318 
  319     # other types than continous: we are done
  320     if theConf.Type != u'C':
  321         myVal = theConf.Value
  322         myVal = expand_here(myVal)
  323         return myVal
  324 
  325     # else we have the continue type: we iterate over all entries where the
  326     # number of entries is stored in the description as range end
  327     _start, end = theConf.Description.split(':')
  328 
  329     # start accumulating the value
  330     value = theConf.Value
  331 
  332     for i in range(int(end)):
  333         search_key = u"%s__[%d:%d]" % (key, i, int(end))
  334         cont_entries = Session.query(Config).filter(
  335                                             Config.Key == search_key).all()
  336         if cont_entries:
  337             value = value + cont_entries[0].Value
  338 
  339     return value
  340 
  341 
  342 def _retrieveAllConfigDB():
  343     """
  344     get the server config from database with one call
  345 
  346     remark: for support for continous entries dedicated dicts for
  347             description and type are used for interim processing
  348 
  349     :return: config dict
  350     """
  351 
  352     config = {}
  353     delay = False
  354 
  355     conf_dict = {}
  356     type_dict = {}
  357     desc_dict = {}
  358     cont_dict = {}
  359 
  360     db_config = Session.query(Config).all()
  361 
  362     # put all information in the dicts for later processing
  363 
  364     for conf in db_config:
  365         log.debug("[retrieveAllConfigDB] key %r:%r" % (conf.Key, conf.Value))
  366 
  367         conf_dict[conf.Key] = conf.Value
  368         type_dict[conf.Key] = conf.Type
  369         desc_dict[conf.Key] = conf.Description
  370 
  371         # a continuous entry is indicated by the type 'C' and the description
  372         # search for the entry which starts with '0:' as it will provide the
  373         # number of continuous entries
  374 
  375         if conf.Type == 'C' and conf.Description[:len('0:')] == '0:':
  376             _start, num = conf.Description.split(':')
  377             cont_dict[conf.Key] = int(num)
  378 
  379     # ---------------------------------------------------------------------- --
  380 
  381     # cleanup the config from continuous entries
  382 
  383     for key, number in cont_dict.items():
  384 
  385         value = conf_dict[key]
  386 
  387         for i in range(number + 1):
  388 
  389             search_key = u"%s__[%d:%d]" % (key, i, number)
  390 
  391             if search_key in conf_dict:
  392                 value = value + conf_dict[search_key]
  393                 del conf_dict[search_key]
  394 
  395         conf_dict[key] = value
  396 
  397         search_key = u"%s__[%d:%d]" % (key, number, number)
  398         # allow the reading of none existing entries
  399         type_dict[key] = type_dict.get(search_key)
  400         desc_dict[key] = desc_dict.get(search_key)
  401 
  402     # ---------------------------------------------------------------------- --
  403 
  404     # normal processing as before continous here
  405 
  406     for key, value in conf_dict.items():
  407 
  408         if key.startswith("linotp.") is False:
  409             key = u"linotp." + key
  410 
  411         if isinstance(key, str):
  412             key = u'' + key
  413 
  414         nVal = expand_here(value)
  415         config[key] = nVal
  416 
  417     # ---------------------------------------------------------------------- --
  418 
  419     # special treatment of encrypted_data / password:
  420     # instead of decrypting the data during the loading of the config, the
  421     # encrypted data is provided EncryptedData object, which allows to only
  422     # decrypt the data when needed.
  423     # This allows to drop the delayed loading handling
  424     #
  425 
  426     for key, value in config.items():
  427 
  428         myTyp = type_dict.get(key)
  429 
  430         if myTyp and myTyp in ['password', 'encrypted_data']:
  431             config[key] = EncryptedData(value)
  432 
  433     return config, False
  434 
  435 # eof #