예제 #1
0
    def __init__(self, node_id, title, destination_node_ids,
                 acquired_skill_ids, prerequisite_skill_ids, outline,
                 outline_is_finalized, exploration_id):
        """Initializes a StoryNode domain object.

        Args:
            node_id: str. The unique id for each node.
            title: str. The title of the story node.
            destination_node_ids: list(str). The list of destination node ids
                that this node points to in the story graph.
            acquired_skill_ids: list(str). The list of skill ids acquired by
                the user on completion of the node.
            prerequisite_skill_ids: list(str). The list of skill ids required
                before starting a node.
            outline: str. Free-form annotations that a lesson implementer
                can use to construct the exploration. It describes the basic
                theme or template of the story and is to be provided in html
                form.
            outline_is_finalized: bool. Whether the outline for the story
                node is finalized or not.
            exploration_id: str or None. The valid exploration id that fits the
                story node. It can be None initially, when the story creator
                has just created a story with the basic storyline (by providing
                outlines) without linking an exploration to any node.
        """
        self.id = node_id
        self.title = title
        self.destination_node_ids = destination_node_ids
        self.acquired_skill_ids = acquired_skill_ids
        self.prerequisite_skill_ids = prerequisite_skill_ids
        self.outline = html_cleaner.clean(outline)
        self.outline_is_finalized = outline_is_finalized
        self.exploration_id = exploration_id
예제 #2
0
    def update_content(self, content):
        """Updates the content of the blog post.

        Args:
            content: str. The new content of the blog post.
        """
        self.content = html_cleaner.clean(content)
예제 #3
0
    def update_html_data(self, new_html_data):
        """The new value for the html data field.

        Args:
            new_html_data: str. The new html data for the subtopic page.
        """
        self.html_data = html_cleaner.clean(new_html_data)
예제 #4
0
    def __init__(self,
                 blog_post_id,
                 author_id,
                 title,
                 content,
                 url_fragment,
                 tags,
                 thumbnail_filename=None,
                 last_updated=None,
                 published_on=None):
        """Constructs a BlogPost domain object.

        Args:
            blog_post_id: str. The unique ID of the blog post.
            author_id: str. The user ID of the author.
            title: str. The title of the blog post.
            content: str. The html content of the blog post.
            url_fragment: str. The url fragment for the blog post.
            tags: list(str). The list of tags for the blog post.
            thumbnail_filename: str|None. The thumbnail filename of blog post .
            last_updated: datetime.datetime. Date and time when the blog post
                was last updated.
            published_on: datetime.datetime. Date and time when the blog post is
                last published.
        """
        self.id = blog_post_id
        self.author_id = author_id
        self.title = title
        self.content = html_cleaner.clean(content)
        self.url_fragment = url_fragment
        self.tags = tags
        self.thumbnail_filename = thumbnail_filename
        self.last_updated = last_updated
        self.published_on = published_on
예제 #5
0
    def test_good_tags_allowed(self):
        test_data = [(
            '<a href="http://www.google.com">Hello</a>',
            '<a href="http://www.google.com">Hello</a>'
        ), (
            '<a href="http://www.google.com" target="_blank">Hello</a>',
            '<a href="http://www.google.com" target="_blank">Hello</a>'
        ), (
            '<a href="http://www.google.com" title="Hello">Hello</a>',
            '<a href="http://www.google.com" title="Hello">Hello</a>'
        ), (
            'Just some text 12345',
            'Just some text 12345'
        ), (
            '<code>Unfinished HTML',
            '<code>Unfinished HTML</code>',
        ), (
            '<br/>',
            '<br>'
        ), (
            'A big mix <div>Hello</div> Yes <span>No</span>',
            'A big mix <div>Hello</div> Yes <span>No</span>'
        )]

        for datum in test_data:
            self.assertEqual(
                html_cleaner.clean(datum[0]), datum[1],
                msg='\n\nOriginal text: %s' % datum[0])
예제 #6
0
    def __init__(
            self, question_id, question_content, misconception_ids,
            interaction_id, question_model_created_on=None,
            question_model_last_updated=None):
        """Constructs a Question Summary domain object.

        Args:
            question_id: str. The ID of the question.
            question_content: str. The static HTML of the question shown to
                the learner.
            misconception_ids: str. The misconception ids addressed in
                the question. This includes tagged misconceptions ids as well
                as inapplicable misconception ids in the question.
            interaction_id: str. The ID of the interaction.
            question_model_created_on: datetime.datetime. Date and time when
                the question model is created.
            question_model_last_updated: datetime.datetime. Date and time
                when the question model was last updated.
        """
        self.id = question_id
        self.question_content = html_cleaner.clean(question_content)
        self.misconception_ids = misconception_ids
        self.interaction_id = interaction_id
        self.created_on = question_model_created_on
        self.last_updated = question_model_last_updated
예제 #7
0
    def __init__(
            self, story_id, title, description, notes,
            story_contents, schema_version, language_code, version,
            created_on=None, last_updated=None):
        """Constructs a Story domain object.

        Args:
            story_id: str. The unique ID of the story.
            title: str. The title of the story.
            description: str. The high level description of the story.
            notes: str. A set of notes, that describe the characters,
                main storyline, and setting. To be provided in html form.
            story_contents: StoryContents. The StoryContents instance
                representing the contents (like nodes) that are part of the
                story.
            created_on: datetime.datetime. Date and time when the story is
                created.
            last_updated: datetime.datetime. Date and time when the
                story was last updated.
            schema_version: int. The schema version for the story nodes object.
            language_code: str. The ISO 639-1 code for the language this
                story is written in.
            version: int. The version of the story.
        """
        self.id = story_id
        self.title = title
        self.description = description
        self.notes = html_cleaner.clean(notes)
        self.story_contents = story_contents
        self.schema_version = schema_version
        self.language_code = language_code
        self.created_on = created_on
        self.last_updated = last_updated
        self.version = version
예제 #8
0
    def test_bad_tags_suppressed(self):
        test_data = [(
            '<incomplete-bad-tag>',
            ''
        ), (
            '<complete-bad-tag></complete-bad-tag>',
            ''
        ), (
            '<incomplete-bad-tag><div>OK tag</div>',
            '<div>OK tag</div>'
        ), (
            '<complete-bad-tag></complete-bad-tag><span>OK tag</span>',
            '<span>OK tag</span>'
        ), (
            '<bad-tag></bad-tag>Just some text 12345',
            'Just some text 12345'
        ), (
            '<script>alert(\'Here is some JS\');</script>',
            'alert(\'Here is some JS\');'
        ), (
            '<iframe src="https://oppiaserver.appspot.com"></iframe>',
            ''
        )]

        for datum in test_data:
            self.assertEqual(
                html_cleaner.clean(datum[0]), datum[1],
                '\n\nOriginal text: %s' % datum[0])
예제 #9
0
    def test_bad_tags_suppressed(self):
        TEST_DATA = [(
            '<incomplete-bad-tag>',
            ''
        ), (
            '<complete-bad-tag></complete-bad-tag>',
            ''
        ), (
            '<incomplete-bad-tag><div>OK tag</div>',
            '<div>OK tag</div>'
        ), (
            '<complete-bad-tag></complete-bad-tag><span>OK tag</span>',
            '<span>OK tag</span>'
        ), (
            '<bad-tag></bad-tag>Just some text 12345',
            'Just some text 12345'
        ), (
            '<script>alert(\'Here is some JS\');</script>',
            'alert(\'Here is some JS\');'
        ), (
            '<iframe src="https://oppiaserver.appspot.com"></iframe>',
            ''
        )]

        for datum in TEST_DATA:
            self.assertEqual(
                html_cleaner.clean(datum[0]), datum[1],
                '\n\nOriginal text: %s' % datum[0])
예제 #10
0
 def normalize(cls, raw):
     """Validates and normalizes a raw Python object."""
     try:
         assert isinstance(raw, basestring)
         return html_cleaner.clean(unicode(raw))
     except Exception as e:
         raise TypeError('Cannot convert to HTML string: %s. Error: %s' %
                         (raw, e))
예제 #11
0
 def normalize(cls, raw):
     """Validates and normalizes a raw Python object."""
     try:
         assert isinstance(raw, basestring)
         return html_cleaner.clean(unicode(raw))
     except Exception as e:
         raise TypeError('Cannot convert to HTML string: %s. Error: %s' %
                         (raw, e))
예제 #12
0
    def __init__(self, misconception_id, name, notes, feedback):
        """Initializes a Misconception domain object.

        Args:
            misconception_id: int. The unique id of each misconception.
            name: str. The name of the misconception.
            notes: str. General advice for creators about the
                misconception (including examples) and general notes. This
                should be an html string.
            feedback: str. This can auto-populate the feedback field
                when an answer group has been tagged with a misconception. This
                should be an html string.
        """
        self.id = misconception_id
        self.name = name
        self.notes = html_cleaner.clean(notes)
        self.feedback = html_cleaner.clean(feedback)
예제 #13
0
    def update_content(self, content: str) -> None:
        """Updates the content of the blog post.

        Args:
            content: str. The new content of the blog post.
        """
        self.content = html_cleaner.clean(
            content)  # type: ignore[no-untyped-call]
예제 #14
0
def _send_bulk_mail(
    recipient_ids, sender_id, intent, email_subject, email_html_body, sender_email, sender_name, instance_id=None
):
    """Sends an email to all given recipients.

    Args:
        recipient_ids: list(str). The user IDs of the email recipients.
        sender_id: str. The ID of the user sending the email.
        intent: str. The intent string, i.e. the purpose of the email.
        email_subject: str. The subject of the email.
        email_html_body: str. The body (message) of the email.
        sender_email: str. The sender's email address.
        sender_name: str. The name to be shown in the "sender" field of the
            email.
        instance_id: str or None. The ID of the BulkEmailModel entity instance.
    """
    _require_sender_id_is_valid(intent, sender_id)

    recipients_settings = user_services.get_users_settings(recipient_ids)
    recipient_emails = [user.email for user in recipients_settings]

    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            "Original email HTML body does not match cleaned HTML body:\n"
            "Original:\n%s\n\nCleaned:\n%s\n" % (email_html_body, cleaned_html_body)
        )
        return

    raw_plaintext_body = (
        cleaned_html_body.replace("<br/>", "\n")
        .replace("<br>", "\n")
        .replace("<li>", "<li>- ")
        .replace("</p><p>", "</p>\n<p>")
    )
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    def _send_bulk_mail_in_transaction(instance_id=None):
        sender_name_email = "%s <%s>" % (sender_name, sender_email)

        email_services.send_bulk_mail(
            sender_name_email, recipient_emails, email_subject, cleaned_plaintext_body, cleaned_html_body
        )

        if instance_id is None:
            instance_id = email_models.BulkEmailModel.get_new_id("")
        email_models.BulkEmailModel.create(
            instance_id,
            recipient_ids,
            sender_id,
            sender_name_email,
            intent,
            email_subject,
            cleaned_html_body,
            datetime.datetime.utcnow(),
        )

    return transaction_services.run_in_transaction(_send_bulk_mail_in_transaction, instance_id)
예제 #15
0
def _send_bulk_mail(recipient_ids,
                    sender_id,
                    intent,
                    email_subject,
                    email_html_body,
                    sender_email,
                    sender_name,
                    instance_id=None):
    """Sends an email to all given recipients.

    Args:
        recipient_ids: list(str). The user IDs of the email recipients.
        sender_id: str. The ID of the user sending the email.
        intent: str. The intent string, i.e. the purpose of the email.
        email_subject: str. The subject of the email.
        email_html_body: str. The body (message) of the email.
        sender_email: str. The sender's email address.
        sender_name: str. The name to be shown in the "sender" field of the
            email.
        instance_id: str or None. The ID of the BulkEmailModel entity instance.
    """
    _require_sender_id_is_valid(intent, sender_id)

    recipients_settings = user_services.get_users_settings(recipient_ids)
    recipient_emails = [user.email for user in recipients_settings]

    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>',
                              '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    def _send_bulk_mail_in_transaction(instance_id=None):
        """Sends the emails in bulk to the recipients."""
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_bulk_mail(sender_name_email, recipient_emails,
                                      email_subject, cleaned_plaintext_body,
                                      cleaned_html_body)

        if instance_id is None:
            instance_id = email_models.BulkEmailModel.get_new_id('')
        email_models.BulkEmailModel.create(instance_id, recipient_ids,
                                           sender_id, sender_name_email,
                                           intent, email_subject,
                                           cleaned_html_body,
                                           datetime.datetime.utcnow())

    transaction_services.run_in_transaction(_send_bulk_mail_in_transaction,
                                            instance_id)
예제 #16
0
    def __init__(self, difficulty, explanation):
        """Initializes a Rubric domain object.

        Args:
            difficulty: str. The question difficulty that this rubric addresses.
            explanation: str. The explanation for the corresponding difficulty.
        """
        self.difficulty = difficulty
        self.explanation = html_cleaner.clean(explanation)
예제 #17
0
def _send_email(recipient_id,
                sender_id,
                intent,
                email_subject,
                email_html_body,
                sender_email,
                bcc_admin=False,
                sender_name=None):
    """Sends an email to the given recipient.

    This function should be used for sending all user-facing emails.

    Raises an Exception if the sender_id is not appropriate for the given
    intent. Currently we support only system-generated emails and emails
    initiated by moderator actions.
    """
    if sender_name is None:
        sender_name = EMAIL_SENDER_NAME.value

    _require_sender_id_is_valid(intent, sender_id)

    recipient_email = user_services.get_email_from_user_id(recipient_id)
    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>',
                              '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    if email_models.SentEmailModel.check_duplicate_message(
            recipient_id, email_subject, cleaned_plaintext_body):
        log_new_error('Duplicate email:\n'
                      'Details:\n%s %s\n%s\n\n' %
                      (recipient_id, email_subject, cleaned_plaintext_body))
        return

    def _send_email_in_transaction():
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_mail(sender_name_email, recipient_email,
                                 email_subject, cleaned_plaintext_body,
                                 cleaned_html_body, bcc_admin)
        email_models.SentEmailModel.create(recipient_id, recipient_email,
                                           sender_id, sender_name_email,
                                           intent, email_subject,
                                           cleaned_html_body,
                                           datetime.datetime.utcnow())

    return transaction_services.run_in_transaction(_send_email_in_transaction)
예제 #18
0
    def __init__(self, misconception_id, name, notes, feedback,
                 must_be_addressed):
        """Initializes a Misconception domain object.

        Args:
            misconception_id: int. The unique id of each misconception.
            name: str. The name of the misconception.
            notes: str. General advice for creators about the
                misconception (including examples) and general notes. This
                should be an html string.
            feedback: str. This can auto-populate the feedback field
                when an answer group has been tagged with a misconception. This
                should be an html string.
            must_be_addressed: bool. Whether the misconception should
                necessarily be addressed in all questions linked to the skill.
        """
        self.id = misconception_id
        self.name = name
        self.notes = html_cleaner.clean(notes)
        self.feedback = html_cleaner.clean(feedback)
        self.must_be_addressed = must_be_addressed
예제 #19
0
    def __init__(self, explanation, worked_examples):
        """Constructs a SkillContents domain object.

        Args:
            explanation: str. An explanation on how to apply the skill.
            worked_examples: list(str). A list of worked examples for the skill.
                Each element should be an html string.
        """
        self.explanation = explanation
        self.worked_examples = [
            html_cleaner.clean(example) for example in worked_examples
        ]
    def test_oppia_custom_tags(self):
        TEST_DATA = [('<oppia-noninteractive-image filepath-with-value="1"/>',
                      '<oppia-noninteractive-image filepath-with-value="1">'
                      '</oppia-noninteractive-image>'),
                     ('<oppia-noninteractive-image filepath-with-value="1">'
                      '</oppia-noninteractive-image>',
                      '<oppia-noninteractive-image filepath-with-value="1">'
                      '</oppia-noninteractive-image>'),
                     ('<oppia-fake-tag></oppia-fake-tag>', '')]

        for datum in TEST_DATA:
            self.assertEqual(html_cleaner.clean(datum[0]), datum[1],
                             '\n\nOriginal text: %s' % datum[0])
    def test_good_tags_allowed(self):
        TEST_DATA = [('<a href="http://www.google.com">Hello</a>',
                      '<a href="http://www.google.com">Hello</a>'),
                     ('Just some text 12345', 'Just some text 12345'),
                     (
                         '<code>Unfinished HTML',
                         '<code>Unfinished HTML</code>',
                     ), ('<br/>', '<br>'),
                     ('A big mix <div>Hello</div> Yes <span>No</span>',
                      'A big mix <div>Hello</div> Yes <span>No</span>')]

        for datum in TEST_DATA:
            self.assertEqual(html_cleaner.clean(datum[0]), datum[1],
                             '\n\nOriginal text: %s' % datum[0])
예제 #22
0
def _send_email(
        recipient_id, sender_id, intent, email_subject, email_html_body,
        sender_email, bcc_admin=False, sender_name=None):
    """Sends an email to the given recipient.

    This function should be used for sending all user-facing emails.

    Raises an Exception if the sender_id is not appropriate for the given
    intent. Currently we support only system-generated emails and emails
    initiated by moderator actions.
    """
    if sender_name is None:
        sender_name = EMAIL_SENDER_NAME.value

    _require_sender_id_is_valid(intent, sender_id)

    recipient_email = user_services.get_email_from_user_id(recipient_id)
    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>', '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    if email_models.SentEmailModel.check_duplicate_message(
            recipient_id, email_subject, cleaned_plaintext_body):
        log_new_error(
            'Duplicate email:\n'
            'Details:\n%s %s\n%s\n\n' %
            (recipient_id, email_subject, cleaned_plaintext_body))
        return

    def _send_email_in_transaction():
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_mail(
            sender_name_email, recipient_email, email_subject,
            cleaned_plaintext_body, cleaned_html_body, bcc_admin)
        email_models.SentEmailModel.create(
            recipient_id, recipient_email, sender_id, sender_name_email, intent,
            email_subject, cleaned_html_body, datetime.datetime.utcnow())

    return transaction_services.run_in_transaction(_send_email_in_transaction)
예제 #23
0
    def test_oppia_custom_tags(self) -> None:
        test_data: List[Tuple[str, ...]] = [
            ('<oppia-noninteractive-image filepath-with-value="1"/>',
             '<oppia-noninteractive-image filepath-with-value="1">'
             '</oppia-noninteractive-image>'),
            ('<oppia-noninteractive-image filepath-with-value="1">'
             '</oppia-noninteractive-image>',
             '<oppia-noninteractive-image filepath-with-value="1">'
             '</oppia-noninteractive-image>'),
            ('<oppia-fake-tag></oppia-fake-tag>', '')
        ]

        for datum in test_data:
            self.assertEqual(html_cleaner.clean(datum[0]),
                             datum[1],
                             msg='\n\nOriginal text: %s' % datum[0])
예제 #24
0
    def __init__(
            self, subtopic_page_id, topic_id, html_data, language_code,
            version):
        """Constructs a SubtopicPage domain object.

        Args:
            subtopic_page_id: str. The unique ID of the subtopic page.
            topic_id: str. The ID of the topic that this subtopic is a part of.
            html_data: str. The HTML content of the subtopic page.
            language_code: str. The ISO 639-1 code for the language this
                subtopic page is written in.
            version: int. The current version of the subtopic.
        """
        self.id = subtopic_page_id
        self.topic_id = topic_id
        self.html_data = html_cleaner.clean(html_data)
        self.language_code = language_code
        self.version = version
예제 #25
0
    def __init__(
            self, question_id, question_content,
            question_model_created_on=None, question_model_last_updated=None):
        """Constructs a Question Summary domain object.

        Args:
            question_id: str. The ID of the question.
            question_content: str. The static HTML of the question shown to
                the learner.
            question_model_created_on: datetime.datetime. Date and time when
                the question model is created.
            question_model_last_updated: datetime.datetime. Date and time
                when the question model was last updated.
        """
        self.id = question_id
        self.question_content = html_cleaner.clean(question_content)
        self.created_on = question_model_created_on
        self.last_updated = question_model_last_updated
예제 #26
0
    def test_oppia_custom_tags(self):
        TEST_DATA = [(
            '<oppia-noninteractive-image filepath-with-value="1"/>',
            '<oppia-noninteractive-image filepath-with-value="1">'
            '</oppia-noninteractive-image>'
        ), (
            '<oppia-noninteractive-image filepath-with-value="1">'
            '</oppia-noninteractive-image>',
            '<oppia-noninteractive-image filepath-with-value="1">'
            '</oppia-noninteractive-image>'
        ), (
            '<oppia-fake-tag></oppia-fake-tag>',
            ''
        )]

        for datum in TEST_DATA:
            self.assertEqual(
                html_cleaner.clean(datum[0]), datum[1],
                '\n\nOriginal text: %s' % datum[0])
예제 #27
0
    def _normalize_value(raw):
        """Validates and normalizes the value fields of the translatable value.

        Args:
            raw: *. A translatable Python object whose values are to be
                normalized.

        Returns:
            dict. A normalized translatable Python object with its values
            normalized.

        Raises:
            TypeError. The Python object cannot be normalized.
        """
        if not isinstance(raw['html'], python_utils.BASESTRING):
            raise TypeError('Invalid HTML: %s' % raw['html'])

        raw['html'] = html_cleaner.clean(raw['html'])

        return raw
예제 #28
0
def _send_email(recipient_id, sender_id, intent, email_subject, email_html_body):
    """Sends an email to the given recipient.

    This function should be used for sending all user-facing emails.

    Raises an Exception if the sender_id is not appropriate for the given
    intent. Currently we support only system-generated emails and emails
    initiated by moderator actions.
    """
    _require_sender_id_is_valid(intent, sender_id)

    recipient_email = user_services.get_email_from_user_id(recipient_id)
    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            "Original email HTML body does not match cleaned HTML body:\n"
            "Original:\n%s\n\nCleaned:\n%s\n" % (email_html_body, cleaned_html_body)
        )
        return

    raw_plaintext_body = cleaned_html_body.replace("<br/>", "\n").replace("<br>", "\n").replace("</p><p>", "</p>\n<p>")
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    def _send_email_in_transaction():
        sender_email = "%s <%s>" % (EMAIL_SENDER_NAME.value, feconf.SYSTEM_EMAIL_ADDRESS)
        email_services.send_mail(
            sender_email, recipient_email, email_subject, cleaned_plaintext_body, cleaned_html_body
        )
        email_models.SentEmailModel.create(
            recipient_id,
            recipient_email,
            sender_id,
            sender_email,
            intent,
            email_subject,
            cleaned_html_body,
            datetime.datetime.utcnow(),
        )

    return transaction_services.run_in_transaction(_send_email_in_transaction)
예제 #29
0
    def test_good_tags_allowed(self):
        TEST_DATA = [(
            '<a href="http://www.google.com">Hello</a>',
            '<a href="http://www.google.com">Hello</a>'
        ), (
            'Just some text 12345',
            'Just some text 12345'
        ), (
            '<code>Unfinished HTML',
            '<code>Unfinished HTML</code>',
        ), (
            '<br/>',
            '<br>'
        ), (
            'A big mix <div>Hello</div> Yes <span>No</span>',
            'A big mix <div>Hello</div> Yes <span>No</span>'
        )]

        for datum in TEST_DATA:
            self.assertEqual(
                html_cleaner.clean(datum[0]), datum[1],
                '\n\nOriginal text: %s' % datum[0])
예제 #30
0
def _send_bulk_mail(
        recipient_ids, sender_id, intent, email_subject, email_html_body,
        sender_email, sender_name, instance_id=None):
    """Sends an email to all given recipients."""
    _require_sender_id_is_valid(intent, sender_id)

    recipients_settings = user_services.get_users_settings(recipient_ids)
    recipient_emails = [user.email for user in recipients_settings]

    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>', '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    def _send_bulk_mail_in_transaction(instance_id=None):
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_bulk_mail(
            sender_name_email, recipient_emails, email_subject,
            cleaned_plaintext_body, cleaned_html_body)

        if instance_id is None:
            instance_id = email_models.BulkEmailModel.get_new_id('')
        email_models.BulkEmailModel.create(
            instance_id, recipient_ids, sender_id, sender_name_email, intent,
            email_subject, cleaned_html_body, datetime.datetime.utcnow())

    return transaction_services.run_in_transaction(
        _send_bulk_mail_in_transaction, instance_id)
예제 #31
0
def normalize_against_schema(
        obj: Any,
        schema: Dict[str, Any],
        apply_custom_validators: bool = True,
        global_validators: Optional[List[Dict[str, Any]]] = None
) -> Any:
    """Validate the given object using the schema, normalizing if necessary.

    Args:
        obj: *. The object to validate and normalize.
        schema: dict(str, *). The schema to validate and normalize the value
            against.
        apply_custom_validators: bool. Whether to validate the normalized
            object using the validators defined in the schema.
        global_validators: list(dict). List of additional validators that will
            verify all the values in the schema.

    Returns:
        *. The normalized object.

    Raises:
        AssertionError. The object fails to validate against the schema.
    """
    normalized_obj: Any = None

    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:
        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:
        # Importing this at the top of the file causes a circular dependency.
        # TODO(sll): Either get rid of custom objects or find a way to merge
        # them into the schema framework -- probably the latter.
        from core.domain import object_registry
        obj_class = object_registry.Registry.get_object_class_by_type( # type: ignore[no-untyped-call]
            schema[SCHEMA_KEY_OBJ_TYPE])
        if not apply_custom_validators:
            normalized_obj = normalize_against_schema(
                obj, obj_class.get_schema(), apply_custom_validators=False)
        else:
            normalized_obj = obj_class.normalize(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:
        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)
        expected_dict_keys = [
            p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]]

        missing_keys = list(sorted(set(expected_dict_keys) - set(obj.keys())))
        extra_keys = list(sorted(set(obj.keys()) - set(expected_dict_keys)))

        assert set(obj.keys()) == set(expected_dict_keys), (
            'Missing keys: %s, Extra keys: %s' % (missing_keys, extra_keys))

        normalized_obj = {}
        for prop in schema[SCHEMA_KEY_PROPERTIES]:
            key = prop[SCHEMA_KEY_NAME]
            normalized_obj[key] = normalize_against_schema(
                obj[key],
                prop[SCHEMA_KEY_SCHEMA],
                global_validators=global_validators
            )
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:
        try:
            obj = float(obj)
        except Exception:
            raise Exception('Could not convert %s to float: %s' % (
                type(obj).__name__, obj))
        assert isinstance(obj, numbers.Real), (
            'Expected float, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:
        try:
            obj = int(obj)
        except Exception:
            raise Exception('Could not convert %s to int: %s' % (
                type(obj).__name__, obj))
        assert isinstance(obj, numbers.Integral), (
            'Expected int, received %s' % obj)
        assert isinstance(obj, int), ('Expected int, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:
        assert isinstance(obj, python_utils.BASESTRING), (
            'Expected unicode HTML string, received %s' % obj)
        if isinstance(obj, bytes):
            obj = obj.decode('utf-8')
        else:
            obj = python_utils.UNICODE(obj)
        assert isinstance(obj, python_utils.UNICODE), (
            'Expected unicode, received %s' % obj)
        normalized_obj = html_cleaner.clean(obj) # type: ignore[no-untyped-call]
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:
        assert isinstance(obj, list), ('Expected list, received %s' % obj)
        item_schema = schema[SCHEMA_KEY_ITEMS]
        if SCHEMA_KEY_LEN in schema:
            assert len(obj) == schema[SCHEMA_KEY_LEN], (
                'Expected length of %s got %s' % (
                    schema[SCHEMA_KEY_LEN], len(obj)))
        normalized_obj = [
            normalize_against_schema(
                item, item_schema, global_validators=global_validators
            ) for item in obj
        ]
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BASESTRING:
        assert isinstance(obj, python_utils.BASESTRING), (
            'Expected string, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:
        assert isinstance(obj, python_utils.BASESTRING), (
            'Expected unicode string, received %s' % obj)
        if isinstance(obj, bytes):
            obj = obj.decode('utf-8')
        else:
            obj = python_utils.UNICODE(obj)
        assert isinstance(obj, python_utils.UNICODE), (
            'Expected unicode, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE_OR_NONE:
        assert obj is None or isinstance(obj, python_utils.BASESTRING), (
            'Expected unicode string or None, received %s' % obj)
        if obj is not None:
            if isinstance(obj, bytes):
                obj = obj.decode('utf-8')
            else:
                obj = python_utils.UNICODE(obj)
            assert isinstance(obj, python_utils.UNICODE), (
                'Expected unicode, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_OBJECT_DICT:
        # The schema type 'object_dict' accepts either of the keys
        # 'object_class' or 'validation_method'.
        # 'object_class' key is the most commonly used case, when the object is
        # initialized from_dict() method and the validation is done from
        # validate() method.
        # 'validation_method' key is used for some rare cases like if they have
        # validate_dict method instead of validate method, or if they need some
        # extra flags like for strict validation. The methods are written in the
        # domain_objects_validator file.

        if SCHEMA_KEY_OBJECT_CLASS in schema:
            validate_class = schema[SCHEMA_KEY_OBJECT_CLASS]
            domain_object = validate_class.from_dict(obj)
            domain_object.validate()
        else:
            validation_method = schema[SCHEMA_KEY_VALIDATION_METHOD]
            validation_method(obj)

        normalized_obj = obj
    else:
        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])

    if SCHEMA_KEY_CHOICES in schema:
        assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (
            'Received %s which is not in the allowed range of choices: %s' %
            (normalized_obj, schema[SCHEMA_KEY_CHOICES]))

    # When type normalization is finished, apply the post-normalizers in the
    # given order.
    if SCHEMA_KEY_POST_NORMALIZERS in schema:
        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:
            kwargs = dict(normalizer)
            del kwargs['id']
            normalized_obj = Normalizers.get(normalizer['id'])(
                normalized_obj, **kwargs)

    # Validate the normalized object.
    if apply_custom_validators:
        if SCHEMA_KEY_VALIDATORS in schema:
            for validator in schema[SCHEMA_KEY_VALIDATORS]:
                kwargs = dict(validator)
                del kwargs['id']
                assert get_validator(
                    validator['id'])(normalized_obj, **kwargs), (
                        'Validation failed: %s (%s) for object %s' % (
                            validator['id'], kwargs, normalized_obj))

    if global_validators is not None:
        for validator in global_validators:
            kwargs = dict(validator)
            del kwargs['id']
            assert get_validator(
                validator['id'])(normalized_obj, **kwargs), (
                    'Validation failed: %s (%s) for object %s' % (
                        validator['id'], kwargs, normalized_obj))

    return normalized_obj
예제 #32
0
def _send_email(
        recipient_id, sender_id, intent, email_subject, email_html_body,
        sender_email, bcc_admin=False, sender_name=None, reply_to_id=None):
    """Sends an email to the given recipient.

    This function should be used for sending all user-facing emails.

    Raises an Exception if the sender_id is not appropriate for the given
    intent. Currently we support only system-generated emails and emails
    initiated by moderator actions.

    Args:
        recipient_id: str. The user ID of the recipient.
        sender_id: str. The user ID of the sender.
        intent: str. The intent string for the email, i.e. the purpose/type.
        email_subject: str. The subject of the email.
        email_html_body: str. The body (message) of the email.
        sender_email: str. The sender's email address.
        bcc_admin: bool. Whether to send a copy of the email to the admin's
            email address.
        sender_name: str or None. The name to be shown in the "sender" field of
            the email.
        reply_to_id: str or None. The unique reply-to id used in reply-to email
            address sent to recipient.
    """

    if sender_name is None:
        sender_name = EMAIL_SENDER_NAME.value

    _require_sender_id_is_valid(intent, sender_id)

    recipient_email = user_services.get_email_from_user_id(recipient_id)
    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>', '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    if email_models.SentEmailModel.check_duplicate_message(
            recipient_id, email_subject, cleaned_plaintext_body):
        log_new_error(
            'Duplicate email:\n'
            'Details:\n%s %s\n%s\n\n' %
            (recipient_id, email_subject, cleaned_plaintext_body))
        return

    def _send_email_in_transaction():
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_mail(
            sender_name_email, recipient_email, email_subject,
            cleaned_plaintext_body, cleaned_html_body, bcc_admin,
            reply_to_id=reply_to_id)
        email_models.SentEmailModel.create(
            recipient_id, recipient_email, sender_id, sender_name_email, intent,
            email_subject, cleaned_html_body, datetime.datetime.utcnow())

    return transaction_services.run_in_transaction(_send_email_in_transaction)
예제 #33
0
def normalize_against_schema(obj, schema, apply_custom_validators=True):
    """Validate the given object using the schema, normalizing if necessary.

    Args:
        obj: *. The object to validate and normalize.
        schema: dict(str, *). The schema to validate and normalize the value
            against.
        apply_custom_validators: bool. Whether to validate the normalized
             object using the validators defined in the schema.

    Returns:
        *. The normalized object.

    Raises:
        AssertionError: The object fails to validate against the schema.
    """
    normalized_obj = None

    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:
        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:
        # Importing this at the top of the file causes a circular dependency.
        # TODO(sll): Either get rid of custom objects or find a way to merge
        # them into the schema framework -- probably the latter.
        from core.domain import obj_services
        obj_class = obj_services.Registry.get_object_class_by_type(
            schema[SCHEMA_KEY_OBJ_TYPE])
        if not apply_custom_validators:
            normalized_obj = normalize_against_schema(
                obj, obj_class.SCHEMA, apply_custom_validators=False)
        else:
            normalized_obj = obj_class.normalize(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:
        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)
        expected_dict_keys = [
            p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]
        ]
        assert set(obj.keys()) == set(expected_dict_keys), (
            'Missing keys: %s, Extra keys: %s' %
            (list(set(expected_dict_keys) - set(obj.keys())),
             list(set(obj.keys()) - set(expected_dict_keys))))

        normalized_obj = {}
        for prop in schema[SCHEMA_KEY_PROPERTIES]:
            key = prop[SCHEMA_KEY_NAME]
            normalized_obj[key] = normalize_against_schema(
                obj[key], prop[SCHEMA_KEY_SCHEMA])
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:
        obj = float(obj)
        assert isinstance(obj,
                          numbers.Real), ('Expected float, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:
        obj = int(obj)
        assert isinstance(
            obj, numbers.Integral), ('Expected int, received %s' % obj)
        assert isinstance(obj, int), ('Expected int, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:
        assert isinstance(obj, python_utils.BASESTRING), (
            'Expected unicode HTML string, received %s' % obj)
        if isinstance(obj, bytes):
            obj = obj.decode('utf-8')
        else:
            obj = python_utils.UNICODE(obj)
        assert isinstance(
            obj, python_utils.UNICODE), ('Expected unicode, received %s' % obj)
        normalized_obj = html_cleaner.clean(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:
        assert isinstance(obj, list), ('Expected list, received %s' % obj)
        item_schema = schema[SCHEMA_KEY_ITEMS]
        if SCHEMA_KEY_LEN in schema:
            assert len(obj) == schema[SCHEMA_KEY_LEN]
        normalized_obj = [
            normalize_against_schema(item, item_schema) for item in obj
        ]
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:
        assert isinstance(
            obj,
            python_utils.BASESTRING), ('Expected unicode string, received %s' %
                                       obj)
        if isinstance(obj, bytes):
            obj = obj.decode('utf-8')
        else:
            obj = python_utils.UNICODE(obj)
        assert isinstance(
            obj, python_utils.UNICODE), ('Expected unicode, received %s' % obj)
        normalized_obj = obj
    else:
        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])

    if SCHEMA_KEY_CHOICES in schema:
        assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (
            'Received %s which is not in the allowed range of choices: %s' %
            (normalized_obj, schema[SCHEMA_KEY_CHOICES]))

    # When type normalization is finished, apply the post-normalizers in the
    # given order.
    if SCHEMA_KEY_POST_NORMALIZERS in schema:
        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:
            kwargs = dict(normalizer)
            del kwargs['id']
            normalized_obj = Normalizers.get(normalizer['id'])(normalized_obj,
                                                               **kwargs)

    # Validate the normalized object.
    if apply_custom_validators:
        if SCHEMA_KEY_VALIDATORS in schema:
            for validator in schema[SCHEMA_KEY_VALIDATORS]:
                kwargs = dict(validator)
                del kwargs['id']
                assert get_validator(validator['id'])(
                    normalized_obj,
                    **kwargs), ('Validation failed: %s (%s) for object %s' %
                                (validator['id'], kwargs, normalized_obj))

    return normalized_obj
예제 #34
0
def normalize_against_schema(obj, schema):
    """Validate the given object using the schema, normalizing if necessary.

    Returns:
        the normalized object.

    Raises:
        AssertionError: if the object fails to validate against the schema.
    """
    normalized_obj = None

    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:
        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:
        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)
        assert set(obj.keys()) == set(schema[SCHEMA_KEY_PROPERTIES].keys())
        normalized_obj = {
            key: normalize_against_schema(obj[key],
                                          schema[SCHEMA_KEY_PROPERTIES][key])
            for key in schema[SCHEMA_KEY_PROPERTIES]
        }
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:
        obj = float(obj)
        assert isinstance(obj,
                          numbers.Real), ('Expected float, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:
        obj = int(obj)
        assert isinstance(
            obj, numbers.Integral), ('Expected int, received %s' % obj)
        assert isinstance(obj, int), ('Expected int, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:
        assert isinstance(
            obj,
            basestring), ('Expected unicode HTML string, received %s' % obj)
        obj = unicode(obj)
        assert isinstance(obj,
                          unicode), ('Expected unicode, received %s' % obj)
        normalized_obj = html_cleaner.clean(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:
        assert isinstance(obj, list), ('Expected list, received %s' % obj)
        item_schema = schema[SCHEMA_KEY_ITEMS]
        if SCHEMA_KEY_LENGTH in schema:
            assert len(obj) == schema[SCHEMA_KEY_LENGTH]
        normalized_obj = [
            normalize_against_schema(item, item_schema) for item in obj
        ]
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:
        assert isinstance(
            obj, basestring), ('Expected unicode string, received %s' % obj)
        obj = unicode(obj)
        assert isinstance(obj,
                          unicode), ('Expected unicode, received %s' % obj)
        normalized_obj = obj
    else:
        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])

    # When type normalization is finished, apply the post-normalizers in the
    # given order.
    if SCHEMA_KEY_POST_NORMALIZERS in schema:
        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:
            kwargs = dict(normalizer)
            del kwargs['id']
            normalized_obj = Normalizers.get(normalizer['id'])(normalized_obj,
                                                               **kwargs)

    return normalized_obj
예제 #35
0
파일: exp_domain.py 프로젝트: aldeka/oppia
    def from_yaml(cls, exploration_id, title, category, yaml_content):
        """Creates and returns exploration from a YAML text string."""
        exploration_dict = utils.dict_from_yaml(yaml_content)

        exploration_schema_version = exploration_dict.get('schema_version')
        if exploration_schema_version is None:
            raise Exception('Invalid YAML file: no schema version specified.')
        if not (1 <= exploration_schema_version
                <= cls.CURRENT_EXPLORATION_SCHEMA_VERSION):
            raise Exception(
                'Sorry, we can only process v1 and v2 YAML files at present.')
        if exploration_schema_version == 1:
            exploration_dict = cls._convert_v1_dict_to_v2_dict(
                exploration_dict)

        exploration = cls.create_default_exploration(
            exploration_id, title, category)
        exploration.param_specs = {
            ps_name: param_domain.ParamSpec.from_dict(ps_val) for
            (ps_name, ps_val) in exploration_dict['param_specs'].iteritems()
        }

        init_state_name = exploration_dict['init_state_name']
        exploration.rename_state(exploration.init_state_name, init_state_name)
        exploration.add_states([
            state_name for state_name in exploration_dict['states']
            if state_name != init_state_name])

        for (state_name, sdict) in exploration_dict['states'].iteritems():
            state = exploration.states[state_name]

            state.content = [
                Content(item['type'], html_cleaner.clean(item['value']))
                for item in sdict['content']
            ]

            state.param_changes = [param_domain.ParamChange(
                pc['name'], pc['generator_id'], pc['customization_args']
            ) for pc in sdict['param_changes']]

            for pc in state.param_changes:
                if pc.name not in exploration.param_specs:
                    raise Exception('Parameter %s was used in a state but not '
                                    'declared in the exploration param_specs.'
                                    % pc.name)

            wdict = sdict['widget']
            widget_handlers = [AnswerHandlerInstance.from_dict({
                'name': handler['name'],
                'rule_specs': [{
                    'definition': rule_spec['definition'],
                    'dest': rule_spec['dest'],
                    'feedback': [html_cleaner.clean(feedback)
                                 for feedback in rule_spec['feedback']],
                    'param_changes': rule_spec.get('param_changes', []),
                } for rule_spec in handler['rule_specs']],
            }) for handler in wdict['handlers']]

            state.widget = WidgetInstance(
                wdict['widget_id'], wdict['customization_args'],
                widget_handlers, wdict['sticky'])

            exploration.states[state_name] = state

        exploration.default_skin = exploration_dict['default_skin']
        exploration.param_changes = [
            param_domain.ParamChange.from_dict(pc)
            for pc in exploration_dict['param_changes']]

        return exploration
예제 #36
0
파일: exp_domain.py 프로젝트: aldeka/oppia
    def update_widget_handlers(self, widget_handlers_dict):
        if not isinstance(widget_handlers_dict, dict):
            raise Exception(
                'Expected widget_handlers to be a dictionary, received %s'
                % widget_handlers_dict)
        ruleset = widget_handlers_dict['submit']
        if not isinstance(ruleset, list):
            raise Exception(
                'Expected widget_handlers[submit] to be a list, received %s'
                % ruleset)

        widget_handlers = [AnswerHandlerInstance('submit', [])]
        generic_widget = widget_registry.Registry.get_widget_by_id(
            'interactive', self.widget.widget_id)

        # TODO(yanamal): Do additional calculations here to get the
        # parameter changes, if necessary.
        for rule_ind in range(len(ruleset)):
            rule_dict = ruleset[rule_ind]
            rule_dict['feedback'] = [html_cleaner.clean(feedback)
                                     for feedback in rule_dict['feedback']]
            if 'param_changes' not in rule_dict:
                rule_dict['param_changes'] = []
            rule_spec = RuleSpec.from_dict(rule_dict)
            rule_type = rule_spec.definition['rule_type']

            if rule_ind == len(ruleset) - 1:
                if rule_type != rule_domain.DEFAULT_RULE_TYPE:
                    raise ValueError(
                        'Invalid ruleset %s: the last rule should be a '
                        'default rule' % rule_dict)
            else:
                if rule_type == rule_domain.DEFAULT_RULE_TYPE:
                    raise ValueError(
                        'Invalid ruleset %s: rules other than the '
                        'last one should not be default rules.' % rule_dict)

                # TODO(sll): Generalize this to Boolean combinations of rules.
                matched_rule = generic_widget.get_rule_by_name(
                    'submit', rule_spec.definition['name'])

                # Normalize and store the rule params.
                # TODO(sll): Generalize this to Boolean combinations of rules.
                rule_inputs = rule_spec.definition['inputs']
                if not isinstance(rule_inputs, dict):
                    raise Exception(
                        'Expected rule_inputs to be a dict, received %s'
                        % rule_inputs)
                for param_name, value in rule_inputs.iteritems():
                    param_type = rule_domain.get_obj_type_for_param_name(
                        matched_rule, param_name)

                    if (isinstance(value, basestring) and
                            '{{' in value and '}}' in value):
                        # TODO(jacobdavis11): Create checks that all parameters
                        # referred to exist and have the correct types
                        normalized_param = value
                    else:
                        try:
                            normalized_param = param_type.normalize(value)
                        except TypeError:
                            raise Exception(
                                '%s has the wrong type. It should be a %s.' %
                                (value, param_type.__name__))
                    rule_inputs[param_name] = normalized_param

            widget_handlers[0].rule_specs.append(rule_spec)
            self.widget.handlers = widget_handlers
예제 #37
0
def normalize_against_schema(obj, schema, apply_custom_validators=True):
    """Validate the given object using the schema, normalizing if necessary.

    Returns:
        the normalized object.

    Raises:
        AssertionError: if the object fails to validate against the schema.
    """
    normalized_obj = None

    if schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_BOOL:
        assert isinstance(obj, bool), ('Expected bool, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_CUSTOM:
        # Importing this at the top of the file causes a circular dependency.
        # TODO(sll): Either get rid of custom objects or find a way to merge
        # them into the schema framework -- probably the latter.
        from core.domain import obj_services  # pylint: disable=relative-import
        obj_class = obj_services.Registry.get_object_class_by_type(
            schema[SCHEMA_KEY_OBJ_TYPE])
        normalized_obj = obj_class.normalize(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_DICT:
        assert isinstance(obj, dict), ('Expected dict, received %s' % obj)
        expected_dict_keys = [
            p[SCHEMA_KEY_NAME] for p in schema[SCHEMA_KEY_PROPERTIES]]
        assert set(obj.keys()) == set(expected_dict_keys)

        normalized_obj = {}
        for prop in schema[SCHEMA_KEY_PROPERTIES]:
            key = prop[SCHEMA_KEY_NAME]
            normalized_obj[key] = normalize_against_schema(
                obj[key], prop[SCHEMA_KEY_SCHEMA])
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_FLOAT:
        obj = float(obj)
        assert isinstance(obj, numbers.Real), (
            'Expected float, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_INT:
        obj = int(obj)
        assert isinstance(obj, numbers.Integral), (
            'Expected int, received %s' % obj)
        assert isinstance(obj, int), ('Expected int, received %s' % obj)
        normalized_obj = obj
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_HTML:
        assert isinstance(obj, basestring), (
            'Expected unicode HTML string, received %s' % obj)
        obj = unicode(obj)
        assert isinstance(obj, unicode), (
            'Expected unicode, received %s' % obj)
        normalized_obj = html_cleaner.clean(obj)
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_LIST:
        assert isinstance(obj, list), ('Expected list, received %s' % obj)
        item_schema = schema[SCHEMA_KEY_ITEMS]
        if SCHEMA_KEY_LEN in schema:
            assert len(obj) == schema[SCHEMA_KEY_LEN]
        normalized_obj = [
            normalize_against_schema(item, item_schema) for item in obj
        ]
    elif schema[SCHEMA_KEY_TYPE] == SCHEMA_TYPE_UNICODE:
        assert isinstance(obj, basestring), (
            'Expected unicode string, received %s' % obj)
        obj = unicode(obj)
        assert isinstance(obj, unicode), (
            'Expected unicode, received %s' % obj)
        normalized_obj = obj
    else:
        raise Exception('Invalid schema type: %s' % schema[SCHEMA_KEY_TYPE])

    if SCHEMA_KEY_CHOICES in schema:
        assert normalized_obj in schema[SCHEMA_KEY_CHOICES], (
            'Received %s which is not in the allowed range of choices: %s' %
            (normalized_obj, schema[SCHEMA_KEY_CHOICES]))

    # When type normalization is finished, apply the post-normalizers in the
    # given order.
    if SCHEMA_KEY_POST_NORMALIZERS in schema:
        for normalizer in schema[SCHEMA_KEY_POST_NORMALIZERS]:
            kwargs = dict(normalizer)
            del kwargs['id']
            normalized_obj = Normalizers.get(normalizer['id'])(
                normalized_obj, **kwargs)

    # Validate the normalized object.
    if apply_custom_validators:
        if SCHEMA_KEY_VALIDATORS in schema:
            for validator in schema[SCHEMA_KEY_VALIDATORS]:
                kwargs = dict(validator)
                del kwargs['id']
                assert _Validators.get(
                    validator['id'])(normalized_obj, **kwargs), (
                        'Validation failed: %s (%s) for object %s' % (
                            validator['id'], kwargs, normalized_obj))

    return normalized_obj
예제 #38
0
def _send_email(recipient_id,
                sender_id,
                intent,
                email_subject,
                email_html_body,
                sender_email,
                bcc_admin=False,
                sender_name=None,
                reply_to_id=None):
    """Sends an email to the given recipient.

    This function should be used for sending all user-facing emails.

    Raises an Exception if the sender_id is not appropriate for the given
    intent. Currently we support only system-generated emails and emails
    initiated by moderator actions.

    Args:
        recipient_id: str. The user ID of the recipient.
        sender_id: str. The user ID of the sender.
        intent: str. The intent string for the email, i.e. the purpose/type.
        email_subject: str. The subject of the email.
        email_html_body: str. The body (message) of the email.
        sender_email: str. The sender's email address.
        bcc_admin: bool. Whether to send a copy of the email to the admin's
            email address.
        sender_name: str or None. The name to be shown in the "sender" field of
            the email.
        reply_to_id: str or None. The unique reply-to id used in reply-to email
            address sent to recipient.
    """

    if sender_name is None:
        sender_name = EMAIL_SENDER_NAME.value

    _require_sender_id_is_valid(intent, sender_id)

    recipient_email = user_services.get_email_from_user_id(recipient_id)
    cleaned_html_body = html_cleaner.clean(email_html_body)
    if cleaned_html_body != email_html_body:
        log_new_error(
            'Original email HTML body does not match cleaned HTML body:\n'
            'Original:\n%s\n\nCleaned:\n%s\n' %
            (email_html_body, cleaned_html_body))
        return

    raw_plaintext_body = cleaned_html_body.replace('<br/>', '\n').replace(
        '<br>', '\n').replace('<li>',
                              '<li>- ').replace('</p><p>', '</p>\n<p>')
    cleaned_plaintext_body = html_cleaner.strip_html_tags(raw_plaintext_body)

    if email_models.SentEmailModel.check_duplicate_message(
            recipient_id, email_subject, cleaned_plaintext_body):
        log_new_error('Duplicate email:\n'
                      'Details:\n%s %s\n%s\n\n' %
                      (recipient_id, email_subject, cleaned_plaintext_body))
        return

    def _send_email_in_transaction():
        """Sends the email to a single recipient."""
        sender_name_email = '%s <%s>' % (sender_name, sender_email)

        email_services.send_mail(sender_name_email,
                                 recipient_email,
                                 email_subject,
                                 cleaned_plaintext_body,
                                 cleaned_html_body,
                                 bcc_admin,
                                 reply_to_id=reply_to_id)
        email_models.SentEmailModel.create(recipient_id, recipient_email,
                                           sender_id, sender_name_email,
                                           intent, email_subject,
                                           cleaned_html_body,
                                           datetime.datetime.utcnow())

    transaction_services.run_in_transaction(_send_email_in_transaction)