Remove the mailman.interface magic. Use the more specific interface imports.
[mailman.git] / mailman / app / registrar.py
blobad291891e8d6b26128f53667231231e4f14b88dc
1 # Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Implementation of the IUserRegistrar interface."""
20 __metaclass__ = type
21 __all__ = [
22 'Registrar',
23 'adapt_domain_to_registrar',
27 import datetime
29 from pkg_resources import resource_string
30 from zope.interface import implements
32 from mailman.Message import UserNotification
33 from mailman.Utils import ValidateEmail
34 from mailman.config import config
35 from mailman.i18n import _
36 from mailman.interfaces.domain import IDomain
37 from mailman.interfaces.member import MemberRole
38 from mailman.interfaces.pending import IPendable
39 from mailman.interfaces.registrar import IRegistrar
43 class PendableRegistration(dict):
44 implements(IPendable)
45 PEND_KEY = 'registration'
49 class Registrar:
50 implements(IRegistrar)
52 def __init__(self, context):
53 self._context = context
55 def register(self, address, real_name=None, mlist=None):
56 """See `IUserRegistrar`."""
57 # First, do validation on the email address. If the address is
58 # invalid, it will raise an exception, otherwise it just returns.
59 ValidateEmail(address)
60 # Create a pendable for the registration.
61 pendable = PendableRegistration(
62 type=PendableRegistration.PEND_KEY,
63 address=address,
64 real_name=real_name)
65 if mlist is not None:
66 pendable['list_name'] = mlist.fqdn_listname
67 token = config.db.pendings.add(pendable)
68 # Set up some local variables for translation interpolation.
69 domain = IDomain(self._context)
70 domain_name = _(domain.email_host)
71 contact_address = domain.contact_address
72 confirm_url = domain.confirm_url(token)
73 confirm_address = domain.confirm_address(token)
74 email_address = address
75 # Calculate the message's Subject header. XXX Have to deal with
76 # translating this subject header properly. XXX Must deal with
77 # VERP_CONFIRMATIONS as well.
78 subject = 'confirm ' + token
79 # Send a verification email to the address.
80 text = _(resource_string('mailman.templates.en', 'verify.txt'))
81 msg = UserNotification(address, confirm_address, subject, text)
82 msg.send(mlist=None)
83 return token
85 def confirm(self, token):
86 """See `IUserRegistrar`."""
87 # For convenience
88 pendable = config.db.pendings.confirm(token)
89 if pendable is None:
90 return False
91 missing = object()
92 address = pendable.get('address', missing)
93 real_name = pendable.get('real_name', missing)
94 list_name = pendable.get('list_name', missing)
95 if pendable.get('type') != PendableRegistration.PEND_KEY:
96 # It seems like it would be very difficult to accurately guess
97 # tokens, or brute force an attack on the SHA1 hash, so we'll just
98 # throw the pendable away in that case. It's possible we'll need
99 # to repend the event or adjust the API to handle this case
100 # better, but for now, the simpler the better.
101 return False
102 # We are going to end up with an IAddress for the verified address
103 # and an IUser linked to this IAddress. See if any of these objects
104 # currently exist in our database.
105 usermgr = config.db.user_manager
106 addr = (usermgr.get_address(address)
107 if address is not missing else None)
108 user = (usermgr.get_user(address)
109 if address is not missing else None)
110 # If there is neither an address nor a user matching the confirmed
111 # record, then create the user, which will in turn create the address
112 # and link the two together
113 if addr is None:
114 assert user is None, 'How did we get a user but not an address?'
115 user = usermgr.create_user(address, real_name)
116 # Because the database changes haven't been flushed, we can't use
117 # IUserManager.get_address() to find the IAddress just created
118 # under the hood. Instead, iterate through the IUser's addresses,
119 # of which really there should be only one.
120 for addr in user.addresses:
121 if addr.address == address:
122 break
123 else:
124 raise AssertionError('Could not find expected IAddress')
125 elif user is None:
126 user = usermgr.create_user()
127 user.real_name = real_name
128 user.link(addr)
129 else:
130 # The IAddress and linked IUser already exist, so all we need to
131 # do is verify the address.
132 pass
133 addr.verified_on = datetime.datetime.now()
134 # If this registration is tied to a mailing list, subscribe the person
135 # to the list right now.
136 list_name = pendable.get('list_name')
137 if list_name is not None:
138 mlist = config.db.list_manager.get(list_name)
139 if mlist:
140 addr.subscribe(mlist, MemberRole.member)
141 return True
143 def discard(self, token):
144 # Throw the record away.
145 config.db.pendings.confirm(token)
149 def adapt_domain_to_registrar(iface, obj):
150 """Adapt `IDomain` to `IRegistrar`.
152 :param iface: The interface to adapt to.
153 :type iface: `zope.interface.Interface`
154 :param obj: The object being adapted.
155 :type obj: `IDomain`
156 :return: An `IRegistrar` instance if adaptation succeeded or None if it
157 didn't.
159 return (Registrar(obj)
160 if IDomain.providedBy(obj) and iface is IRegistrar
161 else None)