def test_check_variable_type(self): # type: () -> None x = 5 # type: Any self.assertEqual(check_variable_type([check_string, check_int])('x', x), None) x = 'x' self.assertEqual(check_variable_type([check_string, check_int])('x', x), None) x = [{}] self.assertEqual(check_variable_type([check_string, check_int])('x', x), 'x is not an allowed_type')
def test_check_variable_type(self): # type: () -> None x = 5 # type: Any self.assertEqual(check_variable_type([check_string, check_int])('x', x), None) x = 'x' self.assertEqual(check_variable_type([check_string, check_int])('x', x), None) x = [{}] self.assertEqual(check_variable_type([check_string, check_int])('x', x), 'x is not an allowed_type')
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": "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: %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 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, )) (stream, recipient, sub) = access_stream_by_name(user_profile, stream_name) if sub is None: return json_error( _("Not subscribed to stream %s") % (stream_name, )) 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': stream_name, 'property': property, 'value': value }) return json_success({"subscription_data": response_data})
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 remove_subscriptions_backend( request: HttpRequest, user_profile: UserProfile, streams_raw: Iterable[str] = REQ("subscriptions", validator=check_list(check_string)), principals: Optional[Union[List[str], List[int]]] = REQ( validator=check_variable_type( [check_list(check_string), check_list(check_int)]), default=None), ) -> HttpResponse: # TODO: Extract this as a function to improve readability removing_someone_else = False if principals is not None and len(principals) > 0: if len(principals) == 1: if isinstance(principals[0], int): removing_someone_else = principals[0] != user_profile.id else: removing_someone_else = principals[0] != user_profile.email else: # nocoverage # TODO: Fix me. removing_someone_else = True if removing_someone_else and not user_profile.is_realm_admin: # You can only unsubscribe other people from a stream if you are a realm # admin (whether the stream is public or private). return json_error(_("This action requires administrative rights")) streams_as_dict = [] for stream_name in streams_raw: streams_as_dict.append({"name": stream_name.strip()}) streams, __ = list_to_streams(streams_as_dict, user_profile) if principals: people_to_unsub = { principal_to_user_profile(user_profile, principal) for principal in principals } else: people_to_unsub = {user_profile} result: Dict[str, List[str]] = dict(removed=[], not_removed=[]) (removed, not_subscribed) = bulk_remove_subscriptions(people_to_unsub, streams, request.client, acting_user=user_profile) for (subscriber, removed_stream) in removed: result["removed"].append(removed_stream.name) for (subscriber, not_subscribed_stream) in not_subscribed: result["not_removed"].append(not_subscribed_stream.name) return json_success(result)
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])]])))): """ 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 } 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 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_color, "in_home_view": check_bool, "desktop_notifications": check_bool, "audible_notifications": check_bool, "push_notifications": check_bool, "email_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 add_subscriptions_backend( request: HttpRequest, user_profile: UserProfile, streams_raw: Iterable[Dict[str, str]]=REQ( "subscriptions", validator=check_list(check_dict_only( [('name', check_string)], optional_keys=[ ('color', check_color), ('description', check_capped_string(Stream.MAX_DESCRIPTION_LENGTH)), ]), )), invite_only: bool=REQ(validator=check_bool, default=False), stream_post_policy: int=REQ(validator=check_int_in( Stream.STREAM_POST_POLICY_TYPES), default=Stream.STREAM_POST_POLICY_EVERYONE), history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None), announce: bool=REQ(validator=check_bool, default=False), principals: Sequence[Union[str, int]]=REQ(validator=check_variable_type([ check_list(check_string), check_list(check_int)]), default=[]), authorization_errors_fatal: bool=REQ(validator=check_bool, default=True), ) -> HttpResponse: stream_dicts = [] color_map = {} for stream_dict in streams_raw: # 'color' field is optional # check for its presence in the streams_raw first if 'color' in stream_dict: color_map[stream_dict['name']] = stream_dict['color'] if 'description' in stream_dict: # We don't allow newline characters in stream descriptions. stream_dict['description'] = stream_dict['description'].replace("\n", " ") stream_dict_copy: Dict[str, Any] = {} for field in stream_dict: stream_dict_copy[field] = stream_dict[field] # Strip the stream name here. stream_dict_copy['name'] = stream_dict_copy['name'].strip() stream_dict_copy["invite_only"] = invite_only stream_dict_copy["stream_post_policy"] = stream_post_policy stream_dict_copy["history_public_to_subscribers"] = history_public_to_subscribers stream_dicts.append(stream_dict_copy) # Validation of the streams arguments, including enforcement of # can_create_streams policy and check_stream_name policy is inside # list_to_streams. existing_streams, created_streams = \ list_to_streams(stream_dicts, user_profile, autocreate=True) authorized_streams, unauthorized_streams = \ filter_stream_authorization(user_profile, existing_streams) if len(unauthorized_streams) > 0 and authorization_errors_fatal: return json_error(_("Unable to access stream ({stream_name}).").format( stream_name=unauthorized_streams[0].name, )) # Newly created streams are also authorized for the creator streams = authorized_streams + created_streams if len(principals) > 0: if user_profile.realm.is_zephyr_mirror_realm and not all(stream.invite_only for stream in streams): return json_error(_("You can only invite other Zephyr mirroring users to private streams.")) if not user_profile.can_subscribe_other_users(): if user_profile.realm.invite_to_stream_policy == Realm.POLICY_ADMINS_ONLY: return json_error(_("Only administrators can modify other users' subscriptions.")) # Realm.POLICY_MEMBERS_ONLY only fails if the # user is a guest, which happens in the decorator above. assert user_profile.realm.invite_to_stream_policy == \ Realm.POLICY_FULL_MEMBERS_ONLY return json_error(_("Your account is too new to modify other users' subscriptions.")) subscribers = {principal_to_user_profile(user_profile, principal) for principal in principals} else: subscribers = {user_profile} (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers, acting_user=user_profile, color_map=color_map) # We can assume unique emails here for now, but we should eventually # convert this function to be more id-centric. email_to_user_profile: Dict[str, UserProfile] = dict() result: Dict[str, Any] = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) for (subscriber, stream) in subscribed: result["subscribed"][subscriber.email].append(stream.name) email_to_user_profile[subscriber.email] = subscriber for (subscriber, stream) in already_subscribed: result["already_subscribed"][subscriber.email].append(stream.name) bots = {subscriber.email: subscriber.is_bot for subscriber in subscribers} newly_created_stream_names = {s.name for s in created_streams} # Inform the user if someone else subscribed them to stuff, # or if a new stream was created with the "announce" option. notifications = [] if len(principals) > 0 and result["subscribed"]: for email, subscribed_stream_names in result["subscribed"].items(): if email == user_profile.email: # Don't send a Zulip if you invited yourself. continue if bots[email]: # Don't send invitation Zulips to bots continue # For each user, we notify them about newly subscribed streams, except for # streams that were newly created. notify_stream_names = set(subscribed_stream_names) - newly_created_stream_names if not notify_stream_names: continue msg = you_were_just_subscribed_message( acting_user=user_profile, stream_names=notify_stream_names, ) sender = get_system_bot(settings.NOTIFICATION_BOT) notifications.append( internal_prep_private_message( realm=user_profile.realm, sender=sender, recipient_user=email_to_user_profile[email], content=msg)) if announce and len(created_streams) > 0: notifications_stream = user_profile.realm.get_notifications_stream() if notifications_stream is not None: if len(created_streams) > 1: content = _("@_**%(user_name)s|%(user_id)d** created the following streams: %(stream_str)s.") else: content = _("@_**%(user_name)s|%(user_id)d** created a new stream %(stream_str)s.") content = content % { 'user_name': user_profile.full_name, 'user_id': user_profile.id, 'stream_str': ", ".join(f'#**{s.name}**' for s in created_streams)} sender = get_system_bot(settings.NOTIFICATION_BOT) topic = _('new streams') notifications.append( internal_prep_stream_message( realm=user_profile.realm, sender=sender, stream=notifications_stream, topic=topic, content=content, ), ) if not user_profile.realm.is_zephyr_mirror_realm and len(created_streams) > 0: sender = get_system_bot(settings.NOTIFICATION_BOT) for stream in created_streams: notifications.append( internal_prep_stream_message( realm=user_profile.realm, sender=sender, stream=stream, topic=Realm.STREAM_EVENTS_NOTIFICATION_TOPIC, content=_('Stream created by @_**{user_name}|{user_id}**.').format( user_name=user_profile.full_name, user_id=user_profile.id, ), ), ) if len(notifications) > 0: do_send_messages(notifications, mark_as_read=[user_profile.id]) result["subscribed"] = dict(result["subscribed"]) result["already_subscribed"] = dict(result["already_subscribed"]) if not authorization_errors_fatal: result["unauthorized"] = [s.name for s in unauthorized_streams] return json_success(result)