Gnumed.wxpython.gmAuthWidgets

Package Gnumed :: Package wxpython :: Module gmAuthWidgets |
[frames] | no frames]

# Source Code for Module Gnumed.wxpython.gmAuthWidgets

      1  # -*- coding: utf-8 -*-
      2  """GNUmed authentication widgets.
      3
      4  This module contains widgets and GUI
      5  functions for authenticating users.
      6  """
      7  #================================================================
      8  __author__ = "karsten.hilbert@gmx.net, H.Herb, H.Berger, R.Terry"
      9  __license__ = "GPL v2 or later (details at http://www.gnu.org)"
     10
     11
     12  # stdlib
     13  import sys
     14  import os.path
     15  import logging
     16  import re as regex
     17
     18
     19  # 3rd party
     20  import wx
     21
     22
     23  # GNUmed
     24  if __name__ == '__main__':
     25          sys.path.insert(0, '../../')
     26  from Gnumed.pycommon import gmLoginInfo
     27  from Gnumed.pycommon import gmPG2
     28  from Gnumed.pycommon import gmBackendListener
     29  from Gnumed.pycommon import gmTools
     30  from Gnumed.pycommon import gmCfg2
     31  from Gnumed.pycommon import gmI18N
     32  from Gnumed.pycommon import gmLog2
     33
     34  from Gnumed.business import gmPraxis
     35
     36  from Gnumed.wxpython import gmGuiHelpers
     37  from Gnumed.wxpython import gmExceptionHandlingWidgets
     38
     39
     40  _log = logging.getLogger('gm.ui')
     41  _cfg = gmCfg2.gmCfgData()
     42
     43
     44  msg_generic = _("""
     45  GNUmed database version mismatch.
     46
     47  This database version cannot be used with this client:
     48
     49   client version: %s
     50   database version detected: %s
     51   database version needed: %s
     52
     53  Currently connected to database:
     54
     55   host: %s
     56   database: %s
     57   user: %s
     58  """)
     59
     60  msg_time_skew_fail = _("""\
     61  The server and client clocks are off
     62  by more than %s minutes !
     63
     64  You must fix the time settings before
     65  you can use this database with this
     66  client.
     67
     68  You may have to contact your
     69  administrator for help.""")
     70
     71  msg_time_skew_warn = _("""\
     72  The server and client clocks are off
     73  by more than %s minutes !
     74
     75  You should fix the time settings.
     76  Otherwise clinical data may appear to
     77  have been entered at the wrong time.
     78
     79  You may have to contact your
     80  administrator for help.""")
     81
     82  msg_insanity = _("""
     83  There is a serious problem with the database settings:
     84
     85  %s
     86
     87  You may have to contact your administrator for help.""")
     88
     89  msg_fail = _("""
     90  You must connect to a different database in order
     91  to use the GNUmed client. You may have to contact
     92  your administrator for help.""")
     93
     94  msg_override = _("""
     95  The client will, however, continue to start up because
     96  you are running a development/test version of GNUmed.
     97
     98  There may be schema related errors. Please report and/or
     99  fix them. Do not rely on this database to work properly
    100  in all cases !""")
    101
    102  #================================================================
    103  # convenience functions
    104  #----------------------------------------------------------------
    105 -def connect_to_database(max_attempts=3, expected_version=None, require_version=True):
    106          """Display the login dialog and try to log into the backend.
    107
    108          - up to max_attempts times
    109          - returns True/False
    110          """
    111          # force programmer to set a valid expected_version
    112          expected_hash = gmPG2.known_schema_hashes[expected_version]
    113          client_version = _cfg.get(option = 'client_version')
    114          global current_db_name
    115          current_db_name = 'gnumed_v%s' % expected_version
    116
    117          attempt = 0
    118
    119          dlg = cLoginDialog(None, -1, client_version = client_version)
    120          dlg.Centre(wx.BOTH)
    121
    122          while attempt < max_attempts:
    123
    124                  _log.debug('login attempt %s of %s', (attempt+1), max_attempts)
    125
    126                  connected = False
    127
    128                  dlg.ShowModal()
    129                  login = dlg.panel.GetLoginInfo()
    130                  if login is None:
    131                          _log.info("user cancelled login dialog")
    132                          break
    133
    134                  gmLog2.add_word2hide(login.password)
    135
    136                  # try getting a connection to verify the DSN works
    137                  dsn = gmPG2.make_psycopg2_dsn (
    138                          database = login.database,
    139                          host = login.host,
    140                          port = login.port,
    141                          user = login.user,
    142                          password = login.password
    143                  )
    144                  try:
    145                          conn = gmPG2.get_raw_connection(dsn = dsn, verbose = True, readonly = True)
    146                          connected = True
    147
    148                  except gmPG2.cAuthenticationError as e:
    149                          attempt += 1
    150                          _log.error("login attempt failed: %s", e)
    151                          if attempt < max_attempts:
    152                                  if ('host=127.0.0.1' in ('%s' % e)) or ('host=' not in ('%s' % e)):
    153                                          msg = _(
    154                                                  'Unable to connect to database:\n\n'
    155                                                  '%s\n\n'
    156                                                  "Are you sure you have got a local database installed ?\n"
    157                                                  '\n'
    158                                                  "Please retry with proper credentials or cancel.\n"
    159                                                  '\n'
    160                                                  ' (for the public and any new GNUmed data-\n'
    161                                                  '  bases the default user name and password\n'
    162                                                  '  are {any-doc, any-doc})\n'
    163                                                  '\n'
    164                                                  'You may also need to check the PostgreSQL client\n'
    165                                                  'authentication configuration in pg_hba.conf. For\n'
    166                                                  'details see:\n'
    167                                                  '\n'
    168                                                  'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    169                                          )
    170                                  else:
    171                                          msg = _(
    172                                                  "Unable to connect to database:\n\n"
    173                                                  "%s\n\n"
    174                                                  "Please retry with proper credentials or cancel.\n"
    175                                                  "\n"
    176                                                  "For the public and any new GNUmed databases the\n"
    177                                                  "default user name and password are {any-doc, any-doc}.\n"
    178                                                  "\n"
    179                                                  'You may also need to check the PostgreSQL client\n'
    180                                                  'authentication configuration in pg_hba.conf. For\n'
    181                                                  'details see:\n'
    182                                                  '\n'
    183                                                  'wiki.gnumed.de/bin/view/Gnumed/ConfigurePostgreSQL'
    184                                          )
    185                                  msg = msg % e
    186                                  msg = regex.sub(r'password=[^\s]+', 'password=%s' % gmTools.u_replacement_character, msg)
    187                                  gmGuiHelpers.gm_show_error (
    188                                          msg,
    189                                          _('Connecting to backend')
    190                                  )
    191                          del e
    192                          continue
    193
    194                  except gmPG2.dbapi.OperationalError as exc:
    195                          exc = gmPG2.make_pg_exception_fields_unicode(exc)
    196                          _log.error("login attempt failed: %s", exc)
    197                          msg = _(
    198                                  "Unable to connect to database:\n\n"
    199                                  "%s\n\n"
    200                                  "Please retry another backend / user / password combination !\n"
    201                                  "\n"
    202                                  " (for the public and any new GNUmed databases\n"
    203                                  "  the default user name and password are\n"
    204                                  "  {any-doc, any-doc})\n"
    205                                  "\n"
    206                          ) % exc.u_pgerror
    207  #                       ) % gmPG2.extract_msg_from_pg_exception(e)
    208                          msg = regex.sub(r'password=[^\s]+', 'password=%s' % gmTools.u_replacement_character, msg)
    209                          gmGuiHelpers.gm_show_error (
    210                                  msg,
    211                                  _('Connecting to backend')
    212                          )
    213                          del exc
    214                          continue
    215
    216                  conn.close()
    217
    218                  # connect was successful
    219                  gmPG2.set_default_login(login = login)
    220                  gmPG2.set_default_client_encoding(encoding = dlg.panel.backend_profile.encoding)
    221
    222                  seems_bootstrapped = gmPG2.schema_exists(schema = 'gm')
    223                  if not seems_bootstrapped:
    224                          _log.error('schema [gm] does not exist - database not bootstrapped ?')
    225                          msg = _(
    226                                  'The database you connected to does not seem\n'
    227                                  'to have been boostrapped properly.\n'
    228                                  '\n'
    229                                  'Make sure you have run the GNUmed database\n'
    230                                  'bootstrapper tool to create a new database.\n'
    231                                  '\n'
    232                                  'Further help can be found on the website at\n'
    233                                  '\n'
    234                                  '  http://wiki.gnumed.de\n'
    235                                  '\n'
    236                                  'or on the GNUmed mailing list.'
    237                          )
    238                          gmGuiHelpers.gm_show_error(msg, _('Verifying database'))
    239                          connected = False
    240                          break
    241
    242                  compatible = gmPG2.database_schema_compatible(version = expected_version)
    243                  if compatible or not require_version:
    244                          dlg.panel.save_state()
    245
    246                  if not compatible:
    247                          connected_db_version = gmPG2.get_schema_version()
    248                          msg = msg_generic % (
    249                                  client_version,
    250                                  connected_db_version,
    251                                  expected_version,
    252                                  gmTools.coalesce(login.host, '<localhost>'),
    253                                  login.database,
    254                                  login.user
    255                          )
    256                          if require_version:
    257                                  gmGuiHelpers.gm_show_error(msg + msg_fail, _('Verifying database version'))
    258                                  connected = False
    259                                  continue
    260                          gmGuiHelpers.gm_show_info(msg + msg_override, _('Verifying database version'))
    261
    262                  # FIXME: make configurable
    263                  max_skew = 1            # minutes
    264                  if _cfg.get(option = 'debug'):
    265                          max_skew = 10
    266                  if not gmPG2.sanity_check_time_skew(tolerance = (max_skew * 60)):
    267                          if _cfg.get(option = 'debug'):
    268                                  gmGuiHelpers.gm_show_warning(msg_time_skew_warn % max_skew, _('Verifying database settings'))
    269                          else:
    270                                  gmGuiHelpers.gm_show_error(msg_time_skew_fail % max_skew, _('Verifying database settings'))
    271                                  connected = False
    272                                  continue
    273
    274                  sanity_level, message = gmPG2.sanity_check_database_settings()
    275                  if sanity_level != 0:
    276                          gmGuiHelpers.gm_show_error((msg_insanity % message), _('Verifying database settings'))
    277                          if sanity_level == 2:
    278                                  connected = False
    279                                  continue
    280
    281                  gmExceptionHandlingWidgets.set_is_public_database(login.public_db)
    282                  gmExceptionHandlingWidgets.set_helpdesk(login.helpdesk)
    283
    284                  conn = gmPG2.get_connection(verbose = True, connection_name = 'GNUmed-[DbListenerThread]', pooled = False)
    285                  listener = gmBackendListener.gmBackendListener(conn = conn)
    286                  break
    287
    288          dlg.Destroy()
    289
    290          return connected
    291
    292  #================================================================
    293 -def get_dbowner_connection(procedure=None, dbo_password=None, dbo_account='gm-dbo'):
    294          if procedure is None:
    295                  procedure = _('<restricted procedure>')
    296
    297          # 1) get password for gm-dbo
    298          if dbo_password is None:
    299                  dbo_password = wx.GetPasswordFromUser (
    300                          message = _("""
    301   [%s]
    302
    303  This is a restricted procedure. We need the
    304  current password for the GNUmed database owner.
    305
    306  Please enter the current password for <%s>:""") % (
    307                                  procedure,
    308                                  dbo_account
    309                          ),
    310                          caption = procedure
    311                  )
    312                  if dbo_password == '':
    313                          return None
    314
    315          gmLog2.add_word2hide(dbo_password)
    316
    317          # 2) connect as gm-dbo
    318          login = gmPG2.get_default_login()
    319          dsn = gmPG2.make_psycopg2_dsn (
    320                  database = login.database,
    321                  host = login.host,
    322                  port = login.port,
    323                  user = dbo_account,
    324                  password = dbo_password
    325          )
    326          try:
    327                  conn = gmPG2.get_connection (
    328                          dsn = dsn,
    329                          readonly = False,
    330                          verbose = True,
    331                          pooled = False
    332                  )
    333          except:
    334                  _log.exception('cannot connect')
    335                  gmGuiHelpers.gm_show_error (
    336                          aMessage = _('Cannot connect as the GNUmed database owner <%s>.') % dbo_account,
    337                          aTitle = procedure
    338                  )
    339                  gmPG2.log_database_access(action = 'failed to connect as database owner for [%s]' % procedure)
    340                  return None
    341
    342          return conn
    343
    344  #================================================================
    345 -def change_gmdbowner_password():
    346
    347          title = _('Changing GNUmed database owner password')
    348
    349          dbo_account = wx.GetTextFromUser (
    350                  message = _("Enter the account name of the GNUmed database owner:"),
    351                  caption = title,
    352                  default_value = ''
    353          )
    354
    355          if dbo_account.strip() == '':
    356                  return False
    357
    358          dbo_conn = get_dbowner_connection (
    359                  procedure = title,
    360                  dbo_account = dbo_account
    361          )
    362          if dbo_conn is None:
    363                  return False
    364
    365          dbo_pwd_new_1 = wx.GetPasswordFromUser (
    366                  message = _("Enter the NEW password for the GNUmed database owner:"),
    367                  caption = title
    368          )
    369          if dbo_pwd_new_1.strip() == '':
    370                  return False
    371
    372          gmLog2.add_word2hide(dbo_pwd_new_1)
    373
    374          dbo_pwd_new_2 = wx.GetPasswordFromUser (
    375                  message = _("""Enter the NEW password for the GNUmed database owner, again.
    376
    377  (This will protect you from typos.)
    378                  """),
    379                  caption = title
    380          )
    381          if dbo_pwd_new_2.strip() == '':
    382                  return False
    383
    384          if dbo_pwd_new_1 != dbo_pwd_new_2:
    385                  return False
    386
    387          # pwd2 == pwd1 at this point so no need to hide (again)
    388
    389          """     On Mon, Mar 13, 2017 at 12:19:22PM -0400, Tom Lane wrote:
    390                  > Date: Mon, 13 Mar 2017 12:19:22 -0400
    391                  > From: Tom Lane <tgl@sss.pgh.pa.us>
    392                  > To: Adrian Klaver <adrian.klaver@aklaver.com>
    393                  > cc: Schmid Andreas <Andreas.Schmid@bd.so.ch>,
    394                  >  "'pgsql-general@postgresql.org'" <pgsql-general@postgresql.org>
    395                  > Subject: Re: [GENERAL] createuser: How to specify a database to connect to
    396                  >
    397                  > Adrian Klaver <adrian.klaver@aklaver.com> writes:
    398                  > > On 03/13/2017 08:52 AM, Tom Lane wrote:
    399                  > >> If by "history" you're worried about the server-side statement log, this
    400                  > >> is merest fantasy: the createuser program is not magic, it just constructs
    401                  > >> and sends a CREATE USER command for you.  You'd actually be more secure
    402                  > >> using psql, where (if you're superuser) you could shut off log_statement
    403                  > >> for your session first.
    404                  >
    405                  > > There is a difference though:
    406                  >
    407                  > > psql> CREATE USER:
    408                  >
    409                  > > postgres-2017-03-13 09:03:27.147 PDT-0LOG:  statement: create user
    410                  > > dummy_user with login password '1234';
    411                  >
    412                  > Well, what you're supposed to do is
    413                  >
    414                  > postgres=# create user dummy_user;
    415                  > postgres=# \password dummy_user
    416                  > Enter new password:
    417                  > Enter it again:
    418                  > postgres=#
    419                  >
    420                  > which will result in sending something like
    421                  >
    422                  > ALTER USER dummy_user PASSWORD 'md5c5e9567bc40082671d02c654260e0e09'
    423                  >
    424                  > You can additionally protect that by wrapping it into one transaction
    425                  > (if you have a setup where the momentary existence of the role without a
    426                  > password would be problematic) and/or shutting off logging beforehand.
    427          """
    428
    429          # this REALLY should be prefixed with md5 and the md5sum sent rather than the pwd
    430          cmd = """ALTER ROLE "%s" ENCRYPTED PASSWORD '%s';""" % (
    431                  dbo_account,
    432                  dbo_pwd_new_2
    433          )
    434          gmPG2.run_rw_queries(link_obj = dbo_conn, queries = [{'cmd': cmd}], end_tx = True)
    435
    436          return True
    437
    438  #================================================================
    439 -class cBackendProfile:
    440          pass
    441
    442  #================================================================
    443 -class cLoginDialog(wx.Dialog):
    444          """cLoginDialog - window holding cLoginPanel"""
    445
    446 -        def __init__(self, parent, id, title = _("Welcome to"), client_version = '*** unknown ***'):
    447                  wx.Dialog.__init__(self, parent, id, title)
    448                  self.panel = cLoginPanel(self, -1, isDialog=1, client_version = client_version)
    449                  self.Fit() # needed for Windoze.
    450                  self.Centre()
    451
    452                  self.SetIcon(gmTools.get_icon(wx = wx))
    453
    454  #================================================================
    455 -class cLoginPanel(wx.Panel):
    456          """GUI panel class that interactively gets Postgres login parameters.
    457
    458                  It features combo boxes which "remember" any number of
    459                  previously entered settings.
    460          """
    461 -        def __init__(self, parent, id,
    462                                          pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TAB_TRAVERSAL,
    463                                          isDialog = 0, client_version = '*** unknown ***'):
    464                  """Create login panel.
    465
    466                  isDialog:       if this panel is the main panel of a dialog, the panel will
    467                                          resize the dialog automatically to display everything neatly
    468                                          if isDialog is set to True
    469                  """
    470                  wx.Panel.__init__(self, parent, id, pos, size, style)
    471                  self.parent = parent
    472
    473                  #True if dialog was cancelled by user
    474                  #if the dialog is closed manually, login should be cancelled
    475                  self.cancelled = True
    476
    477                  # True if this panel is displayed within a dialog (will resize the dialog automatically then)
    478                  self.isDialog = isDialog
    479
    480                  self.topsizer = wx.BoxSizer(wx.VERTICAL)
    481
    482                  # find bitmap
    483                  paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
    484                  bitmap = os.path.join(paths.system_app_data_dir, 'bitmaps', 'gnumedlogo.png')
    485                  try:
    486                          png = wx.Image(bitmap, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
    487                          bmp = wx.StaticBitmap(self, -1, png, wx.Point(10, 10), wx.Size(png.GetWidth(), png.GetHeight()))
    488                          self.topsizer.Add (
    489                                  bmp,
    490                                  proportion = 0,
    491                                  flag = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,
    492                                  border = 10
    493                          )
    494                  except:
    495                          self.topsizer.Add (
    496                                  wx.StaticText (
    497                                          self,
    498                                          -1,
    499                                          label = _("Cannot find image") + bitmap,
    500                                          style = wx.ALIGN_CENTRE
    501                                  ),
    502                                  proportion = 0,
    503                                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
    504                                  border = 10
    505                          )
    506
    507                  paramsbox_caption = _('Workplace "%s" (version %s)') % (gmPraxis.gmCurrentPraxisBranch().active_workplace, client_version)
    508
    509                  # FIXME: why doesn't this align in the centre ?
    510                  self.paramsbox = wx.StaticBox( self, -1, paramsbox_caption, style = wx.ALIGN_CENTRE_HORIZONTAL)
    511                  self.paramsboxsizer = wx.StaticBoxSizer( self.paramsbox, wx.VERTICAL )
    512                  self.paramsbox.SetForegroundColour(wx.Colour(35, 35, 142))
    513                  self.paramsbox.SetFont(wx.Font(
    514                          pointSize = 12,
    515                          family = wx.SWISS,
    516                          style = wx.NORMAL,
    517                          weight = wx.BOLD,
    518                          underline = False
    519                  ))
    520                  self.pboxgrid = wx.FlexGridSizer(5, 2, 5, 5)
    521                  self.pboxgrid.AddGrowableCol(1)
    522
    523                  # PROFILE COMBO
    524                  label = wx.StaticText( self, -1, _('Log into'), wx.DefaultPosition, wx.DefaultSize, 0)
    525                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    526                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    527                  self.__backend_profiles = self.__get_backend_profiles()
    528                  self._CBOX_profile = wx.ComboBox (
    529                          self,
    530                          -1,
    531                          list(self.__backend_profiles.keys())[0],
    532                          wx.DefaultPosition,
    533                          size = wx.Size(550,-1),
    534                          choices = list(self.__backend_profiles.keys()),
    535                          style = wx.CB_READONLY
    536                  )
    537                  self.pboxgrid.Add (self._CBOX_profile, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    538
    539                  # USER NAME COMBO
    540                  label = wx.StaticText( self, -1, _("Username"), wx.DefaultPosition, wx.DefaultSize, 0 )
    541                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    542                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    543                  self.__previously_used_accounts = self.__get_previously_used_accounts()
    544                  self._CBOX_user = wx.ComboBox (
    545                          self,
    546                          -1,
    547                          self.__previously_used_accounts[0],
    548                          wx.DefaultPosition,
    549                          wx.Size(150,-1),
    550                          self.__previously_used_accounts,
    551                          wx.CB_DROPDOWN
    552                  )
    553                  self.pboxgrid.Add( self._CBOX_user, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    554
    555                  #PASSWORD TEXT ENTRY
    556                  label = wx.StaticText( self, -1, _("Password"), wx.DefaultPosition, wx.DefaultSize, 0 )
    557                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    558                  self.pboxgrid.Add( label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    559                  self.pwdentry = wx.TextCtrl( self, 1, '', wx.DefaultPosition, wx.Size(80,-1), wx.TE_PASSWORD )
    560                  # set focus on password entry
    561                  self.pwdentry.SetFocus()
    562                  self.pboxgrid.Add( self.pwdentry, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    563
    564                  # --debug checkbox
    565                  label = wx.StaticText(self, -1, _('Options'), wx.DefaultPosition, wx.DefaultSize, 0)
    566                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    567                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    568                  self._CHBOX_debug = wx.CheckBox(self, -1, _('&Debug mode'))
    569                  self._CHBOX_debug.SetToolTip(_('Check this to run GNUmed client in debugging mode.'))
    570                  self.pboxgrid.Add(self._CHBOX_debug, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    571
    572                  # --slave checkbox
    573                  label = wx.StaticText(self, -1, '', wx.DefaultPosition, wx.DefaultSize, 0)
    574                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    575                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    576                  self._CHBOX_slave = wx.CheckBox(self, -1, _('Enable &remote control'))
    577                  self._CHBOX_slave.SetToolTip(_('Check this to run GNUmed client in slave mode for remote control.'))
    578                  self.pboxgrid.Add(self._CHBOX_slave, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    579
    580                  #----------------------------------------------------------------------
    581                  #new button code inserted rterry 06Sept02
    582                  #button order re-arraged to make it consistant with usual dialog format
    583                  #in most operating systems ie  btns ok and cancel are standard and
    584                  #in that order
    585                  #ie Order is now help, ok and cancel
    586                  #The order of creation is the tab order
    587                  #login-ok button automatically is the default when tabbing (or <enter>)
    588                  #from password
    589                  #this eliminates the heavy border when you use the default
    590                  #?is the default word needed for any other reason?
    591                  #----------------------------------------------------------------------
    592                  self.button_gridsizer = wx.GridSizer(1,3,0,0)
    593                  #---------------------
    594                  #1:create login ok button
    595                  #---------------------
    596                  ID_BUTTON_LOGIN = wx.NewId()
    597                  button_login_ok = wx.Button(self, ID_BUTTON_LOGIN, _("&Ok"), wx.DefaultPosition, wx.DefaultSize, 0 )
    598                  button_login_ok.SetToolTip(wx.ToolTip(_("Proceed with login.")) )
    599                  button_login_ok.SetDefault()
    600
    601                  #---------------------
    602                  #2:create cancel button
    603                  #---------------------
    604                  ID_BUTTON_CANCEL = wx.NewId()
    605                  button_cancel = wx.Button(self, ID_BUTTON_CANCEL, _("&Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 )
    606                  button_cancel.SetToolTip(wx.ToolTip(_("Cancel Login.")) )
    607                  #---------------------
    608                  #3:create Help button
    609                  #---------------------
    610                  ID_BUTTON_HELP = wx.NewId()
    611                  button_help = wx.Button(self, ID_BUTTON_HELP, _("&Help"), wx.DefaultPosition, wx.DefaultSize, 0 )
    612                  button_help.SetToolTip(wx.ToolTip(_("Help for login screen")))
    613                  #----------------------------
    614                  #Add buttons to the gridsizer
    615                  #----------------------------
    616                  self.button_gridsizer.Add (button_help,0,wx.EXPAND|wx.ALL,5)
    617                  self.button_gridsizer.Add (button_login_ok,0,wx.EXPAND|wx.ALL,5)
    618                  self.button_gridsizer.Add (button_cancel,0,wx.EXPAND|wx.ALL,5)
    619
    620                  self.paramsboxsizer.Add(self.pboxgrid, 1, wx.GROW|wx.ALL, 10)
    621                  self.topsizer.Add(self.paramsboxsizer, 1, wx.GROW|wx.ALL, 10)
    622                  self.topsizer.Add( self.button_gridsizer, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    623
    624                  self.__load_state()
    625
    626                  self.SetAutoLayout(True)
    627                  self.SetSizer( self.topsizer)
    628                  self.topsizer.Fit( self )
    629                  if self.isDialog:
    630                          self.topsizer.SetSizeHints(parent)
    631
    632                  button_help.Bind(wx.EVT_BUTTON, self.OnHelp)
    633                  button_login_ok.Bind(wx.EVT_BUTTON, self.__on_login_button_pressed)
    634                  button_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
    635                  #wx.EVT_BUTTON(self, ID_BUTTON_HELP, self.OnHelp)
    636                  #wx.EVT_BUTTON(self, ID_BUTTON_LOGIN, self.__on_login_button_pressed)
    637                  #wx.EVT_BUTTON(self, ID_BUTTON_CANCEL, self.OnCancel)
    638
    639          #----------------------------------------------------------
    640          # internal helper methods
    641          #----------------------------------------------------------
    642 -        def __get_previously_used_accounts(self):
    643
    644                  accounts = gmTools.coalesce (
    645                          _cfg.get (
    646                                  group = 'backend',
    647                                  option = 'logins',
    648                                  source_order = [
    649                                          ('explicit', 'extend'),
    650                                          ('user', 'extend'),
    651                                          ('workbase', 'extend')
    652                                  ]
    653                          ),
    654                          ['any-doc']
    655                  )
    656                  # FIXME: make unique
    657
    658                  return accounts
    659          #----------------------------------------------------
    660 -        def __get_backend_profiles(self):
    661                  """Get server profiles from the configuration files.
    662
    663                  1) from system-wide file
    664                  2) from user file
    665
    666                  Profiles in the user file which have the same name
    667                  as a profile in the system file will override the
    668                  system file.
    669                  """
    670                  # find active profiles
    671                  src_order = [
    672                          ('explicit', 'extend'),
    673                          ('system', 'extend'),
    674                          ('user', 'extend'),
    675                          ('workbase', 'extend')
    676                  ]
    677
    678                  profile_names = gmTools.coalesce (
    679                          _cfg.get(group = 'backend', option = 'profiles', source_order = src_order),
    680                          []
    681                  )
    682
    683                  # find data for active profiles
    684                  src_order = [
    685                          ('explicit', 'return'),
    686                          ('workbase', 'return'),
    687                          ('user', 'return'),
    688                          ('system', 'return')
    689                  ]
    690
    691                  profiles = {}
    692
    693                  for profile_name in profile_names:
    694                          # FIXME: once the profile has been found always use the corresponding source !
    695                          # FIXME: maybe not or else we cannot override parts of the profile
    696                          profile = cBackendProfile()
    697                          profile_section = 'profile %s' % profile_name
    698
    699                          profile.name = profile_name
    700                          profile.host = gmTools.coalesce(_cfg.get(profile_section, 'host', src_order), '').strip()
    701                          port = gmTools.coalesce(_cfg.get(profile_section, 'port', src_order), 5432)
    702                          try:
    703                                  profile.port = int(port)
    704                                  if profile.port < 1024:
    705                                          raise ValueError('refusing to use priviledged port (< 1024)')
    706                          except ValueError:
    707                                  _log.warning('invalid port definition: [%s], skipping profile [%s]', port, profile_name)
    708                                  continue
    709                          profile.database = gmTools.coalesce(_cfg.get(profile_section, 'database', src_order), '').strip()
    710                          if profile.database == '':
    711                                  _log.warning('database name not specified, skipping profile [%s]', profile_name)
    712                                  continue
    713                          profile.encoding = gmTools.coalesce(_cfg.get(profile_section, 'encoding', src_order), 'UTF8')
    714                          profile.public_db = bool(_cfg.get(profile_section, 'public/open access', src_order))
    715                          profile.helpdesk = _cfg.get(profile_section, 'help desk', src_order)
    716
    717                          label = '%s (%s@%s)' % (profile_name, profile.database, profile.host)
    718                          profiles[label] = profile
    719
    720                  # sort out profiles with incompatible database versions if not --debug
    721                  # NOTE: this essentially hardcodes the database name in production ...
    722                  if not (_cfg.get(option = 'debug') or current_db_name.endswith('_devel')):
    723                          profiles2remove = []
    724                          for label in profiles:
    725                                  if profiles[label].database != current_db_name:
    726                                          profiles2remove.append(label)
    727                          for label in profiles2remove:
    728                                  del profiles[label]
    729
    730                  if len(profiles) == 0:
    731                          host = 'publicdb.gnumed.de'
    732                          label = 'public GNUmed database (%s@%s)' % (current_db_name, host)
    733                          profiles[label] = cBackendProfile()
    734                          profiles[label].name = label
    735                          profiles[label].host = host
    736                          profiles[label].port = 5432
    737                          profiles[label].database = current_db_name
    738                          profiles[label].encoding = 'UTF8'
    739                          profiles[label].public_db = True
    740                          profiles[label].helpdesk = 'http://wiki.gnumed.de'
    741
    742                  return profiles
    743          #----------------------------------------------------------
    744 -        def __load_state(self):
    745
    746                  src_order = [
    747                          ('explicit', 'return'),
    748                          ('user', 'return'),
    749                  ]
    750
    751                  self._CBOX_user.SetValue (
    752                          gmTools.coalesce (
    753                                  _cfg.get('preferences', 'login', src_order),
    754                                  self.__previously_used_accounts[0]
    755                          )
    756                  )
    757
    758                  last_used_profile_label = _cfg.get('preferences', 'profile', src_order)
    759                  if last_used_profile_label in self.__backend_profiles.keys():
    760                          self._CBOX_profile.SetValue(last_used_profile_label)
    761                  else:
    762                          self._CBOX_profile.SetValue(list(self.__backend_profiles.keys())[0])
    763
    764                  self._CHBOX_debug.SetValue(_cfg.get(option = 'debug'))
    765                  self._CHBOX_slave.SetValue(_cfg.get(option = 'slave'))
    766          #----------------------------------------------------
    767 -        def save_state(self):
    768                  """Save parameter settings to standard configuration file"""
    769                  prefs_name = _cfg.get(option = 'user_preferences_file')
    770                  _log.debug('saving login preferences in [%s]', prefs_name)
    771
    772                  gmCfg2.set_option_in_INI_file (
    773                          filename = prefs_name,
    774                          group = 'preferences',
    775                          option = 'login',
    776                          value = self._CBOX_user.GetValue()
    777                  )
    778
    779                  gmCfg2.set_option_in_INI_file (
    780                          filename = prefs_name,
    781                          group = 'preferences',
    782                          option = 'profile',
    783                          value = self._CBOX_profile.GetValue()
    784                  )
    785          #############################################################################
    786          # Retrieve current settings from user interface widgets
    787          #############################################################################
    788 -        def GetLoginInfo(self):
    789                  """convenience function for compatibility with gmLoginInfo.LoginInfo"""
    790                  if self.cancelled:
    791                          return None
    792
    793                  # FIXME: do not assume conf file is latin1 !
    794                  profile = self.__backend_profiles[self._CBOX_profile.GetValue().strip()]
    795                  _log.info('backend profile "%s" selected', profile.name)
    796                  _log.info(' details: <%s> on %s@%s:%s (%s, %s)',
    797                          self._CBOX_user.GetValue(),
    798                          profile.database,
    799                          profile.host,
    800                          profile.port,
    801                          profile.encoding,
    802                          gmTools.bool2subst(profile.public_db, 'public', 'private')
    803                  )
    804                  _log.info(' helpdesk: "%s"', profile.helpdesk)
    805                  login = gmLoginInfo.LoginInfo (
    806                          user = self._CBOX_user.GetValue(),
    807                          password = self.pwdentry.GetValue(),
    808                          host = profile.host,
    809                          database = profile.database,
    810                          port = profile.port
    811                  )
    812                  login.public_db = profile.public_db
    813                  login.helpdesk = profile.helpdesk
    814                  login.backend_profile = profile.name
    815                  return login
    816          #----------------------------
    817          # event handlers
    818          #----------------------------
    819 -        def OnHelp(self, event):
    820                  praxis = gmPraxis.gmCurrentPraxisBranch()
    821                  wx.MessageBox(_(
    822  """Unable to connect to the database ?
    823
    824   "PostgreSQL: FATAL:  password authentication failed ..."
    825
    826  The default user name and password are {any-doc, any-doc}
    827  for the public and any new GNUmed databases.
    828
    829   "... could not connect to server ..."
    830
    831  Mostly this is a case of new users who did not yet install
    832  or configure a PostgreSQL server and/or a GNUmed database
    833  of their own, which you must do before you can connect to
    834  anything other than the public demonstration database, see
    835
    836   http://wiki.gnumed.de/bin/view/Gnumed/GmManualServerInstall
    837
    838  For assistance on using GNUmed please consult the wiki:
    839
    840   http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual
    841
    842  For more help than the above, please contact:
    843
    844   GNUmed Development List <gnumed-bugs@gnu.org>
    845
    846  For local assistance please contact:
    847
    848   %s""") % praxis.helpdesk,
    849                  caption = _('HELP for GNUmed main login screen'))
    850
    851          #----------------------------
    852 -        def __on_login_button_pressed(self, event):
    853
    854                  root_logger = logging.getLogger()
    855                  if self._CHBOX_debug.GetValue():
    856                          _log.info('debug mode enabled')
    857                          _cfg.set_option(option = 'debug', value = True)
    858                          root_logger.setLevel(logging.DEBUG)
    859                  else:
    860                          _log.info('debug mode disabled')
    861                          _cfg.set_option(option = 'debug', value = False)
    862                          if _cfg.get(option = '--quiet', source_order = [('cli', 'return')]):
    863                                  root_logger.setLevel(logging.ERROR)
    864                          else:
    865                                  root_logger.setLevel(logging.WARNING)
    866
    867                  if self._CHBOX_slave.GetValue():
    868                          _log.info('slave mode enabled')
    869                          _cfg.set_option(option = 'slave', value = True)
    870                  else:
    871                          _log.info('slave mode disabled')
    872                          _cfg.set_option(option = 'slave', value = False)
    873
    874                  self.backend_profile = self.__backend_profiles[self._CBOX_profile.GetValue().strip()]
    875  #               self.user = self._CBOX_user.GetValue().strip()
    876  #               self.password = self.GetPassword()
    877                  self.cancelled = False
    878                  self.parent.Close()
    879          #----------------------------
    880 -        def OnCancel(self, event):
    881                  self.cancelled = True
    882                  self.parent.Close()
    883
    884  #================================================================
    885  # main
    886  #----------------------------------------------------------------
    887  if __name__ == "__main__":
    888
    889          if len(sys.argv) < 2:
    890                  sys.exit()
    891
    892          if sys.argv[1] != 'test':
    893                  sys.exit()
    894
    895          # we don't have tests yet
    896          sys.exit()
    897
    898          from Gnumed.pycommon import gmI18N
    899
    900          logging.basicConfig(level = logging.DEBUG)
    901
    902          gmI18N.activate_locale()
    903          gmI18N.install_domain(domain='gnumed')
    904          #-----------------------------------------------
    905          #-----------------------------------------------
    906 -        def test():
    907                  app = wx.PyWidgetTester(size = (300,400))
    908                  #show the login panel in a main window
    909  #               app.SetWidget(cLoginPanel, -1)
    910                  #and pop the login dialog up modally
    911                  dlg = cLoginDialog(None, -1) #, png_bitmap = 'bitmaps/gnumedlogo.png')
    912                  dlg.ShowModal()
    913                  #demonstration how to access the login dialog values
    914                  lp = dlg.panel.GetLoginInfo()
    915                  if lp is None:
    916                          wx.MessageBox(_("Dialog was cancelled by user"))
    917                  else:
    918                          wx.MessageBox(_("You tried to log in as [%s] with password [%s].\nHost:%s, DB: %s, Port: %s") % (lp.GetUser(),lp.GetPassword(),lp.GetHost(),lp.GetDatabase(),lp.GetPort()))
    919                  dlg.Destroy()
    920  #               app.MainLoop()
    921
    922  #================================================================
    923