Ejemplo n.º 1
0
    def pad_message(self, parts, to_domain):
        ''' Pad the Message with random characters. '''

        # determine how large the bundled messages are
        original_size = 0
        for part in parts:
            original_size += len(part.as_string())

        # then pad the message with random characters
        current_size = original_size
        target_size = options.bundled_message_max_size()
        while current_size < target_size:
            # using urandom because it's less likely to lock up
            # a messaging program can't afford the potential locks up of /dev/random
            with open('/dev/urandom', 'rb') as rnd:
                rnd_bytes = rnd.read(target_size - current_size)
                if len(rnd_bytes) > target_size - current_size:
                    rnd_bytes = rnd_bytes[:target_size - current_size]
            part = MIMEApplication(b64encode(rnd_bytes),
                                   mime_constants.ALTERNATIVE_SUB_TYPE,
                                   encode_base64)
            current_size += len(part.as_string())
            parts.append(part)

        padded_bytes = target_size - original_size
        if padded_bytes < 0:
            self.log_message('original size: {}'.format(original_size))
            self.log_message('target size: {}'.format(target_size))
        self.log_message('padded with {} random bytes'.format(padded_bytes))

        return parts
Ejemplo n.º 2
0
def main():
    """Submit the program."""
    usage = 'Usage: %prog [options] assignment@class FILES-AND-DIRECTORIES'
    parser = OptionParser(usage=usage)
    parser.add_option('-a', '--action', help='The action to trigger')
    parser.add_option('-e', '--email', help='The email to send the result to')
    options, args = parser.parse_args()

    if len(args) < 2:
        parser.error('Invalid number of arguments')

    # Get project, and destination user
    match = RE_TURNIN.match(args[0])
    if not match:
        parser.error('Invalid assignment@class: {0}'.format(args[0]))
    project, user = match.groups()

    # Call turnin, afterall this is a wrapper
    ret = os.system('turnin {}'.format(' '.join(args)))
    if ret:  # Exit it turnin failed
        return ret

    from_email = '{0}@cs.ucsb.edu'.format(os.getlogin())
    to_email = '{0}@cs.ucsb.edu'.format(user)
    data = json.dumps({
        'action': options.action,
        'base_path': os.getcwd(),
        'project': project,
        'user': os.getlogin(),
        'user_email': options.email
    })

    # Build message
    msg = MIMEApplication(data)
    msg['From'] = from_email
    msg['To'] = to_email
    msg['Subject'] = 'auto_grade email'

    # Notify user of turnin
    sys.stdout.write('Sending submission notification...')
    sys.stdout.flush()
    smtp = smtplib.SMTP()

    for hostname in SMTP_SERVERS:
        try:
            smtp.connect(hostname)
            sys.stdout.write(' connected to {0}...'.format(hostname))
            sys.stdout.flush()
            break
        except socket.error:
            pass
    else:
        print 'could not connect, notify TA'
        return 1

    smtp.sendmail(from_email, to_email, msg.as_string())
    smtp.quit()
    print ' turnin complete!'
    return 0
Ejemplo n.º 3
0
def sendKeyMessage(gpg,server_key_fp,config,recipient):
	listaddress = "{0}@{1}".format(config.listname,config.listdomain)
	server_key_pub = str(gpg.export_keys(server_key_fp))
	# create new message
	msg_parent = MIMEApplication(server_key_pub,'pgp-keys',email.encoders.encode_noop)
	msg_parent.add_header('Content-Disposition', 'attachment; filename="server.asc"')
	
	SendMessage(gpg,server_key_fp,config,recipient,"Public key of "+str(listaddress),msg_parent.as_string())
Ejemplo n.º 4
0
    def email(self, data):
        msg = MIMEApplication(data)
        del msg['Content-Type']
        msg['Content-Disposition'] = 'attachment; filename="smime.p7m"'
        msg['Content-Type'] = 'application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"'

        data = msg.as_string()
        return data
Ejemplo n.º 5
0
    def email(self, data, oaep):
        prefix = ['x-', ''][oaep]
        msg = MIMEApplication(data)
        del msg['Content-Type']
        msg['Content-Disposition'] = 'attachment; filename="smime.p7m"'
        msg['Content-Type'] = 'application/%spkcs7-mime; smime-type=enveloped-data; name="smime.p7m"' % prefix

        data = msg.as_string()
        return data
Ejemplo n.º 6
0
class HTTPRequest(object):
    '''
    Builds an HTTP GET or POST request that ORACC's server understands to send
    and retrieve ATF data.
    '''
    def __init__(self, url, method, **kwargs):
        self.method = method
        self.url = url
        if method == 'POST':
            if 'command' in kwargs.keys():
                self.create_request_message(kwargs['command'], kwargs['keys'],
                                            kwargs['atf_basename'],
                                            kwargs['atf_text'])
            else:
                self.create_response_message(kwargs['keys'])

    def create_request_message(self, command, keys, atf_basename, atf_text):
        """
        Send attachment to server containing ATF file and necessary data to
        run given command (validate, lemmatise, etc).
        """
        self.mtompkg = MIMEMultipart('related')
        self.set_multipart_params()
        self.set_soap_envelope(command=command,
                               keys=keys,
                               atf_basename=atf_basename,
                               atf_text=atf_text)
        self.rootpkg = MIMEApplication(self.envelope, 'xop+xml',
                                       encode_7or8bit)
        self.set_multipart_payload()
        self.document = MIMEBase('*', '*')
        self.set_document_payload(atf_basename, atf_text)

        # The headers can't be created until the body is finished since they
        # need it to populate the Content-Length header
        self.set_multipart_headers()

    def create_response_message(self, keys):
        """
        Asks the server for the response request.zip attachment containing
        validated/lemmatised/etc ATF file.
        """
        self.set_soap_envelope(keys=keys)
        self.mtompkg = MIMEApplication(self.envelope, 'soap+xml',
                                       encode_7or8bit)
        self.mtompkg.add_header("Host", self.url)

    def set_response_headers(self):
        del (self.mtompkg['Content-Transfer-Encoding'])
        headers = ['Host', 'Content-Length', 'Connection']
        values = [self.url, '623', 'close']
        for header, value in zip(headers, values):
            self.mtompkg.add_header(header, value)

    def set_response_params(self):
        self.mtompkg.set_param('charset', 'utf-8')

    def set_multipart_payload(self):
        self.set_payload_headers()
        self.set_payload_params()
        self.mtompkg.attach(self.rootpkg)

    def set_document_payload(self, atf_basename, atf_text):
        self.set_document_headers()

        mem_data = StringIO()
        mem_zip = zipfile.ZipFile(mem_data, "w", zipfile.ZIP_DEFLATED, False)
        mem_zip.writestr("00atf/" + atf_basename, atf_text)
        mem_zip.close()
        mem_data.seek(0)

        self.document.set_payload(mem_data.getvalue())
        self.mtompkg.attach(self.document)

    def set_document_headers(self):
        headers = ['Content-ID', 'Content-Transfer-Encoding']
        values = ['<request_zip>', 'binary']
        for header, value in zip(headers, values):
            self.document.add_header(header, value)

    def set_payload_params(self):
        params = ['charset', 'type']
        values = ['utf-8', 'application/soap+xml']
        for param, value in zip(params, values):
            self.rootpkg.set_param(param, value)

    def set_payload_headers(self):
        # Content-Transfer-Encoding is set to 7bit by default
        del (self.rootpkg['Content-Transfer-Encoding'])
        headers = ['Content-ID', 'Content-Transfer-Encoding']
        values = ['<SOAP-ENV:Envelope>', 'binary']
        for header, value in zip(headers, values):
            self.rootpkg.add_header(header, value)

    def set_multipart_headers(self):
        headers = ['Host', 'Content-Length', 'Connection']
        values = [self.url, str(len(self.get_body())), 'close']
        for header, value in zip(headers, values):
            self.mtompkg.add_header(header, value)

    def set_multipart_params(self):
        params = ['charset', 'type', 'start', 'start-info', 'boundary']
        values = [
            'utf-8', 'application/xop+xml', '<SOAP-ENV:Envelope>',
            'application/soap+xml', '==========boundary========'
        ]
        for param, value in zip(params, values):
            self.mtompkg.set_param(param, value)

    def set_soap_envelope(self, **kwargs):
        """
        Format SOAP envelope to be attached in HTTP POST request.
        """
        # The number of keys in the SOAP envelope depends on the command and
        # the message type (Request/Response)
        keys = ''  # osc-data keys

        # Only Request messages have data, but the template has a reference to
        # it in both cases.
        data = ''

        if 'command' in kwargs.keys():
            keys += '<osc-data:key>{}</osc-data:key>'.format(kwargs['command'])
            message_type = 'Request'
            data += """<osc-data:data>
                            <osc-data:item xmime5:contentType="*/*">
                                <xop:Include href="cid:request_zip"/>
                            </osc-data:item>
                        </osc-data:data>"""
        else:
            message_type = 'Response'

        for key in kwargs['keys']:
            keys += '<osc-data:key>{}</osc-data:key>'.format(key)

        envelope = """<?xml version="1.0" encoding="UTF-8"?>
            <SOAP-ENV:Envelope
                xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
                xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xop="http://www.w3.org/2004/08/xop/include"
                xmlns:xmime5="http://www.w3.org/2005/05/xmlmime"
                xmlns:osc-data="http://oracc.org/wsdl/ows.xsd"
                xmlns:osc-meth="http://oracc.org/wsdl/ows.wsdl">
                <SOAP-ENV:Body>
                    <osc-meth:{type}>
                        <osc-data:keys>
                            {keys}
                        </osc-data:keys>
                        {data}
                    </osc-meth:{type}>
                </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>""".format(type=message_type,
                                           keys=keys,
                                           data=data)
        self.envelope = envelope

    def get_soap_envelope(self):
        return self.envelope

    def get_headers(self):
        """
        Return dict with message headers - ready to use by requests module.
        """
        return dict(self.mtompkg.items())

    def get_body(self):
        """
        Returns the body of the HTTP POST request containing the soap envelope
        including the encoded compressed ATF.
        """
        # Header and body on a HTTP SOAP/MTOM message is separated by \n\n
        body = self.mtompkg.as_string().split('\n\n', 1)[1]

        # This returns the randomly created boundary string, which separates
        # the different elements in the request (document, envelope and
        # payload)
        bound = self.mtompkg.get_boundary()

        # There's two different kinds of SOAP HTTP POST requests handled, due
        # to the communication with the server being asynchronous:
        # * One that contains an ATF attachment submitted for validation/lemm
        # * One that contains only a request ID to ask for the validated/lemm'd
        #   ATF.
        # The boundary will be empty in the second case.
        if bound is not None:
            # Encoded zip files start with "\n\nPK" and end with a new line
            # follwed by the ending boundary (in the form "--random_boundary--"
            attachment = body.split("\n\nPK")[1].split("\n--" + str(bound))[0]
            # Some line endings are \n and some are \r\n. The email module
            # automatically replaces some of them causing the server not to
            # understand line endings correctly. For this, we need to first
            # make sure all of the line endings are \n, then convert them all
            # to \r\n (to avoid having '\r\n' converted to '\r\r\n'.
            # Note the encoding of the zipped ATF might coincidentally contain
            # a '\n' substring. We have to avoid replacing those, otherwise
            # the zipped ATF will be corrupt and imposible to open by the
            # server.
            body = body.replace(attachment, '<attachment>')
            body = body.replace('\r\n', '\n').replace('\n', '\r\n')
            body = body.replace("<attachment>", attachment)
        else:
            body = body.replace('\r\n', '\n').replace('\n', '\r\n')

        return body
Ejemplo n.º 7
0
# coding=utf-8
import xlwt
from io import BytesIO
from email.mime.application import MIMEApplication

excel = xlwt.Workbook('utf8')
bio = BytesIO()
excel.save(bio)

excel_data = bio.getvalue()

excel_msg = MIMEApplication(excel_data, _subtype='vnd.ms-excel')
excel_msg.add_header('Content-Disposition', 'attachment; filename="%s"' % 'test_filename')
print excel_msg.as_string()
Ejemplo n.º 8
0
# coding=utf-8
import xlwt
from io import BytesIO
from email.mime.application import MIMEApplication

excel = xlwt.Workbook('utf8')
bio = BytesIO()
excel.save(bio)

excel_data = bio.getvalue()

excel_msg = MIMEApplication(excel_data, _subtype='vnd.ms-excel')
excel_msg.add_header('Content-Disposition',
                     'attachment; filename="%s"' % 'test_filename')
print excel_msg.as_string()
Ejemplo n.º 9
0
class DisclaimerTestCase(TestCase):

    """ Build up different disclaimer scenarios and check if they react right.
    """

    def setUp(self):

        """ A basic setup with a simple disclaimer, no directory servers,
            a basic rule and a basic action
        """

        self.test_text = "Testmail"
        self.test_address = "*****@*****.**"

        self.disclaimer = models.Disclaimer()

        self.disclaimer.name = "Test"
        self.disclaimer.text = "Test-Disclaimer"

        self.disclaimer.save()

        self.rule = models.Rule()
        self.rule.save()

        self.action = models.Action()

        self.action.action = constants.ACTION_ACTION_ADD
        self.action.disclaimer = self.disclaimer
        self.action.rule = self.rule
        self.action.position = 0

        self.action.save()

        requirement = models.Requirement()

        requirement.rule = self.rule
        requirement.action = constants.REQ_ACTION_ACCEPT

        requirement.save()

    def tool_get_helper(self):

        """ Return a configured milter helper

        :return: A Milter helper
        """

        configuration = build_configuration()

        helper = MilterHelper(configuration)

        # The requirement should basically be enabled

        self.assertTrue(
            helper.enabled,
            "Helper wasn't enabled after initialization."
        )

        return helper

    def tool_add_bodies(self, helper, mail):

        if mail.is_multipart():

            for payload in mail.get_payload():

                self.tool_add_bodies(helper, payload)

        else:

            helper.body(mail.as_string(), {})

    def tool_run_real_test(self, header=None, make_mail=True):

        """ Runs the test using the milter helper and returns the
            action dictionary of eob

        :return: the action dictionary of eob()
        """

        helper = self.tool_get_helper()

        helper.connect("", "", "1.1.1.1", "", {})
        helper.mail_from(self.test_address, {})
        helper.rcpt(self.test_address, {})

        if header is not None:

            for key in header.iterkeys():

                helper.header(key, header[key], {})

            helper.eoh({})

        else:

            # Add at least one header for proper mail processing

            helper.header("From", "nobody", {})
            helper.eoh({})

        if make_mail:

            self.test_mail = MIMEText(self.test_text, "plain", "UTF-8")

        helper.body(self.test_mail.as_string(), {})

        return helper.eob({})

    def tool_make_returned_mail(self, returned):

        headers = []

        for key in self.test_mail.keys():

            # Skip deleted headers

            if "delete_header" in returned and key in returned["delete_header"]:

                continue

            value = self.test_mail[key]

            # Change headers

            if "change_header" in returned and key in returned["change_header"]:

                value = returned["change_header"][key]

            headers.append("%s: %s" % (key, value))

        if "add_header" in returned:

            for key in returned["add_header"]:

                headers.append("%s: %s" % (key, returned["add_header"][key]))

        return email.message_from_string(
            "%s\n\n%s" % (
                "\n".join(headers),
                returned["repl_body"]
            )
        )

    def test_basic_add(self):

        """ The disclaimer will simply be added to the testmail.
            Check the returned body
        """

        returned = self.tool_run_real_test()

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_disabled_action(self):

        """ If we disable the only action, we should get an empty action
            dictionary back
        """

        self.action.enabled = False

        self.action.save()

        returned = self.tool_run_real_test()

        self.assertIsNone(
            returned,
            "We got an action dictionary back! %s" % returned
        )

    def test_disabled_secondaction(self):

        """ If we add an disabled action to a working set, it should just
            work fine.
        """

        action2 = models.Action()
        action2.rule = self.rule
        action2.position = 0
        action2.enabled = False
        action2.disclaimer = self.disclaimer

        action2.save()

        self.action.position = 1
        self.action.save()

        returned = self.tool_run_real_test()

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_basic_replace(self):

        """ Set the action to replace the disclaimer and not add it. Check the
            returned body
        """

        self.test_text = "Testmail #DISCLAIMER#"

        self.action.action = constants.ACTION_ACTION_REPLACETAG
        self.action.action_parameters = "#DISCLAIMER#"

        self.action.save()

        returned = self.tool_run_real_test()

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "Testmail %s" % self.disclaimer.text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1]
            )
        )

    def test_replacements_add(self):

        """ Test the replacement feature of sender, recipient and header
            replacement [add mode]
        """

        self.disclaimer.text = "{sender}|{recipient}|{header[\"Test\"]}"
        self.disclaimer.text_use_template = True

        self.disclaimer.save()

        returned = self.tool_run_real_test({"Test": "Test"})

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s|%s|Test" % (
                self.test_text,
                self.test_address,
                self.test_address
            ),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_replacements_replace(self):

        """ Test the replacement feature of sender, recipient and header
            replacement [add mode]
        """

        self.test_text = "Testmail #DISCLAIMER#"

        self.action.action = constants.ACTION_ACTION_REPLACETAG
        self.action.action_parameters = "#DISCLAIMER#"

        self.action.save()

        self.disclaimer.text = "{sender}|{recipient}|{header[\"Test\"]}"
        self.disclaimer.text_use_template = True

        self.disclaimer.save()

        returned = self.tool_run_real_test({"Test": "Test"})

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "Testmail %s|%s|Test" % (
                self.test_address,
                self.test_address
            ),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_multiple_headers(self):

        """ Test a mail with multiple headers
        """

        returned = self.tool_run_real_test(header={
            "X-TEST1": "TEST",
            "X-TEST2": "TEST"
        })

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_multipart(self):

        """ Test a multipart mail
        """

        test_text = "TestPlain"
        test_html = "<p>TestHTML</p>"

        text_part = MIMEText(test_text, "plain", "UTF-8")
        html_part = MIMEText(test_html, "html", "UTF-8")

        self.test_mail = MIMEMultipart("alternative")
        self.test_mail.attach(text_part)
        self.test_mail.attach(html_part)

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0]
            )[1],
            "%s\n%s" % (test_text, self.disclaimer.text),
            "Text-Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            "<html><body>\n%s\n<p>%s</p>\n</body></html>\n" % (
                test_html,
                self.disclaimer.text
            ),
            "HTML-Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )

    def test_html(self):

        """ Test a HTML mail with an HTML disclaimer
        """

        test_html = "<p>TestHTML</p>"

        self.test_mail = MIMEText(test_html, "html", "UTF-8")

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail
            )[1],
            "<html><body>\n%s\n<p>%s</p>\n</body></html>\n" % (
                test_html,
                self.disclaimer.text
            ),
            "HTML-Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail
                )[1]
            )
        )

    def test_unresolvable_tag(self):

        """ Test an unresolvable tag with template_fail=True inside a
            disclaimer, assuming that the mail isn't modified.
        """

        self.disclaimer.text = "{FAIL}"
        self.disclaimer.template_fail = True
        self.disclaimer.save()

        returned = self.tool_run_real_test()
        returned_mail = self.tool_make_returned_mail(returned)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.test_mail
            )[1],
            milter_helper.MilterHelper.decode_mail(
                returned_mail
            )[1],
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail
                )[1],
            )
        )

    def test_unresolvable_tag_nofail(self):

        """ Test an unresolvable tag with template_fail=False inside a
            disclaimer, assuming that the mail is modified and the
            unresolvable tag is removed.
        """

        self.disclaimer.text = "{FAIL}"
        self.disclaimer.template_fail = False
        self.disclaimer.save()

        returned = self.tool_run_real_test()
        returned_mail = self.tool_make_returned_mail(returned)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail
            )[1],
            "%s\n" % self.test_text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail
                )[1],
            )
        )

    def test_unresolvable_subtag(self):

        """ Test an unresolvable subtag with template_fail=True
            inside a disclaimer assuming it will return an unmodified mail
        """

        self.disclaimer.text = "{FAIL[\"FAIL\"]}"
        self.disclaimer.template_fail = True
        self.disclaimer.save()

        returned = self.tool_run_real_test()
        returned_mail = self.tool_make_returned_mail(returned)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.test_mail
            )[1],
            milter_helper.MilterHelper.decode_mail(
                returned_mail
            )[1],
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail
                )[1],
            )
        )

    def test_quoted_printable(self):

        """ Use a "quoted printable"-encoded mail as a test mail
        """

        self.test_mail = MIMEText(self.test_text, "plain", "iso-8859-1")

        returned = self.tool_run_real_test(make_mail=False)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_html_disclaimer(self):

        """ Test a HTML (not "use text") disclaimer
        """

        self.disclaimer.html_use_text = False
        self.disclaimer.html = "<b>TEST-DISCLAIMER</b>"

        self.disclaimer.save()

        self.test_mail = MIMEText("<p>%s</p>" % self.test_text, "html")

        returned = self.tool_run_real_test(make_mail=False)

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "<html><body>\n<p>%s</p>\n%s\n</body></html>\n" % (
                self.test_text,
                self.disclaimer.html
            ),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_multiple_rules(self):

        """ Add multiple rules assuming that only the first rule will be
        carried out.
        """

        rule2 = models.Rule()
        rule2.position = 1
        rule2.save()

        action2 = models.Action()

        action2.action = constants.ACTION_ACTION_ADD
        action2.disclaimer = self.disclaimer
        action2.rule = rule2
        action2.position = 0

        action2.save()

        requirement2 = models.Requirement()

        requirement2.rule = rule2
        requirement2.action = constants.REQ_ACTION_ACCEPT

        requirement2.save()

        returned = self.tool_run_real_test()

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_multiple_rules_continue(self):

        """ Use the continue flag for multiple rules assuming that all rules
        will be carried out.
        """

        rule2 = models.Rule()
        rule2.position = 1
        rule2.save()

        self.rule.continue_rules = True
        self.rule.save()

        disclaimer2 = models.Disclaimer()

        disclaimer2.name = "Test2"
        disclaimer2.text = "Test-Disclaimer2"

        disclaimer2.save()

        action2 = models.Action()

        action2.action = constants.ACTION_ACTION_ADD
        action2.disclaimer = disclaimer2
        action2.rule = rule2
        action2.position = 0

        action2.save()

        requirement2 = models.Requirement()

        requirement2.rule = rule2
        requirement2.action = constants.REQ_ACTION_ACCEPT

        requirement2.save()

        returned = self.tool_run_real_test()

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                self.tool_make_returned_mail(returned)
            )[1],
            "%s\n%s\nTest-Disclaimer2" % (self.test_text, self.disclaimer.text),
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    self.tool_make_returned_mail(returned)
                )[1],
            )
        )

    def test_disclaimer_add_part(self):

        """ Test an action, that adds a mime part with the disclaimer
        """

        self.action.action = constants.ACTION_ACTION_ADDPART

        self.action.save()

        returned = self.tool_run_real_test()

        returned_mail = self.tool_make_returned_mail(returned)

        # The first part shouldn't have been modified, but added as an
        # rfc-822 attachment

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0].get_payload()[0]
            )[1],
            self.test_text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        # The second part should be our disclaimer

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            self.disclaimer.text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )

    def test_disclaimer_add_part_html(self):

        """ Test an action, that adds a mime part with a HTML disclaimer
        based on the text disclaimer
        """

        self.action.action = constants.ACTION_ACTION_ADDPART

        self.action.save()

        test_html = "<p>TestHTML</p>"

        self.test_mail = MIMEText(test_html, "html", "UTF-8")

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        # The first part shouldn't have been modified, but added as an
        # rfc-822 attachment

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0].get_payload()[0]
            )[1],
            test_html,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        # The second part should be our disclaimer

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            self.disclaimer.text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )

    def test_disclaimer_add_part_html_disclaimer(self):

        """ Test an action, that adds a mime part with a pure HTML disclaimer
        """

        self.action.action = constants.ACTION_ACTION_ADDPART

        self.action.save()

        test_html = "<p>TestHTML</p>"

        self.test_mail = MIMEText(test_html, "html", "UTF-8")

        self.disclaimer.html_use_text = False
        self.disclaimer.html = "<b>TEST-DISCLAIMER</b>"

        self.disclaimer.save()

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        # The first part shouldn't have been modified, but added as an
        # rfc-822 attachment

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0].get_payload()[0]
            )[1],
            test_html,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        # The second part should be our disclaimer

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            self.disclaimer.html,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )

    def test_disclaimer_add_part_fallback(self):

        """ Test an action, that adds a mime part with the disclaimer,
        but uses a not-supported original mime-part (not text/plain or
        /html). This should give a text-disclaimer back.
        """

        self.action.action = constants.ACTION_ACTION_ADDPART

        self.action.save()

        self.disclaimer.html_use_text = False
        self.disclaimer.html = "<b>TEST-DISCLAIMER</b>"

        self.disclaimer.save()

        self.test_mail = MIMEApplication("TEST")

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        # The first part shouldn't have been modified, but added as an
        # rfc-822 attachment

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0].get_payload()[0]
            )[1],
            "TEST",
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        # The second part should be the text-part of the disclaimer

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            self.disclaimer.text,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )

    def test_disclaimer_add_part_fallback_html(self):

        """ Test an action, that adds a mime part with the disclaimer,
        but uses a not-supported original mime-part (not text/plain or
        /html). Use the html fallback to generate a html-disclaimer.
        """

        self.action.action = constants.ACTION_ACTION_ADDPART

        self.action.save()

        self.disclaimer.html_use_text = False
        self.disclaimer.html = "<b>TEST-DISCLAIMER</b>"

        self.disclaimer.use_html_fallback = True

        self.disclaimer.save()

        self.test_mail = MIMEApplication("TEST")

        returned = self.tool_run_real_test(make_mail=False)

        returned_mail = self.tool_make_returned_mail(returned)

        # The first part shouldn't have been modified, but added as an
        # rfc-822 attachment

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[0].get_payload()[0]
            )[1],
            "TEST",
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[0]
                )[1],
            )
        )

        # The second part should be the text-part of the disclaimer

        self.assertEqual(
            milter_helper.MilterHelper.decode_mail(
                returned_mail.get_payload()[1]
            )[1],
            self.disclaimer.html,
            "Body was unexpectedly modified to %s" % (
                milter_helper.MilterHelper.decode_mail(
                    returned_mail.get_payload()[1]
                )[1],
            )
        )
Ejemplo n.º 10
0
def main():
    """Submit the program."""
    usage = "Usage: %prog [options] assignment@class FILES-AND-DIRECTORIES"
    parser = OptionParser(usage=usage)
    parser.add_option("-a", "--action", help="The action to trigger")
    parser.add_option("-e", "--email", help="The email to send the result to")
    options, args = parser.parse_args()

    if len(args) < 2:
        parser.error("Invalid number of arguments")

    # Get project, and destination user
    match = RE_TURNIN.match(args[0])
    if not match:
        parser.error("Invalid assignment@class: {0}".format(args[0]))
    project, user = match.groups()

    # Call turnin, afterall this is a wrapper
    ret = os.system("turnin {}".format(" ".join(args)))
    if ret:  # Exit it turnin failed
        return ret

    from_email = "{0}@cs.ucsb.edu".format(os.getlogin())
    to_email = "{0}@cs.ucsb.edu".format(user)
    data = json.dumps(
        {
            "action": options.action,
            "base_path": os.getcwd(),
            "project": project,
            "user": os.getlogin(),
            "user_email": options.email,
        }
    )

    # Build message
    msg = MIMEApplication(data)
    msg["From"] = from_email
    msg["To"] = to_email
    msg["Subject"] = "auto_grade email"

    # Notify user of turnin
    sys.stdout.write("Sending submission notification...")
    sys.stdout.flush()
    smtp = smtplib.SMTP()

    for hostname in SMTP_SERVERS:
        try:
            smtp.connect(hostname)
            sys.stdout.write(" connected to {0}...".format(hostname))
            sys.stdout.flush()
            break
        except socket.error:
            pass
    else:
        print "could not connect, notify TA"
        return 1

    smtp.sendmail(from_email, to_email, msg.as_string())
    smtp.quit()
    print " turnin complete!"
    return 0
Ejemplo n.º 11
0
class HTTPRequest(object):
    '''
    Builds an HTTP GET or POST request that ORACC's server understands to send
    and retrieve ATF data.
    '''
    def __init__(self, url, method, **kwargs):
        self.method = method
        self.url = url
        if method == 'POST':
            if 'command' in kwargs.keys():
                self.create_request_message(kwargs['command'], kwargs['keys'],
                                            kwargs['atf_basename'],
                                            kwargs['atf_text'])
            else:
                self.create_response_message(kwargs['keys'])

    def create_request_message(self, command, keys, atf_basename, atf_text):
        """
        Send attachment to server containing ATF file and necessary data to
        run given command (validate, lemmatise, etc).
        """
        self.mtompkg = MIMEMultipart('related')
        self.set_multipart_params()
        self.set_soap_envelope(command=command,
                               keys=keys,
                               atf_basename=atf_basename,
                               atf_text=atf_text)
        self.rootpkg = MIMEApplication(self.envelope,
                                       'xop+xml',
                                       encode_7or8bit)
        self.set_multipart_payload()
        self.document = MIMEBase('*', '*')
        self.set_document_payload(atf_basename, atf_text)

        # The headers can't be created until the body is finished since they
        # need it to populate the Content-Length header
        self.set_multipart_headers()

    def create_response_message(self, keys):
        """
        Asks the server for the response request.zip attachment containing
        validated/lemmatised/etc ATF file.
        """
        self.set_soap_envelope(keys=keys)
        self.mtompkg = MIMEApplication(self.envelope,
                                       'soap+xml',
                                       encode_7or8bit)
        self.mtompkg.add_header("Host", self.url)

    def set_response_headers(self):
        del(self.mtompkg['Content-Transfer-Encoding'])
        headers = ['Host', 'Content-Length', 'Connection']
        values = [self.url, '623', 'close']
        for header, value in zip(headers, values):
            self.mtompkg.add_header(header, value)

    def set_response_params(self):
        self.mtompkg.set_param('charset', 'utf-8')

    def set_multipart_payload(self):
        self.set_payload_headers()
        self.set_payload_params()
        self.mtompkg.attach(self.rootpkg)

    def set_document_payload(self, atf_basename, atf_text):
        self.set_document_headers()

        mem_data = StringIO()
        mem_zip = zipfile.ZipFile(mem_data, "w", zipfile.ZIP_DEFLATED, False)
        mem_zip.writestr("00atf/"+atf_basename, atf_text)
        mem_zip.close()
        mem_data.seek(0)

        self.document.set_payload(mem_data.getvalue())
        self.mtompkg.attach(self.document)

    def set_document_headers(self):
        headers = ['Content-ID', 'Content-Transfer-Encoding']
        values = ['<request_zip>', 'binary']
        for header, value in zip(headers, values):
            self.document.add_header(header, value)

    def set_payload_params(self):
        params = ['charset', 'type']
        values = ['utf-8', 'application/soap+xml']
        for param, value in zip(params, values):
            self.rootpkg.set_param(param, value)

    def set_payload_headers(self):
        # Content-Transfer-Encoding is set to 7bit by default
        del(self.rootpkg['Content-Transfer-Encoding'])
        headers = ['Content-ID', 'Content-Transfer-Encoding']
        values = ['<SOAP-ENV:Envelope>', 'binary']
        for header, value in zip(headers, values):
            self.rootpkg.add_header(header, value)

    def set_multipart_headers(self):
        headers = ['Host', 'Content-Length', 'Connection']
        values = [self.url, str(len(self.get_body())), 'close']
        for header, value in zip(headers, values):
            self.mtompkg.add_header(header, value)

    def set_multipart_params(self):
        params = ['charset', 'type', 'start', 'start-info', 'boundary']
        values = ['utf-8', 'application/xop+xml', '<SOAP-ENV:Envelope>',
                  'application/soap+xml', '==========boundary========']
        for param, value in zip(params, values):
            self.mtompkg.set_param(param, value)

    def set_soap_envelope(self, **kwargs):
        """
        Format SOAP envelope to be attached in HTTP POST request.
        """
        # The number of keys in the SOAP envelope depends on the command and
        # the message type (Request/Response)
        keys = ''  # osc-data keys

        # Only Request messages have data, but the template has a reference to
        # it in both cases.
        data = ''

        if 'command' in kwargs.keys():
            keys += '<osc-data:key>{}</osc-data:key>'.format(kwargs['command'])
            message_type = 'Request'
            data += """<osc-data:data>
                            <osc-data:item xmime5:contentType="*/*">
                                <xop:Include href="cid:request_zip"/>
                            </osc-data:item>
                        </osc-data:data>"""
        else:
            message_type = 'Response'

        for key in kwargs['keys']:
            keys += '<osc-data:key>{}</osc-data:key>'.format(key)

        envelope = """<?xml version="1.0" encoding="UTF-8"?>
            <SOAP-ENV:Envelope
                xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
                xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                xmlns:xop="http://www.w3.org/2004/08/xop/include"
                xmlns:xmime5="http://www.w3.org/2005/05/xmlmime"
                xmlns:osc-data="http://oracc.org/wsdl/ows.xsd"
                xmlns:osc-meth="http://oracc.org/wsdl/ows.wsdl">
                <SOAP-ENV:Body>
                    <osc-meth:{type}>
                        <osc-data:keys>
                            {keys}
                        </osc-data:keys>
                        {data}
                    </osc-meth:{type}>
                </SOAP-ENV:Body>
            </SOAP-ENV:Envelope>""".format(type=message_type,
                                           keys=keys,
                                           data=data)
        self.envelope = envelope

    def get_soap_envelope(self):
        return self.envelope

    def get_headers(self):
        """
        Return dict with message headers - ready to use by requests module.
        """
        return dict(self.mtompkg.items())

    def get_body(self):
        """
        Returns the body of the HTTP POST request containing the soap envelope
        including the encoded compressed ATF.
        """
        # Header and body on a HTTP SOAP/MTOM message is separated by \n\n
        body = self.mtompkg.as_string().split('\n\n', 1)[1]

        # This returns the randomly created boundary string, which separates
        # the different elements in the request (document, envelope and
        # payload)
        bound = self.mtompkg.get_boundary()

        # There's two different kinds of SOAP HTTP POST requests handled, due
        # to the communication with the server being asynchronous:
        # * One that contains an ATF attachment submitted for validation/lemm
        # * One that contains only a request ID to ask for the validated/lemm'd
        #   ATF.
        # The boundary will be empty in the second case.
        if bound is not None:
            # Encoded zip files start with "\n\nPK" and end with a new line
            # follwed by the ending boundary (in the form "--random_boundary--"
            attachment = body.split("\n\nPK")[1].split("\n--" + str(bound))[0]
            # Some line endings are \n and some are \r\n. The email module
            # automatically replaces some of them causing the server not to
            # understand line endings correctly. For this, we need to first
            # make sure all of the line endings are \n, then convert them all
            # to \r\n (to avoid having '\r\n' converted to '\r\r\n'.
            # Note the encoding of the zipped ATF might coincidentally contain
            # a '\n' substring. We have to avoid replacing those, otherwise
            # the zipped ATF will be corrupt and imposible to open by the
            # server.
            body = body.replace(attachment, '<attachment>')
            body = body.replace('\r\n', '\n').replace('\n', '\r\n')
            body = body.replace("<attachment>", attachment)
        else:
            body = body.replace('\r\n', '\n').replace('\n', '\r\n')

        return body