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