def is_public_stream(stream, realm): if not valid_stream_name(stream): raise JsonableError(_("Invalid stream name")) stream = get_stream(stream, realm) if stream is None: return False return stream.is_public()
def is_public_stream(stream, realm): if not valid_stream_name(stream): raise JsonableError("Invalid stream name") stream = get_stream(stream, realm) if stream is None: return False return stream.is_public()
def list_to_streams(streams_raw, user_profile, autocreate=False, invite_only=False): # type: (Iterable[text_type], UserProfile, Optional[bool], Optional[bool]) -> Tuple[List[Stream], List[Stream]] """Converts plaintext stream names to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name: that is, that it is shorter than Stream.MAX_NAME_LENGTH characters and passes valid_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream names to process @param user_profile The user for whom we are retreiving the streams @param autocreate Whether we should create streams if they don't already exist @param invite_only Whether newly created streams should have the invite_only bit set """ # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = set(stream_name.strip() for stream_name in streams_raw) for stream_name in stream_set: if len(stream_name) > Stream.MAX_NAME_LENGTH: raise JsonableError(_("Stream name (%s) too long.") % (stream_name,)) if not valid_stream_name(stream_name): raise JsonableError(_("Invalid stream name (%s).") % (stream_name,)) existing_streams = [] # type: List[Stream] missing_stream_names = [] # type: List[text_type] existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) for stream_name in stream_set: stream = existing_stream_map.get(stream_name.lower()) if stream is None: missing_stream_names.append(stream_name) else: existing_streams.append(stream) if not missing_stream_names: # This is the happy path for callers who expected all of these # streams to exist already. created_streams = [] # type: List[Stream] else: # autocreate=True path starts here if not user_profile.can_create_streams(): raise JsonableError(_('User cannot create streams.')) elif not autocreate: raise JsonableError(_("Stream(s) (%s) do not exist") % ", ".join(missing_stream_names)) # We already filtered out existing streams, so dup_streams # will normally be an empty list below, but we protect against somebody # else racing to create the same stream. (This is not an entirely # paranoid approach, since often on Zulip two people will discuss # creating a new stream, and both people eagerly do it.) created_streams, dup_streams = create_streams_if_needed(realm=user_profile.realm, stream_names=missing_stream_names, invite_only=invite_only) existing_streams += dup_streams return existing_streams, created_streams
def list_to_streams(streams_raw, user_profile, autocreate=False, invite_only=False): # type: (List[str], UserProfile, Optional[bool], Optional[bool]) -> Tuple[List[Stream], List[Stream]] """Converts plaintext stream names to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name: that is, that it is shorter than Stream.MAX_NAME_LENGTH characters and passes valid_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream names to process @param user_profile The user for whom we are retreiving the streams @param autocreate Whether we should create streams if they don't already exist @param invite_only Whether newly created streams should have the invite_only bit set """ existing_streams = [] created_streams = [] # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = set(stream_name.strip() for stream_name in streams_raw) rejects = [] for stream_name in stream_set: if len(stream_name) > Stream.MAX_NAME_LENGTH: raise JsonableError( _("Stream name (%s) too long.") % (stream_name, )) if not valid_stream_name(stream_name): raise JsonableError( _("Invalid stream name (%s).") % (stream_name, )) existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) for stream_name in stream_set: stream = existing_stream_map.get(stream_name.lower()) if stream is None: rejects.append(stream_name) else: existing_streams.append(stream) if rejects: if not user_profile.can_create_streams(): raise JsonableError(_('User cannot create streams.')) elif not autocreate: raise JsonableError( _("Stream(s) (%s) do not exist") % ", ".join(rejects)) for stream_name in rejects: stream, created = create_stream_if_needed(user_profile.realm, stream_name, invite_only=invite_only) if created: created_streams.append(stream) else: existing_streams.append(stream) return existing_streams, created_streams
def json_stream_exists(request, user_profile, stream=REQ(), autosubscribe=REQ(default=False)): # type: (HttpRequest, UserProfile, Text, bool) -> HttpResponse if not valid_stream_name(stream): return json_error(_("Invalid characters in stream name")) try: stream_id = Stream.objects.get(realm=user_profile.realm, name=stream).id except Stream.DoesNotExist: stream_id = None return stream_exists_backend(request, user_profile, stream_id, autosubscribe)
def list_to_streams(streams_raw, user_profile, autocreate=False, invite_only=False): # type: (Iterable[text_type], UserProfile, Optional[bool], Optional[bool]) -> Tuple[List[Stream], List[Stream]] """Converts plaintext stream names to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name: that is, that it is shorter than Stream.MAX_NAME_LENGTH characters and passes valid_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream names to process @param user_profile The user for whom we are retreiving the streams @param autocreate Whether we should create streams if they don't already exist @param invite_only Whether newly created streams should have the invite_only bit set """ existing_streams = [] created_streams = [] # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = set(stream_name.strip() for stream_name in streams_raw) rejects = [] for stream_name in stream_set: if len(stream_name) > Stream.MAX_NAME_LENGTH: raise JsonableError(_("Stream name (%s) too long.") % (stream_name,)) if not valid_stream_name(stream_name): raise JsonableError(_("Invalid stream name (%s).") % (stream_name,)) existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) for stream_name in stream_set: stream = existing_stream_map.get(stream_name.lower()) if stream is None: rejects.append(stream_name) else: existing_streams.append(stream) if rejects: if not user_profile.can_create_streams(): raise JsonableError(_('User cannot create streams.')) elif not autocreate: raise JsonableError(_("Stream(s) (%s) do not exist") % ", ".join(rejects)) for stream_name in rejects: stream, created = create_stream_if_needed(user_profile.realm, stream_name, invite_only=invite_only) if created: created_streams.append(stream) else: # We already checked for existing streams above; this # next line is present to handle races where a stream # was created while this function was executing. existing_streams.append(stream) return existing_streams, created_streams
def stream_exists_backend(request, user_profile, stream_name, autosubscribe): if not valid_stream_name(stream_name): return json_error("Invalid characters in stream name") stream = get_stream(stream_name, user_profile.realm) result = {"exists": bool(stream)} if stream is not None: recipient = get_recipient(Recipient.STREAM, stream.id) if autosubscribe: bulk_add_subscriptions([stream], [user_profile]) result["subscribed"] = Subscription.objects.filter( user_profile=user_profile, recipient=recipient, active=True).exists() return json_success(result) # results are ignored for HEAD requests return json_response(data=result, status=404)
def stream_exists_backend(request, user_profile, stream_name, autosubscribe): if not valid_stream_name(stream_name): return json_error("Invalid characters in stream name") stream = get_stream(stream_name, user_profile.realm) result = {"exists": bool(stream)} if stream is not None: recipient = get_recipient(Recipient.STREAM, stream.id) if autosubscribe: bulk_add_subscriptions([stream], [user_profile]) result["subscribed"] = Subscription.objects.filter(user_profile=user_profile, recipient=recipient, active=True).exists() return json_success(result) # results are ignored for HEAD requests return json_response(data=result, status=404)
def stream_exists_backend(request, user_profile, stream_name, autosubscribe): # type: (HttpRequest, UserProfile, text_type, bool) -> HttpResponse if not valid_stream_name(stream_name): return json_error(_("Invalid characters in stream name")) stream = get_stream(stream_name, user_profile.realm) result = {"exists": bool(stream)} if stream is not None: recipient = get_recipient(Recipient.STREAM, stream.id) if autosubscribe: bulk_add_subscriptions([stream], [user_profile]) result["subscribed"] = is_active_subscriber(user_profile=user_profile, recipient=recipient) return json_success(result) # results are ignored for HEAD requests return json_response(data=result, status=404)
def stream_exists_backend(request, user_profile, stream_name, autosubscribe): # type: (HttpRequest, UserProfile, text_type, bool) -> HttpResponse if not valid_stream_name(stream_name): return json_error(_("Invalid characters in stream name")) stream = get_stream(stream_name, user_profile.realm) result = {"exists": bool(stream)} if stream is not None: recipient = get_recipient(Recipient.STREAM, stream.id) if autosubscribe: bulk_add_subscriptions([stream], [user_profile]) result["subscribed"] = is_active_subscriber( user_profile=user_profile, recipient=recipient) return json_success(result) # results are ignored for HEAD requests return json_response(data=result, status=404)
def list_to_streams(streams_raw, user_profile, autocreate=False, invite_only=False): # type: (Iterable[text_type], UserProfile, Optional[bool], Optional[bool]) -> Tuple[List[Stream], List[Stream]] """Converts plaintext stream names to a list of Streams, validating input in the process For each stream name, we validate it to ensure it meets our requirements for a proper stream name: that is, that it is shorter than Stream.MAX_NAME_LENGTH characters and passes valid_stream_name. This function in autocreate mode should be atomic: either an exception will be raised during a precheck, or all the streams specified will have been created if applicable. @param streams_raw The list of stream names to process @param user_profile The user for whom we are retreiving the streams @param autocreate Whether we should create streams if they don't already exist @param invite_only Whether newly created streams should have the invite_only bit set """ # Validate all streams, getting extant ones, then get-or-creating the rest. stream_set = set(stream_name.strip() for stream_name in streams_raw) for stream_name in stream_set: if len(stream_name) > Stream.MAX_NAME_LENGTH: raise JsonableError( _("Stream name (%s) too long.") % (stream_name, )) if not valid_stream_name(stream_name): raise JsonableError( _("Invalid stream name (%s).") % (stream_name, )) existing_streams = [] # type: List[Stream] missing_stream_names = [] # type: List[text_type] existing_stream_map = bulk_get_streams(user_profile.realm, stream_set) for stream_name in stream_set: stream = existing_stream_map.get(stream_name.lower()) if stream is None: missing_stream_names.append(stream_name) else: existing_streams.append(stream) if not missing_stream_names: # This is the happy path for callers who expected all of these # streams to exist already. created_streams = [] # type: List[Stream] else: # autocreate=True path starts here if not user_profile.can_create_streams(): raise JsonableError(_('User cannot create streams.')) elif not autocreate: raise JsonableError( _("Stream(s) (%s) do not exist") % ", ".join(missing_stream_names)) # We already filtered out existing streams, so dup_streams # will normally be an empty list below, but we protect against somebody # else racing to create the same stream. (This is not an entirely # paranoid approach, since often on Zulip two people will discuss # creating a new stream, and both people eagerly do it.) created_streams, dup_streams = create_streams_if_needed( realm=user_profile.realm, stream_names=missing_stream_names, invite_only=invite_only) existing_streams += dup_streams return existing_streams, created_streams
def add_subscriptions_backend( request, user_profile, streams_raw=REQ("subscriptions", validator=check_list(check_dict([('name', check_string) ]))), invite_only=REQ(validator=check_bool, default=False), announce=REQ(validator=check_bool, default=False), principals=REQ(validator=check_list(check_string), default=None), authorization_errors_fatal=REQ(validator=check_bool, default=True)): # type: (HttpRequest, UserProfile, Iterable[Mapping[str, text_type]], bool, bool, Optional[List[text_type]], bool) -> HttpResponse stream_names = [] for stream_dict in streams_raw: stream_name = stream_dict["name"].strip() if len(stream_name) > Stream.MAX_NAME_LENGTH: return json_error( _("Stream name (%s) too long.") % (stream_name, )) if not valid_stream_name(stream_name): return json_error(_("Invalid stream name (%s).") % (stream_name, )) stream_names.append(stream_name) # Enforcement of can_create_streams policy is inside list_to_streams. existing_streams, created_streams = \ list_to_streams(stream_names, user_profile, autocreate=True, invite_only=invite_only) 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 (%s).") % unauthorized_streams[0].name) # Newly created streams are also authorized for the creator streams = authorized_streams + created_streams if principals is not None: 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 invite-only streams." )) subscribers = set( principal_to_user_profile(user_profile, principal) for principal in principals) else: subscribers = set([user_profile]) (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers) result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) # type: Dict[str, Any] for (subscriber, stream) in subscribed: result["subscribed"][subscriber.email].append(stream.name) for (subscriber, stream) in already_subscribed: result["already_subscribed"][subscriber.email].append(stream.name) private_streams = dict( (stream.name, stream.invite_only) for stream in streams) bots = dict( (subscriber.email, subscriber.is_bot) for subscriber in subscribers) # Inform the user if someone else subscribed them to stuff, # or if a new stream was created with the "announce" option. notifications = [] if principals and result["subscribed"]: for email, subscriptions in six.iteritems(result["subscribed"]): 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 if len(subscriptions) == 1: msg = ("Hi there! We thought you'd like to know that %s just " "subscribed you to the%s stream [%s](%s)." % ( user_profile.full_name, " **invite-only**" if private_streams[subscriptions[0]] else "", subscriptions[0], stream_link(subscriptions[0]), )) else: msg = ("Hi there! We thought you'd like to know that %s just " "subscribed you to the following streams: \n\n" % (user_profile.full_name, )) for stream in subscriptions: msg += "* [%s](%s)%s\n" % (stream, stream_link(stream), " (**invite-only**)" if private_streams[stream] else "") if len([s for s in subscriptions if not private_streams[s]]) > 0: msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it." notifications.append( internal_prep_message(settings.NOTIFICATION_BOT, "private", email, "", msg)) if announce and len(created_streams) > 0: notifications_stream = user_profile.realm.notifications_stream if notifications_stream is not None: if len(created_streams) > 1: stream_msg = "the following streams: %s" % \ (", ".join('`%s`' % (s.name,) for s in created_streams),) else: stream_msg = "a new stream `%s`" % (created_streams[0].name) stream_buttons = ' '.join( stream_button(s.name) for s in created_streams) msg = ("%s just created %s. %s" % (user_profile.full_name, stream_msg, stream_buttons)) notifications.append( internal_prep_message(settings.NOTIFICATION_BOT, "stream", notifications_stream.name, "Streams", msg, realm=notifications_stream.realm)) else: msg = ("Hi there! %s just created a new stream '%s'. %s" % (user_profile.full_name, created_streams[0].name, stream_button(created_streams[0].name))) for realm_user_dict in get_active_user_dicts_in_realm( user_profile.realm): # Don't announce to yourself or to people you explicitly added # (who will get the notification above instead). if realm_user_dict['email'] in principals or realm_user_dict[ 'email'] == user_profile.email: continue notifications.append( internal_prep_message(settings.NOTIFICATION_BOT, "private", realm_user_dict['email'], "", msg)) if len(notifications) > 0: do_send_messages(notifications) result["subscribed"] = dict(result["subscribed"]) result["already_subscribed"] = dict(result["already_subscribed"]) if not authorization_errors_fatal: result["unauthorized"] = [ stream.name for stream in unauthorized_streams ] return json_success(result)
def add_subscriptions_backend(request, user_profile, streams_raw = REQ("subscriptions", validator=check_list(check_dict([('name', check_string)]))), invite_only = REQ(validator=check_bool, default=False), announce = REQ(validator=check_bool, default=False), principals = REQ(validator=check_list(check_string), default=None), authorization_errors_fatal = REQ(validator=check_bool, default=True)): # type: (HttpRequest, UserProfile, List[Dict[str, str]], bool, bool, Optional[List[str]], bool) -> HttpResponse stream_names = [] for stream_dict in streams_raw: stream_name = stream_dict["name"].strip() if len(stream_name) > Stream.MAX_NAME_LENGTH: return json_error(_("Stream name (%s) too long.") % (stream_name,)) if not valid_stream_name(stream_name): return json_error(_("Invalid stream name (%s).") % (stream_name,)) stream_names.append(stream_name) # Enforcement of can_create_streams policy is inside list_to_streams. existing_streams, created_streams = \ list_to_streams(stream_names, user_profile, autocreate=True, invite_only=invite_only) 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 (%s).") % unauthorized_streams[0].name) # Newly created streams are also authorized for the creator streams = authorized_streams + created_streams if principals is not None: if user_profile.realm.domain == 'mit.edu' and not all(stream.invite_only for stream in streams): return json_error(_("You can only invite other mit.edu users to invite-only streams.")) subscribers = set(principal_to_user_profile(user_profile, principal) for principal in principals) else: subscribers = set([user_profile]) (subscribed, already_subscribed) = bulk_add_subscriptions(streams, subscribers) result = dict(subscribed=defaultdict(list), already_subscribed=defaultdict(list)) # type: Dict[str, Any] for (subscriber, stream) in subscribed: result["subscribed"][subscriber.email].append(stream.name) for (subscriber, stream) in already_subscribed: result["already_subscribed"][subscriber.email].append(stream.name) private_streams = dict((stream.name, stream.invite_only) for stream in streams) bots = dict((subscriber.email, subscriber.is_bot) for subscriber in subscribers) # Inform the user if someone else subscribed them to stuff, # or if a new stream was created with the "announce" option. notifications = [] if principals and result["subscribed"]: for email, subscriptions in six.iteritems(result["subscribed"]): 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 if len(subscriptions) == 1: msg = ("Hi there! We thought you'd like to know that %s just " "subscribed you to the%s stream [%s](%s)." % (user_profile.full_name, " **invite-only**" if private_streams[subscriptions[0]] else "", subscriptions[0], stream_link(subscriptions[0]), )) else: msg = ("Hi there! We thought you'd like to know that %s just " "subscribed you to the following streams: \n\n" % (user_profile.full_name,)) for stream in subscriptions: msg += "* [%s](%s)%s\n" % ( stream, stream_link(stream), " (**invite-only**)" if private_streams[stream] else "") if len([s for s in subscriptions if not private_streams[s]]) > 0: msg += "\nYou can see historical content on a non-invite-only stream by narrowing to it." notifications.append(internal_prep_message(settings.NOTIFICATION_BOT, "private", email, "", msg)) if announce and len(created_streams) > 0: notifications_stream = user_profile.realm.notifications_stream if notifications_stream is not None: if len(created_streams) > 1: stream_msg = "the following streams: %s" % \ (", ".join('`%s`' % (s.name,) for s in created_streams),) else: stream_msg = "a new stream `%s`" % (created_streams[0].name) stream_buttons = ' '.join(stream_button(s.name) for s in created_streams) msg = ("%s just created %s. %s" % (user_profile.full_name, stream_msg, stream_buttons)) notifications.append(internal_prep_message(settings.NOTIFICATION_BOT, "stream", notifications_stream.name, "Streams", msg, realm=notifications_stream.realm)) else: msg = ("Hi there! %s just created a new stream '%s'. %s" % (user_profile.full_name, created_streams[0].name, stream_button(created_streams[0].name))) for realm_user_dict in get_active_user_dicts_in_realm(user_profile.realm): # Don't announce to yourself or to people you explicitly added # (who will get the notification above instead). if realm_user_dict['email'] in principals or realm_user_dict['email'] == user_profile.email: continue notifications.append(internal_prep_message(settings.NOTIFICATION_BOT, "private", realm_user_dict['email'], "", msg)) if len(notifications) > 0: do_send_messages(notifications) result["subscribed"] = dict(result["subscribed"]) result["already_subscribed"] = dict(result["already_subscribed"]) if not authorization_errors_fatal: result["unauthorized"] = [stream.name for stream in unauthorized_streams] return json_success(result)