~raj-abhilash1/mailman/bugfix

« back to all changes in this revision

Viewing changes to src/mailman/app/registrar.py

  • Committer: Barry Warsaw
  • Date: 2015-04-15 14:05:35 UTC
  • mfrom: (7313.1.28 subpolicy-2)
  • Revision ID: barry@list.org-20150415140535-csj5cobfg9ocf0ry
 * Mailing list subscription policy work flow has been completely rewritten.
   It now properly supports email verification and subscription confirmation
   by the user, and approval by the moderator using unique tokens.
   ``IMailingList`` objects now have a ``subscription_policy`` attribute.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
 
26
26
import logging
27
27
 
 
28
from mailman.app.subscriptions import SubscriptionWorkflow
28
29
from mailman.core.i18n import _
 
30
from mailman.database.transaction import flush
29
31
from mailman.email.message import UserNotification
30
 
from mailman.interfaces.address import IEmailValidator
31
 
from mailman.interfaces.listmanager import IListManager
32
 
from mailman.interfaces.member import DeliveryMode, MemberRole
33
32
from mailman.interfaces.pending import IPendable, IPendings
34
33
from mailman.interfaces.registrar import ConfirmationNeededEvent, IRegistrar
35
34
from mailman.interfaces.templates import ITemplateLoader
36
 
from mailman.interfaces.usermanager import IUserManager
37
 
from mailman.utilities.datetime import now
 
35
from mailman.interfaces.workflow import IWorkflowStateManager
38
36
from zope.component import getUtility
39
 
from zope.event import notify
40
37
from zope.interface import implementer
41
38
 
42
39
 
54
51
class Registrar:
55
52
    """Handle registrations and confirmations for subscriptions."""
56
53
 
57
 
    def register(self, mlist, email, display_name=None, delivery_mode=None):
 
54
    def __init__(self, mlist):
 
55
        self._mlist = mlist
 
56
 
 
57
    def register(self, subscriber=None, *,
 
58
                 pre_verified=False, pre_confirmed=False, pre_approved=False):
58
59
        """See `IRegistrar`."""
59
 
        if delivery_mode is None:
60
 
            delivery_mode = DeliveryMode.regular
61
 
        # First, do validation on the email address.  If the address is
62
 
        # invalid, it will raise an exception, otherwise it just returns.
63
 
        getUtility(IEmailValidator).validate(email)
64
 
        # Create a pendable for the registration.
65
 
        pendable = PendableRegistration(
66
 
            type=PendableRegistration.PEND_KEY,
67
 
            email=email,
68
 
            display_name=display_name,
69
 
            delivery_mode=delivery_mode.name,
70
 
            list_id=mlist.list_id)
71
 
        token = getUtility(IPendings).add(pendable)
72
 
        # We now have everything we need to begin the confirmation dance.
73
 
        # Trigger the event to start the ball rolling, and return the
74
 
        # generated token.
75
 
        notify(ConfirmationNeededEvent(mlist, pendable, token))
76
 
        return token
 
60
        workflow = SubscriptionWorkflow(
 
61
            self._mlist, subscriber,
 
62
            pre_verified=pre_verified,
 
63
            pre_confirmed=pre_confirmed,
 
64
            pre_approved=pre_approved)
 
65
        list(workflow)
 
66
        return workflow.token
77
67
 
78
68
    def confirm(self, token):
79
69
        """See `IRegistrar`."""
80
 
        # For convenience
81
 
        pendable = getUtility(IPendings).confirm(token)
82
 
        if pendable is None:
83
 
            return False
84
 
        missing = object()
85
 
        email = pendable.get('email', missing)
86
 
        display_name = pendable.get('display_name', missing)
87
 
        pended_delivery_mode = pendable.get('delivery_mode', 'regular')
88
 
        try:
89
 
            delivery_mode = DeliveryMode[pended_delivery_mode]
90
 
        except ValueError:
91
 
            log.error('Invalid pended delivery_mode for {0}: {1}',
92
 
                      email, pended_delivery_mode)
93
 
            delivery_mode = DeliveryMode.regular
94
 
        if pendable.get('type') != PendableRegistration.PEND_KEY:
95
 
            # It seems like it would be very difficult to accurately guess
96
 
            # tokens, or brute force an attack on the SHA1 hash, so we'll just
97
 
            # throw the pendable away in that case.  It's possible we'll need
98
 
            # to repend the event or adjust the API to handle this case
99
 
            # better, but for now, the simpler the better.
100
 
            return False
101
 
        # We are going to end up with an IAddress for the verified address
102
 
        # and an IUser linked to this IAddress.  See if any of these objects
103
 
        # currently exist in our database.
104
 
        user_manager = getUtility(IUserManager)
105
 
        address = (user_manager.get_address(email)
106
 
                   if email is not missing else None)
107
 
        user = (user_manager.get_user(email)
108
 
                if email is not missing else None)
109
 
        # If there is neither an address nor a user matching the confirmed
110
 
        # record, then create the user, which will in turn create the address
111
 
        # and link the two together
112
 
        if address is None:
113
 
            assert user is None, 'How did we get a user but not an address?'
114
 
            user = user_manager.create_user(email, display_name)
115
 
            # Because the database changes haven't been flushed, we can't use
116
 
            # IUserManager.get_address() to find the IAddress just created
117
 
            # under the hood.  Instead, iterate through the IUser's addresses,
118
 
            # of which really there should be only one.
119
 
            for address in user.addresses:
120
 
                if address.email == email:
121
 
                    break
122
 
            else:
123
 
                raise AssertionError('Could not find expected IAddress')
124
 
        elif user is None:
125
 
            user = user_manager.create_user()
126
 
            user.display_name = display_name
127
 
            user.link(address)
128
 
        else:
129
 
            # The IAddress and linked IUser already exist, so all we need to
130
 
            # do is verify the address.
131
 
            pass
132
 
        address.verified_on = now()
133
 
        # If this registration is tied to a mailing list, subscribe the person
134
 
        # to the list right now.  That will generate a SubscriptionEvent,
135
 
        # which can be used to send a welcome message.
136
 
        list_id = pendable.get('list_id')
137
 
        if list_id is not None:
138
 
            mlist = getUtility(IListManager).get_by_list_id(list_id)
139
 
            if mlist is not None:
140
 
                member = mlist.subscribe(address, MemberRole.member)
141
 
                member.preferences.delivery_mode = delivery_mode
142
 
        return True
 
70
        workflow = SubscriptionWorkflow(self._mlist)
 
71
        workflow.token = token
 
72
        workflow.restore()
 
73
        list(workflow)
 
74
        return workflow.token
143
75
 
144
76
    def discard(self, token):
145
 
        # Throw the record away.
146
 
        getUtility(IPendings).confirm(token)
 
77
        """See `IRegistrar`."""
 
78
        with flush():
 
79
            getUtility(IPendings).confirm(token)
 
80
            getUtility(IWorkflowStateManager).discard(
 
81
                SubscriptionWorkflow.__name__, token)
147
82
 
148
83
 
149
84
 
156
91
    # the Subject header, or they can click on the URL in the body of the
157
92
    # message and confirm through the web.
158
93
    subject = 'confirm ' + event.token
159
 
    mlist = getUtility(IListManager).get_by_list_id(event.pendable['list_id'])
160
 
    confirm_address = mlist.confirm_address(event.token)
 
94
    confirm_address = event.mlist.confirm_address(event.token)
161
95
    # For i18n interpolation.
162
 
    confirm_url = mlist.domain.confirm_url(event.token)
163
 
    email_address = event.pendable['email']
164
 
    domain_name = mlist.domain.mail_host
165
 
    contact_address = mlist.owner_address
 
96
    confirm_url = event.mlist.domain.confirm_url(event.token)
 
97
    email_address = event.email
 
98
    domain_name = event.mlist.domain.mail_host
 
99
    contact_address = event.mlist.owner_address
166
100
    # Send a verification email to the address.
167
101
    template = getUtility(ITemplateLoader).get(
168
102
        'mailman:///{0}/{1}/confirm.txt'.format(
169
 
            mlist.fqdn_listname,
170
 
            mlist.preferred_language.code))
 
103
            event.mlist.fqdn_listname,
 
104
            event.mlist.preferred_language.code))
171
105
    text = _(template)
172
106
    msg = UserNotification(email_address, confirm_address, subject, text)
173
 
    msg.send(mlist)
 
107
    msg.send(event.mlist)