예제 #1
0
    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' % __version__,
            'X-Trac-Version': __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())
예제 #2
0
파일: mail.py 프로젝트: minimalistduck/trac
    def _do_send(self, transport, event, message, cc_addrs, bcc_addrs):
        notify_sys = NotificationSystem(self.env)
        smtp_from = notify_sys.smtp_from
        smtp_from_name = notify_sys.smtp_from_name or self.env.project_name
        smtp_replyto = notify_sys.smtp_replyto
        if not notify_sys.use_short_addr and notify_sys.smtp_default_domain:
            if smtp_from and '@' not in smtp_from:
                smtp_from = '%s@%s' % (smtp_from,
                                       notify_sys.smtp_default_domain)
            if smtp_replyto and '@' not in smtp_replyto:
                smtp_replyto = '%s@%s' % (smtp_replyto,
                                          notify_sys.smtp_default_domain)

        headers = {}
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software'\
                              % self.env.trac_version
        headers['X-Trac-Version'] = self.env.trac_version
        headers['X-Trac-Project'] = self.env.project_name
        headers['X-URL'] = self.env.project_url
        headers['X-Trac-Realm'] = event.realm
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        if isinstance(event.target, (list, tuple)):
            targetid = ','.join(map(get_target_id, event.target))
        else:
            targetid = get_target_id(event.target)
        rootid = create_message_id(self.env, targetid, smtp_from, None,
                                   more=event.realm)
        if event.category == 'created':
            headers['Message-ID'] = rootid
        else:
            headers['Message-ID'] = create_message_id(self.env, targetid,
                                                      smtp_from, event.time,
                                                      more=event.realm)
            headers['In-Reply-To'] = rootid
            headers['References'] = rootid
        headers['Date'] = formatdate()
        headers['From'] = (smtp_from_name, smtp_from) \
                          if smtp_from_name else smtp_from
        headers['To'] = 'undisclosed-recipients: ;'
        if cc_addrs:
            headers['Cc'] = ', '.join(cc_addrs)
        if bcc_addrs:
            headers['Bcc'] = ', '.join(bcc_addrs)
        headers['Reply-To'] = smtp_replyto

        for k, v in headers.iteritems():
            set_header(message, k, v, self._charset)
        for decorator in self.decorators:
            decorator.decorate_message(event, message, self._charset)

        from_name, from_addr = parseaddr(str(message['From']))
        to_addrs = set()
        for name in ('To', 'Cc', 'Bcc'):
            values = map(str, message.get_all(name, ()))
            to_addrs.update(addr for name, addr in getaddresses(values)
                                 if addr)
        del message['Bcc']
        notify_sys.send_email(from_addr, list(to_addrs), message.as_string())
예제 #3
0
    def _do_delete(self, req, milestone):
        req.perm(milestone.resource).require('MILESTONE_DELETE')

        retarget_to = req.args.get('target') or None
        # Don't translate ticket comment (comment:40:ticket:5658)
        retargeted_tickets = \
            milestone.move_tickets(retarget_to, req.authname,
                "Ticket retargeted after milestone deleted")
        milestone.delete()
        add_notice(req, _('The milestone "%(name)s" has been deleted.',
                          name=milestone.name))
        if retargeted_tickets:
            add_notice(req, _('The tickets associated with milestone '
                              '"%(name)s" have been retargeted to milestone '
                              '"%(retarget)s".', name=milestone.name,
                              retarget=retarget_to))
            new_values = {'milestone': retarget_to}
            comment = _("Tickets retargeted after milestone deleted")
            event = BatchTicketChangeEvent(retargeted_tickets, None,
                                           req.authname, comment, new_values,
                                           None)
            try:
                NotificationSystem(self.env).notify(event)
            except Exception as e:
                self.log.error("Failure sending notification on ticket batch "
                               "change: %s", exception_to_unicode(e))
                add_warning(req, tag_("The changes have been saved, but an "
                                      "error occurred while sending "
                                      "notifications: %(message)s",
                                      message=to_unicode(e)))

        req.redirect(req.href.roadmap())
예제 #4
0
def create_message_id(env, targetid, from_email, time, more=None):
    """Generate a predictable, but sufficiently unique message ID.

    In case you want to set the "Message ID" header, this convenience
    function will generate one by running a hash algorithm over a number
    of properties.

    :param env: the `Environment`
    :param targetid: a string that identifies the target, like
        `NotificationEvent.target`
    :param from_email: the email address that the message is sent from
    :param time: a Python `datetime`
    :param more: a string that contains additional information that
        makes this message unique
    """
    items = [env.project_url, targetid, to_utimestamp(time)]
    if more is not None:
        items.append(more.encode('ascii', 'ignore'))
    source = b'.'.join(
        item if isinstance(item, bytes) else str(item).encode('utf-8')
        for item in items)
    hash_type = NotificationSystem(env).message_id_hash
    try:
        h = hashlib.new(hash_type)
    except:
        raise ConfigurationError(
            _("Unknown hash type '%(type)s'", type=hash_type))
    h.update(source)
    host = from_email[from_email.find('@') + 1:]
    return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
예제 #5
0
 def setUp(self):
     self.env = EnvironmentStub(
         enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber])
     config = self.env.config
     config.set('notification', 'smtp_from', '*****@*****.**')
     config.set('notification', 'smtp_enabled', 'enabled')
     config.set('notification', 'smtp_always_cc', '*****@*****.**')
     config.set('notification', 'smtp_always_bcc', '*****@*****.**')
     config.set('notification', 'email_sender', 'TestEmailSender')
     self.sender = TestEmailSender(self.env)
     self.notsys = NotificationSystem(self.env)
     with self.env.db_transaction:
         self._add_session('foo', email='*****@*****.**')
         self._add_session('bar',
                           email='*****@*****.**',
                           name=u"Bäŕ's name")
    def apply_action_side_effects(self, req, ticket, action):
        """Add a cross-reference comment to the other ticket"""
        # TODO: This needs a lot more error checking.
        id = 'action_%s_xref' % action
        ticketnum = req.args.get(id).strip('#')
        actions = self.get_configurable_workflow().actions
        author = req.authname

        # Add a comment to the "remote" ticket to indicate this ticket is
        # related to it.
        format_string = actions[action].get(
            'xref', "Ticket %s is related "
            "to this ticket")
        comment = format_string % ('#%s' % ticket.id)
        # FIXME: we need a cnum to avoid messing up
        xticket = model.Ticket(self.env, ticketnum)
        # FIXME: We _assume_ we have sufficient permissions to comment on the
        # other ticket.
        now = datetime.now(utc)
        xticket.save_changes(author, comment, now)

        # Send notification on the other ticket
        event = TicketChangeEvent('changed', xticket, now, author)
        try:
            NotificationSystem(self.env).notify(event)
        except Exception, e:
            self.log.exception(
                "Failure sending notification on change to "
                "ticket #%s: %s", ticketnum, e)
예제 #7
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)
예제 #8
0
    def _do_send(self, transport, event, message, cc_addrs, bcc_addrs):
        config = self.config['notification']
        smtp_from = config.get('smtp_from')
        smtp_from_name = config.get('smtp_from_name') or self.env.project_name
        smtp_reply_to = config.get('smtp_replyto')

        headers = dict()
        headers['X-Mailer'] = 'Trac %s, by Edgewall Software' % __version__
        headers['X-Trac-Version'] = __version__
        headers['X-Trac-Project'] = self.env.project_name
        headers['X-URL'] = self.env.project_url
        headers['X-Trac-Realm'] = event.realm
        headers['Precedence'] = 'bulk'
        headers['Auto-Submitted'] = 'auto-generated'
        targetid = get_target_id(event.target)
        rootid = create_message_id(self.env,
                                   targetid,
                                   smtp_from,
                                   None,
                                   more=event.realm)
        if event.category == 'created':
            headers['Message-ID'] = rootid
        else:
            headers['Message-ID'] = create_message_id(self.env,
                                                      targetid,
                                                      smtp_from,
                                                      event.time,
                                                      more=event.realm)
            headers['In-Reply-To'] = rootid
            headers['References'] = rootid
        headers['Date'] = formatdate()
        headers['From'] = (smtp_from_name, smtp_from) \
                          if smtp_from_name else smtp_from
        headers['To'] = 'undisclosed-recipients: ;'
        if cc_addrs:
            headers['Cc'] = ', '.join(cc_addrs)
        if bcc_addrs:
            headers['Bcc'] = ', '.join(bcc_addrs)
        headers['Reply-To'] = smtp_reply_to

        for k, v in headers.iteritems():
            set_header(message, k, v, self._charset)
        for decorator in self.decorators:
            decorator.decorate_message(event, message, self._charset)

        from_name, from_addr = parseaddr(str(message['From']))
        to_addrs = set()
        for name in ('To', 'Cc', 'Bcc'):
            values = map(str, message.get_all(name, ()))
            to_addrs.update(addr for name, addr in getaddresses(values)
                            if addr)
        del message['Bcc']
        NotificationSystem(self.env).send_email(from_addr, list(to_addrs),
                                                message.as_string())
예제 #9
0
 def _notify(self, ticket, date, author, comment):
     """Send a ticket update notification."""
     if not self.notify:
         return
     event = TicketChangeEvent('changed', ticket, date, author, comment)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error(
             "Failure sending notification on change to "
             "ticket #%s: %s", ticket.id, exception_to_unicode(e))
예제 #10
0
파일: batch.py 프로젝트: jacdevos/trac
    def _save_ticket_changes(self, req, selected_tickets, new_values, comment,
                             action):
        """Save changes to tickets."""
        valid = True
        for manipulator in self.ticket_manipulators:
            if hasattr(manipulator, 'validate_comment'):
                for message in manipulator.validate_comment(req, comment):
                    valid = False
                    add_warning(req, tag_("The ticket %(field)s is invalid: "
                                          "%(message)s",
                                          field=tag.strong(_('comment')),
                                          message=message))

        tickets = []
        for id_ in selected_tickets:
            t = Ticket(self.env, id_)
            values = self._get_updated_ticket_values(req, t, new_values)
            for ctlr in self._get_action_controllers(req, t, action):
                values.update(ctlr.get_ticket_changes(req, t, action))
            t.populate(values)
            for manipulator in self.ticket_manipulators:
                for field, message in manipulator.validate_ticket(req, t):
                    valid = False
                    if field:
                        add_warning(req, tag_("The ticket field %(field)s is "
                                              "invalid: %(message)s",
                                              field=tag.strong(field),
                                              message=message))
                    else:
                        add_warning(req, message)
            tickets.append(t)

        if not valid:
            return

        when = datetime_now(utc)
        with self.env.db_transaction:
            for t in tickets:
                t.save_changes(req.authname, comment, when=when)
                for ctlr in self._get_action_controllers(req, t, action):
                    ctlr.apply_action_side_effects(req, t, action)

        event = BatchTicketChangeEvent(selected_tickets, when,
                                       req.authname, comment, new_values,
                                       action)
        try:
            NotificationSystem(self.env).notify(event)
        except Exception as e:
            self.log.error("Failure sending notification on ticket batch"
                           "change: %s", exception_to_unicode(e))
            add_warning(req,
                        tag_("The changes have been saved, but an error "
                             "occurred while sending notifications: "
                             "%(message)s", message=to_unicode(e)))
예제 #11
0
 def setUp(self):
     self.env = EnvironmentStub(enable=['trac.*', TestEmailSender,
                                        TestFormatter, TestSubscriber,
                                        TestEmailAddressResolver])
     self.config = config = self.env.config
     config.set('notification', 'smtp_from', '*****@*****.**')
     config.set('notification', 'smtp_enabled', 'enabled')
     config.set('notification', 'smtp_always_cc', '*****@*****.**')
     config.set('notification', 'smtp_always_bcc', '*****@*****.**')
     config.set('notification', 'email_sender', 'TestEmailSender')
     config.set('notification', 'email_address_resolvers',
                'SessionEmailResolver,TestEmailAddressResolver')
     self.sender = TestEmailSender(self.env)
     self.notsys = NotificationSystem(self.env)
     with self.env.db_transaction:
         self._add_session('foo', email='*****@*****.**')
         self._add_session('bar', email='*****@*****.**',
                           name="Bäŕ's name")
         self._add_session('baz', name='Baz')
         self._add_session('qux', tz='UTC')
         self._add_session('corge', email='corge-mail')
예제 #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.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)
예제 #13
0
 def send_notification(self, ticket, author):
     if TicketNotifyEmail:
         tn = TicketNotifyEmail(self.env)
         tn.notify(ticket, newticket=False, modtime=ticket['changetime'])
     else:
         event = TicketChangeEvent('changed', ticket, ticket['changetime'],
                                   author)
         try:
             NotificationSystem(self.env).notify(event)
         except Exception as e:
             self.log.error(
                 "Failure sending notification on change to "
                 "ticket #%s: %s", ticket.id, exception_to_unicode(e))
예제 #14
0
 def __init__(self, env):
     self.env = env
     addrfmt = EMAIL_LOOKALIKE_PATTERN
     self.notify_sys = NotificationSystem(env)
     admit_domains = self.notify_sys.admit_domains_list
     if admit_domains:
         localfmt, domainfmt = addrfmt.split('@')
         domains = '|'.join(re.escape(x) for x in admit_domains)
         addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains)
     self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt)
     self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt)
     self.ignore_domains = set(x.lower()
                               for x in self.notify_sys.ignore_domains_list)
     self.users = self.env.get_known_users(as_dict=True)
예제 #15
0
    def _do_send(self, id, ticket, author, origin, description):
        ticket = Ticket(self.env, ticket)
        try:
            # We send reminder only for open tickets
            if ticket['status'] != 'closed':
                reminder = self._format_reminder_text(ticket, id, author, origin, description)

                #tn = TicketReminderNotifyEmail(self.env, reminder)
                #tn.notify(ticket)
                event = TicketReminderEvent(ticket, ticket['time'], author, reminder)
                tn = NotificationSystem(self.env).notify(event)
        except Exception, e:
            self.env.log.error("Failure sending reminder notification for ticket #%s: %s", ticket.id, exception_to_unicode(e))
            print "Failure sending reminder notification for ticket #%s: %s" % (ticket.id, exception_to_unicode(e))
예제 #16
0
 def _notify_attachment(self, attachment, category, time):
     resource = attachment.resource.parent
     if resource.realm != 'ticket':
         return
     ticket = Ticket(self.env, resource.id)
     event = TicketChangeEvent(category, ticket, time, ticket['reporter'],
                               attachment=attachment)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error("Failure sending notification when adding "
                        "attachment %s to ticket #%s: %s",
                        attachment.filename, ticket.id,
                        exception_to_unicode(e))
예제 #17
0
파일: mail.py 프로젝트: pkdevbox/trac
 def setUp(self):
     self.env = EnvironmentStub(enable=['trac.*', TestEmailSender,
                                        TestFormatter, TestSubscriber])
     config = self.env.config
     config.set('notification', 'smtp_from', '*****@*****.**')
     config.set('notification', 'smtp_enabled', 'enabled')
     config.set('notification', 'smtp_always_cc', '*****@*****.**')
     config.set('notification', 'smtp_always_bcc', '*****@*****.**')
     config.set('notification', 'email_sender', 'TestEmailSender')
     self.sender = TestEmailSender(self.env)
     self.notsys = NotificationSystem(self.env)
     with self.env.db_transaction:
         self._add_session('foo', email='*****@*****.**')
         self._add_session('bar', email='*****@*****.**',
                           name=u"Bäŕ's name")
예제 #18
0
파일: mail.py 프로젝트: zxfly/trac
def create_message_id(env, targetid, from_email, time, more=None):
    """Generate a predictable, but sufficiently unique message ID."""
    items = [env.project_url.encode('utf-8'), targetid, to_utimestamp(time)]
    if more is not None:
        items.append(more.encode('ascii', 'ignore'))
    source = '.'.join(str(item) for item in items)
    hash_type = NotificationSystem(env).message_id_hash
    try:
        h = hashlib.new(hash_type)
    except:
        raise ConfigurationError(
            _("Unknown hash type '%(type)s'", type=hash_type))
    h.update(source)
    host = from_email[from_email.find('@') + 1:]
    return '<%03d.%s@%s>' % (len(source), h.hexdigest(), host)
예제 #19
0
 def __init__(self, env):
     self.env = env
     addrfmt = EMAIL_LOOKALIKE_PATTERN
     self.notify_sys = NotificationSystem(env)
     admit_domains = self.notify_sys.admit_domains_list
     if admit_domains:
         localfmt, domainfmt = addrfmt.split('@')
         domains = [domainfmt]
         domains.extend(re.escape(x) for x in admit_domains)
         addrfmt = r'%s@(?:%s)' % (localfmt, '|'.join(domains))
     self.shortaddr_re = re.compile(r'<?(%s)>?$' % addrfmt, re.IGNORECASE)
     self.longaddr_re = re.compile(r'(.*)\s+<\s*(%s)\s*>$' % addrfmt,
                                   re.IGNORECASE)
     self.ignore_domains = set(x.lower()
                               for x in self.notify_sys.ignore_domains_list)
예제 #20
0
def get_from_author(env, event):
    """Get the author name and email from a given `event`.

    The `event` parameter should be of the type `NotificationEvent`.
    If you only have the username of a Trac user, you should instead
    use the `RecipientMatcher` to find the user's details.

    The method returns a tuple that contains the name and email address
    of the user. For example: `('developer', '*****@*****.**')`.
    This tuple can be parsed by `set_header()`.
    """
    if event.author and NotificationSystem(env).smtp_from_author:
        matcher = RecipientMatcher(env)
        from_ = matcher.match_from_author(event.author)
        if from_:
            return from_
예제 #21
0
파일: prefs.py 프로젝트: rwbaumg/trac
    def expand_macro(self, formatter, name, content):
        content = content.strip() if content else ''
        name_filter = content.strip('*')
        items = {}
        for subscriber in NotificationSystem(self.env).subscribers:
            name = subscriber.__class__.__name__
            if not name_filter or name.startswith(name_filter):
                items[name] = subscriber.description()

        return tag.div(class_='trac-subscriberlist')(tag.table(class_='wiki')(
            tag.thead(tag.tr(tag.th(_("Subscriber")),
                             tag.th(_("Description")))),
            tag.tbody(
                tag.tr(tag.td(tag.code(name)),
                       tag.td(items[name]),
                       class_='odd' if idx % 2 else 'even')
                for idx, name in enumerate(sorted(items)))))
예제 #22
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)
예제 #23
0
 def _save_ticket_changes(self, req, selected_tickets, new_values, comment,
                          action):
     """Save all of the changes to tickets."""
     when = datetime.now(utc)
     list_fields = self._get_list_fields()
     with self.env.db_transaction as db:
         for id in selected_tickets:
             t = Ticket(self.env, int(id))
             _values = new_values.copy()
             for field in list_fields:
                 if field in new_values:
                     old = t[field] if field in t else ''
                     new = new_values[field]
                     mode = req.args.get('batchmod_value_' + field +
                                         '_mode')
                     new2 = req.args.get(
                         'batchmod_value_' + field + '_secondary', '')
                     _values[field] = self._change_list(
                         old, new, new2, mode)
             controllers = list(self._get_action_controllers(
                 req, t, action))
             for controller in controllers:
                 _values.update(
                     controller.get_ticket_changes(req, t, action))
             t.populate(_values)
             t.save_changes(req.authname, comment, when=when)
             for controller in controllers:
                 controller.apply_action_side_effects(req, t, action)
     event = BatchTicketChangeEvent(selected_tickets, when, req.authname,
                                    comment, new_values, action)
     try:
         NotificationSystem(self.env).notify(event)
     except Exception as e:
         self.log.error(
             "Failure sending notification on ticket batch"
             "change: %s", exception_to_unicode(e))
         add_warning(
             req,
             tag_(
                 "The changes have been saved, but an "
                 "error occurred while sending "
                 "notifications: %(message)s",
                 message=to_unicode(e)))
예제 #24
0
    def __init__(self, env):
        self.env = env
        addrfmt = EMAIL_LOOKALIKE_PATTERN
        notify_sys = NotificationSystem(env)
        admit_domains = notify_sys.admit_domains_list
        if admit_domains:
            localfmt, domainfmt = addrfmt.split('@')
            domains = '|'.join(re.escape(x) for x in admit_domains)
            addrfmt = r'%s@(?:(?:%s)|%s)' % (localfmt, domainfmt, domains)
        self.shortaddr_re = re.compile(r'\s*(%s)\s*$' % addrfmt)
        self.longaddr_re = re.compile(r'^\s*(.*)\s+<\s*(%s)\s*>\s*$' % addrfmt)
        self.ignore_domains = [x.lower()
                               for x in notify_sys.ignore_domains_list]

        # Get the name and email addresses of all known users
        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
예제 #25
0
 def default_subscriptions(self):
     klass = self.__class__.__name__
     return NotificationSystem(self.env).default_subscriptions(klass)
예제 #26
0
파일: mail.py 프로젝트: zxfly/trac
    def distribute(self, transport, recipients, event):
        if transport != 'email':
            return
        if not self.config.getbool('notification', 'smtp_enabled'):
            self.log.debug("%s skipped because smtp_enabled set to false",
                           self.__class__.__name__)
            return

        formats = {}
        for f in self.formatters:
            for style, realm in f.get_supported_styles(transport):
                if realm == event.realm:
                    formats[style] = f
        if not formats:
            self.log.error("%s No formats found for %s %s",
                           self.__class__.__name__, transport, event.realm)
            return
        self.log.debug(
            "%s has found the following formats capable of "
            "handling '%s' of '%s': %s", self.__class__.__name__, transport,
            event.realm, ', '.join(formats))

        matcher = RecipientMatcher(self.env)
        notify_sys = NotificationSystem(self.env)
        always_cc = set(notify_sys.smtp_always_cc_list)
        addresses = {}
        for sid, auth, addr, fmt in recipients:
            if fmt not in formats:
                self.log.debug("%s format %s not available for %s %s",
                               self.__class__.__name__, fmt, transport,
                               event.realm)
                continue

            if sid and not addr:
                for resolver in self.resolvers:
                    addr = resolver.get_address_for_session(sid, auth) or None
                    if addr:
                        self.log.debug(
                            "%s found the address '%s' for '%s [%s]' via %s",
                            self.__class__.__name__, addr, sid, auth,
                            resolver.__class__.__name__)
                        break
            if sid and auth and not addr:
                addr = sid
            if notify_sys.smtp_default_domain and \
                    not notify_sys.use_short_addr and \
                    addr and matcher.nodomaddr_re.match(addr):
                addr = '%s@%s' % (addr, notify_sys.smtp_default_domain)
            if not addr:
                self.log.debug(
                    "%s was unable to find an address for "
                    "'%s [%s]'", self.__class__.__name__, sid, auth)
            elif matcher.is_email(addr) or \
                    notify_sys.use_short_addr and \
                    matcher.nodomaddr_re.match(addr):
                addresses.setdefault(fmt, set()).add(addr)
                if sid and auth and sid in always_cc:
                    always_cc.discard(sid)
                    always_cc.add(addr)
                elif notify_sys.use_public_cc:
                    always_cc.add(addr)
            else:
                self.log.debug(
                    "%s was unable to use an address '%s' for '%s "
                    "[%s]'", self.__class__.__name__, addr, sid, auth)

        outputs = {}
        failed = []
        for fmt, formatter in formats.iteritems():
            if fmt not in addresses and fmt != 'text/plain':
                continue
            try:
                outputs[fmt] = formatter.format(transport, fmt, event)
            except Exception as e:
                self.log.warning(
                    '%s caught exception while '
                    'formatting %s to %s for %s: %s%s',
                    self.__class__.__name__, event.realm, fmt, transport,
                    formatter.__class__, exception_to_unicode(e,
                                                              traceback=True))
                failed.append(fmt)

        # Fallback to text/plain when formatter is broken
        if failed and 'text/plain' in outputs:
            for fmt in failed:
                addresses.setdefault('text/plain', set()) \
                         .update(addresses.pop(fmt, ()))

        for fmt, addrs in addresses.iteritems():
            self.log.debug("%s is sending event as '%s' to: %s",
                           self.__class__.__name__, fmt, ', '.join(addrs))
            message = self._create_message(fmt, outputs)
            if message:
                addrs = set(addrs)
                cc_addrs = sorted(addrs & always_cc)
                bcc_addrs = sorted(addrs - always_cc)
                self._do_send(transport, event, message, cc_addrs, bcc_addrs)
            else:
                self.log.warning("%s cannot send event '%s' as '%s': %s",
                                 self.__class__.__name__, event.realm, fmt,
                                 ', '.join(addrs))
예제 #27
0
파일: mail.py 프로젝트: zxfly/trac
def get_from_author(env, event):
    if event.author and NotificationSystem(env).smtp_from_author:
        matcher = RecipientMatcher(env)
        from_ = matcher.match_from_author(event.author)
        if from_:
            return from_
예제 #28
0
파일: mail.py 프로젝트: starworldx/trac
class EmailDistributorTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(enable=['trac.*', TestEmailSender,
                                           TestFormatter, TestSubscriber,
                                           TestEmailAddressResolver])
        self.config = config = self.env.config
        config.set('notification', 'smtp_from', '*****@*****.**')
        config.set('notification', 'smtp_enabled', 'enabled')
        config.set('notification', 'smtp_always_cc', '*****@*****.**')
        config.set('notification', 'smtp_always_bcc', '*****@*****.**')
        config.set('notification', 'email_sender', 'TestEmailSender')
        config.set('notification', 'email_address_resolvers',
                   'SessionEmailResolver,TestEmailAddressResolver')
        self.sender = TestEmailSender(self.env)
        self.notsys = NotificationSystem(self.env)
        with self.env.db_transaction:
            self._add_session('foo', email='*****@*****.**')
            self._add_session('bar', email='*****@*****.**',
                              name=u"Bäŕ's name")
            self._add_session('baz', name='Baz')
            self._add_session('qux', tz='UTC')
            self._add_session('corge', email='corge-mail')

    def tearDown(self):
        self.env.reset_db()

    def _notify_event(self, text, category='created', time=None, author=None):
        self.sender.history[:] = ()
        event = TestNotificationEvent('test', category, TestModel(text),
                                      time or datetime_now(utc), author=author)
        self.notsys.notify(event)

    def _add_session(self, sid, values=None, **attrs):
        session = DetachedSession(self.env, sid)
        if values is not None:
            attrs.update(values)
        for name, value in attrs.iteritems():
            session[name] = value
        session.save()

    def _add_subscription(self, **kwargs):
        subscription = {'sid': None, 'authenticated': 1, 'distributor': 'email',
                        'format': 'text/plain', 'adverb': 'always',
                        'class': 'TestSubscriber'}
        subscription.update(kwargs)
        Subscription.add(self.env, subscription)

    def test_smtp_disabled(self):
        self.env.config.set('notification', 'smtp_enabled', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
        self._notify_event('blah')
        self.assertEqual([], self.sender.history)

    def _assert_mail(self, message, content_type, body):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual([content_type],
                         [p.get_content_type() for p in payload])
        self.assertEqual([body], [p.get_payload() for p in payload])

    def _assert_alternative_mail(self, message, body_plain, body_html):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual(['multipart/alternative'],
                         [p.get_content_type() for p in payload])
        alternative = list(payload[0].get_payload())
        self.assertEqual(['text/plain', 'text/html'],
                         [p.get_content_type() for p in alternative])
        self.assertEqual([body_plain, body_html],
                         [p.get_payload() for p in alternative])

    def test_plain(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
            self._add_subscription(sid='baz')
            self._add_subscription(sid='qux')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual({'*****@*****.**', '*****@*****.**',
                          '*****@*****.**', '*****@*****.**',
                          '*****@*****.**', '*****@*****.**'},
                         set(recipients))
        self._assert_mail(message, 'text/plain', 'blah')

    def test_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual({'*****@*****.**', '*****@*****.**'},
                                 set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')

    def test_plain_and_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual(
                    {'*****@*****.**', '*****@*****.**', '*****@*****.**'},
                    set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')
            if '*****@*****.**' in recipients:
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')

    def test_formats_in_session_and_tracini(self):
        self.config.set('notification', 'smtp_always_cc', 'bar,quux')
        self.config.set('notification', 'smtp_always_bcc', '')
        self.config.set('notification', 'default_format.email', 'text/html')
        with self.env.db_transaction:
            for user in ('foo', 'bar', 'baz', 'qux', 'quux'):
                self._add_session(user, email='*****@*****.**' % user)
            self._add_subscription(sid='foo', format='text/plain')
            # bar - no subscriptions
            self._add_session('bar',
                              {'notification.format.email': 'text/plain'})
            self._add_subscription(sid='baz', format='text/plain')
            self._add_session('baz',
                              {'notification.format.email': 'text/html'})
            self._add_subscription(sid='qux', format='text/html')
            self._add_session('qux',
                              {'notification.format.email': 'text/plain'})
            # quux - no subscriptions and no preferred format in session
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            self.assertEqual('*****@*****.**', from_addr)
            recipients = sorted(recipients)
            if '*****@*****.**' in recipients:
                self.assertEqual(['*****@*****.**', '*****@*****.**',
                                  '*****@*****.**'], recipients)
                self._assert_mail(message, 'text/plain', 'blah')
            if '*****@*****.**' in recipients:
                self.assertEqual(['*****@*****.**', '*****@*****.**'],
                                 recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')

    def test_broken_plain_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(['*****@*****.**'], recipients)
        self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>')

    def test_broken_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
            self._add_subscription(sid='bar', format='text/plain')
        self._notify_event('raise-text-html')

        # fallback to text/plain
        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual({'*****@*****.**', '*****@*****.**',
                          '*****@*****.**', '*****@*****.**'},
                         set(recipients))
        self._assert_mail(message, 'text/plain', 'raise-text-html')

    def test_broken_plain_and_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain raise-text-html')

        history = self.sender.history
        self.assertEqual([], history)

    def test_username_in_always_cc(self):
        self.env.config.set('notification', 'smtp_always_cc',
                            'foo, [email protected]')
        self.env.config.set('notification', 'smtp_always_bcc',
                            'bar, foo, [email protected]')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual({'*****@*****.**', '*****@*****.**',
                          '*****@*****.**', '*****@*****.**'},
                         set(recipients))
        self.assertEqual('[email protected], [email protected]', message['Cc'])
        self.assertIsNone(message['Bcc'])
        self._assert_mail(message, 'text/plain', 'blah')

    def test_from_author_disabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

    def test_from_author_enabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'enabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>',
                         message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author='foo')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('*****@*****.**', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

    def test_ignore_domains(self):
        config = self.env.config
        config.set('notification', 'smtp_always_cc',
                   '[email protected], [email protected]')
        config.set('notification', 'smtp_always_bcc',
                   '[email protected], [email protected]')
        config.set('notification', 'ignore_domains',
                   'example.org, example.com')

        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
            self._add_subscription(sid='baz')
            self._add_subscription(sid='qux')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(set(('*****@*****.**', '*****@*****.**',
                              '*****@*****.**', '*****@*****.**')),
                         set(recipients))

    def _test_without_domain(self, use_short_addr='disabled',
                             smtp_default_domain=''):
        config = self.env.config
        config.set('notification', 'use_short_addr', use_short_addr)
        config.set('notification', 'smtp_default_domain', smtp_default_domain)
        config.set('notification', 'smtp_from', 'from-trac')
        config.set('notification', 'smtp_always_cc', 'qux, [email protected]')
        config.set('notification', 'smtp_always_bcc', '[email protected], bcc2')
        config.set('notification', 'email_address_resolvers',
                   'SessionEmailResolver')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='baz')
            self._add_subscription(sid='corge')
        self._notify_event('blah')
        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        return history

    def _assert_equal_sets(self, expected, actual):
        expected = set(expected)
        actual = set(actual)
        if expected != actual:
            self.fail('%r != %r' % ((expected - actual, actual - expected)))

    def _cclist(self, cc):
        return _fixup_cc_list(cc).split(', ')

    def test_use_short_addr(self):
        history = self._test_without_domain(use_short_addr='enabled')
        from_addr, recipients, message = history[0]
        self.assertEqual('from-trac', from_addr)
        self.assertEqual('"My Project" <from-trac>', message['From'])
        self._assert_equal_sets(['qux', '*****@*****.**', '*****@*****.**',
                                 'bcc2', '*****@*****.**', 'baz',
                                 'corge-mail'], recipients)
        self._assert_equal_sets(['qux', '*****@*****.**'],
                                self._cclist(message['Cc']))

    def test_smtp_default_domain(self):
        history = self._test_without_domain(smtp_default_domain='example.com')
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>',
                         message['From'])
        self._assert_equal_sets(['*****@*****.**', '*****@*****.**',
                                 '*****@*****.**', '*****@*****.**',
                                 '*****@*****.**', '*****@*****.**',
                                 '*****@*****.**'], recipients)
        self._assert_equal_sets(['*****@*****.**', '*****@*****.**'],
                                self._cclist(message['Cc']))

    def test_username_is_email(self):
        config = self.env.config
        config.set('notification', 'email_address_resolvers',
                   'SessionEmailResolver')
        with self.env.db_transaction:
            self._add_session(sid='*****@*****.**')
            self._add_session(sid='*****@*****.**',
                              email='*****@*****.**')
            self._add_subscription(sid='*****@*****.**')
            self._add_subscription(sid='*****@*****.**')
            self._add_subscription(sid='*****@*****.**')  # no session
        self._notify_event('blah')
        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual({'*****@*****.**', '*****@*****.**',
                          '*****@*****.**', '*****@*****.**',
                          '*****@*****.**'}, set(recipients))
        self._assert_equal_sets(['*****@*****.**'],
                                self._cclist(message['Cc']))
예제 #29
0
파일: mail.py 프로젝트: pkdevbox/trac
class EmailDistributorTestCase(unittest.TestCase):

    def setUp(self):
        self.env = EnvironmentStub(enable=['trac.*', TestEmailSender,
                                           TestFormatter, TestSubscriber])
        config = self.env.config
        config.set('notification', 'smtp_from', '*****@*****.**')
        config.set('notification', 'smtp_enabled', 'enabled')
        config.set('notification', 'smtp_always_cc', '*****@*****.**')
        config.set('notification', 'smtp_always_bcc', '*****@*****.**')
        config.set('notification', 'email_sender', 'TestEmailSender')
        self.sender = TestEmailSender(self.env)
        self.notsys = NotificationSystem(self.env)
        with self.env.db_transaction:
            self._add_session('foo', email='*****@*****.**')
            self._add_session('bar', email='*****@*****.**',
                              name=u"Bäŕ's name")

    def tearDown(self):
        self.env.reset_db()

    def _notify_event(self, text, category='created', time=None, author=None):
        self.sender.history[:] = ()
        event = TestNotificationEvent('test', category, TestModel(text),
                                      time or datetime.now(utc), author=author)
        self.notsys.notify(event)

    def _add_session(self, sid, **attrs):
        session = DetachedSession(self.env, sid)
        for name, value in attrs.iteritems():
            session[name] = value
        session.save()

    def _add_subscription(self, **kwargs):
        subscription = {'sid': None, 'authenticated': 1, 'distributor': 'email',
                        'format': 'text/plain', 'adverb': 'always',
                        'class': 'TestSubscriber'}
        subscription.update(kwargs)
        Subscription.add(self.env, subscription)

    def test_smtp_disabled(self):
        self.env.config.set('notification', 'smtp_enabled', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
        self._notify_event('blah')
        self.assertEqual([], self.sender.history)

    def _assert_mail(self, message, content_type, body):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual([content_type],
                         [p.get_content_type() for p in payload])
        self.assertEqual([body], [p.get_payload() for p in payload])

    def _assert_alternative_mail(self, message, body_plain, body_html):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual(['multipart/alternative'],
                         [p.get_content_type() for p in payload])
        alternative = list(payload[0].get_payload())
        self.assertEqual(['text/plain', 'text/html'],
                         [p.get_content_type() for p in alternative])
        self.assertEqual([body_plain, body_html],
                         [p.get_payload() for p in alternative])

    def test_plain(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(set(('*****@*****.**', '*****@*****.**',
                              '*****@*****.**', '*****@*****.**')),
                         set(recipients))
        self._assert_mail(message, 'text/plain', 'blah')

    def test_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual(set(('*****@*****.**', '*****@*****.**')),
                                 set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')

    def test_plain_and_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual(set(('*****@*****.**', '*****@*****.**',
                                      '*****@*****.**')),
                                 set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')
            if '*****@*****.**' in recipients:
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')

    def test_broken_plain_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(['*****@*****.**'], recipients)
        self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>')

    def test_broken_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
            self._add_subscription(sid='bar', format='text/plain')
        self._notify_event('raise-text-html')

        # fallback to text/plain
        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(set(('*****@*****.**', '*****@*****.**',
                              '*****@*****.**', '*****@*****.**')),
                         set(recipients))
        self._assert_mail(message, 'text/plain', 'raise-text-html')

    def test_broken_plain_and_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain raise-text-html')

        history = self.sender.history
        self.assertEqual([], history)

    def test_username_in_always_cc(self):
        self.env.config.set('notification', 'smtp_always_cc',
                            'foo, [email protected]')
        self.env.config.set('notification', 'smtp_always_bcc',
                            'bar, foo, [email protected]')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(set(('*****@*****.**', '*****@*****.**',
                              '*****@*****.**', '*****@*****.**')),
                         set(recipients))
        self.assertEqual('[email protected], [email protected]', message['Cc'])
        self.assertEqual(None, message['Bcc'])
        self._assert_mail(message, 'text/plain', 'blah')

    def test_from_author_disabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

    def test_from_author_enabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'enabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>',
                         message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author='foo')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('*****@*****.**', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))
예제 #30
0
파일: prefs.py 프로젝트: mugglecloud/trac
    def render_preference_panel(self, req, panel, path_info=None):
        if req.method == 'POST':
            action_arg = req.args.getfirst('action', '').split('_', 1)
            if len(action_arg) == 2:
                action, arg = action_arg
                handler = self.post_handlers.get(action)
                if handler:
                    handler(arg, req)
                    add_notice(req, _("Your preferences have been saved."))
            req.redirect(req.href.prefs('notification'))

        rules = {}
        subscribers = []
        formatters = {}
        selected_format = {}
        default_format = {}
        defaults = []

        for i in self.subscribers:
            description = i.description()
            if not description:
                continue
            if not req.session.authenticated and i.requires_authentication():
                continue
            subscribers.append({
                'class': i.__class__.__name__,
                'description': description
            })
            if hasattr(i, 'default_subscriptions'):
                defaults.extend(i.default_subscriptions())
        desc_map = dict((s['class'], s['description']) for s in subscribers)

        ns = NotificationSystem(self.env)
        for t in self._iter_transports():
            rules[t] = []
            formatters[t] = self._get_supported_styles(t)
            selected_format[t] = req.session.get('notification.format.%s' % t)
            default_format[t] = ns.get_default_format(t)
            for r in self._iter_rules(req, t):
                description = desc_map.get(r['class'])
                if description:
                    values = {'description': description}
                    values.update(
                        (key, r[key])
                        for key in ('id', 'adverb', 'class', 'priority'))
                    rules[t].append(values)

        default_rules = {}
        for r in sorted(defaults, key=itemgetter(3)):  # sort by priority
            klass, dist, format, priority, adverb = r
            default_rules.setdefault(dist, [])
            description = desc_map.get(klass)
            if description:
                default_rules[dist].append({
                    'adverb': adverb,
                    'description': description
                })

        data = {
            'rules': rules,
            'subscribers': subscribers,
            'formatters': formatters,
            'selected_format': selected_format,
            'default_format': default_format,
            'default_rules': default_rules,
            'adverbs': ('always', 'never'),
            'adverb_labels': {
                'always': _("Notify"),
                'never': _("Never notify")
            }
        }
        Chrome(self.env).add_jquery_ui(req)
        return 'prefs_notification.html', dict(data=data)
예제 #31
0
    def save_milestone(self, req, milestone):
        # Instead of raising one single error, check all the constraints
        # and let the user fix them by going back to edit mode and showing
        # the warnings
        warnings = []

        def warn(msg):
            add_warning(req, msg)
            warnings.append(msg)

        milestone.description = req.args.get('description', '')

        if 'due' in req.args:
            duedate = req.args.get('duedate')
            milestone.due = user_time(req, parse_date, duedate,
                                      hint='datetime') \
                            if duedate else None
        else:
            milestone.due = None

        # -- check completed date
        if 'completed' in req.args:
            completed = req.args.get('completeddate', '')
            completed = user_time(req, parse_date, completed,
                                  hint='datetime') if completed else None
            if completed and completed > datetime_now(utc):
                warn(_("Completion date may not be in the future"))
        else:
            completed = None
        milestone.completed = completed

        # -- check the name
        # If the name has changed, check that the milestone doesn't already
        # exist
        # FIXME: the whole .exists business needs to be clarified
        #        (#4130) and should behave like a WikiPage does in
        #        this respect.
        new_name = req.args.get('name')
        try:
            new_milestone = Milestone(self.env, new_name)
        except ResourceNotFound:
            milestone.name = new_name
        else:
            if new_milestone.name != milestone.name:
                if new_milestone.name:
                    warn(
                        _(
                            'Milestone "%(name)s" already exists, please '
                            'choose another name.',
                            name=new_milestone.name))
                else:
                    warn(_("You must provide a name for the milestone."))

        if warnings:
            return False

        # -- actually save changes
        if milestone.exists:
            milestone.update(author=req.authname)
            if completed and 'retarget' in req.args:
                comment = req.args.get('comment', '')
                retarget_to = req.args.get('target') or None
                retargeted_tickets = \
                    milestone.move_tickets(retarget_to, req.authname,
                                           comment, exclude_closed=True)
                add_notice(
                    req,
                    _(
                        'The open tickets associated with '
                        'milestone "%(name)s" have been retargeted '
                        'to milestone "%(retarget)s".',
                        name=milestone.name,
                        retarget=retarget_to))
                new_values = {'milestone': retarget_to}
                comment = comment or \
                          _("Open tickets retargeted after milestone closed")
                event = BatchTicketChangeEvent(retargeted_tickets, None,
                                               req.authname, comment,
                                               new_values, None)
                try:
                    NotificationSystem(self.env).notify(event)
                except Exception as e:
                    self.log.error(
                        "Failure sending notification on ticket "
                        "batch change: %s", exception_to_unicode(e))
                    add_warning(
                        req,
                        tag_(
                            "The changes have been saved, but "
                            "an error occurred while sending "
                            "notifications: %(message)s",
                            message=to_unicode(e)))
            add_notice(req, _("Your changes have been saved."))
        else:
            milestone.insert()
            add_notice(
                req,
                _('The milestone "%(name)s" has been added.',
                  name=milestone.name))

        return True
예제 #32
0
class EmailDistributorTestCase(unittest.TestCase):
    def setUp(self):
        self.env = EnvironmentStub(
            enable=['trac.*', TestEmailSender, TestFormatter, TestSubscriber])
        config = self.env.config
        config.set('notification', 'smtp_from', '*****@*****.**')
        config.set('notification', 'smtp_enabled', 'enabled')
        config.set('notification', 'smtp_always_cc', '*****@*****.**')
        config.set('notification', 'smtp_always_bcc', '*****@*****.**')
        config.set('notification', 'email_sender', 'TestEmailSender')
        self.sender = TestEmailSender(self.env)
        self.notsys = NotificationSystem(self.env)
        with self.env.db_transaction:
            self._add_session('foo', email='*****@*****.**')
            self._add_session('bar',
                              email='*****@*****.**',
                              name=u"Bäŕ's name")

    def tearDown(self):
        self.env.reset_db()

    def _notify_event(self, text, category='created', time=None, author=None):
        self.sender.history[:] = ()
        event = TestNotificationEvent('test',
                                      category,
                                      TestModel(text),
                                      time or datetime.now(utc),
                                      author=author)
        self.notsys.notify(event)

    def _add_session(self, sid, **attrs):
        session = DetachedSession(self.env, sid)
        for name, value in attrs.iteritems():
            session[name] = value
        session.save()

    def _add_subscription(self, **kwargs):
        subscription = {
            'sid': None,
            'authenticated': 1,
            'distributor': 'email',
            'format': 'text/plain',
            'adverb': 'always',
            'class': 'TestSubscriber'
        }
        subscription.update(kwargs)
        Subscription.add(self.env, subscription)

    def test_smtp_disabled(self):
        self.env.config.set('notification', 'smtp_enabled', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
        self._notify_event('blah')
        self.assertEqual([], self.sender.history)

    def _assert_mail(self, message, content_type, body):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual([content_type],
                         [p.get_content_type() for p in payload])
        self.assertEqual([body], [p.get_payload() for p in payload])

    def _assert_alternative_mail(self, message, body_plain, body_html):
        self.assertNotIn('Bcc', message)
        self.assertEqual('multipart/related', message.get_content_type())
        payload = list(message.get_payload())
        self.assertEqual(['multipart/alternative'],
                         [p.get_content_type() for p in payload])
        alternative = list(payload[0].get_payload())
        self.assertEqual(['text/plain', 'text/html'],
                         [p.get_content_type() for p in alternative])
        self.assertEqual([body_plain, body_html],
                         [p.get_payload() for p in alternative])

    def test_plain(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(
            set(('*****@*****.**', '*****@*****.**', '*****@*****.**',
                 '*****@*****.**')), set(recipients))
        self._assert_mail(message, 'text/plain', 'blah')

    def test_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')
            if '*****@*****.**' in recipients:
                self.assertEqual('*****@*****.**', from_addr)
                self.assertEqual(set(('*****@*****.**', '*****@*****.**')),
                                 set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')

    def test_plain_and_html(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(2, len(history))
        for from_addr, recipients, message in history:
            if '*****@*****.**' in recipients:
                self.assertEqual(
                    set(('*****@*****.**', '*****@*****.**',
                         '*****@*****.**')), set(recipients))
                self._assert_mail(message, 'text/plain', 'blah')
            if '*****@*****.**' in recipients:
                self.assertEqual(['*****@*****.**'], recipients)
                self._assert_alternative_mail(message, 'blah', '<p>blah</p>')

    def test_broken_plain_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(['*****@*****.**'], recipients)
        self._assert_mail(message, 'text/html', '<p>raise-text-plain</p>')

    def test_broken_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/html')
            self._add_subscription(sid='bar', format='text/plain')
        self._notify_event('raise-text-html')

        # fallback to text/plain
        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(
            set(('*****@*****.**', '*****@*****.**', '*****@*****.**',
                 '*****@*****.**')), set(recipients))
        self._assert_mail(message, 'text/plain', 'raise-text-html')

    def test_broken_plain_and_html_formatter(self):
        with self.env.db_transaction:
            self._add_subscription(sid='foo', format='text/plain')
            self._add_subscription(sid='bar', format='text/html')
        self._notify_event('raise-text-plain raise-text-html')

        history = self.sender.history
        self.assertEqual([], history)

    def test_username_in_always_cc(self):
        self.env.config.set('notification', 'smtp_always_cc',
                            'foo, [email protected]')
        self.env.config.set('notification', 'smtp_always_bcc',
                            'bar, foo, [email protected]')
        self._notify_event('blah')

        history = self.sender.history
        self.assertNotEqual([], history)
        self.assertEqual(1, len(history))
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual(
            set(('*****@*****.**', '*****@*****.**', '*****@*****.**',
                 '*****@*****.**')), set(recipients))
        self.assertEqual('[email protected], [email protected]', message['Cc'])
        self.assertEqual(None, message['Bcc'])
        self._assert_mail(message, 'text/plain', 'blah')

    def test_from_author_disabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'disabled')
        with self.env.db_transaction:
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

    def test_from_author_enabled(self):
        self.env.config.set('notification', 'smtp_from_author', 'enabled')
        with self.env.db_transaction:
            self._add_subscription(sid='foo')
            self._add_subscription(sid='bar')

        self._notify_event('blah', author='bar')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"=?utf-8?b?QsOkxZUncyBuYW1l?=" <*****@*****.**>',
                         message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author='foo')
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('*****@*****.**', message['From'])
        self.assertEqual(1, len(history))

        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"My Project" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))

        self.env.config.set('notification', 'smtp_from_name', 'Trac')
        self._notify_event('blah', author=None)
        history = self.sender.history
        self.assertNotEqual([], history)
        from_addr, recipients, message = history[0]
        self.assertEqual('*****@*****.**', from_addr)
        self.assertEqual('"Trac" <*****@*****.**>', message['From'])
        self.assertEqual(1, len(history))