Volatile password

- implementation with not persistent password storage. Password is asked to the user
for each new jabber connection (offline -> available status)

darcs-hash:20060131214141-86b55-e595b06a2c1f9a8315189a69b79b094da1e24c1e.gz
This commit is contained in:
David Rousselie
2006-01-31 22:41:41 +01:00
parent 7398e98a68
commit fcf74c6835
7 changed files with 141 additions and 40 deletions

View File

@@ -112,7 +112,12 @@ class MailComponent(Component):
reg_form.add_field(type = "text-private", \ reg_form.add_field(type = "text-private", \
label = lang_class.account_password, \ label = lang_class.account_password, \
var = "password") var = "password")
reg_form.add_field(type = "boolean", \
label = lang_class.account_password_store, \
var = "store_password",
value = "True")
reg_form.add_field(type = "text-single", \ reg_form.add_field(type = "text-single", \
label = lang_class.account_host, \ label = lang_class.account_host, \
var = "host") var = "host")
@@ -239,6 +244,11 @@ class MailComponent(Component):
var = "password", \ var = "password", \
value = account.password) value = account.password)
reg_form_init.add_field(type = "boolean", \
label = lang_class.account_password_store, \
var = "store_password", \
value = str(account.store_password).lower())
reg_form_init.add_field(type = "text-single", \ reg_form_init.add_field(type = "text-single", \
label = lang_class.account_host, \ label = lang_class.account_host, \
var = "host", \ var = "host", \
@@ -551,6 +561,9 @@ class MailComponent(Component):
else: else:
password = u"" password = u""
store_password = x.fields.has_key("store_password") \
and (x.fields["store_password"].value == "1")
if x.fields.has_key("host"): if x.fields.has_key("host"):
host = x.fields["host"].value host = x.fields["host"].value
else: else:
@@ -606,8 +619,8 @@ class MailComponent(Component):
else: else:
interval = None interval = None
self.__logger.debug(u"New Account: %s, %s, %s, %s, %s, %s, %s %i %i %i %i %i %i %i" \ self.__logger.debug(u"New Account: %s, %s, %s, %s, %s, %s, %s, %s %i %i %i %i %i %i %i" \
% (name, login, password, host, str(port), \ % (name, login, password, str(store_password), host, str(port), \
mailbox, type, chat_action, online_action, away_action, \ mailbox, type, chat_action, online_action, away_action, \
xa_action, dnd_action, offline_action, interval)) xa_action, dnd_action, offline_action, interval))
@@ -645,6 +658,7 @@ class MailComponent(Component):
mailconnection_factory.get_new_mail_connection(type) mailconnection_factory.get_new_mail_connection(type)
account.login = login account.login = login
account.password = password account.password = password
account.store_password = store_password
account.host = host account.host = host
account.chat_action = chat_action account.chat_action = chat_action
account.online_action = online_action account.online_action = online_action
@@ -667,6 +681,7 @@ class MailComponent(Component):
self.__logger.debug("PRESENCE_AVAILABLE") self.__logger.debug("PRESENCE_AVAILABLE")
from_jid = stanza.get_from() from_jid = stanza.get_from()
base_from_jid = unicode(from_jid.bare()) base_from_jid = unicode(from_jid.bare())
lang_class = self.get_lang_class(self.get_lang(stanza.get_node()))
name = stanza.get_to().node name = stanza.get_to().node
show = stanza.get_show() show = stanza.get_show()
self.__logger.debug("SHOW : " + str(show)) self.__logger.debug("SHOW : " + str(show))
@@ -683,6 +698,8 @@ class MailComponent(Component):
self.stream.send(p) self.stream.send(p)
elif self.__storage.has_key((base_from_jid, name)): elif self.__storage.has_key((base_from_jid, name)):
account = self.__storage[(base_from_jid, name)] account = self.__storage[(base_from_jid, name)]
account.default_lang_class = lang_class
old_status = account.status
# Make available to receive mail only when online # Make available to receive mail only when online
if show is None: if show is None:
account.status = "online" # TODO get real status = (not show) account.status = "online" # TODO get real status = (not show)
@@ -695,8 +712,23 @@ class MailComponent(Component):
show = show, \ show = show, \
stanza_type = "available") stanza_type = "available")
self.stream.send(p) self.stream.send(p)
if account.store_password == False \
and old_status == "offline":
self.__ask_password(name, from_jid, lang_class, account)
return 1 return 1
def __ask_password(self, name, from_jid, lang_class, account):
if not account.waiting_password_reply \
and account.status != "offline":
account.waiting_password_reply = True
msg = Message(from_jid = name + "@" + unicode(self.jid), \
to_jid = from_jid, \
stanza_type = "message", \
subject = u"[PASSWORD] " + lang_class.ask_password_subject, \
body = lang_class.ask_password_body % \
(account.host, account.login))
self.stream.send(msg)
""" handle presence unavailability """ """ handle presence unavailability """
def presence_unavailable(self, stanza): def presence_unavailable(self, stanza):
self.__logger.debug("PRESENCE_UNAVAILABLE") self.__logger.debug("PRESENCE_UNAVAILABLE")
@@ -704,7 +736,12 @@ class MailComponent(Component):
base_from_jid = unicode(from_jid.bare()) base_from_jid = unicode(from_jid.bare())
if stanza.get_to() == unicode(self.jid): if stanza.get_to() == unicode(self.jid):
for jid, name in self.__storage.keys(): for jid, name in self.__storage.keys():
self.__storage[(base_from_jid, name)].status = "offline" account = self.__storage[(base_from_jid, name)]
account.status = "offline"
account.waiting_password_reply = False
if account.store_password == False:
self.__logger.debug("Forgetting password")
account.password = None
p = Presence(from_jid = name + "@" + unicode(self.jid), \ p = Presence(from_jid = name + "@" + unicode(self.jid), \
to_jid = from_jid, \ to_jid = from_jid, \
stanza_type = "unavailable") stanza_type = "unavailable")
@@ -764,28 +801,29 @@ class MailComponent(Component):
""" Handle new message """ """ Handle new message """
def message(self, message): def message(self, message):
self.__logger.debug("MESSAGE: " + message.get_body()) self.__logger.debug("MESSAGE: " + message.get_body())
lang_class = self.get_lang_class(self.get_lang(message.get_node()))
name = message.get_to().node name = message.get_to().node
base_from_jid = unicode(message.get_from().bare()) base_from_jid = unicode(message.get_from().bare())
# if name and self.__registered.has_key(base_from_jid): if re.compile("\[PASSWORD\]").search(message.get_subject()) is not None \
# body = message.get_body() and self.__storage.has_key((base_from_jid, name)):
# cmd = body.split(' ') account = self.__storage[(base_from_jid, name)]
# if cmd[0] == "check": account.password = message.get_body()
# self.check_mail(base_from_jid, name) account.waiting_password_reply = False
# elif cmd[0] == "dump": msg = Message(from_jid = name + "@" + unicode(self.jid), \
# body = "" to_jid = message.get_from(), \
# for jid in self.__registered.keys(): stanza_type = "message", \
# for name in self.__registered[jid].keys(): subject = lang_class.password_saved_for_session, \
# body += name + " for user " + jid body = lang_class.password_saved_for_session)
# msg = Message(from_jid = self.jid, to_jid = base_from_jid, \ self.stream.send(msg)
# stanza_type = "message", \
# body = body)
# self.stream.send(msg)
return 1 return 1
""" Check mail account """ """ Check mail account """
def check_mail(self, jid, name): def check_mail(self, jid, name):
self.__logger.debug("CHECK_MAIL " + unicode(jid) + " " + name) self.__logger.debug("CHECK_MAIL " + unicode(jid) + " " + name)
account = self.__storage[(jid, name)] account = self.__storage[(jid, name)]
if account.password is None:
self.__ask_password(name, jid, account.default_lang_class, account)
return
action = account.action action = account.action
if action != mailconnection.DO_NOTHING: if action != mailconnection.DO_NOTHING:
try: try:

View File

@@ -29,6 +29,7 @@ class Lang:
account_name = u"Connection name" account_name = u"Connection name"
account_login = u"Login" account_login = u"Login"
account_password = u"Password" account_password = u"Password"
account_password_store = u"Store password on jabber server ?"
account_host = u"Host" account_host = u"Host"
account_port = u"Port" account_port = u"Port"
account_type = u"Mail serveur type" account_type = u"Mail serveur type"
@@ -45,18 +46,27 @@ class Lang:
action_digest = u"Send mail digest" action_digest = u"Send mail digest"
update_title = u"Jabber mail connection update" update_title = u"Jabber mail connection update"
update_instructions = u"Modifying connection '%s'" update_instructions = u"Modifying connection '%s'"
connection_labels = u"%s connection '%s'" connection_label = u"%s connection '%s'"
update_account_message = u"Updated %s connection '%s': Registered with "\ update_account_message = u"Updated %s connection '%s': Registered with "\
"username '%s' and password '%s' on '%s'" "username '%s' and password '%s' on '%s'"
new_account_message = u"New %s connection '%s': Registered with " \ new_account_message = u"New %s connection '%s': Registered with " \
"username '%s' and password '%s' on '%s'" "username '%s' and password '%s' on '%s'"
ask_password_subject = u"Password request"
ask_password_body = u"Reply to this message with the password " \
"for the following account : \n" \
"\thost = %s\n" \
"\tlogin = %s\n"
password_saved_for_session = u"Password will be kept during your jabber session"
check_error_subject = u"Error while checking emails."
check_error_body = u"An error appears while checking emails :\n\t%s"
class fr: class fr:
register_title = u"Enregistrement d'une nouvelle connexion à un serveur email." register_title = u"Enregistrement d'une nouvelle connexion à un serveur email."
register_instructions = u"Entrer les paramètres de connexion" register_instructions = u"Entrer les paramètres de connexion"
account_name = u"Nom de la connexion" account_name = u"Nom de la connexion"
account_login = u"Nom d'utilisateur" account_login = u"Nom d'utilisateur"
account_password = u"Mot de passe" account_password = u"Mot de passe"
account_password_store = u"Sauvegarder le mot de passe sur le serveur Jabber ?"
account_host = u"Adresse du serveur email" account_host = u"Adresse du serveur email"
account_port = u"Port du serveur email" account_port = u"Port du serveur email"
account_type = u"Type du serveur email" account_type = u"Type du serveur email"
@@ -78,5 +88,14 @@ class Lang:
"Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'" "Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'"
new_account_message = u"La connexion %s '%s' a été créée : \n" \ new_account_message = u"La connexion %s '%s' a été créée : \n" \
"Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'" "Nom d'utilisateur : '%s'\nMot de passe : '%s'\nsur : '%s'"
ask_password_subject = u"Demande de mot de passe"
ask_password_body = u"Répondre à ce message avec le mot de passe du " \
"compte suivant : \n" \
"\thost = %s\n" \
"\tlogin = %s\n"
password_saved_for_session = u"Le mot de passe sera garder tout au " \
"long de la session Jabber."
check_error_subject = u"Erreur lors de la vérification des emails."
check_error_body = u"Une erreur est survenue lors de la vérification " \
"des emails :\n\t%s"

View File

@@ -29,6 +29,8 @@ import poplib
import imaplib import imaplib
import socket import socket
from jabber.lang import Lang
IMAP4_TIMEOUT = 10 IMAP4_TIMEOUT = 10
POP3_TIMEOUT = 10 POP3_TIMEOUT = 10
@@ -145,6 +147,7 @@ class MailConnection(object):
- 'ssl': boolean""" - 'ssl': boolean"""
self.login = login self.login = login
self.password = password self.password = password
self.store_password = True
self.host = host self.host = host
self.port = port self.port = port
self.ssl = ssl self.ssl = ssl
@@ -159,11 +162,15 @@ class MailConnection(object):
self.offline_action = DO_NOTHING self.offline_action = DO_NOTHING
self.interval = 5 self.interval = 5
self.lastcheck = 0 self.lastcheck = 0
self.default_lang_class = Lang.en
self.waiting_password_reply = False
self.in_error = False
def __eq__(self, other): def __eq__(self, other):
return self.get_type() == other.get_type() \ return self.get_type() == other.get_type() \
and self.login == other.login \ and self.login == other.login \
and self.password == other.password \ and self.password == other.password \
and self.store_password == other.store_password \
and self.host == other.host \ and self.host == other.host \
and self.port == other.port \ and self.port == other.port \
and self.ssl == other.ssl \ and self.ssl == other.ssl \
@@ -176,10 +183,11 @@ class MailConnection(object):
and self.interval == other.interval and self.interval == other.interval
def __str__(self): def __str__(self):
return self.get_type() + "#" + self.login + "#" + self.password + "#" \ return self.get_type() + "#" + self.login + "#" + \
+ self.host + "#" + str(self.port) + "#" + str(self.chat_action) + "#" \ (self.store_password and self.password or "/\\") + "#" \
+ str(self.online_action) + "#" + str(self.away_action) + "#" + \ + self.host + "#" + str(self.port) + "#" + str(self.chat_action) + "#" \
str(self.xa_action) + "#" + str(self.dnd_action) + "#" + str(self.offline_action) + "#" + str(self.interval) + str(self.online_action) + "#" + str(self.away_action) + "#" + \
str(self.xa_action) + "#" + str(self.dnd_action) + "#" + str(self.offline_action) + "#" + str(self.interval)
def get_decoded_part(self, part): def get_decoded_part(self, part):
content_charset = part.get_content_charset() content_charset = part.get_content_charset()

View File

@@ -61,6 +61,11 @@ def str_to_mail_connection(connection_string):
type = arg_list.pop() type = arg_list.pop()
login = arg_list.pop() login = arg_list.pop()
password = arg_list.pop() password = arg_list.pop()
if password == "/\\":
password = None
store_password = False
else:
store_password = True
host = arg_list.pop() host = arg_list.pop()
port = int(arg_list.pop()) port = int(arg_list.pop())
chat_action = None chat_action = None
@@ -116,6 +121,7 @@ def str_to_mail_connection(connection_string):
ssl = (len(type) == 5)) ssl = (len(type) == 5))
if result is None: if result is None:
raise Exception, "Connection type \"" + type + "\" unknown" raise Exception, "Connection type \"" + type + "\" unknown"
result.store_password = store_password
result.chat_action = chat_action result.chat_action = chat_action
result.online_action = online_action result.online_action = online_action
result.away_action = away_action result.away_action = away_action

View File

@@ -45,23 +45,23 @@ class Storage(UserDict):
self._registered = self.load() self._registered = self.load()
def __setitem__(self, pk_tuple, obj): def __setitem__(self, pk_tuple, obj):
# print "Adding " + "#".join(pk_tuple) + " = " + str(obj) # print "Adding " + "#".join(map(str, pk_tuple)) + " = " + str(obj)
self._registered[str("#".join(pk_tuple))] = obj self._registered[str("#".join(map(str, pk_tuple)))] = obj
def __getitem__(self, pk_tuple): def __getitem__(self, pk_tuple):
# print "Getting " + "#".join(pk_tuple) # print "Getting " + "#".join(map(str, pk_tuple))
if len(pk_tuple) == self.nb_pk_fields: if len(pk_tuple) == self.nb_pk_fields:
return self._registered[str("#".join(pk_tuple))] return self._registered[str("#".join(map(str, pk_tuple)))]
else: else:
partial_key = str("#".join(pk_tuple)) partial_key = str("#".join(map(str, pk_tuple)))
regexp = re.compile(partial_key) regexp = re.compile(partial_key)
return [self._registered[key] return [self._registered[key]
for key in self._registered.keys() for key in self._registered.keys()
if regexp.search(key)] if regexp.search(key)]
def __delitem__(self, pk_tuple): def __delitem__(self, pk_tuple):
# print "Deleting " + "#".join(pk_tuple) # print "Deleting " + "#".join(map(str, pk_tuple))
del self._registered[str("#".join(pk_tuple))] del self._registered[str("#".join(map(str, pk_tuple)))]
def get_spool_dir(self): def get_spool_dir(self):
return self._spool_dir return self._spool_dir
@@ -75,9 +75,9 @@ class Storage(UserDict):
def has_key(self, pk_tuple): def has_key(self, pk_tuple):
if len(pk_tuple) == self.nb_pk_fields: if len(pk_tuple) == self.nb_pk_fields:
return self._registered.has_key(str("#".join(pk_tuple))) return self._registered.has_key(str("#".join(map(str, pk_tuple))))
else: else:
partial_key = str("#".join(pk_tuple)) partial_key = str("#".join(map(str, pk_tuple)))
regexp = re.compile("^" + partial_key) regexp = re.compile("^" + partial_key)
for key in self._registered.keys(): for key in self._registered.keys():
if regexp.search(key): if regexp.search(key):
@@ -89,7 +89,7 @@ class Storage(UserDict):
return [tuple(key.split("#")) for key in self._registered.keys()] return [tuple(key.split("#")) for key in self._registered.keys()]
else: else:
level = len(pk_tuple) level = len(pk_tuple)
partial_key = str("#".join(pk_tuple)) partial_key = str("#".join(map(str, pk_tuple)))
regexp = re.compile("^" + partial_key) regexp = re.compile("^" + partial_key)
result = {} result = {}
for key in self._registered.keys(): for key in self._registered.keys():
@@ -101,6 +101,8 @@ class Storage(UserDict):
for pk in self._registered.keys(): for pk in self._registered.keys():
print pk + " = " + str(self._registered[pk]) print pk + " = " + str(self._registered[pk])
def load(self):
pass
class DBMStorage(Storage): class DBMStorage(Storage):
def __init__(self, nb_pk_fields = 1, spool_dir = ".", db_file = None): def __init__(self, nb_pk_fields = 1, spool_dir = ".", db_file = None):
@@ -112,7 +114,6 @@ class DBMStorage(Storage):
self.sync() self.sync()
def load(self): def load(self):
# print "DBM LOAD"
str_registered = anydbm.open(self.file, \ str_registered = anydbm.open(self.file, \
'c') 'c')
result = {} result = {}

View File

@@ -51,7 +51,7 @@ if __name__ == '__main__':
component2_suite = unittest.makeSuite(MailComponent_TestCase_NoReg, \ component2_suite = unittest.makeSuite(MailComponent_TestCase_NoReg, \
"test") "test")
storage_suite = unittest.makeSuite(Storage_TestCase, \ storage_suite = unittest.makeSuite(Storage_TestCase, \
"test") "test")
dbmstorage_suite = unittest.makeSuite(DBMStorage_TestCase, \ dbmstorage_suite = unittest.makeSuite(DBMStorage_TestCase, \
"test") "test")
sqlitestorage_suite = unittest.makeSuite(SQLiteStorage_TestCase, \ sqlitestorage_suite = unittest.makeSuite(SQLiteStorage_TestCase, \
@@ -61,12 +61,21 @@ if __name__ == '__main__':
pop3_connection_suite, \ pop3_connection_suite, \
imap_connection_suite, \ imap_connection_suite, \
mc_factory_suite, \ mc_factory_suite, \
component_suite, \ # component_suite, \
component2_suite, \ # component2_suite, \
storage_suite, \ storage_suite, \
dbmstorage_suite, \ dbmstorage_suite, \
sqlitestorage_suite)) sqlitestorage_suite))
test_support.run_suite(sqlitestorage_suite) # test_support.run_suite(mail_connection_suite)
# test_support.run_suite(pop3_connection_suite)
# test_support.run_suite(imap_connection_suite)
# test_support.run_suite(mc_factory_suite)
# test_support.run_suite(component_suite)
# test_support.run_suite(component2_suite)
# test_support.run_suite(storage_suite)
# test_support.run_suite(sqlitestorage_suite)
# test_support.run_suite(dbmstorage_suite)
test_support.run_suite(jmc_suite)
# coverage.stop() # coverage.stop()
# coverage.analysis(jabber.mailconnection_factory) # coverage.analysis(jabber.mailconnection_factory)

View File

@@ -27,6 +27,7 @@ from jabber import mailconnection
class MailConnectionFactory_TestCase(unittest.TestCase): class MailConnectionFactory_TestCase(unittest.TestCase):
def test_new_mail_connection_imap(self): def test_new_mail_connection_imap(self):
mc = get_new_mail_connection("imap") mc = get_new_mail_connection("imap")
# TODO
self.assertEquals(mc, mc) self.assertEquals(mc, mc)
def test_new_mail_connection_imaps(self): def test_new_mail_connection_imaps(self):
@@ -46,6 +47,7 @@ class MailConnectionFactory_TestCase(unittest.TestCase):
self.assertEquals(mc.get_type(), "imap") self.assertEquals(mc.get_type(), "imap")
self.assertEquals(mc.login, "login") self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd") self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.store_password, True)
self.assertEquals(mc.host, "host") self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 193) self.assertEquals(mc.port, 193)
self.assertEquals(mc.mailbox, "INBOX") self.assertEquals(mc.mailbox, "INBOX")
@@ -62,6 +64,7 @@ class MailConnectionFactory_TestCase(unittest.TestCase):
self.assertEquals(mc.get_type(), "pop3") self.assertEquals(mc.get_type(), "pop3")
self.assertEquals(mc.login, "login") self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, "passwd") self.assertEquals(mc.password, "passwd")
self.assertEquals(mc.store_password, True)
self.assertEquals(mc.host, "host") self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 110) self.assertEquals(mc.port, 110)
self.assertEquals(mc.chat_action, mailconnection.DIGEST) self.assertEquals(mc.chat_action, mailconnection.DIGEST)
@@ -88,6 +91,23 @@ class MailConnectionFactory_TestCase(unittest.TestCase):
self.assertEquals(mc.offline_action, mailconnection.RETRIEVE) self.assertEquals(mc.offline_action, mailconnection.RETRIEVE)
self.assertEquals(mc.interval, 4) self.assertEquals(mc.interval, 4)
def test_str_to_mail_connection_no_password(self):
mc = str_to_mail_connection("imap#login#/\\#host#193#0#0#0#1#1#2#4#INBOX")
self.assertEquals(mc.get_type(), "imap")
self.assertEquals(mc.login, "login")
self.assertEquals(mc.password, None)
self.assertEquals(mc.store_password, False)
self.assertEquals(mc.host, "host")
self.assertEquals(mc.port, 193)
self.assertEquals(mc.mailbox, "INBOX")
self.assertEquals(mc.chat_action, mailconnection.DO_NOTHING)
self.assertEquals(mc.online_action, mailconnection.DO_NOTHING)
self.assertEquals(mc.away_action, mailconnection.DO_NOTHING)
self.assertEquals(mc.xa_action, mailconnection.DIGEST)
self.assertEquals(mc.dnd_action, mailconnection.DIGEST)
self.assertEquals(mc.offline_action, mailconnection.RETRIEVE)
self.assertEquals(mc.interval, 4)
def test_str_to_mail_connection_imaps(self): def test_str_to_mail_connection_imaps(self):
mc = str_to_mail_connection("imaps#login#passwd#host#993#0#0#0#1#1#2#4#INBOX.SubDir") mc = str_to_mail_connection("imaps#login#passwd#host#993#0#0#0#1#1#2#4#INBOX.SubDir")
self.assertEquals(mc.get_type(), "imaps") self.assertEquals(mc.get_type(), "imaps")