def test_send_warnings_after_disable(self):
        # Test that required number of warnings are sent after the delivery is
        # disabled.
        self._mlist.bounce_notify_owner_on_disable = False
        self._mlist.bounce_you_are_disabled_warnings = 1
        self._mlist.bounce_you_are_disabled_warnings_interval = timedelta(
            days=1)
        self._mlist.bounce_score_threshold = 3
        self._mlist.send_welcome_message = False

        member = self._subscribe_and_add_bounce_event('*****@*****.**')
        member.bounce_score = 3
        member.last_bounce_received = now() - timedelta(days=2)
        # We will process all events now.
        self._process_pending_events()
        self.assertEqual(member.preferences.delivery_status,
                         DeliveryStatus.by_bounces)
        self.assertEqual(member.bounce_score, 4)

        self._processor._send_warnings()
        self.assertEqual(member.last_warning_sent.day, now().day)
        self.assertEqual(member.total_warnings_sent, 1)
        msgs = get_queue_messages('virgin', expected_count=1)
        msg = msgs[0].msg
        self.assertEqual(
            str(msg['Subject']), 'Your subscription for Test mailing list has'
            ' been disabled')
Beispiel #2
0
 def test_member_changes_preferred_address(self):
     with transaction():
         anne = self._usermanager.create_user('*****@*****.**')
         preferred = list(anne.addresses)[0]
         preferred.verified_on = now()
         anne.preferred_address = preferred
         self._mlist.subscribe(anne)
     # Take a look at Anne's current membership.
     content, response = call_api('http://*****:*****@example.com')
     self.assertEqual(
         entry_0['address'],
         'http://*****:*****@example.com')
     # Anne registers a new address and makes it her preferred address.
     # There are no changes to her membership.
     with transaction():
         new_preferred = anne.register('*****@*****.**')
         new_preferred.verified_on = now()
         anne.preferred_address = new_preferred
     # Take another look at Anne's current membership.
     content, response = call_api('http://*****:*****@example.com')
     self.assertEqual(
         entry_0['address'],
         'http://*****:*****@example.com')
Beispiel #3
0
 def test_cannot_set_address_with_preferred_address_subscription(self):
     # A user is subscribed to a mailing list with their preferred address.
     # You cannot set the `address` attribute on such IMembers.
     anne = self._usermanager.create_user("*****@*****.**")
     preferred = list(anne.addresses)[0]
     preferred.verified_on = now()
     anne.preferred_address = preferred
     # Subscribe with the IUser object, not the address.  This makes Anne a
     # member via her preferred address.
     member = self._mlist.subscribe(anne)
     new_address = anne.register("*****@*****.**")
     new_address.verified_on = now()
     self.assertRaises(MembershipError, setattr, member, "address", new_address)
Beispiel #4
0
 def test_cannot_set_address_with_preferred_address_subscription(self):
     # A user is subscribed to a mailing list with their preferred address.
     # You cannot set the `address` attribute on such IMembers.
     anne = self._usermanager.create_user('*****@*****.**')
     preferred = list(anne.addresses)[0]
     preferred.verified_on = now()
     anne.preferred_address = preferred
     # Subscribe with the IUser object, not the address.  This makes Anne a
     # member via her preferred address.
     member = self._mlist.subscribe(anne)
     new_address = anne.register('*****@*****.**')
     new_address.verified_on = now()
     self.assertRaises(MembershipError, setattr, member, 'address',
                       new_address)
Beispiel #5
0
 def test_archive_runner_with_dated_message(self):
     # Date headers don't throw off the archiver runner.
     self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
     self._archiveq.enqueue(self._msg, {},
                            listid=self._mlist.list_id,
                            received_time=now())
     self._runner.run()
     # There should now be a copy of the message in the file system.
     filename = os.path.join(config.MESSAGES_DIR,
                             '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
     with open(filename) as fp:
         archived = message_from_file(fp)
     self.assertEqual(archived['message-id'], '<first>')
     self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
Beispiel #6
0
def ensure_current_suffix_list():
    # Read and parse the organizational domain suffix list.  First look in the
    # cached directory to see if we already have a valid copy of it.
    cached_copy_path = os.path.join(config.VAR_DIR, LOCAL_FILE_NAME)
    lifetime = as_timedelta(config.dmarc.cache_lifetime)
    download = False
    try:
        mtime = os.stat(cached_copy_path).st_mtime
    except FileNotFoundError:
        vlog.info('No cached copy of the public suffix list found')
        download = True
        cache_found = False
    else:
        cache_found = True
        # Is the cached copy out-of-date?  Note that when we write a new cache
        # version we explicitly set its mtime to the time in the future when
        # the cache will expire.
        if mtime < now().timestamp():
            download = True
            vlog.info('Cached copy of public suffix list is out of date')
    if download:
        try:
            content = get(config.dmarc.org_domain_data_url)
        except (URLError, HTTPError) as error:
            elog.error('Unable to retrieve public suffix list from %s: %s',
                       config.dmarc.org_domain_data_url,
                       getattr(error, 'reason', str(error)))
            if cache_found:
                vlog.info('Using out of date public suffix list')
                content = None
            else:
                # We couldn't access the URL and didn't even have an out of
                # date suffix list cached.  Use the shipped version.
                content = resource_bytes('mailman.rules.data', LOCAL_FILE_NAME)
        if content is not None:
            # Content is either a string or UTF-8 encoded bytes.
            if isinstance(content, bytes):
                content = content.decode('utf-8')
            # Write the cache atomically.
            new_path = cached_copy_path + '.new'
            with open(new_path, 'w', encoding='utf-8') as fp:
                fp.write(content)
            # Set the expiry time to the future.
            mtime = (now() + lifetime).timestamp()
            os.utime(new_path, (mtime, mtime))
            # Flip the new file into the cached location.  This does not
            # modify the mtime.
            os.rename(new_path, cached_copy_path)
    return cached_copy_path
Beispiel #7
0
 def test_archive_runner_with_dated_message(self):
     # Date headers don't throw off the archiver runner.
     self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
     self._archiveq.enqueue(
         self._msg, {},
         listid=self._mlist.list_id,
         received_time=now())
     self._runner.run()
     # There should now be a copy of the message in the file system.
     filename = os.path.join(
         config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
     with open(filename) as fp:
         archived = message_from_file(fp)
     self.assertEqual(archived['message-id'], '<first>')
     self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
Beispiel #8
0
 def test_clobber_date_never(self):
     # Even if the Date header is insanely off from the received time of
     # the message, if clobber_date is 'never', the header is not clobbered.
     self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
     self._archiveq.enqueue(self._msg, {},
                            listid=self._mlist.list_id,
                            received_time=now())
     self._runner.run()
     # There should now be a copy of the message in the file system.
     filename = os.path.join(config.MESSAGES_DIR,
                             '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
     with open(filename) as fp:
         archived = message_from_file(fp)
     self.assertEqual(archived['message-id'], '<first>')
     self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
Beispiel #9
0
 def test_update_display_name(self):
     verified_on = now()
     with transaction():
         anne = getUtility(IUserManager).create_address('*****@*****.**')
         anne.verified_on = verified_on
     json, response = call_api(
         'http://*****:*****@example.com')
     # By default, there won't be any display_name since we didn't specify
     # any above when creating the address.
     self.assertFalse('display_name' in json)
     json, response = call_api(
         'http://*****:*****@example.com',
         method='PATCH',
         data={'display_name': 'Anne Person'})
     self.assertEqual(response.status_code, 204)
     # Now the address should have a display_name set.
     json, response = call_api(
         'http://*****:*****@example.com')
     self.assertEqual(json['display_name'], 'Anne Person')
     # This name can also be updated.
     json, response = call_api(
         'http://*****:*****@example.com',
         method='PATCH',
         data={'display_name': 'Anne P. Person'})
     self.assertEqual(response.status_code, 204)
     # Now the address should have a display_name updated to new one.
     json, response = call_api(
         'http://*****:*****@example.com')
     self.assertEqual(json['display_name'], 'Anne P. Person')
Beispiel #10
0
    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._now = now()
        # Enable just the dummy archiver.
        config.push('dummy', """
        [archiver.dummy]
        class: mailman.runners.tests.test_archiver.DummyArchiver
        enable: no
        [archiver.prototype]
        enable: no
        [archiver.mhonarc]
        enable: no
        [archiver.mail_archive]
        enable: no
        """)
        self._archiveq = config.switchboards['archive']
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: My first post
Message-ID: <first>
X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB

First post!
""")
        self._runner = make_testable_runner(ArchiveRunner)
        IListArchiverSet(self._mlist).get('dummy').is_enabled = True
Beispiel #11
0
 def test_clobber_date_never(self):
     # Even if the Date header is insanely off from the received time of
     # the message, if clobber_date is 'never', the header is not clobbered.
     self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
     self._archiveq.enqueue(
         self._msg, {},
         listid=self._mlist.list_id,
         received_time=now())
     self._runner.run()
     # There should now be a copy of the message in the file system.
     filename = os.path.join(
         config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
     with open(filename) as fp:
         archived = message_from_file(fp)
     self.assertEqual(archived['message-id'], '<first>')
     self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
    def test_events_membership_removal_no_warnings(self):
        self._mlist.bounce_notify_owner_on_removal = True
        self._mlist.bounce_you_are_disabled_warnings = 0
        self._mlist.bounce_you_are_disabled_warnings_interval = timedelta(
            days=2)

        anne = self._mlist.members.get_member(self._anne.email)
        anne.preferences.delivery_status = DeliveryStatus.by_bounces
        anne.total_warnings_sent = 0
        # Remove immediately.
        anne.last_warning_sent = now()

        self._runner.run()
        items = get_queue_messages('virgin', expected_count=2)
        if items[0].msg['to'] == '*****@*****.**':
            owner_notif, user_notif = items
        else:
            user_notif, owner_notif = items
        self.assertEqual(user_notif.msg['to'], '*****@*****.**')
        self.assertEqual(
            user_notif.msg['subject'],
            'You have been unsubscribed from the Test mailing list')

        self.assertEqual(
            str(owner_notif.msg['subject']),
            '[email protected] unsubscribed from Test mailing '
            'list due to bounces')
        # The membership should no longer exist.
        self.assertIsNone(self._mlist.members.get_member(self._anne.email))
Beispiel #13
0
 def test_transaction_abort_after_failing_subcommand(self):
     with transaction():
         mlist = create_list('*****@*****.**')
         mlist.volume = 5
         mlist.next_digest_number = 3
         mlist.digest_last_sent_at = now() - timedelta(days=60)
     testargs = [
         'mailman', 'digests', '-b', '-l', '*****@*****.**', '--send'
     ]
     output = StringIO()
     with ExitStack() as resources:
         enter = resources.enter_context
         enter(patch('sys.argv', testargs))
         enter(patch('sys.stdout', output))
         # Force an exception in the subcommand.
         enter(
             patch('mailman.commands.cli_digests.maybe_send_digest_now',
                   side_effect=RuntimeError))
         # Everything is already initialized.
         enter(patch('mailman.bin.mailman.initialize'))
         with self.assertRaises(RuntimeError):
             main()
     # Clear the current transaction to force a database reload.
     config.db.abort()
     # The volume and number haven't changed.
     self.assertEqual(mlist.volume, 5)
     self.assertEqual(mlist.next_digest_number, 3)
Beispiel #14
0
 def __init__(self, email, display_name):
     super(Address, self).__init__()
     lower_case = email.lower()
     self.email = lower_case
     self.display_name = display_name
     self._original = (None if lower_case == email else email)
     self.registered_on = now()
    def memberships_pending_warning(self, store):
        """See `IMembershipManager`."""
        from mailman.model.mailinglist import MailingList
        from mailman.model.preferences import Preferences

        # maxking: We don't care so much about the bounce score here since it
        # could have been reset due to bounce info getting stale. We will send
        # warnings to people who have been disabled already, regardless of
        # their bounce score. Same is true below for removal.
        query = store.query(Member).join(
            MailingList, Member.list_id ==
            MailingList._list_id).join(Member.preferences).filter(
                and_(
                    MailingList.process_bounces == True,  # noqa: E712
                    Member.total_warnings_sent <
                    MailingList.bounce_you_are_disabled_warnings,  # noqa: E501
                    Preferences.delivery_status == DeliveryStatus.by_bounces))

        # XXX(maxking): This is IMO a query that *should* work, but I haven't
        # been able to get it to work in my tests. It could be due to lack of
        # native datetime type in SQLite, which we use for tests. Hence, we are
        # going to do some filtering in Python, which is inefficient, but
        # works. We do filter as much as possible in SQL, so hopefully the
        # output of the above query won't be *too* many.
        # TODO: Port the Python loop below to SQLAlchemy.

        # (func.DATETIME(Member.last_warning_sent) +
        # func.DATETIME(MailingList.bounce_you_are_disabled_warnings_interval))
        # < func.DATETIME(now())))

        for member in query.all():
            if (member.last_warning_sent + member.mailing_list.
                    bounce_you_are_disabled_warnings_interval
                ) <= now():  # noqa: E501
                yield member
Beispiel #16
0
 def setUp(self):
     self._manager = getUtility(IUserManager)
     self._mlist = create_list("*****@*****.**")
     self._anne = self._manager.create_user("*****@*****.**", "Anne Person")
     preferred = list(self._anne.addresses)[0]
     preferred.verified_on = now()
     self._anne.preferred_address = preferred
Beispiel #17
0
    def _generate_lmtp_file(self, fp):
        # The format for Postfix's LMTP transport map is defined here:
        # http://www.postfix.org/transport.5.html
        #
        # Sort all existing mailing list names first by domain, then by
        # local part.  For Postfix we need a dummy entry for the domain.
        list_manager = getUtility(IListManager)
        utility = getUtility(IMailTransportAgentAliases)
        by_domain = {}
        sort_key = attrgetter('list_name')
        for list_name, mail_host in list_manager.name_components:
            mlist = _FakeList(list_name, mail_host)
            by_domain.setdefault(mlist.mail_host, []).append(mlist)
        print("""\
# AUTOMATICALLY GENERATED BY MAILMAN ON {0}
#
# This file is generated by Mailman, and is kept in sync with the binary hash
# file.  YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you know what you're
# doing, and can keep the two files properly in sync.  If you screw it up,
# you're on your own.
    """.format(now().replace(microsecond=0)), file=fp)
        for domain in sorted(by_domain):
            print("""\
# Aliases which are visible only in the @{0} domain.""".format(domain),
                  file=fp)
            for mlist in sorted(by_domain[domain], key=sort_key):
                aliases = list(utility.aliases(mlist))
                width = max(len(alias) for alias in aliases) + 3
                print(ALIASTMPL.format(aliases.pop(0), config, width), file=fp)
                for alias in aliases:
                    print(ALIASTMPL.format(alias, config, width), file=fp)
                print(file=fp)
Beispiel #18
0
 def test_transaction_abort_after_failing_subcommand(self):
     with transaction():
         mlist = create_list('*****@*****.**')
         mlist.volume = 5
         mlist.next_digest_number = 3
         mlist.digest_last_sent_at = now() - timedelta(days=60)
     testargs = ['mailman', 'digests', '-b', '-l', '*****@*****.**',
                 '--send']
     output = StringIO()
     with ExitStack() as resources:
         enter = resources.enter_context
         enter(patch('sys.argv', testargs))
         enter(patch('sys.stdout', output))
         # Force an exception in the subcommand.
         enter(patch('mailman.commands.cli_digests.maybe_send_digest_now',
                     side_effect=RuntimeError))
         # Everything is already initialized.
         enter(patch('mailman.bin.mailman.initialize'))
         with self.assertRaises(RuntimeError):
             main()
     # Clear the current transaction to force a database reload.
     config.db.abort()
     # The volume and number haven't changed.
     self.assertEqual(mlist.volume, 5)
     self.assertEqual(mlist.next_digest_number, 3)
 def test_member_subscribed_address_has_no_display_name(self):
     address = getUtility(IUserManager).create_address("*****@*****.**")
     address.verified_on = now()
     self._mlist.subscribe(address)
     items = get_queue_messages("virgin", expected_count=1)
     message = items[0].msg
     self.assertEqual(message["to"], "*****@*****.**")
 def test_disable_delivery_already_disabled(self):
     # Attempting to disable delivery for an already disabled member does
     # nothing.
     self._mlist.send_welcome_message = False
     member = self._subscribe_and_add_bounce_event('*****@*****.**')
     events = list(self._processor.events)
     self.assertEqual(len(events), 1)
     member.total_warnings_sent = 3
     member.last_warning_sent = now() - timedelta(days=2)
     member.preferences.delivery_status = DeliveryStatus.by_bounces
     mark = LogFileMark('mailman.bounce')
     self._processor._disable_delivery(self._mlist, member, events[0])
     self.assertEqual(mark.read(), '')
     self.assertEqual(member.total_warnings_sent, 3)
     self.assertEqual(member.last_warning_sent, now() - timedelta(days=2))
     get_queue_messages('virgin', expected_count=0)
Beispiel #21
0
 def add(self, store, pendable, lifetime=None):
     verifyObject(IPendable, pendable)
     # Calculate the token and the lifetime.
     if lifetime is None:
         lifetime = as_timedelta(config.mailman.pending_request_life)
     # Calculate a unique token.  Algorithm vetted by the Timbot.  time()
     # has high resolution on Linux, clock() on Windows.  random gives us
     # about 45 bits in Python 2.2, 53 bits on Python 2.3.  The time and
     # clock values basically help obscure the random number generator, as
     # does the hash calculation.  The integral parts of the time values
     # are discarded because they're the most predictable bits.
     for attempts in range(3):
         right_now = time.time()
         x = random.random() + right_now % 1.0 + time.clock() % 1.0
         # Use sha1 because it produces shorter strings.
         token = hashlib.sha1(repr(x).encode("utf-8")).hexdigest()
         # In practice, we'll never get a duplicate, but we'll be anal
         # about checking anyway.
         if store.query(Pended).filter_by(token=token).count() == 0:
             break
     else:
         raise RuntimeError("Could not find a valid pendings token")
     # Create the record, and then the individual key/value pairs.
     pending = Pended(token=token, expiration_date=now() + lifetime)
     for key, value in pendable.items():
         # Both keys and values must be strings.
         if isinstance(key, bytes):
             key = key.decode("utf-8")
         if isinstance(value, bytes):
             # Make sure we can turn this back into a bytes.
             value = dict(__encoding__="utf-8", value=value.decode("utf-8"))
         keyval = PendedKeyValue(key=key, value=json.dumps(value))
         pending.key_values.append(keyval)
     store.add(pending)
     return token
Beispiel #22
0
 def test_delete_nonmembers_on_adding_member(self):
     # GL: #237 - When a new address is subscribed, any existing nonmember
     # subscriptions for this address; or any addresses also controlled by
     # this user, are deleted.
     anne_nonmember = add_member(
         self._mlist,
         RequestRecord('*****@*****.**', 'Anne Person',
                       DeliveryMode.regular,
                       system_preferences.preferred_language),
         MemberRole.nonmember)
     # Add a few other validated addresses to this user, and subscribe them
     # as nonmembers.
     for email in ('*****@*****.**', '*****@*****.**'):
         address = anne_nonmember.user.register(email)
         address.verified_on = now()
         self._mlist.subscribe(address, MemberRole.nonmember)
     # There are now three nonmembers.
     self.assertEqual(
         {address.email for address in self._mlist.nonmembers.addresses},
         {'*****@*****.**',
          '*****@*****.**',
          '*****@*****.**',
          })
     # Let's now add one of Anne's addresses as a member.  This deletes all
     # of Anne's nonmember memberships.
     anne_member = add_member(
         self._mlist,
         RequestRecord('*****@*****.**', 'Anne Person',
                       DeliveryMode.regular,
                       system_preferences.preferred_language),
         MemberRole.member)
     self.assertEqual(self._mlist.nonmembers.member_count, 0)
     members = list(self._mlist.members.members)
     self.assertEqual(len(members), 1)
     self.assertEqual(members[0], anne_member)
Beispiel #23
0
 def add(self, store, pendable, lifetime=None):
     verifyObject(IPendable, pendable)
     # Calculate the token and the lifetime.
     if lifetime is None:
         lifetime = as_timedelta(config.mailman.pending_request_life)
     for attempts in range(3):
         token = token_factory.new()
         # In practice, we'll never get a duplicate, but we'll be anal
         # about checking anyway.
         if store.query(Pended).filter_by(token=token).count() == 0:
             break
     else:
         raise RuntimeError('Could not find a valid pendings token')
     # Create the record, and then the individual key/value pairs.
     pending = Pended(token=token, expiration_date=now() + lifetime)
     pendable_type = pendable.get('type', pendable.PEND_TYPE)
     pending.key_values.append(
         PendedKeyValue(key='type', value=pendable_type))
     for key, value in pendable.items():
         # The type has been handled above.
         if key == 'type':
             continue
         # Both keys and values must be strings.
         if isinstance(key, bytes):
             key = key.decode('utf-8')
         if isinstance(value, bytes):
             # Make sure we can turn this back into a bytes.
             value = dict(__encoding__='utf-8', value=value.decode('utf-8'))
         keyval = PendedKeyValue(key=key, value=json.dumps(value))
         pending.key_values.append(keyval)
     store.add(pending)
     return token
    def test_events_disable_delivery(self):
        self._mlist.bounce_score_threshold = 3
        anne = self._subscribe_and_add_bounce_event('*****@*****.**',
                                                    subscribe=False,
                                                    create=False)
        anne.bounce_score = 2
        anne.last_bounce_received = now() - timedelta(days=2)
        self._runner.run()
        self.assertEqual(anne.bounce_score, 3)
        self.assertEqual(anne.preferences.delivery_status,
                         DeliveryStatus.by_bounces)
        # There should also be a pending notification for the the list
        # administrator.
        items = get_queue_messages('virgin', expected_count=2)
        if items[0].msg['to'] == '*****@*****.**':
            owner_notif, disable_notice = items
        else:
            disable_notice, owner_notif = items
        self.assertEqual(owner_notif.msg['Subject'],
                         "*****@*****.**'s subscription disabled on Test")

        self.assertEqual(disable_notice.msg['to'], '*****@*****.**')
        self.assertEqual(
            str(disable_notice.msg['subject']),
            'Your subscription for Test mailing list has been disabled')
Beispiel #25
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(
        RequestType.subscription, address, data)
    vlog.info('%s: held subscription request from %s',
              mlist.fqdn_listname, address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make('subauth.txt',
                    mailing_list=mlist,
                    username=address,
                    listname=mlist.fqdn_listname,
                    admindb_url=mlist.script_url('admindb'),
                    )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(
            mlist.owner_address, mlist.owner_address,
            subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Beispiel #26
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._anne = getUtility(IUserManager).create_user(
         '*****@*****.**', 'Anne Person')
     preferred = list(self._anne.addresses)[0]
     preferred.verified_on = now()
     self._anne.preferred_address = preferred
Beispiel #27
0
    def _generate_lmtp_file(self, fp):
        # The format for Postfix's LMTP transport map is defined here:
        # http://www.postfix.org/transport.5.html
        #
        # Sort all existing mailing list names first by domain, then by
        # local part.  For Postfix we need a dummy entry for the domain.
        list_manager = getUtility(IListManager)
        utility = getUtility(IMailTransportAgentAliases)
        by_domain = {}
        sort_key = attrgetter('list_name')
        for list_name, mail_host in list_manager.name_components:
            mlist = _FakeList(list_name, mail_host)
            by_domain.setdefault(mlist.mail_host, []).append(mlist)
        print("""\
# AUTOMATICALLY GENERATED BY MAILMAN ON {0}
#
# This file is generated by Mailman, and is kept in sync with the binary hash
# file.  YOU SHOULD NOT MANUALLY EDIT THIS FILE unless you know what you're
# doing, and can keep the two files properly in sync.  If you screw it up,
# you're on your own.
    """.format(now().replace(microsecond=0)), file=fp)
        for domain in sorted(by_domain):
            print("""\
# Aliases which are visible only in the @{0} domain.""".format(domain),
                  file=fp)
            for mlist in sorted(by_domain[domain], key=sort_key):
                aliases = list(utility.aliases(mlist))
                width = max(len(alias) for alias in aliases) + 3
                print(ALIASTMPL.format(aliases.pop(0), config, width), file=fp)
                for alias in aliases:
                    print(ALIASTMPL.format(alias, config, width), file=fp)
                print(file=fp)
Beispiel #28
0
 def add(self, store, pendable, lifetime=None):
     verifyObject(IPendable, pendable)
     # Calculate the token and the lifetime.
     if lifetime is None:
         lifetime = as_timedelta(config.mailman.pending_request_life)
     for attempts in range(3):
         token = token_factory.new()
         # In practice, we'll never get a duplicate, but we'll be anal
         # about checking anyway.
         if store.query(Pended).filter_by(token=token).count() == 0:
             break
     else:
         raise RuntimeError('Could not find a valid pendings token')
     # Create the record, and then the individual key/value pairs.
     pending = Pended(
         token=token,
         expiration_date=now() + lifetime)
     pendable_type = pendable.get('type', pendable.PEND_TYPE)
     pending.key_values.append(
         PendedKeyValue(key='type', value=pendable_type))
     for key, value in pendable.items():
         # The type has been handled above.
         if key == 'type':
             continue
         # Both keys and values must be strings.
         if isinstance(key, bytes):
             key = key.decode('utf-8')
         if isinstance(value, bytes):
             # Make sure we can turn this back into a bytes.
             value = dict(__encoding__='utf-8',
                          value=value.decode('utf-8'))
         keyval = PendedKeyValue(key=key, value=json.dumps(value))
         pending.key_values.append(keyval)
     store.add(pending)
     return token
Beispiel #29
0
def set_preferred(user):
    # Avoid circular imports.
    from mailman.utilities.datetime import now
    preferred = list(user.addresses)[0]
    preferred.verified_on = now()
    user.preferred_address = preferred
    return preferred
Beispiel #30
0
def hold_subscription(mlist, address, display_name, password, mode, language):
    data = dict(when=now().isoformat(),
                address=address,
                display_name=display_name,
                password=password,
                delivery_mode=mode.name,
                language=language)
    # Now hold this request.  We'll use the address as the key.
    requestsdb = IListRequests(mlist)
    request_id = requestsdb.hold_request(RequestType.subscription, address,
                                         data)
    vlog.info('%s: held subscription request from %s', mlist.fqdn_listname,
              address)
    # Possibly notify the administrator in default list language
    if mlist.admin_immed_notify:
        subject = _(
            'New subscription request to $mlist.display_name from $address')
        text = make(
            'subauth.txt',
            mailing_list=mlist,
            username=address,
            listname=mlist.fqdn_listname,
            admindb_url=mlist.script_url('admindb'),
        )
        # This message should appear to come from the <list>-owner so as
        # to avoid any useless bounce processing.
        msg = UserNotification(mlist.owner_address, mlist.owner_address,
                               subject, text, mlist.preferred_language)
        msg.send(mlist, tomoderators=True)
    return request_id
Beispiel #31
0
 def test_member_changes_preferred_address(self):
     with transaction():
         anne = self._usermanager.create_user('*****@*****.**')
         _set_preferred(anne)
         self._mlist.subscribe(anne)
     # Take a look at Anne's current membership.
     content, response = call_api('http://*****:*****@example.com')
     self.assertEqual(
         entry_0['address'],
         'http://*****:*****@example.com')
     # Anne registers a new address and makes it her preferred address.
     # There are no changes to her membership.
     with transaction():
         new_preferred = anne.register('*****@*****.**')
         new_preferred.verified_on = now()
         anne.preferred_address = new_preferred
     # Take another look at Anne's current membership.
     content, response = call_api('http://*****:*****@example.com')
     self.assertEqual(
         entry_0['address'],
         'http://*****:*****@example.com')
Beispiel #32
0
 def test_confirm_because_confirm_then_moderation(self):
     # We have a subscription request which requires the user to confirm
     # (because she does not have a verified address) and the moderator to
     # approve.  Running the workflow gives us a token.  Confirming the
     # token runs the workflow a little farther, but still gives us a
     # token.  Confirming again subscribes the user.
     self._mlist.subscription_policy = (
         SubscriptionPolicy.confirm_then_moderate)
     self._anne.verified_on = now()
     # Runs until subscription confirmation.
     token, token_owner, rmember = self._registrar.register(self._anne)
     self.assertIsNotNone(token)
     self.assertEqual(token_owner, TokenOwner.subscriber)
     self.assertIsNone(rmember)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertIsNone(member)
     # Now confirm the subscription, and wait for the moderator to approve
     # the subscription.  She is still not subscribed.
     new_token, token_owner, rmember = self._registrar.confirm(token)
     # The new token, used for the moderator to approve the message, is not
     # the same as the old token.
     self.assertNotEqual(new_token, token)
     self.assertIsNotNone(new_token)
     self.assertEqual(token_owner, TokenOwner.moderator)
     self.assertIsNone(rmember)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertIsNone(member)
     # Confirm once more, this time as the moderator approving the
     # subscription.  Now she's a member.
     token, token_owner, rmember = self._registrar.confirm(new_token)
     self.assertIsNone(token)
     self.assertEqual(token_owner, TokenOwner.no_one)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertEqual(rmember, member)
     self.assertEqual(member.address, self._anne)
Beispiel #33
0
def _set_preferred(user):
    # Avoid circular imports.
    from mailman.utilities.datetime import now
    preferred = list(user.addresses)[0]
    preferred.verified_on = now()
    user.preferred_address = preferred
    return preferred
Beispiel #34
0
 def __init__(self, list_name, email, msg, context=None):
     self.list_name = list_name
     self.email = email
     self.timestamp = now()
     self.message_id = msg["message-id"]
     self.context = BounceContext.normal if context is None else context
     self.processed = False
Beispiel #35
0
 def __init__(self, list_id, email, msg, context=None):
     self.list_id = list_id
     self.email = email
     self.timestamp = now()
     self.message_id = msg['message-id']
     self.context = (BounceContext.normal if context is None else context)
     self.processed = False
Beispiel #36
0
 def _step_do_confirm_verify(self):
     # Restore a little extra state that can't be stored in the database
     # (because the order of setattr() on restore is indeterminate), then
     # continue with the confirmation/verification step.
     if self.which is WhichSubscriber.address:
         self.subscriber = self.address
     else:
         assert self.which is WhichSubscriber.user
         self.subscriber = self.user
     # Reset the token so it can't be used in a replay attack.
     self._set_token(TokenOwner.no_one)
     # The user has confirmed their subscription request, and also verified
     # their email address if necessary.  This latter needs to be set on the
     # IAddress, but there's nothing more to do about the confirmation step.
     # We just continue along with the workflow.
     if self.address.verified_on is None:
         self.address.verified_on = now()
     # The next step depends on the mailing list's subscription policy.
     next_step = ('moderation_checks'
                  if self.mlist.subscription_policy in (
                      SubscriptionPolicy.moderate,
                      SubscriptionPolicy.confirm_then_moderate,
                      )
                 else 'do_subscription')
     self.push(next_step)
Beispiel #37
0
 def on_post(self, request, response):
     # We don't care about the POST data, just do the action.
     if self._action == 'verify' and self._address.verified_on is None:
         self._address.verified_on = now()
     elif self._action == 'unverify':
         self._address.verified_on = None
     no_content(response)
Beispiel #38
0
 def verify(self, request):
     # We don't care about the POST data, just do the action.
     if self._action == 'verify' and self._address.verified_on is None:
         self._address.verified_on = now()
     elif self._action == 'unverify':
         self._address.verified_on = None
     return no_content()
Beispiel #39
0
 def test_confirm_because_confirm_then_moderation(self):
     # We have a subscription request which requires the user to confirm
     # (because she does not have a verified address) and the moderator to
     # approve.  Running the workflow gives us a token.  Confirming the
     # token runs the workflow a little farther, but still gives us a
     # token.  Confirming again subscribes the user.
     self._mlist.subscription_policy = \
       SubscriptionPolicy.confirm_then_moderate
     self._anne.verified_on = now()
     # Runs until subscription confirmation.
     token, token_owner, rmember = self._registrar.register(self._anne)
     self.assertIsNotNone(token)
     self.assertEqual(token_owner, TokenOwner.subscriber)
     self.assertIsNone(rmember)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertIsNone(member)
     # Now confirm the subscription, and wait for the moderator to approve
     # the subscription.  She is still not subscribed.
     new_token, token_owner, rmember = self._registrar.confirm(token)
     # The new token, used for the moderator to approve the message, is not
     # the same as the old token.
     self.assertNotEqual(new_token, token)
     self.assertIsNotNone(new_token)
     self.assertEqual(token_owner, TokenOwner.moderator)
     self.assertIsNone(rmember)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertIsNone(member)
     # Confirm once more, this time as the moderator approving the
     # subscription.  Now she's a member.
     token, token_owner, rmember = self._registrar.confirm(new_token)
     self.assertIsNone(token)
     self.assertEqual(token_owner, TokenOwner.no_one)
     member = self._mlist.regular_members.get_member('*****@*****.**')
     self.assertEqual(rmember, member)
     self.assertEqual(member.address, self._anne)
Beispiel #40
0
 def test_user_is_subscribed_to_unsubscribe(self):
     # A user must be subscribed to a list when trying to unsubscribe.
     addr = self._user_manager.create_address('*****@*****.**')
     addr.verfied_on = now()
     workflow = UnSubscriptionWorkflow(self._mlist, addr)
     self.assertRaises(AssertionError, workflow.run_thru,
                       'subscription_checks')
 def test_member_subscribed_address_has_no_display_name(self):
     address = getUtility(IUserManager).create_address('*****@*****.**')
     address.verified_on = now()
     self._mlist.subscribe(address)
     items = get_queue_messages('virgin', expected_count=1)
     message = items[0].msg
     self.assertEqual(message['to'], '*****@*****.**')
Beispiel #42
0
    def setUp(self):
        self._mlist = create_list('*****@*****.**')
        self._now = now()
        # Enable just the dummy archiver.
        config.push(
            'dummy', """
        [archiver.dummy]
        class: mailman.runners.tests.test_archiver.DummyArchiver
        enable: no
        [archiver.prototype]
        enable: no
        [archiver.mhonarc]
        enable: no
        [archiver.mail_archive]
        enable: no
        """)
        self._archiveq = config.switchboards['archive']
        self._msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: My first post
Message-ID: <first>
Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB

First post!
""")
        self._runner = make_testable_runner(ArchiveRunner)
        IListArchiverSet(self._mlist).get('dummy').is_enabled = True
Beispiel #43
0
 def add(self, store, pendable, lifetime=None):
     verifyObject(IPendable, pendable)
     # Calculate the token and the lifetime.
     if lifetime is None:
         lifetime = as_timedelta(config.mailman.pending_request_life)
     # Calculate a unique token.  Algorithm vetted by the Timbot.  time()
     # has high resolution on Linux, clock() on Windows.  random gives us
     # about 45 bits in Python 2.2, 53 bits on Python 2.3.  The time and
     # clock values basically help obscure the random number generator, as
     # does the hash calculation.  The integral parts of the time values
     # are discarded because they're the most predictable bits.
     for attempts in range(3):
         right_now = time.time()
         x = random.random() + right_now % 1.0 + time.clock() % 1.0
         # Use sha1 because it produces shorter strings.
         token = hashlib.sha1(repr(x).encode('utf-8')).hexdigest()
         # In practice, we'll never get a duplicate, but we'll be anal
         # about checking anyway.
         if store.query(Pended).filter_by(token=token).count() == 0:
             break
     else:
         raise RuntimeError('Could not find a valid pendings token')
     # Create the record, and then the individual key/value pairs.
     pending = Pended(token=token, expiration_date=now() + lifetime)
     for key, value in pendable.items():
         # Both keys and values must be strings.
         if isinstance(key, bytes):
             key = key.decode('utf-8')
         if isinstance(value, bytes):
             # Make sure we can turn this back into a bytes.
             value = dict(__encoding__='utf-8', value=value.decode('utf-8'))
         keyval = PendedKeyValue(key=key, value=json.dumps(value))
         pending.key_values.append(keyval)
     store.add(pending)
     return token
Beispiel #44
0
 def test_delete_nonmembers_on_adding_member(self):
     # GL: #237 - When a new address is subscribed, any existing nonmember
     # subscriptions for this address; or any addresses also controlled by
     # this user, are deleted.
     anne_nonmember = add_member(
         self._mlist,
         RequestRecord('*****@*****.**', 'Anne Person',
                       DeliveryMode.regular,
                       system_preferences.preferred_language),
         MemberRole.nonmember)
     # Add a few other validated addresses to this user, and subscribe them
     # as nonmembers.
     for email in ('*****@*****.**', '*****@*****.**'):
         address = anne_nonmember.user.register(email)
         address.verified_on = now()
         self._mlist.subscribe(address, MemberRole.nonmember)
     # There are now three nonmembers.
     self.assertEqual(
         {address.email for address in self._mlist.nonmembers.addresses},
         {'*****@*****.**',
          '*****@*****.**',
          '*****@*****.**',
          })
     # Let's now add one of Anne's addresses as a member.  This deletes all
     # of Anne's nonmember memberships.
     anne_member = add_member(
         self._mlist,
         RequestRecord('*****@*****.**', 'Anne Person',
                       DeliveryMode.regular,
                       system_preferences.preferred_language),
         MemberRole.member)
     self.assertEqual(self._mlist.nonmembers.member_count, 0)
     members = list(self._mlist.members.members)
     self.assertEqual(len(members), 1)
     self.assertEqual(members[0], anne_member)
Beispiel #45
0
 def on_post(self, request, response):
     # We don't care about the POST data, just do the action.
     if self._action == 'verify' and self._address.verified_on is None:
         self._address.verified_on = now()
     elif self._action == 'unverify':
         self._address.verified_on = None
     no_content(response)
Beispiel #46
0
 def test_cached_copy_is_expired(self):
     cache_path = os.path.join(config.VAR_DIR, dmarc.LOCAL_FILE_NAME)
     with open(cache_path, 'w', encoding='utf-8') as fp:
         print('xyz', end='', file=fp)
     # Expire the cache file.  That way the current cached file will be
     # invalid and a new one will be downloaded.
     expires = (now() - timedelta(days=1)).timestamp()
     os.utime(cache_path, (expires, expires))
     new_path = dmarc.ensure_current_suffix_list()
     self.assertEqual(cache_path, new_path)
     with open(cache_path, 'r', encoding='utf-8') as fp:
         contents = fp.read()
     self.assertEqual(contents, 'abc')
     self.assertEqual(
         os.stat(new_path).st_mtime,
         (now() + as_timedelta(config.dmarc.cache_lifetime)).timestamp())
Beispiel #47
0
 def _step_do_confirm_verify(self):
     # Restore a little extra state that can't be stored in the database
     # (because the order of setattr() on restore is indeterminate), then
     # continue with the confirmation/verification step.
     if self.which is WhichSubscriber.address:
         self.subscriber = self.address
     else:
         assert self.which is WhichSubscriber.user
         self.subscriber = self.user
     # Reset the token so it can't be used in a replay attack.
     self._set_token(TokenOwner.no_one)
     # The user has confirmed their subscription request, and also verified
     # their email address if necessary.  This latter needs to be set on the
     # IAddress, but there's nothing more to do about the confirmation step.
     # We just continue along with the workflow.
     if self.address.verified_on is None:
         self.address.verified_on = now()
     # The next step depends on the mailing list's subscription policy.
     next_step = ('moderation_checks'
                  if self.mlist.subscription_policy in (
                      SubscriptionPolicy.moderate,
                      SubscriptionPolicy.confirm_then_moderate,
                      )
                  else 'do_subscription')
     self.push(next_step)
Beispiel #48
0
 def setUp(self):
     self._ant = create_list('*****@*****.**')
     self._bee = create_list('*****@*****.**')
     user_manager = getUtility(IUserManager)
     self._anne = user_manager.create_user('*****@*****.**')
     preferred = list(self._anne.addresses)[0]
     preferred.verified_on = now()
     self._anne.preferred_address = preferred
Beispiel #49
0
 def __init__(self, email, display_name):
     super(Address, self).__init__()
     getUtility(IEmailValidator).validate(email)
     lower_case = email.lower()
     self.email = lower_case
     self.display_name = display_name
     self._original = (None if lower_case == email else email)
     self.registered_on = now()
Beispiel #50
0
 def __init__(self, list_id, email, msg, context=None):
     self.list_id = list_id
     self.email = email
     self.timestamp = now()
     msgid = msg['message-id']
     self.message_id = msgid
     self.context = (BounceContext.normal if context is None else context)
     self.processed = False
Beispiel #51
0
 def test_unsubscribe(self):
     address = self._usermanager.create_address('*****@*****.**')
     address.verified_on = now()
     self._mlist.subscribe(address)
     self.assertEqual(len(list(self._mlist.members.members)), 1)
     member = self._mlist.members.get_member('*****@*****.**')
     member.unsubscribe()
     self.assertEqual(len(list(self._mlist.members.members)), 0)
Beispiel #52
0
 def setUp(self):
     self._ant = create_list('*****@*****.**')
     self._bee = create_list('*****@*****.**')
     user_manager = getUtility(IUserManager)
     self._anne = user_manager.make_user('*****@*****.**', 'Anne Person')
     preferred = list(self._anne.addresses)[0]
     preferred.verified_on = now()
     self._anne.preferred_address = preferred
Beispiel #53
0
 def __init__(self, email, display_name):
     super().__init__()
     getUtility(IEmailValidator).validate(email)
     lower_case = email.lower()
     self.email = lower_case
     self.display_name = display_name
     self._original = (None if lower_case == email else email)
     self.registered_on = now()
Beispiel #54
0
 def test_unsubscribe(self):
     address = self._usermanager.create_address('*****@*****.**')
     address.verified_on = now()
     self._mlist.subscribe(address)
     self.assertEqual(len(list(self._mlist.members.members)), 1)
     member = self._mlist.members.get_member('*****@*****.**')
     member.unsubscribe()
     self.assertEqual(len(list(self._mlist.members.members)), 0)
    def on_post(self, request, response):
        """POST to /addresses

        Add a new address to the user record.
        """
        assert self._user is not None
        preferred = None
        user_manager = getUtility(IUserManager)
        validator = Validator(email=str,
                              display_name=str,
                              preferred=as_boolean,
                              absorb_existing=bool,
                              _optional=('display_name', 'absorb_existing',
                                         'preferred'))
        try:
            data = validator(request)
            # We cannot set the address to be preferred when it is
            # created so remove it from the arguments here and
            # set it below.
            preferred = data.pop('preferred', False)
        except ValueError as error:
            bad_request(response, str(error))
            return
        absorb_existing = data.pop('absorb_existing', False)
        try:
            address = user_manager.create_address(**data)
        except InvalidEmailAddressError:
            bad_request(response, b'Invalid email address')
            return
        except ExistingAddressError:
            # If the address is not linked to any user, link it to the current
            # user and return it.  Since we're linking to an existing address,
            # ignore any given display_name attribute.
            address = user_manager.get_address(data['email'])
            if address.user is None:
                address.user = self._user
                location = self.api.path_to('addresses/{}'.format(
                    address.email))
                created(response, location)
                return
            elif not absorb_existing:
                bad_request(response, 'Address belongs to other user')
                return
            else:
                # The address exists and is linked but we can merge the users.
                address = user_manager.get_address(data['email'])
                self._user.absorb(address.user)
        else:
            # Link the address to the current user and return it.
            address.user = self._user

            # Set the preferred address here if we were signalled to do so.
            if preferred:
                address.verified_on = now()
                self._user.preferred_address = address

        location = self.api.path_to('addresses/{}'.format(address.email))
        created(response, location)
 def test_lp_1031391(self):
     # LP: #1031391 msgdata['received_time'] gets added by the LMTP server.
     # The value is a datetime.  If this message gets held, it will break
     # pending requests since they require string keys and values.
     received_time = now()
     msgdata = dict(received_time=received_time)
     request_id = hold_message(self._mlist, self._msg, msgdata)
     key, data = self._request_db.get_request(request_id)
     self.assertEqual(data['received_time'], received_time)
Beispiel #57
0
 def setUp(self):
     with transaction():
         self._mlist = create_list("*****@*****.**")
         self._registrar = IRegistrar(self._mlist)
         manager = getUtility(IUserManager)
         self._anne = manager.create_address("*****@*****.**", "Anne Person")
         self._bart = manager.make_user("*****@*****.**", "Bart Person")
         preferred = list(self._bart.addresses)[0]
         preferred.verified_on = now()
         self._bart.preferred_address = preferred
 def test_member_has_address_and_user_display_name(self):
     user = getUtility(IUserManager).create_user("*****@*****.**", "Anne Person")
     set_preferred(user)
     address = getUtility(IUserManager).create_address("*****@*****.**", "Anne X Person")
     address.verified_on = now()
     user.link(address)
     self._mlist.subscribe(address)
     items = get_queue_messages("virgin", expected_count=1)
     message = items[0].msg
     self.assertEqual(message["to"], "Anne X Person <*****@*****.**>")
Beispiel #59
0
 def __init__(self, list_id, email, msg, context=None):
     self.list_id = list_id
     self.email = email
     self.timestamp = now()
     msgid = msg['message-id']
     if isinstance(msgid, bytes):
         msgid = msgid.decode('ascii')
     self.message_id = msgid
     self.context = (BounceContext.normal if context is None else context)
     self.processed = False
 def test_verification_checks_with_verified_address(self):
     # When the address is already verified, we skip straight to the
     # confirmation checks.
     anne = self._user_manager.create_address(self._anne)
     anne.verified_on = now()
     workflow = SubscriptionWorkflow(self._mlist, anne)
     workflow.run_thru('verification_checks')
     with patch.object(workflow, '_step_confirmation_checks') as step:
         next(workflow)
     step.assert_called_once_with()