Example #1
0
    def POST_wiki_settings(self, page, permlevel, listed):
        """Update the permissions and visibility of wiki `page`"""
        oldpermlevel = page.permlevel
        if oldpermlevel != permlevel:
            VNotInTimeout().run(action_name="wikipermlevel",
                                details_text="edit",
                                target=page)
        if page.listed != listed:
            VNotInTimeout().run(action_name="wikipagelisted",
                                details_text="edit",
                                target=page)

        try:
            page.change_permlevel(permlevel)
        except ValueError:
            self.handle_error(403, 'INVALID_PERMLEVEL')
        if page.listed != listed:
            page.listed = listed
            page._commit()
            verb = 'Relisted' if listed else 'Delisted'
            description = '%s page %s' % (verb, page.name)
            ModAction.create(c.site,
                             c.user,
                             'wikipagelisted',
                             description=description)
        if oldpermlevel != permlevel:
            description = 'Page: %s, Changed from %s to %s' % (
                page.name, oldpermlevel, permlevel)
            ModAction.create(c.site,
                             c.user,
                             'wikipermlevel',
                             description=description)
        return self.GET_wiki_settings(page=page.name)
Example #2
0
 def GET_wiki_create(self, wp, page):
     api = c.render_style in extensions.API_TYPES
     error = c.errors.get(('WIKI_CREATE_ERROR', 'page'))
     if error:
         error = error.msg_params
     if wp[0]:
         VNotInTimeout().run(action_name="wikirevise",
                             details_text="create",
                             target=page)
         return self.redirect(join_urls(c.wiki_base_url, wp[0].name))
     elif api:
         if error:
             self.handle_error(403, **error)
         else:
             self.handle_error(404, 'PAGE_NOT_CREATED')
     elif error:
         error_msg = ''
         if error['reason'] == 'PAGE_NAME_LENGTH':
             error_msg = _(
                 "this wiki cannot handle page names of that magnitude!  please select a page name shorter than %d characters"
             ) % error['max_length']
         elif error['reason'] == 'PAGE_CREATED_ELSEWHERE':
             error_msg = _(
                 "this page is a special page, please go into the subreddit settings and save the field once to create this special page"
             )
         elif error['reason'] == 'PAGE_NAME_MAX_SEPARATORS':
             error_msg = _(
                 'a max of %d separators "/" are allowed in a wiki page name.'
             ) % error['max_separators']
         return BoringPage(_("Wiki error"), infotext=error_msg).render()
     else:
         VNotInTimeout().run(action_name="wikirevise",
                             details_text="create")
         return WikiCreate(page=page, may_revise=True).render()
Example #3
0
 def POST_wiki_allow_editor(self, act, page, user):
     """Allow/deny `username` to edit this wiki `page`"""
     if not user:
         self.handle_error(404, 'UNKNOWN_USER')
     elif act == 'del':
         VNotInTimeout().run(action_name="wikipermlevel",
             details_text="del_editor", target=user)
         page.remove_editor(user._id36)
     elif act == 'add':
         VNotInTimeout().run(action_name="wikipermlevel",
             details_text="allow_editor", target=user)
         page.add_editor(user._id36)
     else:
         self.handle_error(400, 'INVALID_ACTION')
     return json.dumps({})
Example #4
0
    def POST_wiki_revision_revert(self, pv):
        """Revert a wiki `page` to `revision`"""
        page, revision = pv
        if not revision:
            self.handle_error(400, 'INVALID_REVISION')
        VNotInTimeout().run(action_name="wikirevise",
                details_text="revision_revert", target=page)
        content = revision.content
        reason = 'reverted back %s' % timesince(revision.date)
        if page.name == 'config/stylesheet':
            css_errors, parsed = c.site.parse_css(content)
            if css_errors:
                self.handle_error(403, 'INVALID_CSS')
            c.site.change_css(content, parsed, prev=None, reason=reason, force=True)
        else:
            try:
                page.revise(content, author=c.user._id36, reason=reason, force=True)

                # continue storing the special pages as data attributes on the subreddit
                # object. TODO: change this to minimize subreddit get sizes.
                if page.name in ATTRIBUTE_BY_PAGE:
                    setattr(c.site, ATTRIBUTE_BY_PAGE[page.name], content)
                    c.site._commit()
            except ContentLengthError as e:
                self.handle_error(403, 'CONTENT_LENGTH_ERROR', max_length=e.max_length)
        return json.dumps({})
Example #5
0
    def POST_wiki_revision_hide(self, pv):
        """Toggle the public visibility of a wiki page revision"""
        page, revision = pv
        if not revision:
            self.handle_error(400, 'INVALID_REVISION')

        VNotInTimeout().run(action_name="wikirevise",
                details_text="revision_hide", target=page)
        return json.dumps({'status': revision.toggle_hide()})
Example #6
0
    def GET_advertising(self):
        VNotInTimeout().run(action_name="pageview", details_text="advertising")
        subreddit_links = self._get_selfserve_links(3)

        content = Advertising(subreddit_links=subreddit_links, )

        return AdvertisingPage(
            "advertise",
            content=content,
            loginbox=False,
            header=False,
        ).render()
Example #7
0
 def GET_wiki_settings(self, page):
     """Retrieve the current permission settings for `page`"""
     settings = {'permlevel': page._get('permlevel', 0),
                 'listed': page.listed}
     VNotInTimeout().run(action_name="pageview",
             details_text="wikisettings", target=page)
     mayedit = page.get_editor_accounts()
     restricted = (not page.special) and page.restricted
     show_editors = not restricted
     return WikiSettings(settings, mayedit, show_settings=not page.special,
                         page=page.name, show_editors=show_editors,
                         restricted=restricted,
                         may_revise=True).render()
Example #8
0
 def GET_wiki_revise(self, wp, page, message=None, **kw):
     wp = wp[0]
     VNotInTimeout().run(action_name="wikirevise", details_text="revise",
         target=wp)
     error = c.errors.get(('MAY_NOT_REVISE', 'page'))
     if error:
         self.handle_error(403, **(error.msg_params or {}))
     
     previous = kw.get('previous', wp._get('revision'))
     content = kw.get('content', wp.content)
     if not message and wp.name in page_descriptions:
         message = page_descriptions[wp.name]
     return WikiEdit(content, previous, alert=message, page=wp.name,
                     may_revise=True).render()
Example #9
0
    def pre(self):
        # Set user_is_admin property on context,
        # normally set but this controller does not inherit
        # from RedditController
        super(ModmailController, self).pre()

        admin_usernames = [
            name.lower() for name in g.live_config['modmail_admins']
        ]
        c.user_is_admin = False
        if c.user_is_loggedin:
            c.user_is_admin = c.user.name.lower() in admin_usernames

        VNotInTimeout().run()
Example #10
0
File: gold.py Project: z0r0/saidit
    def POST_gild(self, target):
        if not isinstance(target, (Comment, Link)):
            err = RedditError("NO_THING_ID")
            self.on_validation_error(err)

        if target.subreddit_slow.quarantine:
            err = RedditError("GILDING_NOT_ALLOWED")
            self.on_validation_error(err)
        VNotInTimeout().run(target=target, subreddit=target.subreddit_slow)

        self._gift_using_creddits(
            recipient=target.author_slow,
            thing_fullname=target._fullname,
            proxying_for=request.POST.get("proxying_for"),
        )
Example #11
0
    def POST_wiki_edit(self, pageandprevious, content, page_name, reason):
        """Edit a wiki `page`"""
        page, previous = pageandprevious

        if c.user._spam:
            error = _("You are doing that too much, please try again later.")
            self.handle_error(415, 'SPECIAL_ERRORS', special_errors=[error])

        if not page:
            error = c.errors.get(('WIKI_CREATE_ERROR', 'page'))
            if error:
                self.handle_error(403, **(error.msg_params or {}))
            VNotInTimeout().run(action_name="wikirevise",
                                details_text="create")
            page = WikiPage.create(c.site, page_name)
        else:
            VNotInTimeout().run(action_name="wikirevise",
                                details_text="edit",
                                target=page)
            error = c.errors.get(('MAY_NOT_REVISE', 'page'))
            if error:
                self.handle_error(403, **(error.msg_params or {}))

        renderer = RENDERERS_BY_PAGE.get(page.name, 'wiki')
        if renderer in ('wiki', 'reddit'):
            content = VMarkdown(('content'), renderer=renderer).run(content)

        # Use the raw POST value as we need to tell the difference between
        # None/Undefined and an empty string.  The validators use a default
        # value with both of those cases and would need to be changed.
        # In order to avoid breaking functionality, this was done instead.
        previous = previous._id if previous else request.POST.get('previous')
        try:
            # special validation methods
            if page.name == 'config/stylesheet':
                css_errors, parsed = c.site.parse_css(content, verify=False)
                if g.css_killswitch:
                    self.handle_error(403, 'STYLESHEET_EDIT_DENIED')
                if css_errors:
                    error_items = [CssError(x).message for x in css_errors]
                    self.handle_error(415,
                                      'SPECIAL_ERRORS',
                                      special_errors=error_items)
            elif page.name == "config/automoderator":
                try:
                    rules = Ruleset(content)
                except ValueError as e:
                    error_items = [e.message]
                    self.handle_error(415,
                                      "SPECIAL_ERRORS",
                                      special_errors=error_items)

            # special saving methods
            if page.name == "config/stylesheet":
                c.site.change_css(content, parsed, previous, reason=reason)
            else:
                try:
                    page.revise(content, previous, c.user._id36, reason=reason)
                except ContentLengthError as e:
                    self.handle_error(403,
                                      'CONTENT_LENGTH_ERROR',
                                      max_length=e.max_length)

                # continue storing the special pages as data attributes on the subreddit
                # object. TODO: change this to minimize subreddit get sizes.
                if page.special and page.name in ATTRIBUTE_BY_PAGE:
                    setattr(c.site, ATTRIBUTE_BY_PAGE[page.name], content)
                    c.site._commit()

                if page.special or c.is_wiki_mod:
                    description = modactions.get(page.name,
                                                 'Page %s edited' % page.name)
                    ModAction.create(c.site,
                                     c.user,
                                     "wikirevise",
                                     details=description,
                                     description=reason)
        except ConflictException as e:
            self.handle_error(409,
                              'EDIT_CONFLICT',
                              newcontent=e.new,
                              newrevision=page.revision,
                              diffcontent=e.htmldiff)
        return json.dumps({})
Example #12
0
class RobinController(RedditController):
    def pre(self):
        RedditController.pre(self)
        if not feature.is_enabled("robin"):
            self.abort404()

    @validate(
        VUser(),
        VNotInTimeout(),
    )
    def GET_join(self):
        room = RobinRoom.get_room_for_user(c.user)
        if room:
            return self.redirect("/robin")

        return RobinPage(
            title="robin",
            content=RobinJoin(
                robin_heavy_load=g.live_config.get('robin_heavy_load')),
        ).render()

    @validate(
        VAdmin(), )
    def GET_all(self):
        return RobinPage(
            title="robin",
            content=RobinAll(),
        ).render()

    @validate(
        VAdmin(), )
    def GET_admin(self):
        return RobinPage(
            title="robin",
            content=RobinAdmin(),
        ).render()

    @validate(
        VUser(),
        VNotInTimeout(),
    )
    def GET_chat(self):
        room = RobinRoom.get_room_for_user(c.user)
        if not room:
            return self.redirect("/robin/join")

        return self._get_chat_page(room)

    @validate(
        VAdmin(),
        room=VRobinRoom("room_id", allow_admin=True),
    )
    def GET_force_room(self, room):
        """Allow admins to view a specific room"""
        return self._get_chat_page(room)

    @validate(
        VAdmin(),
        user=VAccountByName("user"),
    )
    def GET_user_room(self, user):
        """Redirect admins to a user's room"""
        room = RobinRoom.get_room_for_user(user)
        if not room:
            self.abort404()

        self.redirect("/robin/" + room.id)

    def _get_chat_page(self, room):
        path = posixpath.join("/robin", room.id, c.user._id36)
        websocket_url = websockets.make_url(path, max_age=3600)

        all_user_ids = room.get_all_participants()
        all_present_ids = room.get_present_participants()
        all_votes = room.get_all_votes()

        users = Account._byID(all_user_ids, data=True, stale=True)
        user_list = []

        for user in users.itervalues():
            if user._id in all_votes:
                vote = all_votes.get(user._id)
            else:
                vote = None

            user_list.append({
                "name": user.name,
                "present": user._id in all_present_ids,
                "vote": vote,
            })

        return RobinChatPage(
            title="chat in %s" % room.name,
            content=RobinChat(room=room),
            extra_js_config={
                "robin_room_is_continued": room.is_continued,
                "robin_room_name": room.name,
                "robin_room_id": room.id,
                "robin_websocket_url": websocket_url,
                "robin_user_list": user_list,
                "robin_room_date": js_timestamp(room.date),
                "robin_room_reap_time": js_timestamp(get_reap_time(room)),
            },
        ).render()

    def _has_exceeded_ratelimit(self, form, room):
        # grab the ratelimit (as average events per second) for the room's
        # current level, using the highest level configured that's not bigger
        # than the room.  e.g. if ratelimits are defined for levels 1, 2, and 4
        # and the room is level 3, this will give us the ratelimit specified
        # for 2.
        desired_avg_per_sec = 1
        by_level = g.live_config.get("robin_ratelimit_avg_per_sec", {})
        for level, avg_per_sec in sorted(by_level.items(),
                                         key=lambda (x, y): int(x)):
            if int(level) > room.level:
                break
            desired_avg_per_sec = avg_per_sec

        # now figure out how many events per window that means
        window_size = g.live_config.get("robin_ratelimit_window", 10)
        allowed_events_per_window = int(desired_avg_per_sec * window_size)

        try:
            # now figure out how much they've actually used
            ratelimit_key = "robin/{}".format(c.user._id36)
            time_slice = ratelimit.get_timeslice(window_size)
            usage = ratelimit.get_usage(ratelimit_key, time_slice)

            # ratelimit them if too much
            if usage >= allowed_events_per_window:
                g.stats.simple_event("robin.ratelimit.exceeded")

                period_end = datetime.datetime.utcfromtimestamp(time_slice.end)
                period_end_utc = period_end.replace(tzinfo=pytz.UTC)
                until_reset = utils.timeuntil(period_end_utc)
                c.errors.add(errors.RATELIMIT, {"time": until_reset},
                             field="ratelimit",
                             code=429)
                form.has_errors("ratelimit", errors.RATELIMIT)

                return True

            # or record the usage and move on
            ratelimit.record_usage(ratelimit_key, time_slice)
        except ratelimit.RatelimitError as exc:
            g.log.warning("ratelimit error: %s", exc)
        return False

    @validatedForm(
        VUser(),
        VNotInTimeout(),
        VModhash(),
        room=VRobinRoom("room_id"),
        message=VLength("message", max_length=140),  # TODO: do we want md?
    )
    def POST_message(self, form, jquery, room, message):
        if self._has_exceeded_ratelimit(form, room):
            return

        if form.has_errors("message", errors.NO_TEXT, errors.TOO_LONG):
            return

        websockets.send_broadcast(
            namespace="/robin/" + room.id,
            type="chat",
            payload={
                "from": c.user.name,
                "body": message,
            },
        )

        events.message(
            room=room,
            message=message,
            sent_dt=datetime.datetime.utcnow(),
            context=c,
            request=request,
        )

    @validatedForm(
        VUser(),
        VNotInTimeout(),
        VModhash(),
        room=VRobinRoom("room_id"),
        vote=VOneOf("vote", VALID_VOTES),
    )
    def POST_vote(self, form, jquery, room, vote):
        if self._has_exceeded_ratelimit(form, room):
            return

        if not vote:
            # TODO: error return?
            return

        g.stats.simple_event('robin.vote.%s' % vote)

        room.set_vote(c.user, vote)
        websockets.send_broadcast(
            namespace="/robin/" + room.id,
            type="vote",
            payload={
                "from": c.user.name,
                "vote": vote,
            },
        )

        events.vote(
            room=room,
            vote=vote,
            sent_dt=datetime.datetime.utcnow(),
            context=c,
            request=request,
        )

    @validatedForm(
        VUser(),
        VNotInTimeout(),
        VModhash(),
    )
    def POST_join_room(self, form, jquery):
        if g.live_config.get('robin_heavy_load'):
            request.environ["usable_error_content"] = (
                "Robin is currently experience high load.")
            abort(503)

        room = RobinRoom.get_room_for_user(c.user)
        if room:
            # user is already in a room, they should get redirected by the
            # frontend after polling /api/room_assignment.json
            return

        add_to_waitinglist(c.user)

    @validatedForm(
        VUser(),
        VModhash(),
    )
    def POST_leave_room(self, form, jquery):
        room = RobinRoom.get_room_for_user(c.user)
        if not room:
            return
        room.remove_participants([c.user])
        websockets.send_broadcast(
            namespace="/robin/" + room.id,
            type="users_abandoned",
            payload={
                "users": [c.user.name],
            },
        )

    @json_validate(
        VUser(),
        VNotInTimeout(),
    )
    def GET_room_assignment(self, responder):
        room = RobinRoom.get_room_for_user(c.user)
        if room:
            return {"roomId": room.id}

    @validatedForm(
        VAdmin(),
        VModhash(),
    )
    def POST_admin_prompt(self, form, jquery):
        prompt_for_voting()

    @validatedForm(
        VAdmin(),
        VModhash(),
    )
    def POST_admin_reap(self, form, jquery):
        reap_ripe_rooms()

    @validatedForm(
        VAdmin(),
        VModhash(),
        message=VLength("message", max_length=140),
    )
    def POST_admin_broadcast(self, form, jquery, message):
        if form.has_errors("message", errors.NO_TEXT, errors.TOO_LONG):
            return

        websockets.send_broadcast(
            namespace="/robin",
            type="system_broadcast",
            payload={
                "body": message,
            },
        )
Example #13
0
File: gold.py Project: z0r0/saidit
class APIv1GoldController(OAuth2OnlyController):
    def _gift_using_creddits(self,
                             recipient,
                             months=1,
                             thing_fullname=None,
                             proxying_for=None):
        with creddits_lock(c.user):
            if not c.user.employee and c.user.gold_creddits < months:
                err = RedditError("INSUFFICIENT_CREDDITS")
                self.on_validation_error(err)

            note = None
            buyer = c.user
            if c.user.name.lower() in g.live_config["proxy_gilding_accounts"]:
                note = "proxy-%s" % c.user.name
                if proxying_for:
                    try:
                        buyer = Account._by_name(proxying_for)
                    except NotFound:
                        pass

            send_gift(
                buyer=buyer,
                recipient=recipient,
                months=months,
                days=months * 31,
                signed=False,
                giftmessage=None,
                thing_fullname=thing_fullname,
                note=note,
            )

            if not c.user.employee:
                c.user.gold_creddits -= months
                c.user._commit()

    @require_oauth2_scope("creddits")
    @validate(
        VUser(),
        target=VByName("fullname"),
    )
    @api_doc(
        api_section.gold,
        uri="/api/v1/gold/gild/{fullname}",
    )
    def POST_gild(self, target):
        if not isinstance(target, (Comment, Link)):
            err = RedditError("NO_THING_ID")
            self.on_validation_error(err)

        if target.subreddit_slow.quarantine:
            err = RedditError("GILDING_NOT_ALLOWED")
            self.on_validation_error(err)
        VNotInTimeout().run(target=target, subreddit=target.subreddit_slow)

        self._gift_using_creddits(
            recipient=target.author_slow,
            thing_fullname=target._fullname,
            proxying_for=request.POST.get("proxying_for"),
        )

    @require_oauth2_scope("creddits")
    @validate(
        VUser(),
        user=VAccountByName("username"),
        months=VInt("months", min=1, max=36),
        timeout=VNotInTimeout(),
    )
    @api_doc(
        api_section.gold,
        uri="/api/v1/gold/give/{username}",
    )
    def POST_give(self, user, months, timeout):
        self._gift_using_creddits(
            recipient=user,
            months=months,
            proxying_for=request.POST.get("proxying_for"),
        )