Source code for stoqlib.gui.utils.login

# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4

##
## Copyright (C) 2005-2008 Async Open Source <http://www.async.com.br>
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU Lesser General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU Lesser General Public License for more details.
##
## You should have received a copy of the GNU Lesser General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., or visit: http://www.gnu.org/.
##
##  Author(s): Stoq Team <stoq-devel@async.com.br>
##
""" Login dialog for users authentication"""

import logging

import gtk
from kiwi.component import get_utility, provide_utility
from kiwi.ui.delegates import GladeDelegate

from stoqlib.api import api
from stoqlib.database.interfaces import ICurrentUser
from stoqlib.exceptions import DatabaseError, LoginError, UserProfileError
from stoqlib.domain.person import LoginUser
from stoqlib.gui.base.dialogs import RunnableView
from stoqlib.gui.utils.logo import render_logo_pixbuf
from stoqlib.lib.interfaces import CookieError, ICookieFile
from stoqlib.lib.message import warning
from stoqlib.lib.translation import stoqlib_gettext

_ = stoqlib_gettext

RETRY_NUMBER = 3
log = logging.getLogger(__name__)


[docs]class LoginDialog(GladeDelegate, RunnableView): domain = 'stoq' toplevel_name = gladefile = "LoginDialog" size = (-1, -1) def __init__(self, title=None): self.keyactions = {gtk.keysyms.Escape: self.on_escape_pressed} GladeDelegate.__init__(self, gladefile=self.gladefile, keyactions=self.keyactions, delete_handler=gtk.main_quit) if title: self.set_title(title) self.get_toplevel().set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) self.setup_widgets()
[docs] def setup_widgets(self): self.get_toplevel().set_size_request(*self.size) self.notification_label.set_text('') self.notification_label.set_color('black') if api.sysparam.get_bool('DISABLE_COOKIES'): self.remember.hide() self.remember.set_active(False) gtkimage = gtk.Image() gtkimage.set_from_pixbuf(render_logo_pixbuf('login')) self.logo_container.add(gtkimage) self.logo_container.show_all()
[docs] def force_username(self, username): self.username.set_text(username) self.username.set_sensitive(False) self.password.grab_focus()
def _initialize(self, username=None, password=None): self.username.set_text(username or "") self.password.set_text(password or "") self.retval = None self.username.grab_focus()
[docs] def on_escape_pressed(self): gtk.main_quit()
[docs] def on_delete_event(self, window, event): self.close()
[docs] def on_ok_button__clicked(self, button): self._do_login()
[docs] def on_quit_button__clicked(self, button): gtk.main_quit() self.retval = False # oneiric didn't need this, but it is required for # precise for reasons unknown # application.py as well raise SystemExit
[docs] def on_username__activate(self, entry): self.password.grab_focus()
[docs] def on_password__activate(self, entry): self._do_login()
[docs] def set_field_sensitivity(self, sensitive=True): for widget in (self.username, self.password, self.ok_button, self.quit_button): widget.set_sensitive(sensitive)
def _do_login(self): username = unicode(self.username.get_text().strip()) password = unicode(self.password.get_text().strip()) password = LoginUser.hash(password) self.retval = username, password self.set_field_sensitivity(False) self.notification_label.set_color('black') msg = _(" Authenticating user...") self.notification_label.set_text(msg) while gtk.events_pending(): gtk.main_iteration() gtk.main_quit() self.set_field_sensitivity(True)
[docs] def run(self, username=None, password=None, msg=None): if msg: self.notification_label.set_color('red') self.notification_label.set_text(msg) self._initialize(username, password) self.show() gtk.main() return self.retval
[docs]class LoginHelper: def __init__(self, username=None): self.user = None self._force_username = username def _check_user(self, username, pw_hash): username = unicode(username) pw_hash = unicode(pw_hash) # This function is really just a post-validation item. default_store = api.get_default_store() current_branch = api.get_current_branch(default_store) user = LoginUser.authenticate(default_store, username, pw_hash, current_branch) # Dont know why, but some users have this empty. Prevent user from # login in, since it will break later if not user.profile: msg = (_("User '%s' has no profile set, " "but this should not happen.") % user.username + '\n\n' + _("Please contact your system administrator or Stoq team.")) warning(msg) raise LoginError(_("User does not have a profile")) user.login() # ICurrentUser might already be provided which is the case when # creating a new database, thus we need to replace it. provide_utility(ICurrentUser, user, replace=True) return user
[docs] def cookie_login(self): if api.sysparam.get_bool('DISABLE_COOKIES'): log.info("Cookies disable by parameter") return cookie_file = get_utility(ICookieFile) try: username, password = cookie_file.get() except CookieError: log.info("Not using cookie based login") return def is_md5(password): # This breaks for passwords that are 32 characters long, # uses only digits and lowercase a-f, pretty unlikely as # real-world password if len(password) != 32: return False for c in '1234567890abcdef': password = password.replace(c, '') return password == '' # Migrate old passwords to md5 hashes. if not is_md5(password): password = LoginUser.hash(password) cookie_file.store(username, password) try: user = self._check_user(username, password) except (LoginError, UserProfileError, DatabaseError) as e: log.info("Cookie login failed: %r" % e) return log.info("Logging in using cookie credentials") return user
[docs] def validate_user(self): """ Checks if an user can log in or not. :returns: a user object """ # If there is only one user, and that user is admin with a blank # password, just log the user in store = api.get_default_store() if store.find(LoginUser).count() == 1: try: user = self._check_user(u'admin', LoginUser.hash(u'')) return user except Exception: pass log.info("Showing login dialog") # Loop for logins retry = 0 retry_msg = None dialog = None while retry < RETRY_NUMBER: username = self._force_username password = None if not dialog: dialog = LoginDialog(_("Stoq - Access Control")) if self._force_username: dialog.force_username(username) ret = dialog.run(username, password, msg=retry_msg) # user cancelled (escaped) the login dialog if not ret: return # Use credentials if not (isinstance(ret, (tuple, list)) and len(ret) == 2): raise ValueError('Invalid return value, got %s' % str(ret)) username, password = ret if not username: retry_msg = _("specify an username") continue try: user = self._check_user(username, password) except (LoginError, UserProfileError) as e: # We don't hide the dialog here; it's kept open so the # next loop we just can call run() and display the error cookie = get_utility(ICookieFile, None) if cookie: cookie.clear() retry += 1 retry_msg = str(e) except DatabaseError as e: if dialog: dialog.destroy() self._abort(str(e)) else: log.info("Authenticated user %s" % username) self._force_username = None if dialog.remember.get_active(): get_utility(ICookieFile).store(user.username, user.pw_hash) if dialog: dialog.destroy() return user if dialog: dialog.destroy() raise LoginError(_("Depleted attempts of authentication"))
# # Exit strategies # def _abort(self, msg=None, title=None): if msg: warning(msg) raise SystemExit