Ejemplo n.º 1
0
def _ticket_change_subscribers(subscriber, candidates):
    if not candidates:
        return
    if not isinstance(candidates, (list, set, tuple)):
        candidates = [candidates]

    # Get members of permission groups
    groups = PermissionSystem(subscriber.env).get_groups_dict()
    for cc in set(candidates):
        if cc in groups:
            candidates.remove(cc)
            candidates.update(groups[cc])

    matcher = RecipientMatcher(subscriber.env)
    klass = subscriber.__class__.__name__
    sids = set()
    for candidate in candidates:
        recipient = matcher.match_recipient(candidate)
        if not recipient:
            continue
        sid, auth, addr = recipient

        # Default subscription
        for s in subscriber.default_subscriptions():
            yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
        if sid:
            sids.add((sid, auth))

    for s in Subscription.find_by_sids_and_class(subscriber.env, sids, klass):
        yield s.subscription_tuple()
Ejemplo n.º 2
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        # CC field is stored as comma-separated string. Parse to set.
        chrome = Chrome(self.env)
        to_set = lambda cc: set(chrome.cc_list(cc))
        cc_set = to_set(event.target['cc'] or '')

        # Harvest previous CC field
        if 'fields' in event.changes and 'cc' in event.changes['fields']:
            cc_set.update(to_set(event.changes['fields']['cc']['old']))

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for cc in cc_set:
            recipient = matcher.match_recipient(cc)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4])
            if sid:
                sids.add((sid,auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 3
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category == 'batchmodify':
            for ticket_event in event.get_ticket_change_events(self.env):
                for m in self.matches(ticket_event):
                    yield m
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        ticket = event.target

        matcher = RecipientMatcher(self.env)
        recipient = matcher.match_recipient(ticket['reporter'])
        if not recipient:
            return
        sid, auth, addr = recipient

        # Default subscription
        for s in self.default_subscriptions():
            yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4])

        if sid:
            klass = self.__class__.__name__
            for s in Subscription.find_by_sids_and_class(
                    self.env, ((sid, auth), ), klass):
                yield s.subscription_tuple()
Ejemplo n.º 4
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        updaters = [row[0] for row in self.env.db_query("""
            SELECT DISTINCT author FROM ticket_change
            WHERE ticket=%s
            """, (event.target.id, ))]

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for previous_updater in updaters:
            if previous_updater == event.author:
                continue

            recipient = matcher.match_recipient(previous_updater)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4])
            if sid:
                sids.add((sid,auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 5
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        # CC field is stored as comma-separated string. Parse to set.
        chrome = Chrome(self.env)
        to_set = lambda cc: set(chrome.cc_list(cc))
        cc_set = to_set(event.target['cc'] or '')

        # Harvest previous CC field
        if 'fields' in event.changes and 'cc' in event.changes['fields']:
            cc_set.update(to_set(event.changes['fields']['cc']['old']))

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for cc in cc_set:
            recipient = matcher.match_recipient(cc)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
            if sid:
                sids.add((sid, auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 6
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return
        ticket = event.target

        owners = [ticket['owner']]

        # Harvest previous owner
        if 'fields' in event.changes and 'owner' in event.changes['fields']:
            owners.append(event.changes['fields']['owner']['old'])

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for owner in owners:
            recipient = matcher.match_recipient(owner)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4])
            if sid:
                sids.add((sid,auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 7
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return
        ticket = event.target

        owners = [ticket['owner']]

        # Harvest previous owner
        if 'fields' in event.changes and 'owner' in event.changes['fields']:
            owners.append(event.changes['fields']['owner']['old'])

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for owner in owners:
            recipient = matcher.match_recipient(owner)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
            if sid:
                sids.add((sid, auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 8
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        updaters = [
            row[0] for row in self.env.db_query(
                """
            SELECT DISTINCT author FROM ticket_change
            WHERE ticket=%s
            """, (event.target.id, ))
        ]

        matcher = RecipientMatcher(self.env)
        klass = self.__class__.__name__
        sids = set()
        for previous_updater in updaters:
            if previous_updater == event.author:
                continue

            recipient = matcher.match_recipient(previous_updater)
            if not recipient:
                continue
            sid, auth, addr = recipient

            # Default subscription
            for s in self.default_subscriptions():
                yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
            if sid:
                sids.add((sid, auth))

        for s in Subscription.find_by_sids_and_class(self.env, sids, klass):
            yield s.subscription_tuple()
Ejemplo n.º 9
0
 def decorate_message(self, event, message, charset):
     if event.realm != 'ticket':
         return
     from_email = self._get_from_email(event)
     if event.category == 'batchmodify':
         tickets = sort_tickets_by_priority(self.env, event.target)
         subject = self._format_subj_batchmodify(tickets)
         targetid = ','.join(map(str, tickets))
         msgid = self._get_message_id(targetid, from_email, event.time)
     else:
         subject = self._format_subj(event)
         ticket = event.target
         targetid = '%08d' % ticket.id
         more = ticket['reporter'] or ''
         msgid = self._get_message_id(targetid, from_email, None, more)
         url = self.env.abs_href.ticket(ticket.id)
         if event.category != 'created':
             set_header(message, 'In-Reply-To', msgid, charset)
             set_header(message, 'References', msgid, charset)
             msgid = self._get_message_id(targetid, from_email, event.time,
                                          more)
             cnum = ticket.get_comment_number(event.time)
             if cnum is not None:
                 url += '#comment:%d' % cnum
         set_header(message, 'X-Trac-Ticket-ID', ticket.id, charset)
         set_header(message, 'X-Trac-Ticket-URL', url, charset)
         # When owner, reporter and updater are listed in the Cc header,
         # move the address to To header.
         if NotificationSystem(self.env).use_public_cc:
             to_addrs = set()
             matcher = RecipientMatcher(self.env)
             for rcpt in ticket['owner'], ticket['reporter'], event.author:
                 rcpt = matcher.match_recipient(rcpt)
                 if not rcpt:
                     continue
                 addr = rcpt[2]
                 if addr:
                     to_addrs.add(addr)
             if to_addrs:
                 cc_addrs = get_message_addresses(message, 'Cc')
                 to_addrs &= set(addr for name, addr in cc_addrs)
             if to_addrs:
                 cc_header = ', '.join(create_header('Cc', (name, addr),
                                                     charset)
                                       for name, addr in cc_addrs
                                       if addr not in to_addrs)
                 if cc_header:
                     set_header(message, 'Cc', cc_header, charset)
                 elif 'Cc' in message:
                     del message['Cc']
                 to_header = ', '.join(sorted(to_addrs))
                 set_header(message, 'To', to_header, charset)
     set_header(message, 'Subject', subject, charset)
     set_header(message, 'Message-ID', msgid, charset)
Ejemplo n.º 10
0
    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = self.recipient_matcher.name_map
        self.email_map = self.recipient_matcher.email_map

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)
Ejemplo n.º 11
0
    def test_match_recipient_ignore_domains(self):
        self.config.set('notification', 'ignore_domains',
                        'example.net,example.com')
        with self.env.db_transaction:
            self._add_session('user1', email='*****@*****.**')
            self._add_session('user2', email='*****@*****.**')
            self._add_session('user3', email='*****@*****.**')
            self._add_session('*****@*****.**')
            self._add_session('*****@*****.**')
            self._add_session('*****@*****.**')
        matcher = RecipientMatcher(self.env)

        # authenticated users
        self.assertEqual(('user1', 1, '*****@*****.**'),
                         matcher.match_recipient('user1'))
        self.assertEqual(None, matcher.match_recipient('user2'))
        self.assertEqual(None, matcher.match_recipient('user3'))
        self.assertEqual(('*****@*****.**', 1, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual(None, matcher.match_recipient('*****@*****.**'))
        self.assertEqual(None, matcher.match_recipient('*****@*****.**'))
        # anonymous users
        self.assertEqual((None, 0, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual(None, matcher.match_recipient('*****@*****.**'))
        self.assertEqual(None, matcher.match_recipient('*****@*****.**'))
Ejemplo n.º 12
0
    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = {}
        self.email_map = {}
        for username, name, email in self.env.get_known_users():
            if name:
                self.name_map[username] = name
            if email:
                self.email_map[username] = email

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)
Ejemplo n.º 13
0
    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = self.recipient_matcher.name_map
        self.email_map = self.recipient_matcher.email_map

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)
Ejemplo n.º 14
0
def _ticket_change_subscribers(subscriber, candidates):
    if not candidates:
        return
    if not isinstance(candidates, (list, set, tuple)):
        candidates = [candidates]
    matcher = RecipientMatcher(subscriber.env)
    klass = subscriber.__class__.__name__
    sids = set()
    for candidate in candidates:
        recipient = matcher.match_recipient(candidate)
        if not recipient:
            continue
        sid, auth, addr = recipient

        # Default subscription
        for s in subscriber.default_subscriptions():
            yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]
        if sid:
            sids.add((sid, auth))

    for s in Subscription.find_by_sids_and_class(subscriber.env, sids, klass):
        yield s.subscription_tuple()
Ejemplo n.º 15
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        matcher = RecipientMatcher(self.env)
        recipient = matcher.match_recipient(event.author)
        if not recipient:
            return
        sid, auth, addr = recipient

        # Default subscription
        for s in self.default_subscriptions():
            yield (s[0], s[1], sid, auth, addr, s[2], s[3], s[4])

        if sid:
            klass = self.__class__.__name__
            for s in Subscription.find_by_sids_and_class(self.env,
                    ((sid,auth),), klass):
                yield s.subscription_tuple()
Ejemplo n.º 16
0
    def matches(self, event):
        if event.realm != 'ticket':
            return
        if event.category not in ('created', 'changed', 'attachment added',
                                  'attachment deleted'):
            return

        matcher = RecipientMatcher(self.env)
        recipient = matcher.match_recipient(event.author)
        if not recipient:
            return
        sid, auth, addr = recipient

        # Default subscription
        for s in self.default_subscriptions():
            yield s[0], s[1], sid, auth, addr, s[2], s[3], s[4]

        if sid:
            klass = self.__class__.__name__
            for s in Subscription \
                     .find_by_sids_and_class(self.env, ((sid, auth),), klass):
                yield s.subscription_tuple()
Ejemplo n.º 17
0
 def test_match_recipient_address(self):
     matcher = RecipientMatcher(self.env)
     expected = (None, 0, '*****@*****.**')
     self.assertEqual(expected, matcher.match_recipient('*****@*****.**'))
     self.assertEqual(expected,
                      matcher.match_recipient('<*****@*****.**>'))
     self.assertEqual(expected, matcher.match_recipient(
         'Name name <*****@*****.**>'))
     self.assertEqual(expected, matcher.match_recipient(
         u'Námë ńämé <*****@*****.**>'))
Ejemplo n.º 18
0
    def test_match_recipient_use_short_addr(self):
        self.config.set('notification', 'use_short_addr', 'enabled')
        with self.env.db_transaction:
            self._add_session('user1')
            self._add_session('user2', email='user2-email')
            self._add_session('user3', email='*****@*****.**')
            self._add_session('*****@*****.**', email='user4')
        matcher = RecipientMatcher(self.env)

        self.assertEqual(('user1', 1, 'user1'),
                         matcher.match_recipient('user1'))
        self.assertEqual(('user2', 1, 'user2-email'),
                         matcher.match_recipient('user2'))
        self.assertEqual(('user3', 1, '*****@*****.**'),
                         matcher.match_recipient('user3'))
        self.assertEqual(('*****@*****.**', 1, 'user4'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual((None, 0, 'user9'), matcher.match_recipient('user9'))
Ejemplo n.º 19
0
    def test_match_recipient_smtp_default_domain(self):
        self.config.set('notification', 'smtp_default_domain',
                        'default.example.net')
        with self.env.db_transaction:
            self._add_session('user1')
            self._add_session('user2', email='user2-email')
            self._add_session('user3', email='*****@*****.**')
            self._add_session('*****@*****.**', email='user4')
            self._add_session('*****@*****.**')
        matcher = RecipientMatcher(self.env)

        self.assertEqual(('user1', 1, '*****@*****.**'),
                         matcher.match_recipient('user1'))
        self.assertEqual(('user2', 1, '*****@*****.**'),
                         matcher.match_recipient('user2'))
        self.assertEqual(('user3', 1, '*****@*****.**'),
                         matcher.match_recipient('user3'))
        self.assertEqual(('*****@*****.**', 1, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual(('*****@*****.**', 1, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual((None, 0, '*****@*****.**'),
                         matcher.match_recipient('user9'))
Ejemplo n.º 20
0
class NotifyEmail(Notify):
    """Baseclass for notification by email.

    :since 1.1.3: deprecated and will be removed in 1.3.1
    """

    from_email = 'trac+tickets@localhost'
    subject = ''
    template_name = None
    nodomaddr_re = RecipientMatcher.nodomaddr_re
    addrsep_re = re.compile(r'[;\s,]+')

    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = self.recipient_matcher.name_map
        self.email_map = self.recipient_matcher.email_map

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)

    def notify(self, resid, subject, author=None):
        self.subject = subject
        config = self.config['notification']
        if not config.getbool('smtp_enabled'):
            return
        from_email, from_name = '', ''
        if author and config.getbool('smtp_from_author'):
            from_email = self.get_smtp_address(author)
            if from_email:
                from_name = self.name_map.get(author, '')
                if not from_name:
                    mo = self.longaddr_re.search(author)
                    if mo:
                        from_name = mo.group(1)
        if not from_email:
            from_email = config.get('smtp_from')
            from_name = config.get('smtp_from_name') or self.env.project_name
        self.replyto_email = config.get('smtp_replyto')
        self.from_email = from_email or self.replyto_email
        self.from_name = from_name
        if not self.from_email and not self.replyto_email:
            message = tag(
                tag.p(_('Unable to send email due to identity crisis.')),
                # convert explicitly to `Fragment` to avoid breaking message
                # when passing `LazyProxy` object to `Fragment`
                tag.p(to_fragment(tag_(
                    "Neither %(from_)s nor %(reply_to)s are specified in the "
                    "configuration.",
                    from_=tag.strong("[notification] smtp_from"),
                    reply_to=tag.strong("[notification] smtp_replyto")))))
            raise TracError(message, _("SMTP Notification Error"))

        Notify.notify(self, resid)

    def format_header(self, key, name, email=None):
        header = create_header(key, name, self._charset)
        if not email:
            return header
        else:
            header = str(header).replace('\\', r'\\') \
                                .replace('"', r'\"')
            return '"%s" <%s>' % (header, email)

    def add_headers(self, msg, headers):
        for h in headers:
            msg[h] = self.encode_header(h, headers[h])

    def get_smtp_address(self, address):
        recipient = self.recipient_matcher.match_recipient(address)
        if not recipient:
            return None
        return recipient[2]

    def encode_header(self, key, value):
        if isinstance(value, tuple):
            return self.format_header(key, value[0], value[1])
        mo = self.longaddr_re.match(value)
        if mo:
            return self.format_header(key, mo.group(1), mo.group(2))
        return self.format_header(key, value)

    def _format_body(self):
        stream = self.template.generate(**self.data)
        # don't translate the e-mail stream
        t = deactivate()
        try:
            return stream.render('text', encoding='utf-8')
        finally:
            reactivate(t)

    def send(self, torcpts, ccrcpts, mime_headers={}):
        body = self._format_body()
        public_cc = self.config.getbool('notification', 'use_public_cc')
        headers = {
            'X-Mailer': 'Trac %s, by Edgewall Software'
                        % self.env.trac_version,
            'X-Trac-Version': self.env.trac_version,
            'X-Trac-Project': self.env.project_name,
            'X-URL': self.env.project_url,
            'Precedence': 'bulk',
            'Auto-Submitted': 'auto-generated',
            'Subject': self.subject,
            'From': (self.from_name, self.from_email) if self.from_name
                                                      else self.from_email,
            'Reply-To': self.replyto_email
        }

        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x,
                          [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return tmp, all

        notify_sys = NotificationSystem(self.env)
        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)
        accaddrs = notify_sys.smtp_always_cc_list
        bccaddrs = notify_sys.smtp_always_bcc_list

        recipients = []
        toaddrs, recipients = remove_dup(toaddrs, recipients)
        ccaddrs, recipients = remove_dup(ccaddrs, recipients)
        accaddrs, recipients = remove_dup(accaddrs, recipients)
        bccaddrs, recipients = remove_dup(bccaddrs, recipients)

        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info("no recipient for a ticket notification")
            return

        pcc = accaddrs
        if public_cc:
            pcc += ccaddrs
            if toaddrs:
                headers['To'] = ', '.join(toaddrs)
        if pcc:
            headers['Cc'] = ', '.join(pcc)
        headers['Date'] = formatdate()
        msg = create_mime_text(body, 'plain', self._charset)
        self.add_headers(msg, headers)
        self.add_headers(msg, mime_headers)
        NotificationSystem(self.env).send_email(self.from_email, recipients,
                                                msg.as_string())
Ejemplo n.º 21
0
    def test_match_recipient_admit_domains(self):
        self.config.set('notification', 'admit_domains', 'LOCALDOMAIN')
        with self.env.db_transaction:
            self._add_session('user1', email='user1@localhost')
            self._add_session('user2', email='user2@localdomain')
            self._add_session('user3', email='*****@*****.**')
            self._add_session('user4@localhost')
            self._add_session('user5@localdomain')
            self._add_session('*****@*****.**')
            self._add_session('user7@localhost', email='*****@*****.**')
            self._add_session('user8@localdomain', email='user8@localhost')
            self._add_session('*****@*****.**', email='user9@localdomain')
        matcher = RecipientMatcher(self.env)

        # authenticated users
        self.assertEqual(None, matcher.match_recipient('user1'))
        self.assertEqual(('user2', 1, 'user2@localdomain'),
                         matcher.match_recipient('user2'))
        self.assertEqual(('user3', 1, '*****@*****.**'),
                         matcher.match_recipient('user3'))
        self.assertEqual(None, matcher.match_recipient('user4@localhost'))
        self.assertEqual(('user5@localdomain', 1, 'user5@localdomain'),
                         matcher.match_recipient('user5@localdomain'))
        self.assertEqual(('*****@*****.**', 1, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
        self.assertEqual(('user7@localhost', 1, '*****@*****.**'),
                         matcher.match_recipient('user7@localhost'))
        self.assertEqual(None, matcher.match_recipient('user8@localdomain'))
        self.assertEqual(('*****@*****.**', 1, 'user9@localdomain'),
                         matcher.match_recipient('*****@*****.**'))
        # anonymous users
        self.assertEqual(None, matcher.match_recipient('foobar'))
        self.assertEqual(None, matcher.match_recipient('anon@localhost'))
        self.assertEqual((None, 0, 'anon@localdomain'),
                         matcher.match_recipient('anon@localdomain'))
        self.assertEqual((None, 0, '*****@*****.**'),
                         matcher.match_recipient('*****@*****.**'))
Ejemplo n.º 22
0
 def test_match_recipient_anonymous(self):
     matcher = RecipientMatcher(self.env)
     self.assertEqual(None, matcher.match_recipient('anonymous'))
Ejemplo n.º 23
0
 def test_match_recipient_empty(self):
     matcher = RecipientMatcher(self.env)
     self.assertEqual(None, matcher.match_recipient(None))
     self.assertEqual(None, matcher.match_recipient(u''))
Ejemplo n.º 24
0
class NotifyEmail(Notify):
    """Baseclass for notification by email.

    :since 1.1.3: deprecated and will be removed in 1.3.1
    """

    from_email = 'trac+tickets@localhost'
    subject = ''
    template_name = None
    nodomaddr_re = RecipientMatcher.nodomaddr_re
    addrsep_re = re.compile(r'[;\s,]+')

    def __init__(self, env):
        super(NotifyEmail, self).__init__(env)

        self.recipient_matcher = RecipientMatcher(env)
        self.shortaddr_re = self.recipient_matcher.shortaddr_re
        self.longaddr_re = self.recipient_matcher.longaddr_re
        self._ignore_domains = self.recipient_matcher.ignore_domains
        self.name_map = self.recipient_matcher.name_map
        self.email_map = self.recipient_matcher.email_map

        notify_sys = NotificationSystem(self.env)
        self._charset = create_charset(notify_sys.mime_encoding)

    def notify(self, resid, subject, author=None):
        self.subject = subject
        config = self.config['notification']
        if not config.getbool('smtp_enabled'):
            return
        from_email, from_name = '', ''
        if author and config.getbool('smtp_from_author'):
            from_email = self.get_smtp_address(author)
            if from_email:
                from_name = self.name_map.get(author, '')
                if not from_name:
                    mo = self.longaddr_re.search(author)
                    if mo:
                        from_name = mo.group(1)
        if not from_email:
            from_email = config.get('smtp_from')
            from_name = config.get('smtp_from_name') or self.env.project_name
        self.replyto_email = config.get('smtp_replyto')
        self.from_email = from_email or self.replyto_email
        self.from_name = from_name
        if not self.from_email and not self.replyto_email:
            message = tag(
                tag.p(_('Unable to send email due to identity crisis.')),
                # convert explicitly to `Fragment` to avoid breaking message
                # when passing `LazyProxy` object to `Fragment`
                tag.p(
                    to_fragment(
                        tag_(
                            "Neither %(from_)s nor %(reply_to)s are specified in the "
                            "configuration.",
                            from_=tag.strong("[notification] smtp_from"),
                            reply_to=tag.strong(
                                "[notification] smtp_replyto")))))
            raise TracError(message, _("SMTP Notification Error"))

        Notify.notify(self, resid)

    def format_header(self, key, name, email=None):
        header = create_header(key, name, self._charset)
        if not email:
            return header
        else:
            header = str(header).replace('\\', r'\\') \
                                .replace('"', r'\"')
            return '"%s" <%s>' % (header, email)

    def add_headers(self, msg, headers):
        for h in headers:
            msg[h] = self.encode_header(h, headers[h])

    def get_smtp_address(self, address):
        recipient = self.recipient_matcher.match_recipient(address)
        if not recipient:
            return None
        return recipient[2]

    def encode_header(self, key, value):
        if isinstance(value, tuple):
            return self.format_header(key, value[0], value[1])
        mo = self.longaddr_re.match(value)
        if mo:
            return self.format_header(key, mo.group(1), mo.group(2))
        return self.format_header(key, value)

    def _format_body(self):
        stream = self.template.generate(**self.data)
        # don't translate the e-mail stream
        t = deactivate()
        try:
            return stream.render('text', encoding='utf-8')
        finally:
            reactivate(t)

    def send(self, torcpts, ccrcpts, mime_headers={}):
        body = self._format_body()
        public_cc = self.config.getbool('notification', 'use_public_cc')
        headers = {
            'X-Mailer':
            'Trac %s, by Edgewall Software' % self.env.trac_version,
            'X-Trac-Version': self.env.trac_version,
            'X-Trac-Project': self.env.project_name,
            'X-URL': self.env.project_url,
            'Precedence': 'bulk',
            'Auto-Submitted': 'auto-generated',
            'Subject': self.subject,
            'From': (self.from_name,
                     self.from_email) if self.from_name else self.from_email,
            'Reply-To': self.replyto_email
        }

        def build_addresses(rcpts):
            """Format and remove invalid addresses"""
            return filter(lambda x: x,
                          [self.get_smtp_address(addr) for addr in rcpts])

        def remove_dup(rcpts, all):
            """Remove duplicates"""
            tmp = []
            for rcpt in rcpts:
                if not rcpt in all:
                    tmp.append(rcpt)
                    all.append(rcpt)
            return tmp, all

        notify_sys = NotificationSystem(self.env)
        toaddrs = build_addresses(torcpts)
        ccaddrs = build_addresses(ccrcpts)
        accaddrs = notify_sys.smtp_always_cc_list
        bccaddrs = notify_sys.smtp_always_bcc_list

        recipients = []
        toaddrs, recipients = remove_dup(toaddrs, recipients)
        ccaddrs, recipients = remove_dup(ccaddrs, recipients)
        accaddrs, recipients = remove_dup(accaddrs, recipients)
        bccaddrs, recipients = remove_dup(bccaddrs, recipients)

        # if there is not valid recipient, leave immediately
        if len(recipients) < 1:
            self.env.log.info("no recipient for a ticket notification")
            return

        pcc = accaddrs
        if public_cc:
            pcc += ccaddrs
            if toaddrs:
                headers['To'] = ', '.join(toaddrs)
        if pcc:
            headers['Cc'] = ', '.join(pcc)
        headers['Date'] = formatdate()
        msg = create_mime_text(body, 'plain', self._charset)
        self.add_headers(msg, headers)
        self.add_headers(msg, mime_headers)
        NotificationSystem(self.env).send_email(self.from_email, recipients,
                                                msg.as_string())