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 DSN works
    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                  conn = gmPG2.get_connection(verbose = True, connection_name = 'GNUmed-[DbListenerThread]', pooled = False)
    279                  listener = gmBackendListener.gmBackendListener(conn = conn)
    280                  break
    281
    282          dlg.DestroyLater()
    283
    284          return connected
    285
    286  #================================================================
    287 -def get_dbowner_connection(procedure=None, dbo_password=None, dbo_account='gm-dbo'):
    288          if procedure is None:
    289                  procedure = _('<restricted procedure>')
    290
    291          # 1) get password for gm-dbo
    292          if dbo_password is None:
    293                  dbo_password = wx.GetPasswordFromUser (
    294                          message = _("""
    295   [%s]
    296
    297  This is a restricted procedure. We need the
    298  current password for the GNUmed database owner.
    299
    300  Please enter the current password for <%s>:""") % (
    301                                  procedure,
    302                                  dbo_account
    303                          ),
    304                          caption = procedure
    305                  )
    306                  if dbo_password == '':
    307                          return None
    308
    309          gmLog2.add_word2hide(dbo_password)
    310
    311          # 2) connect as gm-dbo
    312          pool = gmConnectionPool.gmConnectionPool()
    313          conn = None
    314          try:
    315                  conn = pool.get_dbowner_connection (
    316                          readonly = False,
    317                          verbose = True,
    318                          dbo_password = dbo_password,
    319                          dbo_account = dbo_account
    320                  )
    321          except Exception:
    322                  _log.exception('cannot connect')
    323                  gmGuiHelpers.gm_show_error (
    324                          aMessage = _('Cannot connect as the GNUmed database owner <%s>.') % dbo_account,
    325                          aTitle = procedure
    326                  )
    327                  gmPG2.log_database_access(action = 'failed to connect as database owner for [%s]' % procedure)
    328          return conn
    329
    330  #================================================================
    331 -def change_gmdbowner_password():
    332
    333          title = _('Changing GNUmed database owner password')
    334
    335          dbo_account = wx.GetTextFromUser (
    336                  message = _("Enter the account name of the GNUmed database owner:"),
    337                  caption = title,
    338                  default_value = ''
    339          )
    340
    341          if dbo_account.strip() == '':
    342                  return False
    343
    344          dbo_conn = get_dbowner_connection (
    345                  procedure = title,
    346                  dbo_account = dbo_account
    347          )
    348          if dbo_conn is None:
    349                  return False
    350
    351          dbo_pwd_new_1 = wx.GetPasswordFromUser (
    352                  message = _("Enter the NEW password for the GNUmed database owner:"),
    353                  caption = title
    354          )
    355          if dbo_pwd_new_1.strip() == '':
    356                  return False
    357
    358          gmLog2.add_word2hide(dbo_pwd_new_1)
    359
    360          dbo_pwd_new_2 = wx.GetPasswordFromUser (
    361                  message = _("""Enter the NEW password for the GNUmed database owner, again.
    362
    363  (This will protect you from typos.)
    364                  """),
    365                  caption = title
    366          )
    367          if dbo_pwd_new_2.strip() == '':
    368                  return False
    369
    370          if dbo_pwd_new_1 != dbo_pwd_new_2:
    371                  return False
    372
    373          # pwd2 == pwd1 at this point so no need to hide (again)
    374
    375          """     On Mon, Mar 13, 2017 at 12:19:22PM -0400, Tom Lane wrote:
    376                  > Date: Mon, 13 Mar 2017 12:19:22 -0400
    377                  > From: Tom Lane <tgl@sss.pgh.pa.us>
    378                  > To: Adrian Klaver <adrian.klaver@aklaver.com>
    379                  > cc: Schmid Andreas <Andreas.Schmid@bd.so.ch>,
    380                  >  "'pgsql-general@postgresql.org'" <pgsql-general@postgresql.org>
    381                  > Subject: Re: [GENERAL] createuser: How to specify a database to connect to
    382                  >
    383                  > Adrian Klaver <adrian.klaver@aklaver.com> writes:
    384                  > > On 03/13/2017 08:52 AM, Tom Lane wrote:
    385                  > >> If by "history" you're worried about the server-side statement log, this
    386                  > >> is merest fantasy: the createuser program is not magic, it just constructs
    387                  > >> and sends a CREATE USER command for you.  You'd actually be more secure
    388                  > >> using psql, where (if you're superuser) you could shut off log_statement
    389                  > >> for your session first.
    390                  >
    391                  > > There is a difference though:
    392                  >
    393                  > > psql> CREATE USER:
    394                  >
    395                  > > postgres-2017-03-13 09:03:27.147 PDT-0LOG:  statement: create user
    396                  > > dummy_user with login password '1234';
    397                  >
    398                  > Well, what you're supposed to do is
    399                  >
    400                  > postgres=# create user dummy_user;
    401                  > postgres=# \password dummy_user
    402                  > Enter new password:
    403                  > Enter it again:
    404                  > postgres=#
    405                  >
    406                  > which will result in sending something like
    407                  >
    408                  > ALTER USER dummy_user PASSWORD 'md5c5e9567bc40082671d02c654260e0e09'
    409                  >
    410                  > You can additionally protect that by wrapping it into one transaction
    411                  > (if you have a setup where the momentary existence of the role without a
    412                  > password would be problematic) and/or shutting off logging beforehand.
    413          """
    414
    415          # this REALLY should be prefixed with md5 and the md5sum sent rather than the pwd
    416          cmd = """ALTER ROLE "%s" ENCRYPTED PASSWORD '%s';""" % (
    417                  dbo_account,
    418                  dbo_pwd_new_2
    419          )
    420          gmPG2.run_rw_queries(link_obj = dbo_conn, queries = [{'cmd': cmd}], end_tx = True)
    421          return True
    422
    423  #================================================================
    424 -class cBackendProfile:
    425          pass
    426
    427  #================================================================
    428 -class cLoginDialog(wx.Dialog):
    429          """cLoginDialog - window holding cLoginPanel"""
    430
    431 -        def __init__(self, parent, id, title = _("Welcome to"), client_version = '*** unknown ***'):
    432                  wx.Dialog.__init__(self, parent, id, title)
    433                  self.panel = cLoginPanel(self, -1, isDialog=1, client_version = client_version)
    434                  self.Fit() # needed for Windoze.
    435                  self.Centre()
    436
    437                  self.SetIcon(gmTools.get_icon(wx = wx))
    438
    439  #================================================================
    440 -class cLoginPanel(wx.Panel):
    441          """GUI panel class that interactively gets Postgres login parameters.
    442
    443                  It features combo boxes which "remember" any number of
    444                  previously entered settings.
    445          """
    446 -        def __init__(self, parent, id,
    447                                          pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.TAB_TRAVERSAL,
    448                                          isDialog = 0, client_version = '*** unknown ***'):
    449                  """Create login panel.
    450
    451                  isDialog:       if this panel is the main panel of a dialog, the panel will
    452                                          resize the dialog automatically to display everything neatly
    453                                          if isDialog is set to True
    454                  """
    455                  wx.Panel.__init__(self, parent, id, pos, size, style)
    456                  self.parent = parent
    457
    458                  #True if dialog was cancelled by user
    459                  #if the dialog is closed manually, login should be cancelled
    460                  self.cancelled = True
    461
    462                  # True if this panel is displayed within a dialog (will resize the dialog automatically then)
    463                  self.isDialog = isDialog
    464
    465                  self.topsizer = wx.BoxSizer(wx.VERTICAL)
    466
    467                  # find bitmap
    468                  paths = gmTools.gmPaths(app_name = 'gnumed', wx = wx)
    469                  bitmap = os.path.join(paths.system_app_data_dir, 'bitmaps', 'gnumedlogo.png')
    470                  try:
    471                          png = wx.Image(bitmap, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
    472                          bmp = wx.StaticBitmap(self, -1, png, wx.Point(10, 10), wx.Size(png.GetWidth(), png.GetHeight()))
    473                          self.topsizer.Add (
    474                                  bmp,
    475                                  proportion = 0,
    476                                  flag = wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALL,
    477                                  border = 10
    478                          )
    479                  except Exception:
    480                          self.topsizer.Add (
    481                                  wx.StaticText (
    482                                          self,
    483                                          -1,
    484                                          label = _("Cannot find image") + bitmap,
    485                                          style = wx.ALIGN_CENTRE
    486                                  ),
    487                                  proportion = 0,
    488                                  flag = wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL,
    489                                  border = 10
    490                          )
    491
    492                  paramsbox_caption = _('Workplace "%s" (version %s)') % (gmPraxis.gmCurrentPraxisBranch().active_workplace, client_version)
    493
    494                  # FIXME: why doesn't this align in the centre ?
    495                  self.paramsbox = wx.StaticBox( self, -1, paramsbox_caption, style = wx.ALIGN_CENTRE_HORIZONTAL)
    496                  self.paramsboxsizer = wx.StaticBoxSizer( self.paramsbox, wx.VERTICAL )
    497                  self.paramsbox.SetForegroundColour(wx.Colour(35, 35, 142))
    498                  self.paramsbox.SetFont(wx.Font(
    499                          pointSize = 12,
    500                          family = wx.SWISS,
    501                          style = wx.NORMAL,
    502                          weight = wx.BOLD,
    503                          underline = False
    504                  ))
    505                  self.pboxgrid = wx.FlexGridSizer(5, 2, 5, 5)
    506                  self.pboxgrid.AddGrowableCol(1)
    507
    508                  # PROFILE COMBO
    509                  label = wx.StaticText( self, -1, _('Log into'), wx.DefaultPosition, wx.DefaultSize, 0)
    510                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    511                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    512                  self.__backend_profiles = self.__get_backend_profiles()
    513                  self._CBOX_profile = wx.ComboBox (
    514                          self,
    515                          -1,
    516                          list(self.__backend_profiles.keys())[0],
    517                          wx.DefaultPosition,
    518                          size = wx.Size(550,-1),
    519                          choices = list(self.__backend_profiles.keys()),
    520                          style = wx.CB_READONLY
    521                  )
    522                  self.pboxgrid.Add (self._CBOX_profile, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    523
    524                  # USER NAME COMBO
    525                  label = wx.StaticText( self, -1, _("Username"), wx.DefaultPosition, wx.DefaultSize, 0 )
    526                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    527                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    528                  self.__previously_used_accounts = self.__get_previously_used_accounts()
    529                  self._CBOX_user = wx.ComboBox (
    530                          self,
    531                          -1,
    532                          self.__previously_used_accounts[0],
    533                          wx.DefaultPosition,
    534                          wx.Size(150,-1),
    535                          self.__previously_used_accounts,
    536                          wx.CB_DROPDOWN
    537                  )
    538                  self.pboxgrid.Add( self._CBOX_user, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    539
    540                  #PASSWORD TEXT ENTRY
    541                  label = wx.StaticText( self, -1, _("Password"), wx.DefaultPosition, wx.DefaultSize, 0 )
    542                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    543                  self.pboxgrid.Add( label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    544                  self.pwdentry = wx.TextCtrl( self, 1, '', wx.DefaultPosition, wx.Size(80,-1), wx.TE_PASSWORD )
    545                  # set focus on password entry
    546                  self.pwdentry.SetFocus()
    547                  self.pboxgrid.Add( self.pwdentry, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    548
    549                  # --debug checkbox
    550                  label = wx.StaticText(self, -1, _('Options'), wx.DefaultPosition, wx.DefaultSize, 0)
    551                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    552                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    553                  self._CHBOX_debug = wx.CheckBox(self, -1, _('&Debug mode'))
    554                  self._CHBOX_debug.SetToolTip(_('Check this to run GNUmed client in debugging mode.'))
    555                  self.pboxgrid.Add(self._CHBOX_debug, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    556
    557                  # --slave checkbox
    558                  label = wx.StaticText(self, -1, '', wx.DefaultPosition, wx.DefaultSize, 0)
    559                  label.SetForegroundColour(wx.Colour(35, 35, 142))
    560                  self.pboxgrid.Add(label, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
    561                  self._CHBOX_slave = wx.CheckBox(self, -1, _('Enable &remote control'))
    562                  self._CHBOX_slave.SetToolTip(_('Check this to run GNUmed client in slave mode for remote control.'))
    563                  self.pboxgrid.Add(self._CHBOX_slave, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 5)
    564
    565                  #----------------------------------------------------------------------
    566                  #new button code inserted rterry 06Sept02
    567                  #button order re-arraged to make it consistant with usual dialog format
    568                  #in most operating systems ie  btns ok and cancel are standard and
    569                  #in that order
    570                  #ie Order is now help, ok and cancel
    571                  #The order of creation is the tab order
    572                  #login-ok button automatically is the default when tabbing (or <enter>)
    573                  #from password
    574                  #this eliminates the heavy border when you use the default
    575                  #?is the default word needed for any other reason?
    576                  #----------------------------------------------------------------------
    577                  self.button_gridsizer = wx.GridSizer(1,3,0,0)
    578                  #---------------------
    579                  #1:create login ok button
    580                  #---------------------
    581                  ID_BUTTON_LOGIN = wx.NewId()
    582                  button_login_ok = wx.Button(self, ID_BUTTON_LOGIN, _("&Ok"), wx.DefaultPosition, wx.DefaultSize, 0 )
    583                  button_login_ok.SetToolTip(wx.ToolTip(_("Proceed with login.")) )
    584                  button_login_ok.SetDefault()
    585
    586                  #---------------------
    587                  #2:create cancel button
    588                  #---------------------
    589                  ID_BUTTON_CANCEL = wx.NewId()
    590                  button_cancel = wx.Button(self, ID_BUTTON_CANCEL, _("&Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 )
    591                  button_cancel.SetToolTip(wx.ToolTip(_("Cancel Login.")) )
    592                  #---------------------
    593                  #3:create Help button
    594                  #---------------------
    595                  ID_BUTTON_HELP = wx.NewId()
    596                  button_help = wx.Button(self, ID_BUTTON_HELP, _("&Help"), wx.DefaultPosition, wx.DefaultSize, 0 )
    597                  button_help.SetToolTip(wx.ToolTip(_("Help for login screen")))
    598                  #----------------------------
    599                  #Add buttons to the gridsizer
    600                  #----------------------------
    601                  self.button_gridsizer.Add (button_help,0,wx.EXPAND|wx.ALL,5)
    602                  self.button_gridsizer.Add (button_login_ok,0,wx.EXPAND|wx.ALL,5)
    603                  self.button_gridsizer.Add (button_cancel,0,wx.EXPAND|wx.ALL,5)
    604
    605                  self.paramsboxsizer.Add(self.pboxgrid, 1, wx.GROW|wx.ALL, 10)
    606                  self.topsizer.Add(self.paramsboxsizer, 1, wx.GROW|wx.ALL, 10)
    607                  self.topsizer.Add( self.button_gridsizer, 0, wx.GROW|wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
    608
    609                  self.__load_state()
    610
    611                  self.SetAutoLayout(True)
    612                  self.SetSizer( self.topsizer)
    613                  self.topsizer.Fit( self )
    614                  if self.isDialog:
    615                          self.topsizer.SetSizeHints(parent)
    616
    617                  button_help.Bind(wx.EVT_BUTTON, self.OnHelp)
    618                  button_login_ok.Bind(wx.EVT_BUTTON, self.__on_login_button_pressed)
    619                  button_cancel.Bind(wx.EVT_BUTTON, self.OnCancel)
    620                  #wx.EVT_BUTTON(self, ID_BUTTON_HELP, self.OnHelp)
    621                  #wx.EVT_BUTTON(self, ID_BUTTON_LOGIN, self.__on_login_button_pressed)
    622                  #wx.EVT_BUTTON(self, ID_BUTTON_CANCEL, self.OnCancel)
    623
    624          #----------------------------------------------------------
    625          # internal helper methods
    626          #----------------------------------------------------------
    627 -        def __get_previously_used_accounts(self):
    628
    629                  accounts = gmTools.coalesce (
    630                          _cfg.get (
    631                                  group = 'backend',
    632                                  option = 'logins',
    633                                  source_order = [
    634                                          ('explicit', 'extend'),
    635                                          ('user', 'extend'),
    636                                          ('workbase', 'extend')
    637                                  ]
    638                          ),
    639                          ['any-doc']
    640                  )
    641                  # FIXME: make unique
    642
    643                  return accounts
    644          #----------------------------------------------------
    645 -        def __get_backend_profiles(self):
    646                  """Get server profiles from the configuration files.
    647
    648                  1) from system-wide file
    649                  2) from user file
    650
    651                  Profiles in the user file which have the same name
    652                  as a profile in the system file will override the
    653                  system file.
    654                  """
    655                  # find active profiles
    656                  src_order = [
    657                          ('explicit', 'extend'),
    658                          ('system', 'extend'),
    659                          ('user', 'extend'),
    660                          ('workbase', 'extend')
    661                  ]
    662
    663                  profile_names = gmTools.coalesce (
    664                          _cfg.get(group = 'backend', option = 'profiles', source_order = src_order),
    665                          []
    666                  )
    667
    668                  # find data for active profiles
    669                  src_order = [
    670                          ('explicit', 'return'),
    671                          ('workbase', 'return'),
    672                          ('user', 'return'),
    673                          ('system', 'return')
    674                  ]
    675
    676                  profiles = {}
    677
    678                  for profile_name in profile_names:
    679                          # FIXME: once the profile has been found always use the corresponding source !
    680                          # FIXME: maybe not or else we cannot override parts of the profile
    681                          profile = cBackendProfile()
    682                          profile_section = 'profile %s' % profile_name
    683
    684                          profile.name = profile_name
    685                          profile.host = gmTools.coalesce(_cfg.get(profile_section, 'host', src_order), '').strip()
    686                          port = gmTools.coalesce(_cfg.get(profile_section, 'port', src_order), 5432)
    687                          try:
    688                                  profile.port = int(port)
    689                                  if profile.port < 1024:
    690                                          raise ValueError('refusing to use priviledged port (< 1024)')
    691                          except ValueError:
    692                                  _log.warning('invalid port definition: [%s], skipping profile [%s]', port, profile_name)
    693                                  continue
    694                          profile.database = gmTools.coalesce(_cfg.get(profile_section, 'database', src_order), '').strip()
    695                          if profile.database == '':
    696                                  _log.warning('database name not specified, skipping profile [%s]', profile_name)
    697                                  continue
    698                          profile.encoding = gmTools.coalesce(_cfg.get(profile_section, 'encoding', src_order), 'UTF8')
    699                          profile.public_db = bool(_cfg.get(profile_section, 'public/open access', src_order))
    700                          profile.helpdesk = _cfg.get(profile_section, 'help desk', src_order)
    701
    702                          label = '%s (%s@%s)' % (profile_name, profile.database, profile.host)
    703                          profiles[label] = profile
    704
    705                  # sort out profiles with incompatible database versions if not --debug
    706                  # NOTE: this essentially hardcodes the database name in production ...
    707                  if not (_cfg.get(option = 'debug') or current_db_name.endswith('_devel')):
    708                          profiles2remove = []
    709                          for label in profiles:
    710                                  if profiles[label].database != current_db_name:
    711                                          profiles2remove.append(label)
    712                          for label in profiles2remove:
    713                                  del profiles[label]
    714
    715                  if len(profiles) == 0:
    716                          host = 'publicdb.gnumed.de'
    717                          label = 'public GNUmed database (%s@%s)' % (current_db_name, host)
    718                          profiles[label] = cBackendProfile()
    719                          profiles[label].name = label
    720                          profiles[label].host = host
    721                          profiles[label].port = 5432
    722                          profiles[label].database = current_db_name
    723                          profiles[label].encoding = 'UTF8'
    724                          profiles[label].public_db = True
    725                          profiles[label].helpdesk = 'http://wiki.gnumed.de'
    726
    727                  return profiles
    728          #----------------------------------------------------------
    729 -        def __load_state(self):
    730
    731                  src_order = [
    732                          ('explicit', 'return'),
    733                          ('user', 'return'),
    734                  ]
    735
    736                  self._CBOX_user.SetValue (
    737                          gmTools.coalesce (
    738                                  _cfg.get('preferences', 'login', src_order),
    739                                  self.__previously_used_accounts[0]
    740                          )
    741                  )
    742
    743                  last_used_profile_label = _cfg.get('preferences', 'profile', src_order)
    744                  if last_used_profile_label in self.__backend_profiles.keys():
    745                          self._CBOX_profile.SetValue(last_used_profile_label)
    746                  else:
    747                          self._CBOX_profile.SetValue(list(self.__backend_profiles.keys())[0])
    748
    749                  self._CHBOX_debug.SetValue(_cfg.get(option = 'debug'))
    750                  self._CHBOX_slave.SetValue(_cfg.get(option = 'slave'))
    751          #----------------------------------------------------
    752 -        def save_state(self):
    753                  """Save parameter settings to standard configuration file"""
    754                  prefs_name = _cfg.get(option = 'user_preferences_file')
    755                  _log.debug('saving login preferences in [%s]', prefs_name)
    756
    757                  gmCfg2.set_option_in_INI_file (
    758                          filename = prefs_name,
    759                          group = 'preferences',
    760                          option = 'login',
    761                          value = self._CBOX_user.GetValue()
    762                  )
    763
    764                  gmCfg2.set_option_in_INI_file (
    765                          filename = prefs_name,
    766                          group = 'preferences',
    767                          option = 'profile',
    768                          value = self._CBOX_profile.GetValue()
    769                  )
    770          #############################################################################
    771          # Retrieve current settings from user interface widgets
    772          #############################################################################
    773 -        def GetLoginInfo(self):
    774                  """convenience function for compatibility with gmLoginInfo.LoginInfo"""
    775                  if self.cancelled:
    776                          return None
    777
    778                  # FIXME: do not assume conf file is latin1 !
    779                  profile = self.__backend_profiles[self._CBOX_profile.GetValue().strip()]
    780                  _log.info('backend profile "%s" selected', profile.name)
    781                  _log.info(' details: <%s> on %s@%s:%s (%s, %s)',
    782                          self._CBOX_user.GetValue(),
    783                          profile.database,
    784                          profile.host,
    785                          profile.port,
    786                          profile.encoding,
    787                          gmTools.bool2subst(profile.public_db, 'public', 'private')
    788                  )
    789                  _log.info(' helpdesk: "%s"', profile.helpdesk)
    790                  login = gmLoginInfo.LoginInfo (
    791                          user = self._CBOX_user.GetValue(),
    792                          password = self.pwdentry.GetValue(),
    793                          host = profile.host,
    794                          database = profile.database,
    795                          port = profile.port
    796                  )
    797                  _cfg.set_option (
    798                          option = 'is_public_db',
    799                          value = profile.public_db
    800                  )
    801                  _cfg.set_option (
    802                          option = 'helpdesk',
    803                          value = profile.helpdesk
    804                  )
    805                  _cfg.set_option (
    806                          option = 'backend_profile',
    807                          value = profile.name
    808                  )
    809                  return login
    810          #----------------------------
    811          # event handlers
    812          #----------------------------
    813 -        def OnHelp(self, event):
    814                  praxis = gmPraxis.gmCurrentPraxisBranch()
    815                  wx.MessageBox(_(
    816  """Unable to connect to the database ?
    817
    818   "PostgreSQL: FATAL:  password authentication failed ..."
    819
    820  The default user name and password are {any-doc, any-doc}
    821  for the public and any new GNUmed databases.
    822
    823   "... could not connect to server ..."
    824
    825  Mostly this is a case of new users who did not yet install
    826  or configure a PostgreSQL server and/or a GNUmed database
    827  of their own, which you must do before you can connect to
    828  anything other than the public demonstration database, see
    829
    830   http://wiki.gnumed.de/bin/view/Gnumed/GmManualServerInstall
    831
    832  For assistance on using GNUmed please consult the wiki:
    833
    834   http://wiki.gnumed.de/bin/view/Gnumed/GnumedManual
    835
    836  For more help than the above, please contact:
    837
    838   GNUmed Development List <gnumed-bugs@gnu.org>
    839
    840  For local assistance please contact:
    841
    842   %s""") % praxis.helpdesk,
    843                  caption = _('HELP for GNUmed main login screen'))
    844
    845          #----------------------------
    846 -        def __on_login_button_pressed(self, event):
    847
    848                  root_logger = logging.getLogger()
    849                  if self._CHBOX_debug.GetValue():
    850                          _log.info('debug mode enabled')
    851                          _cfg.set_option(option = 'debug', value = True)
    852                          root_logger.setLevel(logging.DEBUG)
    853                  else:
    854                          _log.info('debug mode disabled')
    855                          _cfg.set_option(option = 'debug', value = False)
    856                          if _cfg.get(option = '--quiet', source_order = [('cli', 'return')]):
    857                                  root_logger.setLevel(logging.ERROR)
    858                          else:
    859                                  root_logger.setLevel(logging.WARNING)
    860
    861                  if self._CHBOX_slave.GetValue():
    862                          _log.info('slave mode enabled')
    863                          _cfg.set_option(option = 'slave', value = True)
    864                  else:
    865                          _log.info('slave mode disabled')
    866                          _cfg.set_option(option = 'slave', value = False)
    867
    868                  self.backend_profile = self.__backend_profiles[self._CBOX_profile.GetValue().strip()]
    869  #               self.user = self._CBOX_user.GetValue().strip()
    870  #               self.password = self.GetPassword()
    871                  self.cancelled = False
    872                  self.parent.Close()
    873          #----------------------------
    874 -        def OnCancel(self, event):
    875                  self.cancelled = True
    876                  self.parent.Close()
    877
    878  #================================================================
    879  # main
    880  #----------------------------------------------------------------
    881  if __name__ == "__main__":
    882
    883          if len(sys.argv) < 2:
    884                  sys.exit()
    885
    886          if sys.argv[1] != 'test':
    887                  sys.exit()
    888
    889          # we don't have tests yet
    890          sys.exit()
    891
    892          from Gnumed.pycommon import gmI18N
    893
    894          logging.basicConfig(level = logging.DEBUG)
    895
    896          gmI18N.activate_locale()
    897          gmI18N.install_domain(domain='gnumed')
    898          #-----------------------------------------------
    899          #-----------------------------------------------
    900 -        def test():
    901                  app = wx.PyWidgetTester(size = (300,400))
    902                  #show the login panel in a main window
    903  #               app.SetWidget(cLoginPanel, -1)
    904                  #and pop the login dialog up modally
    905                  dlg = cLoginDialog(None, -1) #, png_bitmap = 'bitmaps/gnumedlogo.png')
    906                  dlg.ShowModal()
    907                  #demonstration how to access the login dialog values
    908                  lp = dlg.panel.GetLoginInfo()
    909                  if lp is None:
    910                          wx.MessageBox(_("Dialog was cancelled by user"))
    911                  else:
    912                          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()))
    913                  dlg.DestroyLater()
    914  #               app.MainLoop()
    915
    916  #================================================================
    917