VirtualMailManager/account.py
branchv0.6.x
changeset 444 95275b61ff8a
parent 442 abcd73f5e980
child 451 2408a3cd4bea
equal deleted inserted replaced
443:e2b9e3de2b51 444:95275b61ff8a
    18 from VirtualMailManager.errors import VMMError, AccountError as AErr
    18 from VirtualMailManager.errors import VMMError, AccountError as AErr
    19 from VirtualMailManager.maillocation import MailLocation
    19 from VirtualMailManager.maillocation import MailLocation
    20 from VirtualMailManager.password import pwhash
    20 from VirtualMailManager.password import pwhash
    21 from VirtualMailManager.quotalimit import QuotaLimit
    21 from VirtualMailManager.quotalimit import QuotaLimit
    22 from VirtualMailManager.transport import Transport
    22 from VirtualMailManager.transport import Transport
    23 
    23 from VirtualMailManager.serviceset import ServiceSet
    24 __all__ = ('SERVICES', 'Account', 'get_account_by_uid')
    24 
    25 
    25 __all__ = ('Account', 'get_account_by_uid')
    26 SERVICES = ('imap', 'pop3', 'smtp', 'sieve')
       
    27 
    26 
    28 _ = lambda msg: msg
    27 _ = lambda msg: msg
    29 cfg_dget = lambda option: None
    28 cfg_dget = lambda option: None
    30 
    29 
    31 
    30 
    32 class Account(object):
    31 class Account(object):
    33     """Class to manage e-mail accounts."""
    32     """Class to manage e-mail accounts."""
    34     __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
    33     __slots__ = ('_addr', '_dbh', '_domain', '_mail', '_new', '_passwd',
    35                  '_qlimit', '_transport', '_uid')
    34                  '_qlimit', '_services', '_transport', '_uid')
    36 
    35 
    37     def __init__(self, dbh, address):
    36     def __init__(self, dbh, address):
    38         """Creates a new Account instance.
    37         """Creates a new Account instance.
    39 
    38 
    40         When an account with the given *address* could be found in the
    39         When an account with the given *address* could be found in the
    59             raise AErr(_(u"The domain '%s' does not exist.") %
    58             raise AErr(_(u"The domain '%s' does not exist.") %
    60                        self._addr.domainname, NO_SUCH_DOMAIN)
    59                        self._addr.domainname, NO_SUCH_DOMAIN)
    61         self._uid = 0
    60         self._uid = 0
    62         self._mail = None
    61         self._mail = None
    63         self._qlimit = self._domain.quotalimit
    62         self._qlimit = self._domain.quotalimit
       
    63         self._services = self._domain.serviceset
    64         self._transport = self._domain.transport
    64         self._transport = self._domain.transport
    65         self._passwd = None
    65         self._passwd = None
    66         self._new = True
    66         self._new = True
    67         self._load()
    67         self._load()
    68 
    68 
    69     def __nonzero__(self):
    69     def __nonzero__(self):
    70         """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."""
    71         return not self._new
    71         return not self._new
    72 
    72 
    73     def _load(self):
    73     def _load(self):
    74         """Load 'uid', 'mid', 'qid' and 'tid' from the database and set
    74         """Load 'uid', 'mid', 'qid', 'ssid'  and 'tid' from the database and
    75         _new to `False` - if the user could be found. """
    75         set _new to `False` - if the user could be found. """
    76         dbc = self._dbh.cursor()
    76         dbc = self._dbh.cursor()
    77         dbc.execute('SELECT uid, mid, qid, tid FROM users WHERE gid = %s AND '
    77         dbc.execute('SELECT uid, mid, qid, ssid, tid FROM users WHERE gid = '
    78                     'local_part=%s', (self._domain.gid, self._addr.localpart))
    78                     '%s AND local_part = %s', (self._domain.gid,
       
    79                                                self._addr.localpart))
    79         result = dbc.fetchone()
    80         result = dbc.fetchone()
    80         dbc.close()
    81         dbc.close()
    81         if result:
    82         if result:
    82             self._uid, _mid, _qid, _tid = result
    83             self._uid, _mid, _qid, _ssid, _tid = result
    83             if _qid != self._qlimit.qid:
    84             if _qid != self._qlimit.qid:
    84                 self._qlimit = QuotaLimit(self._dbh, qid=_qid)
    85                 self._qlimit = QuotaLimit(self._dbh, qid=_qid)
       
    86             if _ssid != self._services.ssid:
       
    87                 self._services = ServiceSet(self._dbh, ssid=_ssid)
    85             if _tid != self._transport.tid:
    88             if _tid != self._transport.tid:
    86                 self._transport = Transport(self._dbh, tid=_tid)
    89                 self._transport = Transport(self._dbh, tid=_tid)
    87             self._mail = MailLocation(self._dbh, mid=_mid)
    90             self._mail = MailLocation(self._dbh, mid=_mid)
    88             self._new = False
    91             self._new = False
    89 
    92 
   112                        {'transport': self._transport,
   115                        {'transport': self._transport,
   113                         'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
   116                         'mbfmt': maillocation.mbformat}, INVALID_MAIL_LOCATION)
   114         self._mail = maillocation
   117         self._mail = maillocation
   115         self._set_uid()
   118         self._set_uid()
   116 
   119 
   117     def _update_services(self, activate, *services):
       
   118         """Activate or deactivate the Account's services.
       
   119 
       
   120         Arguments:
       
   121 
       
   122         `activate`: bool
       
   123           When `True` the Account's user will be able to login to the
       
   124           services, otherwise the login will fail.
       
   125         `*services`
       
   126           No or one or more of the services: imap, pop3, smtp and sieve
       
   127         """
       
   128         self._chk_state()
       
   129         if services:
       
   130             services = set(services)
       
   131             for service in services:
       
   132                 if service not in SERVICES:
       
   133                     raise AErr(_(u"Unknown service: '%s'") % service,
       
   134                                UNKNOWN_SERVICE)
       
   135         else:
       
   136             services = SERVICES
       
   137         state = ('FALSE', 'TRUE')[activate]
       
   138         sql = 'UPDATE users SET %s WHERE uid = %u' % (
       
   139                     (' = %(s)s, '.join(services) + ' = %(s)s') % {'s': state},
       
   140                     self._uid)
       
   141         if 'sieve' in services and \
       
   142            cfg_dget('misc.dovecot_version') < 0x10200b02:
       
   143             sql = sql.replace('sieve', 'managesieve')
       
   144         dbc = self._dbh.cursor()
       
   145         dbc.execute(sql)
       
   146         if dbc.rowcount > 0:
       
   147             self._dbh.commit()
       
   148         dbc.close()
       
   149 
       
   150     def _update_tables(self, column, value):
   120     def _update_tables(self, column, value):
   151         """Update various columns in the users table.
   121         """Update various columns in the users table.
   152 
   122 
   153         Arguments:
   123         Arguments:
   154 
   124 
   155         `column` : basestring
   125         `column` : basestring
   156           Name of the table column. Currently: qid and tid
   126           Name of the table column. Currently: qid, ssid and tid
   157         `value` : long
   127         `value` : long
   158           The referenced key
   128           The referenced key
   159         """
   129         """
   160         if column not in ('qid', 'tid'):
   130         if column not in ('qid', 'ssid', 'tid'):
   161             raise ValueError('Unknown column: %r' % column)
   131             raise ValueError('Unknown column: %r' % column)
   162         dbc = self._dbh.cursor()
   132         dbc = self._dbh.cursor()
   163         dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column,
   133         dbc.execute('UPDATE users SET %s = %%s WHERE uid = %%s' % column,
   164                     (value, self._uid))
   134                     (value, self._uid))
   165         if dbc.rowcount > 0:
   135         if dbc.rowcount > 0:
   235                        ACCOUNT_EXISTS)
   205                        ACCOUNT_EXISTS)
   236         if not isinstance(password, basestring) or not password:
   206         if not isinstance(password, basestring) or not password:
   237             raise AErr(_(u"Could not accept password: '%s'") % password,
   207             raise AErr(_(u"Could not accept password: '%s'") % password,
   238                        ACCOUNT_MISSING_PASSWORD)
   208                        ACCOUNT_MISSING_PASSWORD)
   239         self._passwd = password
   209         self._passwd = password
   240 
       
   241     def enable(self, *services):
       
   242         """Enable all or the given service/s for the Account.
       
   243 
       
   244         Possible *services* are: 'imap', 'pop3', 'sieve' and 'smtp'.
       
   245         When all services should be enabled, give no service name.
       
   246 
       
   247         Arguments:
       
   248 
       
   249         `*services` : basestring
       
   250           No or one or more of the services 'imap', 'pop3', 'smtp', and
       
   251           'sieve'.
       
   252         """
       
   253         self._update_services(True, *services)
       
   254 
       
   255     def disable(self, *services):
       
   256         """Disable all or the given service/s for the Account.
       
   257 
       
   258         For more information see: Account.enable()."""
       
   259         self._update_services(False, *services)
       
   260 
   210 
   261     def save(self):
   211     def save(self):
   262         """Save the new Account in the database."""
   212         """Save the new Account in the database."""
   263         if not self._new:
   213         if not self._new:
   264             raise AErr(_(u"The account '%s' already exists.") % self._addr,
   214             raise AErr(_(u"The account '%s' already exists.") % self._addr,
   265                        ACCOUNT_EXISTS)
   215                        ACCOUNT_EXISTS)
   266         if not self._passwd:
   216         if not self._passwd:
   267             raise AErr(_(u"No password set for account: '%s'") % self._addr,
   217             raise AErr(_(u"No password set for account: '%s'") % self._addr,
   268                        ACCOUNT_MISSING_PASSWORD)
   218                        ACCOUNT_MISSING_PASSWORD)
   269         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
       
   270             sieve_col = 'sieve'
       
   271         else:
       
   272             sieve_col = 'managesieve'
       
   273         self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
   219         self._prepare(MailLocation(self._dbh, mbfmt=cfg_dget('mailbox.format'),
   274                                    directory=cfg_dget('mailbox.root')))
   220                                    directory=cfg_dget('mailbox.root')))
   275         dbc = self._dbh.cursor()
   221         dbc = self._dbh.cursor()
   276         dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
   222         dbc.execute('INSERT INTO users (local_part, passwd, uid, gid, mid, '
   277                     'qid, tid, smtp, pop3, imap, %s) VALUES' % (sieve_col,) + \
   223                     'qid, ssid, tid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)',
   278                     '(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)',
       
   279                     (self._addr.localpart,
   224                     (self._addr.localpart,
   280                      pwhash(self._passwd, user=self._addr), self._uid,
   225                      pwhash(self._passwd, user=self._addr), self._uid,
   281                      self._domain.gid, self._mail.mid, self._qlimit.qid,
   226                      self._domain.gid, self._mail.mid, self._qlimit.qid,
   282                      self._transport.tid, cfg_dget('account.smtp'),
   227                      self._services.ssid, self._transport.tid))
   283                      cfg_dget('account.pop3'), cfg_dget('account.imap'),
       
   284                      cfg_dget('account.sieve')))
       
   285         self._dbh.commit()
   228         self._dbh.commit()
   286         dbc.close()
   229         dbc.close()
   287         self._new = False
   230         self._new = False
   288 
   231 
   289     def modify(self, field, value):
   232     def modify(self, field, value):
   327         assert isinstance(quotalimit, QuotaLimit)
   270         assert isinstance(quotalimit, QuotaLimit)
   328         if quotalimit == self._qlimit:
   271         if quotalimit == self._qlimit:
   329             return
   272             return
   330         self._update_tables('qid', quotalimit.qid)
   273         self._update_tables('qid', quotalimit.qid)
   331         self._qlimit = quotalimit
   274         self._qlimit = quotalimit
       
   275 
       
   276     def update_serviceset(self, serviceset):
       
   277         """Assign a different set of services to the Account.
       
   278 
       
   279         Argument:
       
   280 
       
   281         `serviceset` : VirtualMailManager.serviceset.ServiceSet
       
   282           the new service set.
       
   283         """
       
   284         self._chk_state()
       
   285         assert isinstance(serviceset, ServiceSet)
       
   286         if serviceset == self._services:
       
   287             return
       
   288         self._update_tables('ssid', serviceset.ssid)
       
   289         self._services = serviceset
   332 
   290 
   333     def update_transport(self, transport):
   291     def update_transport(self, transport):
   334         """Sets a new transport for the Account.
   292         """Sets a new transport for the Account.
   335 
   293 
   336         Arguments:
   294         Arguments:
   357         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   315         The keys of the dict are: 'address', 'gid', 'home', 'imap'
   358         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid',
   316         'mail_location', 'name', 'pop3', 'sieve', 'smtp', transport', 'uid',
   359         'uq_bytes', 'uq_messages', 'ql_bytes', and 'ql_messages'.
   317         'uq_bytes', 'uq_messages', 'ql_bytes', and 'ql_messages'.
   360         """
   318         """
   361         self._chk_state()
   319         self._chk_state()
   362         if cfg_dget('misc.dovecot_version') >= 0x10200b02:
   320         dbc = self._dbh.cursor()
   363             sieve_col = 'sieve'
   321         dbc.execute('SELECT name, CASE WHEN bytes IS NULL THEN 0 ELSE bytes '
   364         else:
   322                     'END, CASE WHEN messages IS NULL THEN 0 ELSE messages END '
   365             sieve_col = 'managesieve'
   323                     'FROM users LEFT JOIN userquota USING (uid) WHERE '
   366         dbc = self._dbh.cursor()
   324                     'users.uid = %s', (self._uid,))
   367         dbc.execute('SELECT name, smtp, pop3, imap, %s, CASE WHEN bytes IS '
       
   368                     'NULL THEN 0 ELSE bytes END, CASE WHEN messages IS NULL '
       
   369                     'THEN 0 ELSE messages END FROM users LEFT JOIN userquota '
       
   370                     'USING (uid) WHERE users.uid = %u' % (sieve_col,
       
   371                         self._uid))
       
   372         info = dbc.fetchone()
   325         info = dbc.fetchone()
   373         dbc.close()
   326         dbc.close()
   374         if info:
   327         if info:
   375             keys = ('name', 'smtp', 'pop3', 'imap', sieve_col, 'uq_bytes',
   328             info = dict(zip(('name', 'uq_bytes', 'uq_messages'), info))
   376                     'uq_messages')
   329             for service, state in self._services.services.iteritems():
   377             info = dict(zip(keys, info))
   330                 # TP: A service (e.g. pop3 or imap) may be enabled/usable or
   378             for service in keys[1:5]:
   331                 # disabled/unusable for a user.
   379                 if info[service]:
   332                 info[service] = (_('disabled'), _('enabled'))[state]
   380                     # TP: A service (pop3/imap) is enabled/usable for a user
       
   381                     info[service] = _('enabled')
       
   382                 else:
       
   383                     # TP: A service (pop3/imap) isn't enabled/usable for a user
       
   384                     info[service] = _('disabled')
       
   385             info['address'] = self._addr
   333             info['address'] = self._addr
   386             info['gid'] = self._domain.gid
   334             info['gid'] = self._domain.gid
   387             info['home'] = '%s/%s' % (self._domain.directory, self._uid)
   335             info['home'] = '%s/%s' % (self._domain.directory, self._uid)
   388             info['mail_location'] = self._mail.mail_location
   336             info['mail_location'] = self._mail.mail_location
   389             info['ql_bytes'] = self._qlimit.bytes
   337             info['ql_bytes'] = self._qlimit.bytes
   444             self._dbh.commit()
   392             self._dbh.commit()
   445         dbc.close()
   393         dbc.close()
   446         self._new = True
   394         self._new = True
   447         self._uid = 0
   395         self._uid = 0
   448         self._addr = self._dbh = self._domain = self._passwd = None
   396         self._addr = self._dbh = self._domain = self._passwd = None
   449         self._mail = self._qlimit = self._transport = None
   397         self._mail = self._qlimit = self._services = self._transport = None
   450 
   398 
   451 
   399 
   452 def get_account_by_uid(uid, dbh):
   400 def get_account_by_uid(uid, dbh):
   453     """Search an Account by its UID.
   401     """Search an Account by its UID.
   454 
   402