Exemple #1
0
    def test_rename_stream(self):
        realm = get_realm('zulip.com')
        stream, _ = create_stream_if_needed(realm, 'old_name')
        new_name = u'stream with a brand new name'
        self.subscribe_to_stream(self.user_profile.email, stream.name)

        action = lambda: do_rename_stream(realm, stream.name, new_name)
        events = self.do_test(action)

        schema_checker = check_dict([
            ('type', equals('stream')),
            ('op', equals('update')),
            ('property', equals('email_address')),
            ('value', check_string),
            ('name', equals('old_name')),
        ])
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        schema_checker = check_dict([
            ('type', equals('stream')),
            ('op', equals('update')),
            ('property', equals('name')),
            ('value', equals(new_name)),
            ('name', equals('old_name')),
        ])
        error = schema_checker('events[1]', events[1])
        self.assert_on_error(error)
Exemple #2
0
def api_newrelic_webhook(request, user_profile, alert=REQ(validator=check_dict([]), default=None),
                             deployment=REQ(validator=check_dict([]), default=None)):
    try:
        stream = request.GET['stream']
    except (AttributeError, KeyError):
        return json_error("Missing stream parameter.")

    if alert:
        # Use the message as the subject because it stays the same for
        # "opened", "acknowledged", and "closed" messages that should be
        # grouped.
        subject = alert['message']
        content = "%(long_description)s\n[View alert](%(alert_url)s)" % (alert)
    elif deployment:
        subject = "%s deploy" % (deployment['application_name'])
        content = """`%(revision)s` deployed by **%(deployed_by)s**
%(description)s

%(changelog)s""" % (deployment)
    else:
        return json_error("Unknown webhook request")

    check_send_message(user_profile, get_client("ZulipNewRelicWebhook"), "stream",
                       [stream], subject, content)
    return json_success()
Exemple #3
0
    def test_register_events(self):
        realm_user_add_checker = check_dict([
            ('type', equals('realm_user')),
            ('op', equals('add')),
            ('person', check_dict([
                ('email', check_string),
                ('full_name', check_string),
                ('is_admin', check_bool),
                ('is_bot', check_bool),
            ])),
        ])
        stream_create_checker = check_dict([
            ('type', equals('stream')),
            ('op', equals('create')),
            ('streams', check_list(check_dict([
                ('description', check_string),
                ('invite_only', check_bool),
                ('name', check_string),
                ('stream_id', check_int),
            ])))
        ])

        events = self.do_test(lambda: self.register("test1", "test1"))
        error = realm_user_add_checker('events[0]', events[0])
        self.assert_on_error(error)
        error = stream_create_checker('events[1]', events[1])
        self.assert_on_error(error)
Exemple #4
0
    def test_check_dict(self):
        keys = [
            ('names', check_list(check_string)),
            ('city', check_string),
        ]

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, None)

        x = 999
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x is not a dict')

        x = {}
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'names key is missing from x')

        x = {
            'names': ['alice', 'bob', {}]
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["names"][2] is not a string')

        x = {
            'names': ['alice', 'bob'],
            'city': 5
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["city"] is not a string')
Exemple #5
0
def do_rest_call(rest_operation: Dict[str, Any],
                 request_data: Optional[Dict[str, Any]],
                 event: Dict[str, Any],
                 service_handler: Any,
                 timeout: Any=None) -> None:
    rest_operation_validator = check_dict([
        ('method', check_string),
        ('relative_url_path', check_string),
        ('request_kwargs', check_dict([])),
        ('base_url', check_string),
    ])

    error = rest_operation_validator('rest_operation', rest_operation)
    if error:
        raise JsonableError(error)

    http_method = rest_operation['method']
    final_url = urllib.parse.urljoin(rest_operation['base_url'], rest_operation['relative_url_path'])
    request_kwargs = rest_operation['request_kwargs']
    request_kwargs['timeout'] = timeout

    try:
        response = requests.request(http_method, final_url, data=request_data, **request_kwargs)
        if str(response.status_code).startswith('2'):
            response_message = service_handler.process_success(response, event)
            if response_message is not None:
                succeed_with_message(event, response_message)
        else:
            logging.warning("Message %(message_url)s triggered an outgoing webhook, returning status "
                            "code %(status_code)s.\n Content of response (in quotes): \""
                            "%(response)s\""
                            % {'message_url': get_message_url(event, request_data),
                               'status_code': response.status_code,
                               'response': response.content})
            failure_message = "Third party responded with %d" % (response.status_code)
            fail_with_message(event, failure_message)
            notify_bot_owner(event, request_data, response.status_code, response.content)

    except requests.exceptions.Timeout as e:
        logging.info("Trigger event %s on %s timed out. Retrying" % (
            event["command"], event['service_name']))
        request_retry(event, request_data, 'Unable to connect with the third party.', exception=e)

    except requests.exceptions.ConnectionError as e:
        response_message = ("The message `%s` resulted in a connection error when "
                            "sending a request to an outgoing "
                            "webhook! See the Zulip server logs for more information." % (event["command"],))
        logging.info("Trigger event %s on %s resulted in a connection error. Retrying"
                     % (event["command"], event['service_name']))
        request_retry(event, request_data, response_message, exception=e)

    except requests.exceptions.RequestException as e:
        response_message = ("An exception of type *%s* occurred for message `%s`! "
                            "See the Zulip server logs for more information." % (
                                type(e).__name__, event["command"],))
        logging.exception("Outhook trigger failed:\n %s" % (e,))
        fail_with_message(event, response_message)
        notify_bot_owner(event, request_data, exception=e)
Exemple #6
0
 def realm_bot_schema(self, field_name, check):
     return check_dict([
         ('type', equals('realm_bot')),
         ('op', equals('update')),
         ('bot', check_dict([
             ('email', check_string),
             (field_name, check),
         ])),
     ])
Exemple #7
0
    def test_send_message_events(self):
        schema_checker = check_dict([
            ('type', equals('message')),
            ('flags', check_list(None)),
            ('message', check_dict([
                ('avatar_url', check_string),
                ('client', check_string),
                ('content', check_string),
                ('content_type', equals('text/html')),
                ('display_recipient', check_string),
                ('gravatar_hash', check_string),
                ('id', check_int),
                ('recipient_id', check_int),
                ('sender_domain', check_string),
                ('sender_email', check_string),
                ('sender_full_name', check_string),
                ('sender_id', check_int),
                ('sender_short_name', check_string),
                ('subject', check_string),
                ('subject_links', check_list(None)),
                ('timestamp', check_int),
                ('type', check_string),
            ])),
        ])
        events = self.do_test(lambda: self.send_message("*****@*****.**", "Verona", Recipient.STREAM, "hello"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        schema_checker = check_dict([
            ('type', equals('update_message')),
            ('flags', check_list(None)),
            ('content', check_string),
            ('edit_timestamp', check_int),
            ('flags', check_list(None)),
            ('message_id', check_int),
            ('message_ids', check_list(check_int)),
            ('orig_content', check_string),
            ('orig_rendered_content', check_string),
            ('orig_subject', check_string),
            ('propagate_mode', check_string),
            ('rendered_content', check_string),
            ('sender', check_string),
            ('stream_id', check_int),
            ('subject', check_string),
            ('subject_links', check_list(None)),
            # There is also a timestamp field in the event, but we ignore it, as
            # it's kind of an unwanted but harmless side effect of calling log_event.
        ])

        message_id = Message.objects.order_by('-id')[0].id
        topic = 'new_topic'
        propagate_mode = 'change_all'
        content = 'new content'
        events = self.do_test(lambda: do_update_message(self.user_profile, message_id, topic, propagate_mode, content))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Exemple #8
0
 def test_change_full_name(self):
     schema_checker = check_dict([
         ('type', equals('realm_user')),
         ('op', equals('update')),
         ('person', check_dict([
             ('email', check_string),
             ('full_name', check_string),
         ])),
     ])
     events = self.do_test(lambda: do_change_full_name(self.user_profile, 'Sir Hamlet'))
     error = schema_checker('events[0]', events[0])
     self.assert_on_error(error)
Exemple #9
0
 def test_do_deactivate_user(self):
     bot_deactivate_checker = check_dict([
         ('type', equals('realm_bot')),
         ('op', equals('remove')),
         ('bot', check_dict([
             ('email', check_string),
             ('full_name', check_string),
         ])),
     ])
     bot = self.create_bot('*****@*****.**')
     action = lambda: do_deactivate_user(bot)
     events = self.do_test(action)
     error = bot_deactivate_checker('events[1]', events[1])
     self.assert_on_error(error)
    def test_check_dict(self):
        # type: () -> None
        keys = [
            ('names', check_list(check_string)),
            ('city', check_string),
        ]  # type: List[Tuple[str, Validator]]

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }  # type: Any
        error = check_dict(keys)('x', x)
        self.assertEqual(error, None)

        x = 999
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x is not a dict')

        x = {}
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'names key is missing from x')

        x = {
            'names': ['alice', 'bob', {}]
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["names"][2] is not a string')

        x = {
            'names': ['alice', 'bob'],
            'city': 5
        }
        error = check_dict(keys)('x', x)
        self.assertEqual(error, 'x["city"] is not a string')

        # test dict_only
        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, None)

        x = {
            'names': ['alice', 'bob'],
            'city': 'Boston',
            'state': 'Massachusetts',
        }
        error = check_dict_only(keys)('x', x)
        self.assertEqual(error, 'Unexpected arguments: state')
Exemple #11
0
    def test_realm_emoji_events(self):
        schema_checker = check_dict([
            ('type', equals('realm_emoji')),
            ('op', equals('update')),
            ('realm_emoji', check_dict([])),
        ])
        events = self.do_test(lambda: do_add_realm_emoji(get_realm("zulip.com"), "my_emoji",
                                                         "https://realm.com/my_emoji"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        events = self.do_test(lambda: do_remove_realm_emoji(get_realm("zulip.com"), "my_emoji"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Exemple #12
0
 def test_change_is_admin(self):
     schema_checker = check_dict([
         ('type', equals('realm_user')),
         ('op', equals('update')),
         ('person', check_dict([
             ('email', check_string),
             ('is_admin', check_bool),
         ])),
     ])
     # The first False is probably a noop, then we get transitions in both directions.
     for is_admin in [False, True, False]:
         events = self.do_test(lambda: do_change_is_admin(self.user_profile, is_admin))
         error = schema_checker('events[0]', events[0])
         self.assert_on_error(error)
Exemple #13
0
def api_travis_webhook(request, user_profile, client,
                       stream=REQ(default='travis'),
                       topic=REQ(default=None),
                       message=REQ('payload', validator=check_dict([
                           ('author_name', check_string),
                           ('status_message', check_string),
                           ('compare_url', check_string),
                       ]))):
    author = message['author_name']
    message_type = message['status_message']
    changes = message['compare_url']

    good_status = ['Passed', 'Fixed']
    bad_status  = ['Failed', 'Broken', 'Still Failing']
    emoji = ''
    if message_type in good_status:
        emoji = ':thumbsup:'
    elif message_type in bad_status:
        emoji = ':thumbsdown:'
    else:
        emoji = "(No emoji specified for status '%s'.)" % (message_type,)

    build_url = message['build_url']

    template = (
        u'Author: %s\n'
        u'Build status: %s %s\n'
        u'Details: [changes](%s), [build log](%s)')

    body = template % (author, message_type, emoji, changes, build_url)

    check_send_message(user_profile, client, 'stream', [stream], topic, body)
    return json_success()
def update_user_custom_profile_data(
        request: HttpRequest,
        user_profile: UserProfile,
        data: List[Dict[str, Union[int, str]]]=REQ(validator=check_list(
            check_dict([('id', check_int)])))) -> HttpResponse:
    for item in data:
        field_id = item['id']
        try:
            field = CustomProfileField.objects.get(id=field_id)
        except CustomProfileField.DoesNotExist:
            return json_error(_('Field id {id} not found.').format(id=field_id))

        validators = CustomProfileField.FIELD_VALIDATORS
        extended_validators = CustomProfileField.EXTENDED_FIELD_VALIDATORS
        field_type = field.field_type
        value = item['value']
        var_name = '{}'.format(field.name)
        if field_type in validators:
            validator = validators[field_type]
            result = validator(var_name, value)
        else:
            # Check extended validators.
            extended_validator = extended_validators[field_type]
            field_data = field.field_data
            result = extended_validator(var_name, field_data, value)

        if result is not None:
            return json_error(result)

    do_update_user_custom_profile_data(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
Exemple #15
0
def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dict([])),
                          stream=REQ(default='commits')):
    # type: (HttpRequest, UserProfile, Mapping[Text, Any], Text) -> HttpResponse
    repository = payload['repository']

    commits = [
        {
            'sha': commit.get('raw_node'),
            'message': commit.get('message'),
            'url': u'{}{}commits/{}'.format(
                payload.get('canon_url'),
                repository.get('absolute_url'),
                commit.get('raw_node'))
        }
        for commit in payload.get('commits')
    ]

    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        subject = repository['name']
        content = (u"%s [force pushed](%s)"
                   % (payload['user'],
                      payload['canon_url'] + repository['absolute_url']))
    else:
        branch = payload['commits'][-1]['branch']
        content = get_push_commits_event_message(payload.get('user'), None, branch, commits)
        subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=repository['name'], branch=branch)

    check_send_message(user_profile, get_client("ZulipBitBucketWebhook"), "stream",
                       [stream], subject, content)
    return json_success()
Exemple #16
0
def api_travis_webhook(request, user_profile, client,
                       stream=REQ(default='travis'),
                       topic=REQ(default=None),
                       ignore_pull_requests=REQ(validator=check_bool, default=True),
                       message=REQ('payload', validator=check_dict([
                           ('author_name', check_string),
                           ('status_message', check_string),
                           ('compare_url', check_string),
                       ]))):
    # type: (HttpRequest, UserProfile, Client, str, str, str, Dict[str, str]) -> HttpResponse

    message_status = message['status_message']
    if ignore_pull_requests and message['type'] == 'pull_request':
        return json_success()

    if message_status in GOOD_STATUSES:
        emoji = ':thumbsup:'
    elif message_status in BAD_STATUSES:
        emoji = ':thumbsdown:'
    else:
        emoji = "(No emoji specified for status '{}'.)".format(message_status)

    body = MESSAGE_TEMPLATE.format(
        message['author_name'],
        message_status,
        emoji,
        message['compare_url'],
        message['build_url']
    )

    check_send_message(user_profile, client, 'stream', [stream], topic, body)
    return json_success()
Exemple #17
0
def update_storage(request, user_profile, storage=REQ(validator=check_dict([]))):
    # type: (HttpRequest, UserProfile, Optional[Dict[str, str]]) -> HttpResponse
    try:
        set_bot_storage(user_profile, list(storage.items()))
    except StateError as e:
        return json_error(str(e))
    return json_success()
Exemple #18
0
def api_beanstalk_webhook(request, user_profile,
                          payload=REQ(validator=check_dict([])),
                          branches=REQ(default=None)):
    # type: (HttpRequest, UserProfile, Dict[str, Any], Optional[Text]) -> HttpResponse
    # Beanstalk supports both SVN and git repositories
    # We distinguish between the two by checking for a
    # 'uri' key that is only present for git repos
    git_repo = 'uri' in payload
    if git_repo:
        if branches is not None and branches.find(payload['branch']) == -1:
            return json_success()
        # To get a linkable url,
        for commit in payload['commits']:
            commit['author'] = {'username': commit['author']['name']}

        subject, content = build_message_from_gitlog(user_profile, payload['repository']['name'],
                                                     payload['ref'], payload['commits'],
                                                     payload['before'], payload['after'],
                                                     payload['repository']['url'],
                                                     payload['pusher_name'])
    else:
        author = payload.get('author_full_name')
        url = payload.get('changeset_url')
        revision = payload.get('revision')
        (short_commit_msg, _, _) = payload.get('message').partition("\n")

        subject = "svn r%s" % (revision,)
        content = "%s pushed [revision %s](%s):\n\n> %s" % (author, revision, url, short_commit_msg)

    check_send_message(user_profile, get_client("ZulipBeanstalkWebhook"), "stream",
                       ["commits"], subject, content)
    return json_success()
Exemple #19
0
 def check_person(val):
     error = check_dict([
         ['name', check_string],
         ['age', check_int],
     ])('_', val)
     if error:
         return 'This is not a valid person'
Exemple #20
0
def api_travis_webhook(request: HttpRequest, user_profile: UserProfile,
                       ignore_pull_requests: bool = REQ(validator=check_bool, default=True),
                       message: Dict[str, str]=REQ('payload', validator=check_dict([
                           ('author_name', check_string),
                           ('status_message', check_string),
                           ('compare_url', check_string),
                       ]))) -> HttpResponse:

    message_status = message['status_message']
    if ignore_pull_requests and message['type'] == 'pull_request':
        return json_success()

    if message_status in GOOD_STATUSES:
        emoji = ':thumbs_up:'
    elif message_status in BAD_STATUSES:
        emoji = ':thumbs_down:'
    else:
        emoji = "(No emoji specified for status '{}'.)".format(message_status)

    body = MESSAGE_TEMPLATE.format(
        message['author_name'],
        message_status,
        emoji,
        message['compare_url'],
        message['build_url']
    )
    topic = 'builds'

    check_send_webhook_message(request, user_profile, topic, body)
    return json_success()
Exemple #21
0
def json_report_error(request, user_profile, message=REQ(), stacktrace=REQ(),
                      ui_message=REQ(validator=check_bool), user_agent=REQ(),
                      href=REQ(), log=REQ(),
                      more_info=REQ(validator=check_dict([]), default=None)):
    # type: (HttpRequest, UserProfile, text_type, text_type, bool, text_type, text_type, text_type, Dict[str, Any]) -> HttpResponse
    if not settings.ERROR_REPORTING:
        return json_success()

    if js_source_map:
        stacktrace = js_source_map.annotate_stacktrace(stacktrace)

    try:
        version = subprocess.check_output(["git", "log", "HEAD^..HEAD", "--oneline"], universal_newlines=True)
    except Exception:
        version = None

    queue_json_publish('error_reports', dict(
        type = "browser",
        report = dict(
            user_email = user_profile.email,
            user_full_name = user_profile.full_name,
            user_visible = ui_message,
            server_path = settings.DEPLOY_ROOT,
            version = version,
            user_agent = user_agent,
            href = href,
            message = message,
            stacktrace = stacktrace,
            log = log,
            more_info = more_info,
        )
    ), lambda x: None)

    return json_success()
Exemple #22
0
def update_storage(request: HttpRequest, user_profile: UserProfile,
                   storage: Dict[str, str]=REQ(validator=check_dict([]))) -> HttpResponse:
    try:
        set_bot_storage(user_profile, list(storage.items()))
    except StateError as e:
        return json_error(str(e))
    return json_success()
Exemple #23
0
def api_beanstalk_webhook(request, user_profile,
                          payload=REQ(validator=check_dict([]))):
    # Beanstalk supports both SVN and git repositories
    # We distinguish between the two by checking for a
    # 'uri' key that is only present for git repos
    git_repo = 'uri' in payload
    if git_repo:
        # To get a linkable url,
        subject, content = build_message_from_gitlog(user_profile, payload['repository']['name'],
                                                     payload['ref'], payload['commits'],
                                                     payload['before'], payload['after'],
                                                     payload['repository']['url'],
                                                     payload['pusher_name'])
    else:
        author = payload.get('author_full_name')
        url = payload.get('changeset_url')
        revision = payload.get('revision')
        (short_commit_msg, _, _) = payload.get('message').partition("\n")

        subject = "svn r%s" % (revision,)
        content = "%s pushed [revision %s](%s):\n\n> %s" % (author, revision, url, short_commit_msg)

    check_send_message(user_profile, get_client("ZulipBeanstalkWebhook"), "stream",
                       ["commits"], subject, content)
    return json_success()
Exemple #24
0
def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dict([])),
                          stream=REQ(default='commits')):
    # type: (HttpRequest, UserProfile, Dict[str, Any], str) -> None
    repository = payload['repository']
    commits = [{'id': commit['raw_node'], 'message': commit['message'],
                'url': '%s%scommits/%s' % (payload['canon_url'],
                                           repository['absolute_url'],
                                           commit['raw_node'])}
               for commit in payload['commits']]

    subject = repository['name']
    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        content = ("%s [force pushed](%s)"
                   % (payload['user'],
                      payload['canon_url'] + repository['absolute_url']))
    else:
        branch = payload['commits'][-1]['branch']
        content = build_commit_list_content(commits, branch, None, payload['user'])
        subject += '/%s' % (branch,)

    check_send_message(user_profile, get_client("ZulipBitBucketWebhook"), "stream",
                       [stream], subject, content)
    return json_success()
Exemple #25
0
def report_error(request: HttpRequest, user_profile: UserProfile, message: str=REQ(),
                 stacktrace: str=REQ(), ui_message: bool=REQ(validator=check_bool),
                 user_agent: str=REQ(), href: str=REQ(), log: str=REQ(),
                 more_info: Optional[Dict[str, Any]]=REQ(validator=check_dict([]), default=None)
                 ) -> HttpResponse:
    """Accepts an error report and stores in a queue for processing.  The
    actual error reports are later handled by do_report_error (below)"""
    if not settings.BROWSER_ERROR_REPORTING:
        return json_success()
    if more_info is None:
        more_info = {}

    js_source_map = get_js_source_map()
    if js_source_map:
        stacktrace = js_source_map.annotate_stacktrace(stacktrace)

    try:
        version = subprocess.check_output(["git", "log", "HEAD^..HEAD", "--oneline"],
                                          universal_newlines=True)  # type: Optional[str]
    except Exception:
        version = None

    # Get the IP address of the request
    remote_ip = request.META.get('HTTP_X_REAL_IP')
    if remote_ip is None:
        remote_ip = request.META['REMOTE_ADDR']

    # For the privacy of our users, we remove any actual text content
    # in draft_content (from drafts rendering exceptions).  See the
    # comment on privacy_clean_markdown for more details.
    if more_info.get('draft_content'):
        more_info['draft_content'] = privacy_clean_markdown(more_info['draft_content'])

    if user_profile.is_authenticated:
        email = user_profile.delivery_email
        full_name = user_profile.full_name
    else:
        email = "*****@*****.**"
        full_name = "Anonymous User"

    queue_json_publish('error_reports', dict(
        type = "browser",
        report = dict(
            host = request.get_host().split(":")[0],
            ip_address = remote_ip,
            user_email = email,
            user_full_name = full_name,
            user_visible = ui_message,
            server_path = settings.DEPLOY_ROOT,
            version = version,
            user_agent = user_agent,
            href = href,
            message = message,
            stacktrace = stacktrace,
            log = log,
            more_info = more_info,
        )
    ))

    return json_success()
Exemple #26
0
    def convert_term(elem):
        # type: (Union[Dict, List]) -> Dict[str, Any]

        # We have to support a legacy tuple format.
        if isinstance(elem, list):
            if (len(elem) != 2 or
                any(not isinstance(x, str) and not isinstance(x, Text)
                    for x in elem)):
                raise ValueError("element is not a string pair")
            return dict(operator=elem[0], operand=elem[1])

        if isinstance(elem, dict):
            validator = check_dict([
                ('operator', check_string),
                ('operand', check_string),
            ])

            error = validator('elem', elem)
            if error:
                raise JsonableError(error)

            # whitelist the fields we care about for now
            return dict(
                operator=elem['operator'],
                operand=elem['operand'],
                negated=elem.get('negated', False),
            )

        raise ValueError("element is not a dictionary")
Exemple #27
0
def api_bitbucket_webhook(request: HttpRequest, user_profile: UserProfile,
                          payload: Mapping[str, Any]=REQ(validator=check_dict([])),
                          branches: Optional[str]=REQ(default=None)) -> HttpResponse:
    repository = payload['repository']

    commits = [
        {
            'name': payload.get('user'),
            'sha': commit.get('raw_node'),
            'message': commit.get('message'),
            'url': u'{}{}commits/{}'.format(
                payload.get('canon_url'),
                repository.get('absolute_url'),
                commit.get('raw_node'))
        }
        for commit in payload['commits']
    ]

    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        subject = repository['name']
        content = (u"%s [force pushed](%s)"
                   % (payload['user'],
                      payload['canon_url'] + repository['absolute_url']))
    else:
        branch = payload['commits'][-1]['branch']
        if branches is not None and branches.find(branch) == -1:
            return json_success()
        content = get_push_commits_event_message(payload['user'], None, branch, commits)
        subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=repository['name'], branch=branch)

    check_send_webhook_message(request, user_profile, subject, content)
    return json_success()
Exemple #28
0
def update_realm(request, user_profile, name=REQ(validator=check_string, default=None),
                 restricted_to_domain=REQ(validator=check_bool, default=None),
                 invite_required=REQ(validator=check_bool, default=None),
                 invite_by_admins_only=REQ(validator=check_bool, default=None),
                 create_stream_by_admins_only=REQ(validator=check_bool, default=None),
                 add_emoji_by_admins_only=REQ(validator=check_bool, default=None),
                 allow_message_editing=REQ(validator=check_bool, default=None),
                 message_content_edit_limit_seconds=REQ(converter=to_non_negative_int, default=None),
                 default_language=REQ(validator=check_string, default=None),
                 waiting_period_threshold=REQ(converter=to_non_negative_int, default=None),
                 authentication_methods=REQ(validator=check_dict([]), default=None)):
    # type: (HttpRequest, UserProfile, Optional[str], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[bool], Optional[int], Optional[str], Optional[int], Optional[dict]) -> HttpResponse
    # Validation for default_language
    if default_language is not None and default_language not in get_available_language_codes():
        raise JsonableError(_("Invalid language '%s'" % (default_language,)))
    realm = user_profile.realm
    data = {} # type: Dict[str, Any]
    if name is not None and realm.name != name:
        do_set_realm_name(realm, name)
        data['name'] = 'updated'
    if restricted_to_domain is not None and realm.restricted_to_domain != restricted_to_domain:
        do_set_realm_restricted_to_domain(realm, restricted_to_domain)
        data['restricted_to_domain'] = restricted_to_domain
    if invite_required is not None and realm.invite_required != invite_required:
        do_set_realm_invite_required(realm, invite_required)
        data['invite_required'] = invite_required
    if invite_by_admins_only is not None and realm.invite_by_admins_only != invite_by_admins_only:
        do_set_realm_invite_by_admins_only(realm, invite_by_admins_only)
        data['invite_by_admins_only'] = invite_by_admins_only
    if authentication_methods is not None and realm.authentication_methods != authentication_methods:
        if True not in list(authentication_methods.values()):
            return json_error(_("At least one authentication method must be enabled."),
                              data={"reason": "no authentication"}, status=403)
        else:
            do_set_realm_authentication_methods(realm, authentication_methods)
        data['authentication_methods'] = authentication_methods
    if create_stream_by_admins_only is not None and realm.create_stream_by_admins_only != create_stream_by_admins_only:
        do_set_realm_create_stream_by_admins_only(realm, create_stream_by_admins_only)
        data['create_stream_by_admins_only'] = create_stream_by_admins_only
    if add_emoji_by_admins_only is not None and realm.add_emoji_by_admins_only != add_emoji_by_admins_only:
        do_set_realm_add_emoji_by_admins_only(realm, add_emoji_by_admins_only)
        data['add_emoji_by_admins_only'] = add_emoji_by_admins_only
    if (allow_message_editing is not None and realm.allow_message_editing != allow_message_editing) or \
       (message_content_edit_limit_seconds is not None and
            realm.message_content_edit_limit_seconds != message_content_edit_limit_seconds):
        if allow_message_editing is None:
            allow_message_editing = realm.allow_message_editing
        if message_content_edit_limit_seconds is None:
            message_content_edit_limit_seconds = realm.message_content_edit_limit_seconds
        do_set_realm_message_editing(realm, allow_message_editing, message_content_edit_limit_seconds)
        data['allow_message_editing'] = allow_message_editing
        data['message_content_edit_limit_seconds'] = message_content_edit_limit_seconds
    if default_language is not None and realm.default_language != default_language:
        do_set_realm_default_language(realm, default_language)
        data['default_language'] = default_language
    if waiting_period_threshold is not None and realm.waiting_period_threshold != waiting_period_threshold:
        do_set_realm_waiting_period_threshold(realm, waiting_period_threshold)
        data['waiting_period_threshold'] = waiting_period_threshold
    return json_success(data)
Exemple #29
0
 def test_pointer_events(self):
     schema_checker = check_dict([
         ('type', equals('pointer')),
         ('pointer', check_int)
     ])
     events = self.do_test(lambda: do_update_pointer(self.user_profile, 1500))
     error = schema_checker('events[0]', events[0])
     self.assert_on_error(error)
Exemple #30
0
 def test_muted_topics_events(self):
     muted_topics_checker = check_dict([
         ('type', equals('muted_topics')),
         ('muted_topics', check_list(check_list(check_string, 2))),
     ])
     events = self.do_test(lambda: do_set_muted_topics(self.user_profile, [["Denmark", "topic"]]))
     error = muted_topics_checker('events[0]', events[0])
     self.assert_on_error(error)
Exemple #31
0
def json_report_error(request,
                      user_profile,
                      message=REQ(),
                      stacktrace=REQ(),
                      ui_message=REQ(validator=check_bool),
                      user_agent=REQ(),
                      href=REQ(),
                      log=REQ(),
                      more_info=REQ(validator=check_dict([]), default=None)):
    # type: (HttpRequest, UserProfile, Text, Text, bool, Text, Text, Text, Dict[str, Any]) -> HttpResponse
    """Accepts an error report and stores in a queue for processing.  The
    actual error reports are later handled by do_report_error (below)"""
    if not settings.BROWSER_ERROR_REPORTING:
        return json_success()

    js_source_map = get_js_source_map()
    if js_source_map:
        stacktrace = js_source_map.annotate_stacktrace(stacktrace)

    try:
        version = subprocess.check_output(
            ["git", "log", "HEAD^..HEAD", "--oneline"],
            universal_newlines=True)  # type: Optional[Text]
    except Exception:
        version = None

    # Get the IP address of the request
    remote_ip = request.META.get('HTTP_X_REAL_IP')
    if remote_ip is None:
        remote_ip = request.META['REMOTE_ADDR']

    queue_json_publish(
        'error_reports',
        dict(type="browser",
             report=dict(
                 host=request.get_host().split(":")[0],
                 ip_address=remote_ip,
                 user_email=user_profile.email,
                 user_full_name=user_profile.full_name,
                 user_visible=ui_message,
                 server_path=settings.DEPLOY_ROOT,
                 version=version,
                 user_agent=user_agent,
                 href=href,
                 message=message,
                 stacktrace=stacktrace,
                 log=log,
                 more_info=more_info,
             )), lambda x: None)

    return json_success()
Exemple #32
0
def update_user_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    user_id: int,
    full_name: Optional[str] = REQ(default="", validator=check_string),
    is_admin: Optional[bool] = REQ(default=None, validator=check_bool),
    is_guest: Optional[bool] = REQ(default=None, validator=check_bool),
    profile_data: List[Dict[str, Union[int, str, List[int]]]] = REQ(
        default=None, validator=check_list(check_dict([('id', check_int)])))
) -> HttpResponse:
    target = access_user_by_id(user_profile,
                               user_id,
                               allow_deactivated=True,
                               allow_bots=True)

    # This condition is a bit complicated, because the user could
    # already be a guest/admin, or the request could be to make the
    # user a guest/admin.  In any case, the point is that we outright
    # reject requests that would result in a user who is both an admin
    # and a guest.
    if (((is_guest is None and target.is_guest) or is_guest)
            and ((is_admin is None and target.is_realm_admin) or is_admin)):
        return json_error(_("Guests cannot be organization administrators"))

    if is_admin is not None and target.is_realm_admin != is_admin:
        if not is_admin and check_last_admin(user_profile):
            return json_error(
                _('Cannot remove the only organization administrator'))
        do_change_is_admin(target, is_admin)

    if is_guest is not None and target.is_guest != is_guest:
        do_change_is_guest(target, is_guest)

    if (full_name is not None and target.full_name != full_name
            and full_name.strip() != ""):
        # We don't respect `name_changes_disabled` here because the request
        # is on behalf of the administrator.
        check_change_full_name(target, full_name, user_profile)

    if profile_data is not None:
        clean_profile_data = []
        for entry in profile_data:
            if not entry["value"]:
                field_id = entry["id"]
                check_remove_custom_profile_field_value(target, field_id)
            else:
                clean_profile_data.append(entry)
        validate_user_custom_profile_data(target.realm.id, clean_profile_data)
        do_update_user_custom_profile_data(target, clean_profile_data)

    return json_success()
Exemple #33
0
def update_user_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    user_id: int,
    full_name: Optional[str] = REQ(default="", validator=check_string),
    is_admin: Optional[bool] = REQ(default=None, validator=check_bool),
    is_guest: Optional[bool] = REQ(default=None, validator=check_bool),
    profile_data: Optional[List[Dict[str, Union[int, str, List[int]]]]] = REQ(
        default=None, validator=check_list(check_dict([('id', check_int)])))
) -> HttpResponse:
    target = access_user_by_id(user_profile,
                               user_id,
                               allow_deactivated=True,
                               allow_bots=True)

    # Historically, UserProfile had two fields, is_guest and is_realm_admin.
    # This condition protected against situations where update_user_backend
    # could cause both is_guest and is_realm_admin to be set.
    # Once we update the frontend to just send a 'role' value, we can remove this check.
    if (((is_guest is None and target.is_guest) or is_guest)
            and ((is_admin is None and target.is_realm_admin) or is_admin)):
        return json_error(_("Guests cannot be organization administrators"))

    if is_admin is not None and target.is_realm_admin != is_admin:
        if not is_admin and check_last_admin(user_profile):
            return json_error(
                _('Cannot remove the only organization administrator'))
        do_change_is_admin(target, is_admin)

    if is_guest is not None and target.is_guest != is_guest:
        do_change_is_guest(target, is_guest)

    if (full_name is not None and target.full_name != full_name
            and full_name.strip() != ""):
        # We don't respect `name_changes_disabled` here because the request
        # is on behalf of the administrator.
        check_change_full_name(target, full_name, user_profile)

    if profile_data is not None:
        clean_profile_data = []
        for entry in profile_data:
            if not entry["value"]:
                field_id = entry["id"]
                check_remove_custom_profile_field_value(target, field_id)
            else:
                clean_profile_data.append(entry)
        validate_user_custom_profile_data(target.realm.id, clean_profile_data)
        do_update_user_custom_profile_data_if_changed(target,
                                                      clean_profile_data)

    return json_success()
Exemple #34
0
def update_subscription_properties_backend(
        request: HttpRequest, user_profile: UserProfile,
        subscription_data: List[Dict[str, Any]]=REQ(
            validator=check_list(
                check_dict([("stream_id", check_int),
                            ("property", check_string),
                            ("value", check_variable_type([check_string, check_bool]))])
            )
        ),
) -> HttpResponse:
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream_id": "1", "property": "in_home_view", "value": False},
     {"stream_id": "1", "property": "color", "value": "#c2c2c2"}]
    """
    property_converters = {"color": check_string, "in_home_view": check_bool,
                           "desktop_notifications": check_bool,
                           "audible_notifications": check_bool,
                           "push_notifications": check_bool,
                           "pin_to_top": check_bool}
    response_data = []

    for change in subscription_data:
        stream_id = change["stream_id"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(_("Unknown subscription property: %s") % (property,))

        (stream, recipient, sub) = access_stream_by_id(user_profile, stream_id)
        if sub is None:
            return json_error(_("Not subscribed to stream id %d") % (stream_id,))

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream,
                                        property, value)

        response_data.append({'stream_id': stream_id,
                              'property': property,
                              'value': value})

    return json_success({"subscription_data": response_data})
Exemple #35
0
    def convert_term(elem: Union[Dict[str, Any], List[str]]) -> Dict[str, Any]:

        # We have to support a legacy tuple format.
        if isinstance(elem, list):
            if (len(elem) != 2 or any(not isinstance(x, str) for x in elem)):
                raise ValueError("element is not a string pair")
            return dict(operator=elem[0], operand=elem[1])

        if isinstance(elem, dict):
            # Make sure to sync this list to frontend also when adding a new operator.
            # that supports user IDs. Relevant code is located in static/js/message_fetch.js
            # in handle_operators_supporting_id_based_api function where you will need to update
            # operators_supporting_id, or operators_supporting_ids array.
            operators_supporting_id = ['sender', 'group-pm-with', 'stream']
            operators_supporting_ids = ['pm-with']
            operators_non_empty_operand = {'search'}

            operator = elem.get('operator', '')
            if operator in operators_supporting_id:
                operand_validator: Validator[object] = check_string_or_int
            elif operator in operators_supporting_ids:
                operand_validator = check_string_or_int_list
            elif operator in operators_non_empty_operand:
                operand_validator = check_required_string
            else:
                operand_validator = check_string

            validator = check_dict(
                required_keys=[
                    ('operator', check_string),
                    ('operand', operand_validator),
                ],
                optional_keys=[
                    ('negated', check_bool),
                ],
            )

            try:
                validator('elem', elem)
            except ValidationError as error:
                raise JsonableError(error.message)

            # whitelist the fields we care about for now
            return dict(
                operator=elem['operator'],
                operand=elem['operand'],
                negated=elem.get('negated', False),
            )

        raise ValueError("element is not a dictionary")
Exemple #36
0
def update_subscriptions_backend(
        request: HttpRequest, user_profile: UserProfile,
        delete: Iterable[str]=REQ(validator=check_list(check_string), default=[]),
        add: Iterable[Mapping[str, Any]]=REQ(
            validator=check_list(check_dict([('name', check_string)])), default=[]),
) -> HttpResponse:
    if not add and not delete:
        return json_error(_('Nothing to do. Specify at least one of "add" or "delete".'))

    method_kwarg_pairs = [
        (add_subscriptions_backend, dict(streams_raw=add)),
        (remove_subscriptions_backend, dict(streams_raw=delete))
    ]  # type: List[FuncKwargPair]
    return compose_views(request, user_profile, method_kwarg_pairs)
Exemple #37
0
def update_subscriptions_backend(request, user_profile,
                                 delete=REQ(validator=check_list(check_string), default=[]),
                                 add=REQ(validator=check_list(check_dict([['name', check_string]])), default=[])):
    if not add and not delete:
        return json_error('Nothing to do. Specify at least one of "add" or "delete".')

    json_dict = {} # type: Dict[str, Any]
    for method, items in ((add_subscriptions_backend, add), (remove_subscriptions_backend, delete)):
        response = method(request, user_profile, streams_raw=items)
        if response.status_code != 200:
            transaction.rollback()
            return response
        json_dict.update(ujson.loads(response.content))
    return json_success(json_dict)
Exemple #38
0
    def test_alert_words_events(self):
        alert_words_checker = check_dict([
            ('type', equals('alert_words')),
            ('alert_words', check_list(check_string)),
        ])

        events = self.do_test(
            lambda: do_add_alert_words(self.user_profile, ["alert_word"]))
        error = alert_words_checker('events[0]', events[0])
        self.assert_on_error(error)

        events = self.do_test(
            lambda: do_remove_alert_words(self.user_profile, ["alert_word"]))
        error = alert_words_checker('events[0]', events[0])
        self.assert_on_error(error)
Exemple #39
0
    def f(var_name: str, val: object) -> str:
        block = check_dict([
            ("type", type_validator),
            ("text", check_string),
        ], )(var_name, val)

        # We can't use `value_validator=check_string` above to let
        # mypy know this is a str, because there's an optional boolean
        # `emoji` key which can appear -- hence the assert.
        text = block["text"]
        assert isinstance(text, str)

        # Ideally we would escape the content if it was plain text,
        # but out flavor of Markdown doesn't support escapes. :(
        return text
Exemple #40
0
def update_user_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    user_id: int,
    full_name: Optional[str] = REQ(default=None, validator=check_string),
    role: Optional[int] = REQ(default=None,
                              validator=check_int_in(UserProfile.ROLE_TYPES)),
    profile_data: Optional[List[Dict[str, Union[int, str, List[int]]]]] = REQ(
        default=None, validator=check_list(check_dict([('id', check_int)])))
) -> HttpResponse:
    target = access_user_by_id(user_profile,
                               user_id,
                               allow_deactivated=True,
                               allow_bots=True)

    if role is not None and target.role != role:
        if target.role == UserProfile.ROLE_REALM_OWNER and check_last_owner(
                user_profile):
            return json_error(
                _('The owner permission cannot be removed from the only organization owner.'
                  ))
        if UserProfile.ROLE_REALM_OWNER in [
                role, target.role
        ] and not user_profile.is_realm_owner:
            return json_error(
                _('Only organization owners can add or remove the owner permission.'
                  ))
        do_change_user_role(target, role)

    if (full_name is not None and target.full_name != full_name
            and full_name.strip() != ""):
        # We don't respect `name_changes_disabled` here because the request
        # is on behalf of the administrator.
        check_change_full_name(target, full_name, user_profile)

    if profile_data is not None:
        clean_profile_data = []
        for entry in profile_data:
            if not entry["value"]:
                field_id = entry["id"]
                check_remove_custom_profile_field_value(target, field_id)
            else:
                clean_profile_data.append(entry)
        validate_user_custom_profile_data(target.realm.id, clean_profile_data)
        do_update_user_custom_profile_data_if_changed(target,
                                                      clean_profile_data)

    return json_success()
Exemple #41
0
def json_subscription_property(request, user_profile, subscription_data=REQ(
        validator=check_list(
            check_dict([("stream", check_string),
                        ("property", check_string),
                        ("value", check_variable_type(
                            [check_string, check_bool]))])))):
    # type: (HttpRequest, UserProfile, List[Dict[str, Any]]) -> HttpResponse
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream": "devel", "property": "in_home_view", "value": False},
     {"stream": "devel", "property": "color", "value": "#c2c2c2"}]
    """
    if request.method != "POST":
        return json_error(_("Invalid verb"))

    property_converters = {"color": check_string, "in_home_view": check_bool,
                           "desktop_notifications": check_bool,
                           "audible_notifications": check_bool,
                           "pin_to_top": check_bool}
    response_data = []

    for change in subscription_data:
        stream_name = change["stream"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(_("Unknown subscription property: %s") % (property,))

        sub = get_subscription_or_die(stream_name, user_profile)[0]

        property_conversion = property_converters[property](property, value)
        if property_conversion:
            return json_error(property_conversion)

        do_change_subscription_property(user_profile, sub, stream_name,
                                        property, value)

        response_data.append({'stream': stream_name,
                              'property': property,
                              'value': value})

    return json_success({"subscription_data": response_data})
Exemple #42
0
    def test_realm_filter_events(self):
        schema_checker = check_dict([
            ('type', equals('realm_filters')),
            ('realm_filters',
             check_list(None)),  # TODO: validate tuples in the list
        ])
        events = self.do_test(lambda: do_add_realm_filter(
            get_realm("zulip.com"), "#[123]",
            "https://realm.com/my_realm_filter/%(id)s"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)

        self.do_test(
            lambda: do_remove_realm_filter(get_realm("zulip.com"), "#[123]"))
        error = schema_checker('events[0]', events[0])
        self.assert_on_error(error)
Exemple #43
0
def events_register_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    apply_markdown: bool = REQ(default=False, validator=check_bool),
    client_gravatar: bool = REQ(default=False, validator=check_bool),
    all_public_streams: Optional[bool] = REQ(default=None,
                                             validator=check_bool),
    include_subscribers: bool = REQ(default=False, validator=check_bool),
    client_capabilities: Optional[Dict[str, bool]] = REQ(
        validator=check_dict([
            ("notification_settings_null", check_bool),
        ]),
        default=None,
        documentation_pending=True),
    event_types: Optional[Iterable[str]] = REQ(
        validator=check_list(check_string), default=None),
    fetch_event_types: Optional[Iterable[str]] = REQ(
        validator=check_list(check_string), default=None),
    narrow: NarrowT = REQ(validator=check_list(
        check_list(check_string, length=2)),
                          default=[]),
    queue_lifespan_secs: int = REQ(converter=int,
                                   default=0,
                                   documentation_pending=True)
) -> HttpResponse:
    all_public_streams = _default_all_public_streams(user_profile,
                                                     all_public_streams)
    narrow = _default_narrow(user_profile, narrow)

    if client_capabilities is None:
        client_capabilities = {}
    notification_settings_null = client_capabilities.get(
        "notification_settings_null", False)

    ret = do_events_register(
        user_profile,
        request.client,
        apply_markdown,
        client_gravatar,
        event_types,
        queue_lifespan_secs,
        all_public_streams,
        narrow=narrow,
        include_subscribers=include_subscribers,
        notification_settings_null=notification_settings_null,
        fetch_event_types=fetch_event_types)
    return json_success(ret)
Exemple #44
0
def api_bitbucket_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    payload: Mapping[str, Any] = REQ(validator=check_dict([])),
    branches: Optional[str] = REQ(default=None)
) -> HttpResponse:
    repository = payload['repository']

    commits = [{
        'name':
        commit.get('author') or payload.get('user'),
        'sha':
        commit.get('raw_node'),
        'message':
        commit.get('message'),
        'url':
        '{}{}commits/{}'.format(payload.get('canon_url'),
                                repository.get('absolute_url'),
                                commit.get('raw_node')),
    } for commit in payload['commits']]

    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        subject = repository['name']
        content = "{} [force pushed]({}).".format(
            payload.get('user', 'Someone'),
            payload['canon_url'] + repository['absolute_url'],
        )
    else:
        branch = payload['commits'][-1]['branch']
        if branches is not None and branches.find(branch) == -1:
            return json_success()

        committer = payload.get('user')
        content = get_push_commits_event_message(
            committer if committer is not None else 'Someone', None, branch,
            commits)
        subject = TOPIC_WITH_BRANCH_TEMPLATE.format(repo=repository['name'],
                                                    branch=branch)

    check_send_webhook_message(request,
                               user_profile,
                               subject,
                               content,
                               unquote_url_parameters=True)
    return json_success()
Exemple #45
0
def api_bitbucket_webhook(
        request: HttpRequest,
        user_profile: UserProfile,
        payload: Mapping[str, Any] = REQ(json_validator=check_dict([])),
        branches: Optional[str] = REQ(default=None),
) -> HttpResponse:
    repository = payload["repository"]

    commits = [{
        "name":
        commit.get("author") or payload.get("user"),
        "sha":
        commit.get("raw_node"),
        "message":
        commit.get("message"),
        "url":
        "{}{}commits/{}".format(payload.get("canon_url"),
                                repository.get("absolute_url"),
                                commit.get("raw_node")),
    } for commit in payload["commits"]]

    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        subject = repository["name"]
        content = "{} [force pushed]({}).".format(
            payload.get("user", "Someone"),
            payload["canon_url"] + repository["absolute_url"],
        )
    else:
        branch = payload["commits"][-1]["branch"]
        if branches is not None and branches.find(branch) == -1:
            return json_success()

        committer = payload.get("user")
        content = get_push_commits_event_message(
            committer if committer is not None else "Someone", None, branch,
            commits)
        subject = TOPIC_WITH_BRANCH_TEMPLATE.format(repo=repository["name"],
                                                    branch=branch)

    check_send_webhook_message(request,
                               user_profile,
                               subject,
                               content,
                               unquote_url_parameters=True)
    return json_success()
Exemple #46
0
def update_user_custom_profile_data(
    request: HttpRequest,
    user_profile: UserProfile,
    data: List[Dict[str, Union[int, str, List[int]]]] = REQ(
        validator=check_list(
            check_dict(
                [('id', check_int)],
                value_validator=check_union([check_int, check_string, check_list(check_int)]),
            ),
        )
    ),
) -> HttpResponse:

    validate_user_custom_profile_data(user_profile.realm.id, data)
    do_update_user_custom_profile_data_if_changed(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
Exemple #47
0
def api_slack_incoming_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    user_specified_topic: Optional[str] = REQ("topic", default=None),
    payload: Optional[Dict[str, Any]] = REQ("payload",
                                            json_validator=check_dict(),
                                            default=None),
) -> HttpResponse:

    # Slack accepts webhook payloads as payload="encoded json" as
    # application/x-www-form-urlencoded, as well as in the body as
    # application/json. We use has_request_variables to try to get
    # the form encoded version, and parse the body out ourselves if
    # # we were given JSON.
    if payload is None:
        try:
            payload = orjson.loads(request.body)
        except orjson.JSONDecodeError:  # nocoverage
            raise InvalidJSONError(_("Malformed JSON"))

    if user_specified_topic is None and "channel" in payload:
        user_specified_topic = re.sub("^[@#]", "", payload["channel"])

    if user_specified_topic is None:
        user_specified_topic = "(no topic)"

    body = ""

    if "blocks" in payload:
        for block in payload["blocks"]:
            body = add_block(block, body)

    if "attachments" in payload:
        for attachment in payload["attachments"]:
            body = add_attachment(attachment, body)

    if body == "" and "text" in payload and payload["text"] is not None:
        body += payload["text"]
        if "icon_emoji" in payload and payload["icon_emoji"] is not None:
            body = "{} {}".format(payload["icon_emoji"], body)

    if body != "":
        body = replace_formatting(replace_links(body).strip())
        check_send_webhook_message(request, user_profile, user_specified_topic,
                                   body)
    return json_success(request)
Exemple #48
0
def api_bitbucket_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    payload: Mapping[Text, Any] = REQ(validator=check_dict([])),
    stream: Text = REQ(default='commits'),
    branches: Optional[Text] = REQ(default=None)
) -> HttpResponse:
    repository = payload['repository']

    commits = [{
        'name':
        payload.get('user'),
        'sha':
        commit.get('raw_node'),
        'message':
        commit.get('message'),
        'url':
        u'{}{}commits/{}'.format(payload.get('canon_url'),
                                 repository.get('absolute_url'),
                                 commit.get('raw_node'))
    } for commit in payload['commits']]

    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        subject = repository['name']
        content = (u"%s [force pushed](%s)" %
                   (payload['user'],
                    payload['canon_url'] + repository['absolute_url']))
    else:
        branch = payload['commits'][-1]['branch']
        if branches is not None and branches.find(branch) == -1:
            return json_success()
        content = get_push_commits_event_message(payload['user'], None, branch,
                                                 commits)
        subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=repository['name'],
                                                      branch=branch)

    check_send_stream_message(user_profile,
                              get_client("ZulipBitBucketWebhook"), stream,
                              subject, content)
    return json_success()
Exemple #49
0
def complete_zoom_user_in_realm(
    request: HttpRequest,
    code: str = REQ(),
    state: Dict[str, str] = REQ(validator=check_dict([("sid", check_string)], value_validator=check_string)),
) -> HttpResponse:
    if not constant_time_compare(state["sid"], get_zoom_sid(request)):
        raise JsonableError(_("Invalid Zoom session identifier"))

    oauth = get_zoom_session(request.user)
    try:
        token = oauth.fetch_token(
            "https://zoom.us/oauth/token",
            code=code,
            client_secret=settings.VIDEO_ZOOM_CLIENT_SECRET,
        )
    except OAuth2Error:
        raise JsonableError(_("Invalid Zoom credentials"))

    do_set_zoom_token(request.user, token)
    return render(request, "zerver/close_window.html")
Exemple #50
0
def json_report_error(request,
                      user_profile,
                      message=REQ(),
                      stacktrace=REQ(),
                      ui_message=REQ(validator=check_bool),
                      user_agent=REQ(),
                      href=REQ(),
                      log=REQ(),
                      more_info=REQ(validator=check_dict([]), default=None)):
    # type: (HttpRequest, UserProfile, text_type, text_type, bool, text_type, text_type, text_type, Dict[str, Any]) -> HttpResponse
    if not settings.ERROR_REPORTING:
        return json_success()

    if js_source_map:
        stacktrace = js_source_map.annotate_stacktrace(stacktrace)

    try:
        version = subprocess.check_output(
            ["git", "log", "HEAD^..HEAD", "--oneline"],
            universal_newlines=True)
    except Exception:
        version = None

    queue_json_publish(
        'error_reports',
        dict(type="browser",
             report=dict(
                 user_email=user_profile.email,
                 user_full_name=user_profile.full_name,
                 user_visible=ui_message,
                 server_path=settings.DEPLOY_ROOT,
                 version=version,
                 user_agent=user_agent,
                 href=href,
                 message=message,
                 stacktrace=stacktrace,
                 log=log,
                 more_info=more_info,
             )), lambda x: None)

    return json_success()
Exemple #51
0
def update_user_custom_profile_data(
    request,
    user_profile,
    data=REQ(validator=check_list(check_dict([('id', check_int)])))):
    # type: (HttpRequest, UserProfile, List[Dict[str, Union[int, Text]]]) -> HttpResponse
    for item in data:
        field_id = item['id']
        try:
            field = CustomProfileField.objects.get(id=field_id)
        except CustomProfileField.DoesNotExist:
            return json_error(
                _('Field id {id} not found.').format(id=field_id))

        validator = CustomProfileField.FIELD_VALIDATORS[field.field_type]
        result = validator('value[{}]'.format(field_id), item['value'])
        if result is not None:
            return json_error(result)

    do_update_user_custom_profile_data(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
Exemple #52
0
def api_travis_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    ignore_pull_requests: bool = REQ(json_validator=check_bool, default=True),
    message: Dict[str, object] = REQ(
        "payload",
        json_validator=check_dict(
            [
                ("author_name", check_string),
                ("status_message", check_string),
                ("compare_url", check_string),
            ]
        ),
    ),
) -> HttpResponse:
    event = str(message["type"])
    message_status = message["status_message"]
    if ignore_pull_requests and message["type"] == "pull_request":
        return json_success()

    if message_status in GOOD_STATUSES:
        emoji = ":thumbs_up:"
    elif message_status in BAD_STATUSES:
        emoji = ":thumbs_down:"
    elif message_status in PENDING_STATUSES:
        emoji = ":counterclockwise:"
    else:
        emoji = f"(No emoji specified for status '{message_status}'.)"

    body = MESSAGE_TEMPLATE.format(
        message["author_name"],
        message_status,
        emoji,
        message["compare_url"],
        message["build_url"],
    )
    topic = "builds"

    check_send_webhook_message(request, user_profile, topic, body, event)
    return json_success()
Exemple #53
0
def update_user_custom_profile_data(
    request: HttpRequest,
    user_profile: UserProfile,
    data: List[Dict[str, Union[int, str, List[int]]]] = REQ(
        validator=check_list(check_dict([('id', check_int)])))
) -> HttpResponse:
    for item in data:
        field_id = item['id']
        try:
            field = CustomProfileField.objects.get(id=field_id)
        except CustomProfileField.DoesNotExist:
            return json_error(
                _('Field id {id} not found.').format(id=field_id))

        validators = CustomProfileField.FIELD_VALIDATORS
        field_type = field.field_type
        var_name = '{}'.format(field.name)
        value = item['value']
        if field_type in validators:
            validator = validators[field_type]
            result = validator(var_name, value)
        elif field_type == CustomProfileField.CHOICE:
            choice_field_validator = CustomProfileField.CHOICE_FIELD_VALIDATORS[
                field_type]
            field_data = field.field_data
            result = choice_field_validator(var_name, field_data, value)
        elif field_type == CustomProfileField.USER:
            user_field_validator = CustomProfileField.USER_FIELD_VALIDATORS[
                field_type]
            result = user_field_validator(user_profile.realm.id,
                                          cast(List[int], value), False)
        else:
            raise AssertionError("Invalid field type")

        if result is not None:
            return json_error(result)

    do_update_user_custom_profile_data(user_profile, data)
    # We need to call this explicitly otherwise constraints are not check
    return json_success()
Exemple #54
0
def api_beanstalk_webhook(
        request: HttpRequest,
        user_profile: UserProfile,
        payload: Dict[str, Any] = REQ(json_validator=check_dict([])),
        branches: Optional[str] = REQ(default=None),
) -> HttpResponse:
    # Beanstalk supports both SVN and Git repositories
    # We distinguish between the two by checking for a
    # 'uri' key that is only present for Git repos
    git_repo = "uri" in payload
    if git_repo:
        if branches is not None and branches.find(payload["branch"]) == -1:
            return json_success()
        # To get a linkable url,
        for commit in payload["commits"]:
            commit["author"] = {"username": commit["author"]["name"]}

        subject, content = build_message_from_gitlog(
            user_profile,
            payload["repository"]["name"],
            payload["ref"],
            payload["commits"],
            payload["before"],
            payload["after"],
            payload["repository"]["url"],
            payload["pusher_name"],
        )
    else:
        author = payload.get("author_full_name")
        url = payload.get("changeset_url")
        revision = payload.get("revision")
        (short_commit_msg, _, _) = payload["message"].partition("\n")

        subject = f"svn r{revision}"
        content = f"{author} pushed [revision {revision}]({url}):\n\n> {short_commit_msg}"

    check_send_webhook_message(request, user_profile, subject, content)
    return json_success()
Exemple #55
0
def update_subscriptions_backend(request,
                                 user_profile,
                                 delete=REQ(validator=check_list(check_string),
                                            default=[]),
                                 add=REQ(validator=check_list(
                                     check_dict([('name', check_string)])),
                                         default=[])):
    # type: (HttpRequest, UserProfile, Iterable[text_type], Iterable[Mapping[str, Any]]) -> HttpResponse
    if not add and not delete:
        return json_error(
            _('Nothing to do. Specify at least one of "add" or "delete".'))

    json_dict = {}  # type: Dict[str, Any]
    method_items_pairs = ((add_subscriptions_backend,
                           add), (remove_subscriptions_backend, delete)
                          )  # type: Tuple[FuncItPair, FuncItPair]
    for method, items in method_items_pairs:
        response = method(request, user_profile, streams_raw=items)
        if response.status_code != 200:
            transaction.rollback()
            return response
        json_dict.update(ujson.loads(response.content))
    return json_success(json_dict)
Exemple #56
0
def api_librato_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    payload: Dict[str, Any] = REQ(json_validator=check_dict(), default={}),
) -> HttpResponse:
    try:
        attachments = orjson.loads(request.body).get("attachments", [])
    except orjson.JSONDecodeError:
        attachments = []

    if not attachments and not payload:
        raise JsonableError(_("Malformed JSON input"))

    message_handler = LibratoWebhookHandler(payload, attachments)
    topic = message_handler.generate_topic()

    try:
        content = message_handler.handle()
    except Exception as e:
        raise JsonableError(str(e))

    check_send_webhook_message(request, user_profile, topic, content)
    return json_success(request)
Exemple #57
0
def api_travis_webhook(request,
                       user_profile,
                       client,
                       stream=REQ(default='travis'),
                       topic=REQ(default=None),
                       message=REQ('payload',
                                   validator=check_dict([
                                       ('author_name', check_string),
                                       ('status_message', check_string),
                                       ('compare_url', check_string),
                                   ]))):
    # type: (HttpRequest, UserProfile, Client, str, str, Dict[str, str]) -> HttpResponse
    author = message['author_name']
    message_type = message['status_message']
    changes = message['compare_url']

    good_status = ['Passed', 'Fixed']
    bad_status = ['Failed', 'Broken', 'Still Failing']
    emoji = ''
    if message_type in good_status:
        emoji = ':thumbsup:'
    elif message_type in bad_status:
        emoji = ':thumbsdown:'
    else:
        emoji = "(No emoji specified for status '%s'.)" % (message_type, )

    build_url = message['build_url']

    template = (u'Author: %s\n'
                u'Build status: %s %s\n'
                u'Details: [changes](%s), [build log](%s)')

    body = template % (author, message_type, emoji, changes, build_url)

    check_send_message(user_profile, client, 'stream', [stream], topic, body)
    return json_success()
Exemple #58
0
def api_travis_webhook(
    request: HttpRequest,
    user_profile: UserProfile,
    ignore_pull_requests: bool = REQ(validator=check_bool, default=True),
    message: Dict[str, object] = REQ('payload',
                                     validator=check_dict([
                                         ('author_name', check_string),
                                         ('status_message', check_string),
                                         ('compare_url', check_string),
                                     ]))
) -> HttpResponse:

    message_status = message['status_message']
    if ignore_pull_requests and message['type'] == 'pull_request':
        return json_success()

    if message_status in GOOD_STATUSES:
        emoji = ':thumbs_up:'
    elif message_status in BAD_STATUSES:
        emoji = ':thumbs_down:'
    elif message_status in PENDING_STATUSES:
        emoji = ':counterclockwise:'
    else:
        emoji = f"(No emoji specified for status '{message_status}'.)"

    body = MESSAGE_TEMPLATE.format(
        message['author_name'],
        message_status,
        emoji,
        message['compare_url'],
        message['build_url'],
    )
    topic = 'builds'

    check_send_webhook_message(request, user_profile, topic, body)
    return json_success()
Exemple #59
0
def api_bitbucket_webhook(request, user_profile, payload=REQ(validator=check_dict([])),
                          stream=REQ(default='commits')):
    repository = payload['repository']
    commits = [{'id': commit['raw_node'], 'message': commit['message'],
                'url': '%s%scommits/%s' % (payload['canon_url'],
                                           repository['absolute_url'],
                                           commit['raw_node'])}
               for commit in payload['commits']]

    subject = repository['name']
    if len(commits) == 0:
        # Bitbucket doesn't give us enough information to really give
        # a useful message :/
        content = ("%s [force pushed](%s)"
                   % (payload['user'],
                      payload['canon_url'] + repository['absolute_url']))
    else:
        branch = payload['commits'][-1]['branch']
        content = build_commit_list_content(commits, branch, None, payload['user'])
        subject += '/%s' % (branch,)

    check_send_message(user_profile, get_client("ZulipBitBucketWebhook"), "stream",
                       [stream], subject, content)
    return json_success()
Exemple #60
0
def update_subscription_properties_backend(
    request: HttpRequest,
    user_profile: UserProfile,
    subscription_data: List[Dict[str, Any]] = REQ(json_validator=check_list(
        check_dict([
            ("stream_id", check_int),
            ("property", check_string),
            ("value", check_union([check_string, check_bool])),
        ]), ), ),
) -> HttpResponse:
    """
    This is the entry point to changing subscription properties. This
    is a bulk endpoint: requestors always provide a subscription_data
    list containing dictionaries for each stream of interest.

    Requests are of the form:

    [{"stream_id": "1", "property": "is_muted", "value": False},
     {"stream_id": "1", "property": "color", "value": "#c2c2c2"}]
    """
    property_converters = {
        "color": check_color,
        "in_home_view": check_bool,
        "is_muted": check_bool,
        "desktop_notifications": check_bool,
        "audible_notifications": check_bool,
        "push_notifications": check_bool,
        "email_notifications": check_bool,
        "pin_to_top": check_bool,
        "wildcard_mentions_notify": check_bool,
    }
    response_data = []

    for change in subscription_data:
        stream_id = change["stream_id"]
        property = change["property"]
        value = change["value"]

        if property not in property_converters:
            return json_error(
                _("Unknown subscription property: {}").format(property))

        (stream, sub) = access_stream_by_id(user_profile, stream_id)
        if sub is None:
            return json_error(
                _("Not subscribed to stream id {}").format(stream_id))

        try:
            value = property_converters[property](property, value)
        except ValidationError as error:
            return json_error(error.message)

        do_change_subscription_property(user_profile,
                                        sub,
                                        stream,
                                        property,
                                        value,
                                        acting_user=user_profile)

        response_data.append({
            "stream_id": stream_id,
            "property": property,
            "value": value
        })

    return json_success({"subscription_data": response_data})