Gnumed.pycommon.gmHooks

Package Gnumed :: Package pycommon :: Module gmHooks |
[frames] | no frames]

# Source Code for Module Gnumed.pycommon.gmHooks

      1  """GNUmed hooks framework.
      2
      3  This module provides convenience functions and definitions
      4  for accessing the GNUmed hooks framework.
      5
      6  This framework calls the script
      7
      8          ~/.gnumed/scripts/hook_script.py
      9
     10  at various times during client execution. The script must
     11  contain a function
     12
     13  def run_script(hook=None):
     14          pass
     15
     16  which accepts a single argument <hook>. That argument will
     17  contain the hook that is being activated.
     18  """
     19  # ========================================================================
     20  __author__  = "K. Hilbert <Karsten.Hilbert@gmx.net>"
     21  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     22
     23  # stdlib
     24  import os
     25  import sys
     26  import stat
     27  import logging
     28  import io
     29
     30
     31  # GNUmed libs
     32  if __name__ == '__main__':
     33          sys.path.insert(0, '../../')
     34  from Gnumed.pycommon import gmDispatcher
     35  from Gnumed.pycommon import gmTools
     36
     37
     38  _log = logging.getLogger('gm.hook')
     39  # ========================================================================
     40  known_hooks = [
     41          'post_patient_activation',
     42          'post_person_creation',
     43
     44          'after_waiting_list_modified',
     45
     46          'shutdown-post-GUI',
     47          'startup-after-GUI-init',
     48          'startup-before-GUI',
     49
     50          'request_user_attention',
     51          'app_activated_startup',
     52          'app_activated',
     53          'app_deactivated',
     54
     55          'after_substance_intake_modified',
     56          'after_test_result_modified',
     57          'after_soap_modified',
     58          'after_code_link_modified',
     59
     60          'after_new_doc_created',
     61          'before_print_doc',
     62          'before_fax_doc',
     63          'before_mail_doc',
     64          'before_print_doc_part',
     65          'before_fax_doc_part',
     66          'before_mail_doc_part',
     67          'before_external_doc_access',
     68
     69          'db_maintenance_warning'
     70  ]
     71
     72  _log.debug('known hooks:')
     73  for hook in known_hooks:
     74          _log.debug(hook)
     75
     76  # ========================================================================
     77  hook_module = None
     78
     79 -def import_hook_module(reimport=False):
     80
     81          global hook_module
     82          if not reimport:
     83                  if hook_module is not None:
     84                          return True
     85
     86          # hardcoding path and script name allows us to
     87          # not need configuration for it, the environment
     88          # can always be detected at runtime (workplace etc)
     89          script_name = 'hook_script.py'
     90          script_path = os.path.expanduser(os.path.join('~', '.gnumed', 'scripts'))
     91          full_script = os.path.join(script_path, script_name)
     92
     93          if not os.access(full_script, os.F_OK):
     94                  _log.warning('creating default hook script')
     95                  f = io.open(full_script, mode = 'wt', encoding = 'utf8')
     96                  f.write("""
     97  # known hooks:
     98  #  %s
     99
    100  def run_script(hook=None):
    101          pass
    102  """ % '#  '.join(known_hooks))
    103                  f.close()
    104                  os.chmod(full_script, 384)
    105
    106          if os.path.islink(full_script):
    107                  gmDispatcher.send (
    108                          signal = 'statustext',
    109                          msg = _('Script must not be a link: [%s].') % full_script
    110                  )
    111                  return False
    112
    113          if not os.access(full_script, os.R_OK):
    114                  gmDispatcher.send (
    115                          signal = 'statustext',
    116                          msg = _('Script must be readable by the calling user: [%s].') % full_script
    117                  )
    118                  return False
    119
    120          script_stat_val = os.stat(full_script)
    121          _log.debug('hook script stat(): %s', script_stat_val)
    122          script_perms = stat.S_IMODE(script_stat_val.st_mode)
    123          _log.debug('hook script mode: %s (oktal: %s)', script_perms, oct(script_perms))
    124          if script_perms != 384:                         # octal 0600
    125                  if os.name in ['nt']:
    126                          _log.warning('this platform does not support os.stat() file permission checking')
    127                  else:
    128                          gmDispatcher.send (
    129                                  signal = 'statustext',
    130                                  msg = _('Script must be readable by the calling user only (permissions "0600"): [%s].') % full_script
    131                          )
    132                          return False
    133
    134          try:
    135                  tmp = gmTools.import_module_from_directory(script_path, script_name)
    136          except Exception:
    137                  _log.exception('cannot import hook script')
    138                  return False
    139
    140          hook_module = tmp
    141  #       if reimport:
    142  #               imp.reload(tmp)                 # this has well-known shortcomings !
    143
    144          _log.info('hook script: %s', full_script)
    145          return True
    146
    147  # ========================================================================
    148  __current_hook_stack = []
    149
    150 -def run_hook_script(hook=None):
    151          # NOTE: this just *might* be a huge security hole
    152
    153          _log.info('told to pull hook [%s]', hook)
    154
    155          if hook not in known_hooks:
    156                  raise ValueError('run_hook_script(): unknown hook [%s]' % hook)
    157
    158          if not import_hook_module(reimport = False):
    159                  _log.debug('cannot import hook module, not pulling hook')
    160                  return False
    161
    162          if hook in __current_hook_stack:
    163                  _log.error('hook-code cycle detected, aborting')
    164                  _log.error('current hook stack: %s', __current_hook_stack)
    165                  return False
    166
    167          __current_hook_stack.append(hook)
    168
    169          try:
    170                  hook_module.run_script(hook = hook)
    171          except Exception:
    172                  _log.exception('error running hook script for [%s]', hook)
    173                  gmDispatcher.send (
    174                          signal = 'statustext',
    175                          msg = _('Error running hook [%s] script.') % hook,
    176                          beep = True
    177                  )
    178                  if __current_hook_stack[-1] != hook:
    179                          _log.error('hook nesting errror detected')
    180                          _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1])
    181                          _log.error('current hook stack: %s', __current_hook_stack)
    182                  else:
    183                          __current_hook_stack.pop()
    184                  return False
    185
    186          if __current_hook_stack[-1] != hook:
    187                  _log.error('hook nesting errror detected')
    188                  _log.error('latest hook: expected [%s], found [%s]', hook, __current_hook_stack[-1])
    189                  _log.error('current hook stack: %s', __current_hook_stack)
    190          else:
    191                  __current_hook_stack.pop()
    192
    193          return True
    194
    195  # ========================================================================
    196  if __name__ == '__main__':
    197
    198          if len(sys.argv) < 2:
    199                  sys.exit()
    200
    201          if sys.argv[1] != 'test':
    202                  sys.exit()
    203
    204          run_hook_script(hook = 'shutdown-post-GUI')
    205          run_hook_script(hook = 'invalid hook')
    206
    207  # ========================================================================
    208