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)
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()
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)
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')
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)
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), ])), ])
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)
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)
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')
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)
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)
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()
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()
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()
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()
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()
def check_person(val): error = check_dict([ ['name', check_string], ['age', check_int], ])('_', val) if error: return 'This is not a valid person'
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()
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()
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()
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()
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()
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()
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")
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()
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)
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)
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)
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()
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()
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()
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})
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")
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)
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)
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)
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
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()
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})
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)
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)
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()
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()
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()
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)
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()
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")
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()
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()
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()
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()
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()
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)
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)
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()
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()
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()
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})