summaryrefslogtreecommitdiff
path: root/src/mailman/app/registrar.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/app/registrar.py')
-rw-r--r--src/mailman/app/registrar.py163
1 files changed, 163 insertions, 0 deletions
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
new file mode 100644
index 000000000..6a2abeba9
--- /dev/null
+++ b/src/mailman/app/registrar.py
@@ -0,0 +1,163 @@
+# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Implementation of the IUserRegistrar interface."""
+
+from __future__ import unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'Registrar',
+ 'adapt_domain_to_registrar',
+ ]
+
+
+import datetime
+
+from pkg_resources import resource_string
+from zope.interface import implements
+
+from mailman.Message import UserNotification
+from mailman.Utils import ValidateEmail
+from mailman.config import config
+from mailman.i18n import _
+from mailman.interfaces.domain import IDomain
+from mailman.interfaces.member import MemberRole
+from mailman.interfaces.pending import IPendable
+from mailman.interfaces.registrar import IRegistrar
+
+
+
+class PendableRegistration(dict):
+ implements(IPendable)
+ PEND_KEY = 'registration'
+
+
+
+class Registrar:
+ implements(IRegistrar)
+
+ def __init__(self, context):
+ self._context = context
+
+ def register(self, address, real_name=None, mlist=None):
+ """See `IUserRegistrar`."""
+ # First, do validation on the email address. If the address is
+ # invalid, it will raise an exception, otherwise it just returns.
+ ValidateEmail(address)
+ # Create a pendable for the registration.
+ pendable = PendableRegistration(
+ type=PendableRegistration.PEND_KEY,
+ address=address,
+ real_name=real_name)
+ if mlist is not None:
+ pendable['list_name'] = mlist.fqdn_listname
+ token = config.db.pendings.add(pendable)
+ # Set up some local variables for translation interpolation.
+ domain = IDomain(self._context)
+ domain_name = _(domain.email_host)
+ contact_address = domain.contact_address
+ confirm_url = domain.confirm_url(token)
+ confirm_address = domain.confirm_address(token)
+ email_address = address
+ # Calculate the message's Subject header. XXX Have to deal with
+ # translating this subject header properly. XXX Must deal with
+ # VERP_CONFIRMATIONS as well.
+ subject = 'confirm ' + token
+ # Send a verification email to the address.
+ text = _(resource_string('mailman.templates.en', 'verify.txt'))
+ msg = UserNotification(address, confirm_address, subject, text)
+ msg.send(mlist=None)
+ return token
+
+ def confirm(self, token):
+ """See `IUserRegistrar`."""
+ # For convenience
+ pendable = config.db.pendings.confirm(token)
+ if pendable is None:
+ return False
+ missing = object()
+ address = pendable.get('address', missing)
+ real_name = pendable.get('real_name', missing)
+ list_name = pendable.get('list_name', missing)
+ if pendable.get('type') != PendableRegistration.PEND_KEY:
+ # It seems like it would be very difficult to accurately guess
+ # tokens, or brute force an attack on the SHA1 hash, so we'll just
+ # throw the pendable away in that case. It's possible we'll need
+ # to repend the event or adjust the API to handle this case
+ # better, but for now, the simpler the better.
+ return False
+ # We are going to end up with an IAddress for the verified address
+ # and an IUser linked to this IAddress. See if any of these objects
+ # currently exist in our database.
+ usermgr = config.db.user_manager
+ addr = (usermgr.get_address(address)
+ if address is not missing else None)
+ user = (usermgr.get_user(address)
+ if address is not missing else None)
+ # If there is neither an address nor a user matching the confirmed
+ # record, then create the user, which will in turn create the address
+ # and link the two together
+ if addr is None:
+ assert user is None, 'How did we get a user but not an address?'
+ user = usermgr.create_user(address, real_name)
+ # Because the database changes haven't been flushed, we can't use
+ # IUserManager.get_address() to find the IAddress just created
+ # under the hood. Instead, iterate through the IUser's addresses,
+ # of which really there should be only one.
+ for addr in user.addresses:
+ if addr.address == address:
+ break
+ else:
+ raise AssertionError('Could not find expected IAddress')
+ elif user is None:
+ user = usermgr.create_user()
+ user.real_name = real_name
+ user.link(addr)
+ else:
+ # The IAddress and linked IUser already exist, so all we need to
+ # do is verify the address.
+ pass
+ addr.verified_on = datetime.datetime.now()
+ # If this registration is tied to a mailing list, subscribe the person
+ # to the list right now.
+ list_name = pendable.get('list_name')
+ if list_name is not None:
+ mlist = config.db.list_manager.get(list_name)
+ if mlist:
+ addr.subscribe(mlist, MemberRole.member)
+ return True
+
+ def discard(self, token):
+ # Throw the record away.
+ config.db.pendings.confirm(token)
+
+
+
+def adapt_domain_to_registrar(iface, obj):
+ """Adapt `IDomain` to `IRegistrar`.
+
+ :param iface: The interface to adapt to.
+ :type iface: `zope.interface.Interface`
+ :param obj: The object being adapted.
+ :type obj: `IDomain`
+ :return: An `IRegistrar` instance if adaptation succeeded or None if it
+ didn't.
+ """
+ return (Registrar(obj)
+ if IDomain.providedBy(obj) and iface is IRegistrar
+ else None)