VirtualMailManager/account.py
branchv0.6.x
changeset 390 660b42391c8e
parent 389 5f7e9f778b29
child 404 0c52094447b0
equal deleted inserted replaced
389:5f7e9f778b29 390:660b42391c8e
     8     Virtual Mail Manager's Account class to manage e-mail accounts.
     8     Virtual Mail Manager's Account class to manage e-mail accounts.
     9 """
     9 """
    10 
    10 
    11 from VirtualMailManager.domain import Domain
    11 from VirtualMailManager.domain import Domain
    12 from VirtualMailManager.emailaddress import EmailAddress
    12 from VirtualMailManager.emailaddress import EmailAddress
       
    13 from VirtualMailManager.quotalimit import QuotaLimit
    13 from VirtualMailManager.transport import Transport
    14 from VirtualMailManager.transport import Transport
    14 from VirtualMailManager.common import version_str
    15 from VirtualMailManager.common import version_str
    15 from VirtualMailManager.constants import \
    16 from VirtualMailManager.constants import \
    16      ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
    17      ACCOUNT_EXISTS, ACCOUNT_MISSING_PASSWORD, ALIAS_PRESENT, \
    17      INVALID_ARGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, \
    18      INVALID_ARGUMENT, INVALID_MAIL_LOCATION, NO_SUCH_ACCOUNT, \
    29 
    30 
    30 
    31 
    31 class Account(object):
    32 class Account(object):
    32     """Class to manage e-mail accounts."""
    33     """Class to manage e-mail accounts."""
    33     __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
    34     __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
    34                  '_transport', '_uid')
    35                  '_qlimit', '_transport', '_uid')
    35 
    36 
    36     def __init__(self, dbh, address):
    37     def __init__(self, dbh, address):
    37         """Creates a new Account instance.
    38         """Creates a new Account instance.
    38 
    39 
    39         When an account with the given *address* could be found in the
    40         When an account with the given *address* could be found in the
    57             # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
    58             # http://en.wikipedia.org/wiki/Quotation_mark,_non-English_usage
    58             raise AErr(_(u"The domain '%s' doesn't exist.") %
    59             raise AErr(_(u"The domain '%s' doesn't exist.") %
    59                        self._addr.domainname, NO_SUCH_DOMAIN)
    60                        self._addr.domainname, NO_SUCH_DOMAIN)
    60         self._uid = 0
    61         self._uid = 0
    61         self._mail = None
    62         self._mail = None
       
    63         self._qlimit = self._domain.quotalimit
    62         self._transport = self._domain.transport
    64         self._transport = self._domain.transport
    63         self._passwd = None
    65         self._passwd = None
    64         self._new = True
    66         self._new = True
    65         self._load()
    67         self._load()
    66 
    68 
    67     def __nonzero__(self):
    69     def __nonzero__(self):
    68         """Returns `True` if the Account is known, `False` if it's new."""
    70         """Returns `True` if the Account is known, `False` if it's new."""
    69         return not self._new
    71         return not self._new
    70 
    72 
    71     def _load(self):
    73     def _load(self):
    72         """Load 'uid', 'mid' and 'tid' from the database and set _new to
    74         """Load 'uid', 'mid', 'qid' and 'tid' from the database and set
    73         `False` - if the user could be found. """
    75         _new to `False` - if the user could be found. """
    74         dbc = self._dbh.cursor()
    76         dbc = self._dbh.cursor()
    75         dbc.execute('SELECT uid, mid, tid FROM users WHERE gid = %s AND '
    77         dbc.execute('SELECT uid, mid, qid, tid FROM users WHERE gid = %s AND '
    76                     'local_part=%s', (self._domain.gid, self._addr.localpart))
    78                     'local_part=%s', (self._domain.gid, self._addr.localpart))
    77         result = dbc.fetchone()
    79         result = dbc.fetchone()
    78         dbc.close()
    80         dbc.close()
    79         if result:
    81         if result:
    80             self._uid, _mid, _tid = result
    82             self._uid, _mid, _qid, _tid = result
       
    83             if _qid != self._qlimit.qid:
       
    84                 self._qlimit = QuotaLimit(self._dbh, qid=_qid)
    81             if _tid != self._transport.tid:
    85             if _tid != self._transport.tid:
    82                 self._transport = Transport(self._dbh, tid=_tid)
    86                 self._transport = Transport(self._dbh, tid=_tid)
    83             self._mail = MailLocation(self._dbh, mid=_mid)
    87             self._mail = MailLocation(self._dbh, mid=_mid)
    84             self._new = False
    88             self._new = False
    85 
    89 
   140         dbc.execute(sql)
   144         dbc.execute(sql)
   141         if dbc.rowcount > 0:
   145         if dbc.rowcount > 0:
   142             self._dbh.commit()
   146             self._dbh.commit()
   143         dbc.close()
   147         dbc.close()
   144 
   148 
       
   149     def _update_tables(self, column, value):
       
   150         """Update various columns in the users table.
       
   151 
       
   152         Arguments:
       
   153 
       
   154         `column` : basestring
       
   155           Name of the table column. Currently: qid and tid
       
   156         `value` : long
       
   157           The referenced key
       
   158         """
       
   159         if column not in ('qid', 'tid'):
       
   160             raise ValueError('Unknown column: %r' % column)
       
   161         dbc = self._dbh.cursor()
       
   162         dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column,
       
   163                     (value, self._uid))
       
   164         if dbc.rowcount > 0:
       
   165             self._dbh.commit()
       
   166         dbc.close()
       
   167 
   145     def _count_aliases(self):
   168     def _count_aliases(self):
   146         """Count all alias addresses where the destination address is the
   169         """Count all alias addresses where the destination address is the
   147         address of the Account."""
   170         address of the Account."""
   148         dbc = self._dbh.cursor()
   171         dbc = self._dbh.cursor()
   149         dbc.execute('SELECT COUNT(destination) FROM alias WHERE destination '
   172         dbc.execute('SELECT COUNT(destination) FROM alias WHERE destination '
   204         Argument:
   227         Argument:
   205 
   228 
   206         `password` : basestring
   229         `password` : basestring
   207           The password for the new Account.
   230           The password for the new Account.
   208         """
   231         """
       
   232         if not self._new:
       
   233             raise AErr(_(u"The account '%s' already exists.") % self._addr,
       
   234                        ACCOUNT_EXISTS)
   209         if not isinstance(password, basestring) or not password:
   235         if not isinstance(password, basestring) or not password:
   210             raise AErr(_(u"Could not accept password: '%s'") % password,
   236             raise AErr(_(u"Could not accept password: '%s'") % password,
   211                        ACCOUNT_MISSING_PASSWORD)
   237                        ACCOUNT_MISSING_PASSWORD)
   212         self._passwd = password
   238         self._passwd = password
   213 
   239 
   245             sieve_col = 'managesieve'
   271             sieve_col = 'managesieve'
   246         self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
   272         self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
   247                                    directory=cfg_dget('mailbox.root')))
   273                                    directory=cfg_dget('mailbox.root')))
   248         dbc = self._dbh.cursor()
   274         dbc = self._dbh.cursor()
   249         dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
   275         dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
   250                     'tid, smtp, pop3, imap, %s) VALUES' % (sieve_col,) + \
   276                     'qid, tid, smtp, pop3, imap, %s) VALUES' % (sieve_col,) + \
   251                     '(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
   277                     '(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
   252                     (self._addr.localpart,
   278                     (self._addr.localpart,
   253                      pwhash(self._passwd, user=self._addr), self._uid,
   279                      pwhash(self._passwd, user=self._addr), self._uid,
   254                      self._domain.gid, self._mail.mid, self._transport.tid,
   280                      self._domain.gid, self._mail.mid, self._qlimit.qid,
   255                      cfg_dget('account.smtp'), cfg_dget('account.pop3'),
   281                      self._transport.tid, cfg_dget('account.smtp'),
   256                      cfg_dget('account.imap'), cfg_dget('account.sieve')))
   282                      cfg_dget('account.pop3'), cfg_dget('account.imap'),
       
   283                      cfg_dget('account.sieve')))
   257         self._dbh.commit()
   284         self._dbh.commit()
   258         dbc.close()
   285         dbc.close()
   259         self._new = False
   286         self._new = False
   260 
   287 
   261     def modify(self, field, value):
   288     def modify(self, field, value):
   262         """Update the Account's *field* to the new *value*.
   289         """Update the Account's *field* to the new *value*.
   263 
   290 
   264         Possible values for *field* are: 'name', 'password' and
   291         Possible values for *field* are: 'name', 'password'.
   265         'transport'.  *value* is the *field*'s new value.
       
   266 
   292 
   267         Arguments:
   293         Arguments:
   268 
   294 
   269         `field` : basestring
   295         `field` : basestring
   270           The attribute name: 'name', 'password' or 'transport'
   296           The attribute name: 'name' or 'password'
   271         `value` : basestring
   297         `value` : basestring
   272           The new value of the attribute.
   298           The new value of the attribute.
   273         """
   299         """
   274         if field not in ('name', 'password', 'transport'):
   300         if field not in ('name', 'password'):
   275             raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
   301             raise AErr(_(u"Unknown field: '%s'") % field, INVALID_ARGUMENT)
   276         self._chk_state()
   302         self._chk_state()
   277         dbc = self._dbh.cursor()
   303         dbc = self._dbh.cursor()
   278         if field == 'password':
   304         if field == 'password':
   279             dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
   305             dbc.execute('UPDATE users SET passwd = %s WHERE uid = %s',
   280                         (pwhash(value, user=self._addr), self._uid))
   306                         (pwhash(value, user=self._addr), self._uid))
   281         elif field == 'transport':
       
   282             if value != self._transport.transport:
       
   283                 self._transport = Transport(self._dbh, transport=value)
       
   284                 dbc.execute('UPDATE users SET tid = %s WHERE uid = %s',
       
   285                             (self._transport.tid, self._uid))
       
   286         else:
   307         else:
   287             dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
   308             dbc.execute('UPDATE users SET name = %s WHERE uid = %s',
   288                         (value, self._uid))
   309                         (value, self._uid))
   289         if dbc.rowcount > 0:
   310         if dbc.rowcount > 0:
   290             self._dbh.commit()
   311             self._dbh.commit()
   291         dbc.close()
   312         dbc.close()
   292 
   313 
       
   314     def update_quotalimit(self, quotalimit):
       
   315         """Update the user's quota limit.
       
   316 
       
   317         Arguments:
       
   318 
       
   319         `quotalimit` : VirtualMailManager.quotalimit.QuotaLimit
       
   320           the new quota limit of the domain.
       
   321         """
       
   322         self._chk_state()
       
   323         assert isinstance(quotalimit, QuotaLimit)
       
   324         if quotalimit == self._qlimit:
       
   325             return
       
   326         self._update_tables('qid', quotalimit.qid)
       
   327         self._qlimit = quotalimit
       
   328 
       
   329     def update_transport(self, transport):
       
   330         """Sets a new transport for the Account.
       
   331 
       
   332         Arguments:
       
   333 
       
   334         `transport` : VirtualMailManager.transport.Transport
       
   335           the new transport
       
   336         """
       
   337         self._chk_state()
       
   338         assert isinstance(transport, Transport)
       
   339         if transport == self._transport:
       
   340             return
       
   341         if transport.transport.lower() in ('virtual', 'virtual:') and \
       
   342            not self._mail.postfix:
       
   343             raise AErr(_(u"Invalid transport '%(transport)s' for mailbox "
       
   344                          u"format '%(mbfmt)s'") %
       
   345                        {'transport': transport, 'mbfmt': self._mail.mbformat},
       
   346                        INVALID_MAIL_LOCATION)
       
   347         self._update_tables('tid', transport.tid)
       
   348         self._transport = transport
       
   349 
   293     def get_info(self):
   350     def get_info(self):
   294         """Returns a dict with some information about the Account.
   351         """Returns a dict with some information about the Account.
   295 
   352 
   296         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   353         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   297         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport' and
   354         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid',
   298         'uid'.
   355         'uq_bytes', 'uq_messages', 'ql_bytes', and 'ql_messages'.
   299         """
   356         """
   300         self._chk_state()
   357         self._chk_state()
   301         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
   358         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
   302             sieve_col = 'sieve'
   359             sieve_col = 'sieve'
   303         else:
   360         else:
   304             sieve_col = 'managesieve'
   361             sieve_col = 'managesieve'
   305         sql = 'SELECT name, smtp, pop3, imap, %s FROM users WHERE uid = %d' % \
   362         dbc = self._dbh.cursor()
   306             (sieve_col, self._uid)
   363         dbc.execute('SELECT name, smtp, pop3, imap, %s, CASE WHEN bytes IS '
   307         dbc = self._dbh.cursor()
   364                     'NULL THEN 0 ELSE bytes END, CASE WHEN messages IS NULL '
   308         dbc.execute(sql)
   365                     'THEN 0 ELSE messages END FROM users LEFT JOIN userquota '
       
   366                     'USING (uid) WHERE users.uid = %u' % (sieve_col,
       
   367                         self._uid))
   309         info = dbc.fetchone()
   368         info = dbc.fetchone()
   310         dbc.close()
   369         dbc.close()
   311         if info:
   370         if info:
   312             keys = ('name', 'smtp', 'pop3', 'imap', sieve_col)
   371             keys = ('name', 'smtp', 'pop3', 'imap', sieve_col, 'uq_bytes',
       
   372                     'uq_messages')
   313             info = dict(zip(keys, info))
   373             info = dict(zip(keys, info))
   314             for service in keys[1:]:
   374             for service in keys[1:5]:
   315                 if info[service]:
   375                 if info[service]:
   316                     # TP: A service (pop3/imap) is enabled/usable for a user
   376                     # TP: A service (pop3/imap) is enabled/usable for a user
   317                     info[service] = _('enabled')
   377                     info[service] = _('enabled')
   318                 else:
   378                 else:
   319                     # TP: A service (pop3/imap) isn't enabled/usable for a user
   379                     # TP: A service (pop3/imap) isn't enabled/usable for a user
   320                     info[service] = _('disabled')
   380                     info[service] = _('disabled')
   321             info['address'] = self._addr
   381             info['address'] = self._addr
   322             info['gid'] = self._domain.gid
   382             info['gid'] = self._domain.gid
   323             info['home'] = '%s/%s' % (self._domain.directory, self._uid)
   383             info['home'] = '%s/%s' % (self._domain.directory, self._uid)
   324             info['mail_location'] = self._mail.mail_location
   384             info['mail_location'] = self._mail.mail_location
       
   385             info['ql_bytes'] = self._qlimit.bytes
       
   386             info['ql_messages'] = self._qlimit.messages
   325             info['transport'] = self._transport.transport
   387             info['transport'] = self._transport.transport
   326             info['uid'] = self._uid
   388             info['uid'] = self._uid
   327             return info
   389             return info
   328         # nearly impossibleā€½
   390         # nearly impossibleā€½
   329         raise AErr(_(u"Could not fetch information for account: '%s'") %
   391         raise AErr(_(u"Could not fetch information for account: '%s'") %
   378             self._dbh.commit()
   440             self._dbh.commit()
   379         dbc.close()
   441         dbc.close()
   380         self._new = True
   442         self._new = True
   381         self._uid = 0
   443         self._uid = 0
   382         self._addr = self._dbh = self._domain = self._passwd = None
   444         self._addr = self._dbh = self._domain = self._passwd = None
   383         self._mail = self._transport = None
   445         self._mail = self._qlimit = self._transport = None
   384 
   446 
   385 
   447 
   386 def get_account_by_uid(uid, dbh):
   448 def get_account_by_uid(uid, dbh):
   387     """Search an Account by its UID.
   449     """Search an Account by its UID.
   388 
   450