예제 #1
0
def delete_members(id: int, user_ids: List[int]):
    conv = PrivateConversation.from_pk(id,
                                       _404=True,
                                       asrt=MessagePermissions.VIEW_OTHERS)
    not_members = [
        str(uid) for uid in user_ids
        if uid not in {u.id
                       for u in conv.members}
    ]
    if not_members:
        raise APIException(
            'The following user_ids are not in the conversation: '  # type: ignore
            f'{", ".join(not_members)}.')

    states = []
    og_members = []
    for uid in list(set(user_ids)):
        st = PrivateConversationState.from_attrs(conv_id=id, user_id=uid)
        states.append(st)
        if st.original_member:
            og_members.append(User.from_pk(st.user_id).username)
    if og_members:
        raise APIException(
            'The following original members cannot be removed from the conversation: '
            f'{", ".join(og_members)}.')
    for st in states:
        st.deleted = True
    db.session.commit()
    conv.del_property_cache('members')
    cache.delete(
        PrivateConversationState.__cache_key_members__.format(conv_id=conv.id))
    return flask.jsonify(conv.members)
예제 #2
0
 def new(
     cls,
     article_id: int,
     title: str,
     language_id: int,
     contents: str,
     user_id: int,
 ) -> 'WikiArticle':
     User.is_valid(user_id, error=True)
     cache.delete(
         cls.__cache_key_from_article__.format(article_id=article_id)
     )
     translation = super()._new(
         article_id=article_id,
         language_id=language_id,
         title=title,
         contents=contents,
     )
     if WikiAlias.is_valid(title):
         WikiAlias.new(alias=title, article_id=article_id)
     WikiRevision.new(
         article_id=article_id,
         language_id=language_id,
         title=title,
         editor_id=user_id,
         contents=contents,
     )
     return translation
예제 #3
0
 def new(cls, alias: str, article_id: int) -> Optional['WikiAlias']:
     # Validity of the new alias should already have been checked when this is called.
     WikiArticle.is_valid(article_id, error=True)
     cache.delete(
         cls.__cache_key_of_article__.format(article_id=article_id)
     )
     return cls._new(article_id=article_id, alias=cls.str_to_alias(alias))
예제 #4
0
 def delete_answers(self):
     db.session.execute(
         ForumPollAnswer.__table__.delete().where(
             ForumPollAnswer.choice_id == self.id
         )
     )
     cache.delete(self.__cache_key_answers__.format(id=self.id))
예제 #5
0
 def new(
     cls, *, user_id: int, thread_id: int
 ) -> Optional['ForumThreadSubscription']:
     ForumThread.is_valid(thread_id, error=True)
     User.is_valid(user_id, error=True)
     cache.delete(cls.__cache_key_users__.format(thread_id=thread_id))
     cache.delete(cls.__cache_key_of_user__.format(user_id=user_id))
     return super()._new(user_id=user_id, thread_id=thread_id)
예제 #6
0
 def new(
     cls, *, post_id: int, editor_id: int, contents: str, time: datetime
 ) -> Optional[ForumPost]:
     ForumPost.is_valid(post_id, error=True)
     User.is_valid(editor_id, error=True)
     cache.delete(cls.__cache_key_of_post__.format(id=post_id))
     return super()._new(
         post_id=post_id, editor_id=editor_id, contents=contents, time=time
     )
예제 #7
0
def alter_thread_subscription(thread_id: int) -> flask.Response:
    """
    This is the endpoint for forum thread subscription. The ``forums_subscriptions_modify``
    permission is required to access this endpoint. A POST request creates a subscription,
    whereas a DELETE request removes a subscription.

    .. :quickref: ForumThreadSubscription; Subscribe to a forum thread.

    **Example response**:

    .. parsed-literal::

       {
         "status": "success",
         "response": "Successfully subscribed to thread 2."
       }

    :>json str response: Success or failure message

    :statuscode 200: Subscription alteration successful
    :statuscode 400: Subscription alteration unsuccessful
    :statuscode 404: Forum thread does not exist
    """
    thread = ForumThread.from_pk(thread_id, _404=True)
    subscription = ForumThreadSubscription.from_attrs(
        user_id=flask.g.user.id, thread_id=thread.id
    )
    if flask.request.method == 'POST':
        if subscription:
            raise APIException(
                f'You are already subscribed to thread {thread_id}.'
            )
        ForumThreadSubscription.new(
            user_id=flask.g.user.id, thread_id=thread_id
        )
        return flask.jsonify(f'Successfully subscribed to thread {thread_id}.')
    else:  # method = DELETE
        if not subscription:
            raise APIException(
                f'You are not subscribed to thread {thread_id}.'
            )
        db.session.delete(subscription)
        db.session.commit()
        cache.delete(
            ForumThreadSubscription.__cache_key_users__.format(
                thread_id=thread_id
            )
        )
        cache.delete(
            ForumThreadSubscription.__cache_key_of_user__.format(
                user_id=flask.g.user.id
            )
        )
        return flask.jsonify(
            f'Successfully unsubscribed from thread {thread_id}.'
        )
예제 #8
0
 def from_cache(cls, key: str, *, query: BaseQuery = None) -> Optional[PKB]:
     data = cache.get(key)
     obj = cls._create_obj_from_cache(data)
     if obj:
         return obj
     else:
         cache.delete(key)
     if query:
         obj = query.scalar()
         cache.cache_model(obj)
     return obj
예제 #9
0
 def new(cls, user_id: int, type: str,
         contents: Dict[str, Union[Dict, str]]) -> 'Notification':
     User.is_valid(user_id, error=True)
     noti_type = NotificationType.from_type(type, create_new=True)
     cache.delete(
         cls.__cache_key_of_user__.format(user_id=user_id, type=type))
     cache.delete(
         cls.__cache_key_notification_count__.format(user_id=user_id,
                                                     type=type))
     return super()._new(user_id=user_id,
                         type_id=noti_type.id,
                         contents=contents)
예제 #10
0
 def new(
     cls, *, thread_id: int, user_id: int, contents: str
 ) -> Optional['ForumPost']:
     ForumThread.is_valid(thread_id, error=True)
     User.is_valid(user_id, error=True)
     cache.delete(cls.__cache_key_of_thread__.format(id=thread_id))
     post = super()._new(
         thread_id=thread_id, user_id=user_id, contents=contents
     )
     send_subscription_notices(post)
     check_post_contents_for_quotes(post)
     check_post_contents_for_mentions(post)
     return post
예제 #11
0
    def new(
        cls, *, poll_id: int, user_id: int, choice_id: bool
    ) -> 'ForumPollAnswer':
        User.is_valid(user_id, error=True)
        # ForumPollChoice also validates ForumPoll.
        ForumPollChoice.is_valid_choice(choice_id, poll_id=poll_id, error=True)
        if cls.from_attrs(poll_id=poll_id, user_id=user_id):
            raise APIException('You have already voted for this poll.')

        cache.delete(
            ForumPollChoice.__cache_key_answers__.format(id=choice_id)
        )
        return cls._new(poll_id=poll_id, user_id=user_id, choice_id=choice_id)
예제 #12
0
 def new(cls, title: str, contents: str, user_id: int) -> 'WikiArticle':
     User.is_valid(user_id, error=True)
     WikiAlias.is_valid(title, error=True)
     cache.delete(cls.__cache_key_all__)
     article = super()._new(title=title, contents=contents)
     WikiAlias.new(alias=title, article_id=article.id)
     WikiRevision.new(
         article_id=article.id,
         language_id=1,
         title=title,
         editor_id=user_id,
         contents=contents,
     )
     return article
예제 #13
0
 def new(
     cls, topic: str, forum_id: int, creator_id: int, post_contents: str
 ) -> Optional['ForumThread']:
     Forum.is_valid(forum_id, error=True)
     User.is_valid(creator_id, error=True)
     cache.delete(cls.__cache_key_of_forum__.format(id=forum_id))
     thread = super()._new(
         topic=topic, forum_id=forum_id, creator_id=creator_id
     )
     subscribe_users_to_new_thread(thread)
     ForumPost.new(
         thread_id=thread.id, user_id=creator_id, contents=post_contents
     )
     return thread
예제 #14
0
 def new(
     cls,
     name: str,
     category_id: int,
     description: str = None,
     position: int = 0,
 ) -> Optional['Forum']:
     ForumCategory.is_valid(category_id, error=True)
     cache.delete(cls.__cache_key_of_category__.format(id=category_id))
     return super()._new(
         name=name,
         category_id=category_id,
         description=description,
         position=position,
     )
예제 #15
0
    def test_route():
        cache.set('key3', 1)
        cache.inc('key1')
        cache.get('key2')
        cache.ttl('key2')
        cache.ttl('key3')
        cache.get('key3')
        cache.delete('key3')
        cache.delete('key4')

        assert cache.has('key1')
        assert flask.g.cache_keys['inc'] == {'key1'}
        assert flask.g.cache_keys['get'] == {'key3'}
        assert flask.g.cache_keys['set'] == {'key3'}
        assert flask.g.cache_keys['delete'] == {'key3'}
        assert flask.g.cache_keys['ttl'] == {'key3'}
        assert flask.g.cache_keys['has'] == {'key1'}
        return flask.jsonify('complete')
예제 #16
0
    def new(cls, inviter_id: int, email: str, ip: int) -> 'Invite':
        """
        Generate a random invite code.

        :param inviter_id: User ID of the inviter
        :param email:      E-mail to send the invite to
        :param ip:         IP address the invite was sent from
        """
        while True:
            code = secrets.token_urlsafe(24)[:24]
            if not cls.from_pk(code, include_dead=True):
                break
        cache.delete(cls.__cache_key_of_user__.format(user_id=inviter_id))
        return super()._new(
            inviter_id=inviter_id,
            code=code,
            email=email.lower().strip(),
            from_ip=ip,
        )
예제 #17
0
 def new(
     cls,
     conv_id: int,
     user_id: int,
     original_member: bool = False,
     read: bool = False,
 ) -> Optional['PrivateConversationState']:
     """
     Create a private message object, set states for the sender and receiver,
     and create the initial message.
     """
     PrivateConversation.is_valid(conv_id, error=True)
     User.is_valid(user_id, error=True)
     cache.delete(cls.__cache_key_members__.format(conv_id=conv_id))
     return super()._new(
         conv_id=conv_id,
         user_id=user_id,
         original_member=original_member,
         read=read,
     )
예제 #18
0
    def execute(self):
        action = self._get_str_parameter("action")
        if action == "delcache":
            caKey = self._get_str_parameter("caKey")
            cache.delete(caKey)

        cl = cache.populate()
        html = "<table>"
        for ca in cl:
            tr = "<tr>"
            tr += "<td><p>%s</p></td>" % ca["key"]
            value = strutil.replce_html_entities(ca["value"])
            tr += "<td><p>%s</p></td>" % (value if len(value) < 50 else value[0:50] + "...")
            tr += "<td><p>%d</p></td>" % ca["hits"]
            tr += "<td><span onclick='sysman.delcache(\"%s\")' title='Delete'>X</span></td>" % (ca["key"])
            tr += "</tr>"
            html += tr

        html += "</table>"
        return html
예제 #19
0
def change_poll_choices(poll: ForumPoll, add: List[str],
                        delete: List[int]) -> None:
    """
    Change the choices to a poll. Create new choices or delete existing ones.
    The choices parameter should contain a dictionary of answer name keys and
    their status as booleans. True = Add, False = Delete.

    :param poll:    The forum poll to alter
    :param choices: The choices to edit
    """
    poll_choice_choices = {c.choice for c in poll.choices}
    poll_choice_ids = {c.id for c in poll.choices}
    errors = {
        'add': {choice
                for choice in add if choice in poll_choice_choices},
        'delete':
        {choice
         for choice in delete if choice not in poll_choice_ids},
    }

    error_message = []
    if errors['add']:
        error_message.append(
            f'The following poll choices could not be added: '  # type: ignore
            f'{", ".join(errors["add"])}.')
    if errors['delete']:
        error_message.append(
            f'The following poll choices could not be deleted: '  # type: ignore
            f'{", ".join([str(d) for d in errors["delete"]])}.')
    if error_message:
        raise APIException(' '.join(error_message))

    for choice in delete:
        choice = ForumPollChoice.from_pk(choice)
        choice.delete_answers()  # type: ignore
        db.session.delete(choice)
    for choice_new in add:
        db.session.add(ForumPollChoice(poll_id=poll.id, choice=choice_new))
    cache.delete(ForumPollChoice.__cache_key_of_poll__.format(poll_id=poll.id))
    poll.del_property_cache('choices')
    db.session.commit()
예제 #20
0
def update_api_key(api_key: APIKey) -> None:
    """
    Update the provided session or api key's last seen times,
    user agent, and IP fields.

    :param session_key: The session or API key to update
    """
    cache_key = f'{api_key.cache_key}_updated'
    if (
        not cache.get(cache_key)
        or api_key.ip != flask.request.remote_addr
        or api_key.user_agent != flask.request.headers.get('User-Agent')
    ):
        api_key.last_used = datetime.utcnow().replace(tzinfo=pytz.utc)
        api_key.user_agent = flask.request.headers.get('User-Agent')
        api_key.ip = flask.request.remote_addr
        db.session.commit()

        cache.delete(api_key.cache_key)
        cache.set(
            cache_key, 1, timeout=60 * 2
        )  # 2 minute wait before next update
예제 #21
0
def change_user_permissions(user: User, permissions: Dict[str, bool]) -> None:
    """
    Change the permissions belonging to a user. Permissions can be
    added to a user, deleted from a user, and ungranted from a user.
    Adding a permission occurs when the user does not have the specified
    permission, through custom or userclass. There are two types of permission
    removal: deletion and ungranting. Deletion ocrurs when the user has the
    permission through custom, while ungranting occurs when the user has the
    permission through userclass. If they have both custom and userclass, they
    will lose both.

    :param user:          The user to change permissions for
    :param permissions:   The permissions to change

    :raises APIException: Invalid permissions to change
    """
    to_add, to_ungrant, to_delete = check_permissions(user, permissions)
    for p in to_ungrant:
        if not Permissions.is_valid_permission(p):
            raise APIException(f'{p} is not a valid permission.')
    alter_permissions(user, to_add, to_ungrant, to_delete)
    cache.delete(user.__cache_key_permissions__.format(id=user.id))
    user.del_property_cache('permissions')
예제 #22
0
    def clear_cache_keys(
        cls, user_ids: List[int] = None, thread_id: int = None
    ) -> None:
        """
        Clear the cache keys associated with specific users and/or threads. Clearing a thread
        cache key also clears the cache keys for all of its users

        :param user_ids: The IDs of the users whose cache keys should be cleared
        :param thread_id: The ID of the thread for which the cache key should be cleared
        """
        user_ids = (
            user_ids or []
        )  # Don't put a mutable object as default kwarg!
        if thread_id:
            cache.delete(cls.__cache_key_users__.format(thread_id=thread_id))
            user_ids += cls.user_ids_from_thread(thread_id)
        if user_ids:
            cache.delete_many(
                *(
                    cls.__cache_key_of_user__.format(user_id=uid)
                    for uid in user_ids
                )
            )
예제 #23
0
    def new(
        cls,
        user_id: int,
        ip: str,
        user_agent: str,
        permanent: bool = False,
        timeout: int = 60 * 30,
        permissions: List[str] = None,
    ) -> Tuple[str, 'APIKey']:
        """
        Create a new API Key with randomly generated secret keys and the
        user details passed in as params. Generated keys are hashed and
        salted for storage in the database.

        :param user_id:    API Key will belong to this user
        :param ip:         The IP that this session was created with
        :param user_agent: User Agent the session was created with

        :return:           A tuple containing the identifier and the new API Key
        """
        while True:
            hash = secrets.token_urlsafe(10)[:10]
            if not cls.from_pk(hash, include_dead=True):
                break
        key = secrets.token_urlsafe(16)[:16]
        cache.delete(cls.__cache_key_of_user__.format(user_id=user_id))
        api_key = super()._new(
            user_id=user_id,
            hash=hash,
            keyhashsalt=generate_password_hash(key),
            ip=ip,
            user_agent=user_agent,
            permanent=permanent,
            timeout=timeout,
            permissions=permissions or [],
        )
        return (hash + key, api_key)
예제 #24
0
 def unfeature_existing(cls) -> None:
     poll = cls.get_featured()
     if poll:
         poll.featured = False
         db.session.commit()
         cache.delete(cls.__cache_key_featured__)
예제 #25
0
 def new(cls, *, poll_id: int, choice: str) -> 'ForumPollChoice':
     ForumPoll.is_valid(poll_id, error=True)
     cache.delete(cls.__cache_key_of_poll__.format(poll_id=poll_id))
     return cls._new(poll_id=poll_id, choice=choice)