plugin IMAP, plugin Maildir: profile management

- IMAP Login/pass is now checked against profile name/jabber pass
- Mailboxes are now per-profile
- flags are now checked without case sensitiveness
This commit is contained in:
Goffi 2011-01-18 00:57:26 +01:00
commit bf514891c2
3 changed files with 159 additions and 99 deletions

View file

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""
SAT plugin for managing imap server
SàT plugin for managing imap server
Copyright (C) 2011 Jérôme Poisson (goffi@goffi.org)
This program is free software: you can redistribute it and/or modify
@ -21,9 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
from logging import debug, info, error
import warnings
from twisted.internet import protocol
from twisted.internet import protocol,defer
from twisted.words.protocols.jabber import error as jab_error
from twisted.cred import portal,checkers
from twisted.cred import portal,checkers,credentials
from twisted.cred import error as cred_error
from twisted.mail import imap4
from email.parser import Parser
import email.message
@ -86,7 +87,6 @@ class Message():
"""Retrieve the unique identifier associated with this message.
"""
debug('getUID (message)')
debug ('===>%i', self.uid)
return self.uid
def getFlags(self):
@ -152,14 +152,13 @@ class Message():
class SatMailbox:
implements(imap4.IMailbox)
def __init__(self,host,name):
def __init__(self,host,name,profile):
self.host = host
self.listeners=set()
debug ('Mailbox init (%s)', name)
if name!="INBOX":
raise imap4.MailboxException("Only INBOX is managed for the moment")
self.name=name
self.mailbox=self.host.plugins["Maildir"].accessMessageBox("INBOX",self.newMessage)
self.mailbox=self.host.plugins["Maildir"].accessMessageBox(name,self.newMessage, profile)
def newMessage(self):
"""Called when a new message is in the mailbox"""
@ -207,7 +206,7 @@ class SatMailbox:
"""Return the number of messages with the 'Unseen' flag.
"""
debug('getUnseenCount')
return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\Seen'))
return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\SEEN'))
def isWriteable(self):
"""Get the read/write status of the mailbox.
@ -271,7 +270,7 @@ class SatMailbox:
invoked otherwise.
"""
debug('addMessage')
raise NotImplementedError
raise imap4.MailboxException("Client message addition not implemented yet")
def expunge(self):
"""Remove all messages flagged \\Deleted.
@ -323,6 +322,8 @@ class SatMailbox:
"""
debug('store')
flags=[flag.upper() for flag in flags]
def updateFlags(getF,setF):
ret = {}
for mess_id in messages:
@ -335,16 +336,27 @@ class SatMailbox:
_flags.update(set(flags))
new_flags=list(_flags)
setF(mess_id, new_flags)
ret[mess_id] = new_flags
ret[mess_id] = tuple(new_flags)
return ret
if uid:
messages.last = self.mailbox.getMaxUid()
messages.getnext = self.mailbox.getNextExistingUid
return updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid)
ret = updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid)
for listener in self.listeners:
listener.flagsChanged(ret)
return ret
else:
messages.last = self.getMessageCount()
return updateFlags(self.mailbox.getFlags,self.mailbox.setFlags)
ret = updateFlags(self.mailbox.getFlags,self.mailbox.setFlags)
newFlags={}
for idx in ret:
#we have to convert idx to uid for the listeners
newFlags[self.mailbox.getUid(idx)] = ret[idx]
for listener in self.listeners:
listener.flagsChanged(newFlags)
return ret
def getFlags(self):
"""Return the flags defined in this mailbox
@ -352,7 +364,7 @@ class SatMailbox:
@return: A list of the flags that can be set on messages in this mailbox.
"""
debug('getFlags')
return ['\Seen','\Answered','\Flagged','\Deleted','\Draft'] #TODO: add '\Recent'
return ['\\SEEN','\\ANSWERED','\\FLAGGED','\\DELETED','\\DRAFT'] #TODO: add '\\RECENT'
def getHierarchicalDelimiter(self):
"""Get the character which delimits namespaces for in this mailbox.
@ -360,21 +372,19 @@ class SatMailbox:
debug('getHierarchicalDelimiter')
return '.'
class ImapAccount(imap4.MemoryAccount):
class ImapSatAccount(imap4.MemoryAccount):
#implements(imap4.IAccount)
# Actually implement the interface here
def __init__(self, host, name):
def __init__(self, host, profile):
debug("ImapAccount init")
self.host=host
imap4.MemoryAccount.__init__(self,name)
self.profile=profile
imap4.MemoryAccount.__init__(self,profile)
self.addMailbox("Inbox") #We only manage Inbox for the moment
debug ('INBOX added')
def _emptyMailbox(self, name, id):
return SatMailbox(self.host,name)
return SatMailbox(self.host,name,self.profile)
class ImapRealm:
@ -385,9 +395,40 @@ class ImapRealm:
def requestAvatar(self, avatarID, mind, *interfaces):
debug('requestAvatar')
profile=avatarID.decode('utf-8')
if imap4.IAccount not in interfaces:
raise NotImplementedError
return imap4.IAccount, ImapAccount(self.host,avatarID), lambda:None
return imap4.IAccount, ImapSatAccount(self.host,profile), lambda:None
class SatProfileCredentialChecker:
"""
This credential checker check against SàT's profile and associated jabber's password
Check if the profile exists, and if the password is OK
Return the profile as avatarId
"""
implements(checkers.ICredentialsChecker)
credentialInterfaces = (credentials.IUsernamePassword,
credentials.IUsernameHashedPassword)
def __init__(self, host):
self.host = host
def _cbPasswordMatch(self, matched, profile):
if matched:
return profile.encode('utf-8')
else:
return failure.Failure(cred_error.UnauthorizedLogin())
def requestAvatarId(self, credentials):
profiles = self.host.memory.getProfilesList()
if not credentials.username in profiles:
return defer.fail(cred_error.UnauthorizedLogin())
password = self.host.memory.getParamA("Password", "Connection", profile_key=credentials.username)
return defer.maybeDeferred(
credentials.checkPassword,
password).addCallback(
self._cbPasswordMatch, credentials.username)
class ImapServerFactory(protocol.ServerFactory):
protocol = imap4.IMAP4Server
@ -405,5 +446,5 @@ class ImapServerFactory(protocol.ServerFactory):
debug ("Building protocole")
prot = protocol.ServerFactory.buildProtocol(self, addr)
prot.portal = portal.Portal(ImapRealm(self.host))
prot.portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(goffi="toto"))
prot.portal.registerChecker(SatProfileCredentialChecker(self.host))
return prot