Ejemplo n.º 1
0
class DonateController(RedditController):
    def GET_closed(self):
        return pages.DonatePage(
            title=_("reddit donate"),
            content=pages.DonateClosed(),
        ).render()

    @validate(
        eligible=VAccountEligible(),
        organization=VOrganization("organization"),
    )
    def GET_landing(self, eligible, organization):
        if not feature.is_enabled('reddit_donate'):
            return self.abort404()

        if c.user_is_loggedin:
            nomination_count = DonationNominationsByAccount.count(c.user)
        else:
            nomination_count = None

        if organization:
            wrapped_organization = inject_nomination_status([organization])
        else:
            wrapped_organization = None

        content = pages.DonateLanding(eligible=eligible, )

        og_data = {
            "site_name": "reddit.com",
        }

        if organization:
            og_data[
                "title"] = "reddit donate: vote for %s" % organization.data[
                    "DisplayName"]
            og_data["url"] = join_urls(
                g.origin, "donate?organization=%s" % organization.data["EIN"])
        else:
            og_data["title"] = "reddit donate: giving 10% back"
            og_data["url"] = join_urls(g.origin, "donate")

        return pages.DonatePage(
            title=_("reddit donate"),
            content=content,
            og_data=og_data,
            extra_js_config={
                "unloadedNominations": nomination_count,
                "accountIsEligible": eligible,
                "organization": wrapped_organization,
            },
        ).render()

    @validatedForm(
        VUser(),
        VModhash(),
        VRatelimit(rate_user=True, prefix="donate_nominate_"),
        VAccountEligible(),
        organization=VOrganization("organization"),
    )
    def POST_nominate(self, form, jquery, organization):
        if not feature.is_enabled('reddit_donate'):
            return self.abort404()

        if form.has_errors("organization", errors.DONATE_UNKNOWN_ORGANIZATION):
            return

        if form.has_errors("eligible", errors.DONATE_ACCOUNT_NOT_ELIGIBLE):
            return

        if form.has_errors("ratelimit", errors.RATELIMIT):
            return
        else:
            VRatelimit.ratelimit(
                rate_user=True,
                prefix="donate_nominate_",
                seconds=NOMINATION_COOLDOWN,
            )

        DonationNominationsByAccount.nominate(
            c.user,
            organization,
        )

    @validatedForm(
        VUser(),
        VModhash(),
        organization=VOrganization("organization"),
    )
    def POST_unnominate(self, form, jquery, organization):
        if not feature.is_enabled('reddit_donate'):
            return self.abort404()

        if form.has_errors("organization", errors.DONATE_UNKNOWN_ORGANIZATION):
            return

        DonationNominationsByAccount.unnominate(
            c.user,
            organization,
        )

    @json_validate(
        organization=VOrganization("organization"), )
    def GET_organization(self, responder, organization):
        """Look up a single org by EIN."""

        if not feature.is_enabled('reddit_donate'):
            return self.abort404()

        if responder.has_errors("organization",
                                errors.DONATE_UNKNOWN_ORGANIZATION):
            return

        wrapped = inject_nomination_status([organization])
        return wrapped[0]

    @json_validate(
        prefix=VLength("prefix", min_length=3, max_length=100), )
    def GET_search(self, responder, prefix):
        """Get organizations by display-name prefix."""

        if not feature.is_enabled('reddit_donate'):
            return self.abort404()

        if responder.has_errors("prefix", errors.TOO_LONG, errors.TOO_SHORT):
            return

        organizations = DonationOrganizationsByPrefix.byPrefix(prefix)
        return inject_nomination_status(organizations)

    @json_validate(
        VUser(), )
    def GET_nominations(self, responder):
        if not feature.is_enabled('reddit_donate'):
            return self.abort404()
        nominated_org_ids = DonationNominationsByAccount.get_for(c.user)
        orgs = DonationOrganization.byEIN(nominated_org_ids)
        wrapped = inject_nomination_status(orgs, assume_nominated=True)
        return wrapped
Ejemplo n.º 2
0
class MultiApiController(RedditController, OAuth2ResourceController):
    on_validation_error = staticmethod(abort_with_error)

    def pre(self):
        set_extension(request.environ, "json")
        self.check_for_bearer_token()
        RedditController.pre(self)

    @require_oauth2_scope("read")
    @validate(VUser())
    @api_doc(api_section.multis, uri="/api/multi/mine")
    def GET_my_multis(self):
        """Fetch a list of multis belonging to the current user."""
        multis = LabeledMulti.by_owner(c.user)
        wrapped = wrap_things(*multis)
        resp = [w.render() for w in wrapped]
        return self.api_wrapper(resp)

    def _format_multi(self, multi):
        resp = wrap_things(multi)[0].render()
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(multi=VMultiByPath("multipath", require_view=True))
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}",
    )
    def GET_multi(self, multi):
        """Fetch a multi's data and subreddit list by name."""
        return self._format_multi(multi)

    def _check_new_multi_path(self, path_info):
        if path_info['username'].lower() != c.user.name.lower():
            raise RedditError('MULTI_CANNOT_EDIT',
                              code=403,
                              fields='multipath')

    def _add_multi_srs(self, multi, sr_datas):
        srs = Subreddit._by_name(sr_data['name'] for sr_data in sr_datas)

        for sr in srs.itervalues():
            if isinstance(sr, FakeSubreddit):
                raise RedditError('MULTI_SPECIAL_SUBREDDIT',
                                  msg_params={'path': sr.path},
                                  code=400)

        sr_props = {}
        for sr_data in sr_datas:
            try:
                sr = srs[sr_data['name']]
            except KeyError:
                raise RedditError('SUBREDDIT_NOEXIST', code=400)
            else:
                # name is passed in via the API data format, but should not be
                # stored on the model.
                del sr_data['name']
                sr_props[sr] = sr_data

        try:
            multi.add_srs(sr_props)
        except TooManySubredditsError as e:
            raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409)

        return sr_props

    def _write_multi_data(self, multi, data):
        multi.visibility = data['visibility']

        multi.clear_srs()
        try:
            self._add_multi_srs(multi, data['subreddits'])
        except:
            multi._revert()
            raise

        multi._commit()
        return multi

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        path_info=VMultiPath("multipath"),
        data=VValidatedJSON("model", multi_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def POST_multi(self, path_info, data):
        """Create a multi. Responds with 409 Conflict if it already exists."""

        self._check_new_multi_path(path_info)

        try:
            LabeledMulti._byID(path_info['path'])
        except tdb_cassandra.NotFound:
            multi = LabeledMulti.create(path_info['path'], c.user)
            response.status = 201
        else:
            raise RedditError('MULTI_EXISTS', code=409, fields='multipath')

        self._write_multi_data(multi, data)
        return self._format_multi(multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        path_info=VMultiPath("multipath"),
        data=VValidatedJSON("model", multi_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def PUT_multi(self, path_info, data):
        """Create or update a multi."""

        self._check_new_multi_path(path_info)

        try:
            multi = LabeledMulti._byID(path_info['path'])
        except tdb_cassandra.NotFound:
            multi = LabeledMulti.create(path_info['path'], c.user)
            response.status = 201

        self._write_multi_data(multi, data)
        return self._format_multi(multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def DELETE_multi(self, multi):
        """Delete a multi."""
        multi.delete()

    def _copy_multi(self, from_multi, to_path_info):
        self._check_new_multi_path(to_path_info)

        to_owner = Account._by_name(to_path_info['username'])

        try:
            LabeledMulti._byID(to_path_info['path'])
        except tdb_cassandra.NotFound:
            to_multi = LabeledMulti.copy(to_path_info['path'],
                                         from_multi,
                                         owner=to_owner)
        else:
            raise RedditError('MULTI_EXISTS', code=409, fields='multipath')

        return to_multi

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        from_multi=VMultiByPath("from", require_view=True),
        to_path_info=VMultiPath(
            "to",
            docs={"to": "destination multireddit url path"},
        ),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/copy",
    )
    def POST_multi_copy(self, from_multi, to_path_info):
        """Copy a multi.

        Responds with 409 Conflict if the target already exists.

        A "copied from ..." line will automatically be appended to the
        description.

        """
        to_multi = self._copy_multi(from_multi, to_path_info)
        from_path = from_multi.path
        to_multi.copied_from = from_path
        if to_multi.description_md:
            to_multi.description_md += '\n\n'
        to_multi.description_md += _('copied from %(source)s') % {
            # force markdown linking since /user/foo is not autolinked
            'source': '[%s](%s)' % (from_path, from_path)
        }
        to_multi.visibility = 'private'
        to_multi._commit()
        return self._format_multi(to_multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        from_multi=VMultiByPath("from", require_edit=True),
        to_path_info=VMultiPath(
            "to",
            docs={"to": "destination multireddit url path"},
        ),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/rename",
    )
    def POST_multi_rename(self, from_multi, to_path_info):
        """Rename a multi."""

        to_multi = self._copy_multi(from_multi, to_path_info)
        from_multi.delete()
        return self._format_multi(to_multi)

    def _get_multi_subreddit(self, multi, sr):
        resp = LabeledMultiJsonTemplate.sr_props(multi, [sr])[0]
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        multi=VMultiByPath("multipath", require_view=True),
        sr=VSRByName('srname'),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/r/{srname}",
    )
    def GET_multi_subreddit(self, multi, sr):
        """Get data about a subreddit in a multi."""
        return self._get_multi_subreddit(multi, sr)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
        sr_name=VSubredditName('srname', allow_language_srs=True),
        data=VValidatedJSON("model", multi_sr_data_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi_subreddit)
    def PUT_multi_subreddit(self, multi, sr_name, data):
        """Add a subreddit to a multi."""

        new = not any(sr.name.lower() == sr_name.lower() for sr in multi.srs)

        data['name'] = sr_name
        sr_props = self._add_multi_srs(multi, [data])
        sr = sr_props.items()[0][0]
        multi._commit()

        if new:
            response.status = 201

        return self._get_multi_subreddit(multi, sr)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
        sr=VSRByName('srname'),
    )
    @api_doc(api_section.multis, extends=GET_multi_subreddit)
    def DELETE_multi_subreddit(self, multi, sr):
        """Remove a subreddit from a multi."""
        multi.del_srs(sr)
        multi._commit()

    def _format_multi_description(self, multi):
        resp = LabeledMultiDescriptionJsonTemplate().render(multi).finalize()
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        multi=VMultiByPath("multipath", require_view=True),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/description",
    )
    def GET_multi_description(self, multi):
        """Get a multi's description."""
        return self._format_multi_description(multi)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        multi=VMultiByPath("multipath", require_edit=True),
        data=VValidatedJSON('model', multi_description_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi_description)
    def PUT_multi_description(self, multi, data):
        """Change a multi's markdown description."""
        multi.description_md = data['body_md']
        multi._commit()
        return self._format_multi_description(multi)
Ejemplo n.º 3
0
class QrCodeController(RedditController):
    @validate(
        meetup=validators.VMeetup("codename"),
    )
    def GET_portal(self, meetup):
        if meetup.state != "closed":
            if c.user_is_loggedin:
                content = pages.MeetupPortal(meetup=meetup)
            else:
                content = pages.LoggedOutMeetupPortal(meetup=meetup)
        else:
            content = pages.ClosedMeetupPortal(meetup=meetup)

        return pages.MeatspacePage(
            content=content,
            page_classes=["meatspace-portal"]
        ).render()

    @validate(
        VUser(),
        meetup=validators.VMeetup("codename"),
    )
    def GET_configure_badge(self, meetup):
        if meetup.state not in BADGE_STATES:
            return redirect_to("/meetup/%s" % str(meetup._id))

        content = pages.ConversationStarterSelector(meetup, c.user)
        return pages.MeatspacePage(content=content).render()

    @validate(
        VUser(),
        meetup=validators.VMeetup("codename"),
        topic=validators.VConversationStarter("topic"),
    )
    def GET_badge(self, meetup, topic):
        if meetup.state not in BADGE_STATES:
            return redirect_to("/meetup/%s" % str(meetup._id))

        content = pages.QrCodeBadge(meetup, c.user, topic)
        return pages.MeatspaceBadgePage(content=content).render()

    @validate(
        VUser(),
        meetup=validators.VMeetup("codename"),
    )
    def GET_mobile_badge(self, meetup):
        if meetup.state not in BADGE_STATES:
            return redirect_to("/meetup/%s" % str(meetup._id))

        content = pages.MobileQrCodeBadge(meetup, c.user)
        return content.render()

    @validate(
        VUser(),
        meetup=validators.VMeetup("codename"),
        other=VExistingUname("user"),
        connected_with=VExistingUname("connected-with"),
        code=VInt("code"),
    )
    def GET_connect(self, meetup, other, code, connected_with):
        if meetup.state not in CONNECT_STATES:
            self.abort404()

        content = pages.QrCodeForm(
            meetup=meetup,
            other=other,
            code=code,
            connected_with=connected_with,
        )
        return pages.MeatspacePage(content=content).render()

    @validatedForm(
        VUser(),
        VModhash(),
        meetup=validators.VMeetup("codename"),
        other=VExistingUname("username"),
        code=VInt("code"),
    )
    def POST_connect(self, form, jquery, meetup, other, code):
        if meetup.state not in CONNECT_STATES:
            self.abort403()

        jquery("body .connection-success").hide()

        if form.has_errors("username",
                           errors.NO_USER,
                           errors.USER_DOESNT_EXIST):
            return

        if c.user == other:
            c.errors.add(errors.MEETUP_NOT_WITH_SELF, field="username")
            form.set_error(errors.MEETUP_NOT_WITH_SELF, "username")
            return

        expected_code = utils.make_secret_code(meetup, other)
        if code != expected_code:
            g.log.warning("%r just tried an invalid code on %r",
                          c.user.name, other.name)
            c.errors.add(errors.MEETUP_INVALID_CODE, field="code")
            form.set_error(errors.MEETUP_INVALID_CODE, "code")
            return

        models.MeetupConnections._connect(meetup, c.user, other)
        models.MeetupConnectionsByAccount._connect(meetup, c.user, other)
        g.stats.simple_event("meetup.connection")

        form.redirect("/meetup/%s/connect?connected-with=%s" % (meetup._id,
                                                                other.name))

    @validate(
        VUser(),
        meetup=validators.VMeetup("codename"),
    )
    def GET_connections(self, meetup):
        all_connections = models.MeetupConnectionsByAccount._connections(
            meetup, c.user)
        connections = [a for a in all_connections if not a._deleted]

        content = pages.QrCodeConnections(
            meetup=meetup,
            connections=connections,
        )
        return pages.MeatspacePage(content=content).render()

    @validate(meetup=validators.VMeetup("codename"))
    def GET_connect_shortlink(self, meetup, user, code):
        if meetup.state not in CONNECT_STATES:
            self.abort404()

        params = urllib.urlencode({
            "user": user,
            "code": code,
        })
        return redirect_to("/meetup/%s/connect?%s" % (str(meetup._id), params),
                           _code=301)
Ejemplo n.º 4
0
class StripeController(GoldPaymentController):
    name = 'stripe'
    webhook_secret = g.STRIPE_WEBHOOK_SECRET
    event_type_mappings = {
        'charge.succeeded': 'succeeded',
        'charge.failed': 'failed',
        'charge.refunded': 'refunded',
        'charge.dispute.created': 'noop',
        'charge.dispute.updated': 'noop',
        'charge.dispute.closed': 'noop',
        'customer.created': 'noop',
        'customer.card.created': 'noop',
        'customer.card.deleted': 'noop',
        'transfer.created': 'noop',
        'transfer.paid': 'noop',
        'balance.available': 'noop',
        'invoice.created': 'noop',
        'invoice.updated': 'noop',
        'invoice.payment_succeeded': 'noop',
        'invoice.payment_failed': 'failed_subscription',
        'invoiceitem.deleted': 'noop',
        'customer.subscription.created': 'noop',
        'customer.deleted': 'noop',
        'customer.updated': 'noop',
        'customer.subscription.deleted': 'noop',
        'customer.subscription.trial_will_end': 'noop',
        'customer.subscription.updated': 'noop',
        'dummy': 'noop',
    }

    @classmethod
    def process_response(cls):
        event_dict = json.loads(request.body)
        event = stripe.Event.construct_from(event_dict, g.STRIPE_SECRET_KEY)
        status = event.type

        if status == 'invoice.created':
            # sent 1 hr before a subscription is charged or immediately for
            # a new subscription
            invoice = event.data.object
            customer_id = invoice.customer
            account = account_from_stripe_customer_id(customer_id)
            # if the charge hasn't been attempted (meaning this is 1 hr before
            # the charge) check that the account can receive the gold
            if (not invoice.attempted
                    and (not account or (account and account._banned))):
                # there's no associated account - delete the subscription
                # to cancel the charge
                g.log.error('no account for stripe invoice: %s', invoice)
                try:
                    customer = stripe.Customer.retrieve(customer_id)
                    customer.delete()
                except stripe.InvalidRequestError:
                    pass
        elif status == 'invoice.payment_failed':
            invoice = event.data.object
            customer_id = invoice.customer
            buyer = account_from_stripe_customer_id(customer_id)
            webhook = Webhook(subscr_id=customer_id, buyer=buyer)
            return status, webhook

        event_type = cls.event_type_mappings.get(status)
        if not event_type:
            raise ValueError('Stripe: unrecognized status %s' % status)
        elif event_type == 'noop':
            return status, None

        charge = event.data.object
        description = charge.description
        invoice_id = charge.invoice
        transaction_id = 'S%s' % charge.id
        pennies = charge.amount
        months, days = months_and_days_from_pennies(pennies)

        if status == 'charge.failed' and invoice_id:
            # we'll get an additional failure notification event of
            # "invoice.payment_failed", don't double notify
            return 'dummy', None
        elif status == 'charge.failed' and not description:
            # create_customer can POST successfully but fail to create a
            # customer because the card is declined. This will trigger a
            # 'charge.failed' notification but without description so we can't
            # do anything with it
            return 'dummy', None
        elif invoice_id:
            # subscription charge - special handling
            customer_id = charge.customer
            buyer = account_from_stripe_customer_id(customer_id)
            if not buyer:
                charge_date = datetime.fromtimestamp(charge.created, tz=g.tz)

                # don't raise exception if charge date is within the past hour
                # db replication lag may cause the account lookup to fail
                if charge_date < timeago('1 hour'):
                    raise ValueError('no buyer for charge: %s' % charge.id)
                else:
                    abort(404, "not found")
            webhook = Webhook(transaction_id=transaction_id,
                              subscr_id=customer_id,
                              pennies=pennies,
                              months=months,
                              goldtype='autorenew',
                              buyer=buyer)
            return status, webhook
        else:
            try:
                passthrough, buyer_name = description.split('-', 1)
            except (AttributeError, ValueError):
                g.log.error('stripe_error on charge: %s', charge)
                raise

            webhook = Webhook(passthrough=passthrough,
                              transaction_id=transaction_id,
                              pennies=pennies,
                              months=months)
            return status, webhook

    @classmethod
    @handle_stripe_error
    def create_customer(cls, form, token):
        description = c.user.name
        customer = stripe.Customer.create(card=token, description=description)

        if (customer['active_card']['address_line1_check'] == 'fail'
                or customer['active_card']['address_zip_check'] == 'fail'):
            form.set_html('.status', _('error: address verification failed'))
            form.find('.stripe-submit').removeAttr('disabled').end()
            return None
        elif customer['active_card']['cvc_check'] == 'fail':
            form.set_html('.status', _('error: cvc check failed'))
            form.find('.stripe-submit').removeAttr('disabled').end()
            return None
        else:
            return customer

    @classmethod
    @handle_stripe_error
    def charge_customer(cls, form, customer, pennies, passthrough):
        charge = stripe.Charge.create(amount=pennies,
                                      currency="usd",
                                      customer=customer['id'],
                                      description='%s-%s' %
                                      (passthrough, c.user.name))
        return charge

    @classmethod
    @handle_stripe_error
    def set_creditcard(cls, form, user, token):
        if not user.has_stripe_subscription:
            return

        customer = stripe.Customer.retrieve(user.gold_subscr_id)
        customer.card = token
        customer.save()
        return customer

    @classmethod
    @handle_stripe_error
    def set_subscription(cls, form, customer, plan_id):
        subscription = customer.update_subscription(plan=plan_id)
        return subscription

    @classmethod
    @handle_stripe_error
    def cancel_subscription(cls, form, user):
        if not user.has_stripe_subscription:
            return

        customer = stripe.Customer.retrieve(user.gold_subscr_id)
        customer.delete()

        user.gold_subscr_id = None
        user._commit()
        subject = _('your gold subscription has been cancelled')
        message = _('if you have any questions please email %(email)s')
        message %= {'email': g.goldthanks_email}
        send_system_message(user, subject, message)
        return customer

    @validatedForm(VUser(),
                   token=nop('stripeToken'),
                   passthrough=VPrintable("passthrough", max_length=50),
                   pennies=VInt('pennies'),
                   months=VInt("months"),
                   period=VOneOf("period", ("monthly", "yearly")))
    def POST_goldcharge(self, form, jquery, token, passthrough, pennies,
                        months, period):
        """
        Submit charge to stripe.

        Called by GoldPayment form. This submits the charge to stripe, and gold
        will be applied once we receive a webhook from stripe.

        """

        try:
            payment_blob = validate_blob(passthrough)
        except GoldException as e:
            # This should never happen. All fields in the payment_blob
            # are validated on creation
            form.set_html('.status',
                          _('something bad happened, try again later'))
            g.log.debug('POST_goldcharge: %s' % e.message)
            return

        if period:
            plan_id = (g.STRIPE_MONTHLY_GOLD_PLAN
                       if period == 'monthly' else g.STRIPE_YEARLY_GOLD_PLAN)
            if c.user.has_gold_subscription:
                form.set_html(
                    '.status',
                    _('your account already has a gold subscription'))
                return
        else:
            plan_id = None
            penny_months, days = months_and_days_from_pennies(pennies)
            if not months or months != penny_months:
                form.set_html('.status', _('stop trying to trick the form'))
                return

        customer = self.create_customer(form, token)
        if not customer:
            return

        if period:
            subscription = self.set_subscription(form, customer, plan_id)
            if not subscription:
                return

            c.user.gold_subscr_id = customer.id
            c.user._commit()

            status = _('subscription created')
            subject = _('reddit gold subscription')
            body = _('Your subscription is being processed and reddit gold '
                     'will be delivered shortly.')
        else:
            charge = self.charge_customer(form, customer, pennies, passthrough)
            if not charge:
                return

            status = _('payment submitted')
            subject = _('reddit gold payment')
            body = _('Your payment is being processed and reddit gold '
                     'will be delivered shortly.')

        form.set_html('.status', status)
        body = append_random_bottlecap_phrase(body)
        send_system_message(c.user, subject, body, distinguished='gold-auto')

    @validatedForm(VUser(), VModhash(), token=nop('stripeToken'))
    def POST_modify_subscription(self, form, jquery, token):
        customer = self.set_creditcard(form, c.user, token)
        if not customer:
            return

        form.set_html('.status', _('your payment details have been updated'))

    @validatedForm(VUser(), VModhash(), user=VByName('user'))
    def POST_cancel_subscription(self, form, jquery, user):
        if user != c.user and not c.user_is_admin:
            self.abort403()
        customer = self.cancel_subscription(form, user)
        if not customer:
            return

        form.set_html(".status", _("your subscription has been cancelled"))
Ejemplo n.º 5
0
class OAuth2FrontendController(RedditController):
    def check_for_bearer_token(self):
        pass

    def pre(self):
        RedditController.pre(self)
        require_https()

    def _check_redirect_uri(self, client, redirect_uri):
        if not redirect_uri or not client or redirect_uri != client.redirect_uri:
            abort(ForbiddenError(errors.OAUTH2_INVALID_REDIRECT_URI))

    def _error_response(self, state, redirect_uri, as_fragment=False):
        """Return an error redirect, but only if client_id and redirect_uri are valid."""

        resp = {"state": state}

        if (errors.OAUTH2_INVALID_CLIENT, "client_id") in c.errors:
            resp["error"] = "unauthorized_client"
        elif (errors.OAUTH2_ACCESS_DENIED, "authorize") in c.errors:
            resp["error"] = "access_denied"
        elif (errors.BAD_HASH, None) in c.errors:
            resp["error"] = "access_denied"
        elif (errors.INVALID_OPTION, "response_type") in c.errors:
            resp["error"] = "unsupported_response_type"
        elif (errors.OAUTH2_INVALID_SCOPE, "scope") in c.errors:
            resp["error"] = "invalid_scope"
        else:
            resp["error"] = "invalid_request"

        final_redirect = _update_redirect_uri(redirect_uri, resp, as_fragment)
        return self.redirect(final_redirect, code=302)

    @validate(VUser(),
              response_type = VOneOf("response_type", ("code", "token")),
              client = VOAuth2ClientID(),
              redirect_uri = VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI),
              scope = VOAuth2Scope(),
              state = VRequired("state", errors.NO_TEXT),
              duration = VOneOf("duration", ("temporary", "permanent"),
                                default="temporary"))
    def GET_authorize(self, response_type, client, redirect_uri, scope, state,
                      duration):
        """
        First step in [OAuth 2.0](http://oauth.net/2/) authentication.
        End users will be prompted for their credentials (username/password)
        and asked if they wish to authorize the application identified by
        the **client_id** parameter with the permissions specified by the
        **scope** parameter.  They are then redirected to the endpoint on
        the client application's side specified by **redirect_uri**.

        If the user granted permission to the application, the response will
        contain a **code** parameter with a temporary authorization code
        which can be exchanged for an access token at
        [/api/v1/access_token](#api_method_access_token).

        **redirect_uri** must match the URI configured for the client in the
        [app preferences](/prefs/apps).  If **client_id** or **redirect_uri**
        is not valid, or if the call does not take place over SSL, a 403
        error will be returned.  For all other errors, a redirect to
        **redirect_uri** will be returned, with a **error** parameter
        indicating why the request failed.
        """

        # Check redirect URI first; it will ensure client exists
        self._check_redirect_uri(client, redirect_uri)

        if response_type == "token" and client.is_confidential():
            # Prevent "confidential" clients from distributing tokens
            # in a non-confidential manner
            c.errors.add((errors.OAUTH2_INVALID_CLIENT, "client_id"))
        if response_type == "token" and duration != "temporary":
            # implicit grant -> No refresh tokens allowed
            c.errors.add((errors.INVALID_OPTION, "duration"))

        if not c.errors:
            return OAuth2AuthorizationPage(client, redirect_uri, scope, state,
                                           duration, response_type).render()
        else:
            return self._error_response(state, redirect_uri,
                                        as_fragment=(response_type == "token"))

    @validate(VUser(),
              VModhash(fatal=False),
              client = VOAuth2ClientID(),
              redirect_uri = VRequired("redirect_uri", errors.OAUTH2_INVALID_REDIRECT_URI),
              scope = VOAuth2Scope(),
              state = VRequired("state", errors.NO_TEXT),
              duration = VOneOf("duration", ("temporary", "permanent"),
                                default="temporary"),
              authorize = VRequired("authorize", errors.OAUTH2_ACCESS_DENIED),
              response_type = VOneOf("response_type", ("code", "token"),
                                     default="code"))
    def POST_authorize(self, authorize, client, redirect_uri, scope, state,
                       duration, response_type):
        """Endpoint for OAuth2 authorization."""

        if response_type == "token" and client.is_confidential():
            # Prevent "confidential" clients from distributing tokens
            # in a non-confidential manner
            c.errors.add((errors.OAUTH2_INVALID_CLIENT, "client_id"))
        if response_type == "token" and duration != "temporary":
            # implicit grant -> No refresh tokens allowed
            c.errors.add((errors.INVALID_OPTION, "duration"))
        self._check_redirect_uri(client, redirect_uri)

        if c.errors:
            return self._error_response(state, redirect_uri,
                                        as_fragment=(response_type == "token"))

        if response_type == "code":
            code = OAuth2AuthorizationCode._new(client._id, redirect_uri,
                                            c.user._id36, scope,
                                            duration == "permanent")
            resp = {"code": code._id, "state": state}
            final_redirect = _update_redirect_uri(redirect_uri, resp)
        elif response_type == "token":
            token = OAuth2AccessToken._new(client._id, c.user._id36, scope)
            token_data = OAuth2AccessController._make_token_dict(token)
            token_data["state"] = state
            final_redirect = _update_redirect_uri(redirect_uri, token_data, as_fragment=True)

        return self.redirect(final_redirect, code=302)
class LiveUpdateController(RedditController):
    def __before__(self, event):
        RedditController.__before__(self)

        if event:
            try:
                c.liveupdate_event = LiveUpdateEvent._byID(event)
            except tdb_cassandra.NotFound:
                pass

        if not c.liveupdate_event:
            self.abort404()

        if c.user_is_loggedin:
            c.liveupdate_permissions = \
                    c.liveupdate_event.get_permissions(c.user)

            # revoke some permissions from everyone after closing
            if c.liveupdate_event.state != "live":
                c.liveupdate_permissions = (c.liveupdate_permissions
                    .without("update")
                    .without("close")
                )

            if c.user_is_admin:
                c.liveupdate_permissions = ContributorPermissionSet.SUPERUSER
        else:
            c.liveupdate_permissions = ContributorPermissionSet.NONE

        if c.liveupdate_event.banned and not c.liveupdate_permissions:
            error_page = RedditError(
                title=_("this thread has been banned"),
                message="",
                image="subreddit-banned.png",
            )
            request.environ["usable_error_content"] = error_page.render()
            self.abort403()

        if (c.liveupdate_event.nsfw and
                not c.over18 and
                request.host != g.media_domain and  # embeds are special
                c.render_style == "html"):
            return self.intermediate_redirect("/over18", sr_path=False)

    @require_oauth2_scope("read")
    @validate(
        num=VLimit("limit", default=25, max_limit=100),
        after=VLiveUpdateID("after"),
        before=VLiveUpdateID("before"),
        count=VCount("count"),
        is_embed=VBoolean("is_embed", docs={"is_embed": "(internal use only)"}),
        style_sr=VSRByName("stylesr"),
    )
    @api_doc(
        section=api_section.live,
        uri="/live/{thread}",
        supports_rss=True,
        notes=[paginated_listing.doc_note],
    )
    def GET_listing(self, num, after, before, count, is_embed, style_sr):
        """Get a list of updates posted in this thread.

        See also: [/api/live/*thread*/update](#POST_api_live_{thread}_update).

        """
        reverse = False
        if before:
            reverse = True
            after = before

        query = LiveUpdateStream.query([c.liveupdate_event._id],
                                       count=num, reverse=reverse)
        if after:
            query.column_start = after
        builder = LiveUpdateBuilder(query=query, skip=True,
                                    reverse=reverse, num=num,
                                    count=count)
        listing = pages.LiveUpdateListing(builder)
        wrapped_listing = listing.listing()

        if c.user_is_loggedin:
            report_type = LiveUpdateReportsByAccount.get_report(
                c.user, c.liveupdate_event)
        else:
            report_type = None

        content = pages.LiveUpdateEventApp(
            event=c.liveupdate_event,
            listing=wrapped_listing,
            show_sidebar=not is_embed,
            report_type=report_type,
        )

        c.js_preload.set_wrapped(
            "/live/" + c.liveupdate_event._id + "/about.json",
            Wrapped(c.liveupdate_event),
        )

        c.js_preload.set_wrapped(
            "/live/" + c.liveupdate_event._id + ".json",
            wrapped_listing,
        )

        if not is_embed:
            return pages.LiveUpdateEventAppPage(
                content=content,
                page_classes=['liveupdate-app'],
            ).render()
        else:
            # ensure we're off the cookie domain before allowing embedding
            if request.host != g.media_domain:
                abort(404)
            c.allow_framing = True

            # interstitial redirects and nsfw settings are funky on the media
            # domain. just disable nsfw embeds.
            if c.liveupdate_event.nsfw:
                embed_page = pages.LiveUpdateEventEmbed(
                    content=pages.LiveUpdateNSFWEmbed(),
                )
                request.environ["usable_error_content"] = embed_page.render()
                abort(403)

            embed_page = pages.LiveUpdateEventEmbed(
                content=content,
                page_classes=['liveupdate-app'],
            )

            if style_sr and getattr(style_sr, "type", "private") != "private":
                c.can_apply_styles = True
                c.allow_styles = True
                embed_page.subreddit_stylesheet_url = \
                    Reddit.get_subreddit_stylesheet_url(style_sr)

            return embed_page.render()

    def GET_focus(self, target):
        try:
            target = uuid.UUID(target)
        except (TypeError, ValueError):
            self.abort404()

        try:
            query = LiveUpdateStream.query_focus(c.liveupdate_event, target)
        except tdb_cassandra.NotFound:
            self.abort404()

        builder = LiveUpdateBuilder(
            query=query, skip=True, reverse=True, num=1, count=0)
        listing = pages.LiveUpdateListing(builder)
        wrapped_listing = listing.listing()

        c.js_preload.set_wrapped(
            "/live/" + c.liveupdate_event._id + ".json",
            wrapped_listing,
        )

        content = pages.LiveUpdateFocusApp(
            event=c.liveupdate_event,
            listing=wrapped_listing,
        )

        return pages.LiveUpdateEventFocusPage(
            content=content,
            page_classes=["liveupdate-focus"],
        ).render()

    @require_oauth2_scope("read")
    @api_doc(
        section=api_section.live,
        uri="/live/{thread}/about",
    )
    def GET_about(self):
        """Get some basic information about the live thread.

        See also: [/api/live/*thread*/edit](#POST_api_live_{thread}_edit).

        """
        if not is_api():
            self.abort404()
        content = Wrapped(c.liveupdate_event)
        return pages.LiveUpdateEventPage(content=content).render()

    @require_oauth2_scope("read")
    @base_listing
    @api_doc(
        section=api_section.live,
        uri="/live/{thread}/discussions",
        supports_rss=True,
    )
    def GET_discussions(self, num, after, reverse, count):
        """Get a list of reddit submissions linking to this thread."""
        builder = url_links_builder(
            url="/live/" + c.liveupdate_event._id,
            num=num,
            after=after,
            reverse=reverse,
            count=count,
        )
        listing = LinkListing(builder).listing()
        return pages.LiveUpdateEventPage(
            content=listing,
        ).render()

    @validate(
        VLiveUpdateContributorWithPermission("settings"),
    )
    def GET_edit(self):
        return pages.LiveUpdateEventPage(
            content=pages.LiveUpdateEventConfiguration(),
        ).render()

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("settings"),
        VModhash(),
        **EVENT_CONFIGURATION_VALIDATORS
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_edit(self, form, jquery, title, description, resources, nsfw):
        """Configure the thread.

        Requires the `settings` permission for this thread.

        See also: [/live/*thread*/about.json](#GET_live_{thread}_about.json).

        """
        if not is_event_configuration_valid(form):
            return

        changes = {}
        if title != c.liveupdate_event.title:
            changes["title"] = title
        if description != c.liveupdate_event.description:
            changes["description"] = description
            changes["description_html"] = safemarkdown(description, nofollow=True) or ""
        if resources != c.liveupdate_event.resources:
            changes["resources"] = resources
            changes["resources_html"] = safemarkdown(resources, nofollow=True) or ""
        if nsfw != c.liveupdate_event.nsfw:
            changes["nsfw"] = nsfw
        _broadcast(type="settings", payload=changes)

        c.liveupdate_event.title = title
        c.liveupdate_event.description = description
        c.liveupdate_event.resources = resources
        c.liveupdate_event.nsfw = nsfw
        c.liveupdate_event._commit()

        form.set_html(".status", _("saved"))
        form.refresh()

    # TODO: pass listing params on
    @require_oauth2_scope("read")
    @api_doc(
        section=api_section.live,
        uri="/live/{thread}/contributors",
    )
    def GET_contributors(self):
        """Get a list of users that contribute to this thread.

        See also: [/api/live/*thread*/invite_contributor]
        (#POST_api_live_{thread}_invite_contributor), and
        [/api/live/*thread*/rm_contributor]
        (#POST_api_live_{thread}_rm_contributor).

        """
        editable = c.liveupdate_permissions.allow("manage")

        content = [pages.LinkBackToLiveUpdate()]

        contributors = c.liveupdate_event.contributors
        invites = LiveUpdateContributorInvitesByEvent.get_all(c.liveupdate_event)

        contributor_builder = LiveUpdateContributorBuilder(
            c.liveupdate_event, contributors, editable)
        contributor_listing = pages.LiveUpdateContributorListing(
            c.liveupdate_event,
            contributor_builder,
            has_invite=c.user_is_loggedin and c.user._id in invites,
            is_contributor=c.user_is_loggedin and c.user._id in contributors,
        ).listing()
        content.append(contributor_listing)

        if editable:
            invite_builder = LiveUpdateInvitedContributorBuilder(
                c.liveupdate_event, invites, editable)
            invite_listing = pages.LiveUpdateInvitedContributorListing(
                c.liveupdate_event,
                invite_builder,
                editable=editable,
            ).listing()
            content.append(invite_listing)

        return pages.LiveUpdateEventPage(
            content=PaneStack(content),
        ).render()

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("manage"),
        VModhash(),
        user=VExistingUname("name"),
        type_and_perms=VLiveUpdatePermissions("type", "permissions"),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_invite_contributor(self, form, jquery, user, type_and_perms):
        """Invite another user to contribute to the thread.

        Requires the `manage` permission for this thread.  If the recipient
        accepts the invite, they will be granted the permissions specified.

        See also: [/api/live/*thread*/accept_contributor_invite]
        (#POST_api_live_{thread}_accept_contributor_invite), and
        [/api/live/*thread*/rm_contributor_invite]
        (#POST_api_live_{thread}_rm_contributor_invite).

        """
        if form.has_errors("name", errors.USER_DOESNT_EXIST,
                                   errors.NO_USER):
            return
        if form.has_errors("type", errors.INVALID_PERMISSION_TYPE):
            return
        if form.has_errors("permissions", errors.INVALID_PERMISSIONS):
            return

        type, permissions = type_and_perms

        invites = LiveUpdateContributorInvitesByEvent.get_all(c.liveupdate_event)
        if user._id in invites or user._id in c.liveupdate_event.contributors:
            c.errors.add(errors.LIVEUPDATE_ALREADY_CONTRIBUTOR, field="name")
            form.has_errors("name", errors.LIVEUPDATE_ALREADY_CONTRIBUTOR)
            return

        if len(invites) >= g.liveupdate_invite_quota:
            c.errors.add(errors.LIVEUPDATE_TOO_MANY_INVITES, field="name")
            form.has_errors("name", errors.LIVEUPDATE_TOO_MANY_INVITES)
            return

        LiveUpdateContributorInvitesByEvent.create(
            c.liveupdate_event, user, permissions)

        # TODO: make this i18n-friendly when we have such a system for PMs
        send_system_message(
            user,
            subject="invitation to contribute to " + c.liveupdate_event.title,
            body=INVITE_MESSAGE % {
                "title": c.liveupdate_event.title,
                "url": "/live/" + c.liveupdate_event._id,
            },
        )

        # add the user to the table
        contributor = LiveUpdateContributor(user, permissions)
        user_row = pages.InvitedLiveUpdateContributorTableItem(
            contributor, c.liveupdate_event, editable=True)
        jquery(".liveupdate_contributor_invite-table").show(
            ).find("table").insert_table_rows(user_row)

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VUser(),
        VModhash(),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_leave_contributor(self, form, jquery):
        """Abdicate contributorship of the thread.

        See also: [/api/live/*thread*/accept_contributor_invite]
        (#POST_api_live_{thread}_accept_contributor_invite), and
        [/api/live/*thread*/invite_contributor]
        (#POST_api_live_{thread}_invite_contributor).

        """
        c.liveupdate_event.remove_contributor(c.user)

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("manage"),
        VModhash(),
        user=VByName("id", thing_cls=Account),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_rm_contributor_invite(self, form, jquery, user):
        """Revoke an outstanding contributor invite.

        Requires the `manage` permission for this thread.

        See also: [/api/live/*thread*/invite_contributor]
        (#POST_api_live_{thread}_invite_contributor).

        """
        LiveUpdateContributorInvitesByEvent.remove(
            c.liveupdate_event, user)

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VUser(),
        VModhash(),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_accept_contributor_invite(self, form, jquery):
        """Accept a pending invitation to contribute to the thread.

        See also: [/api/live/*thread*/leave_contributor]
        (#POST_api_live_{thread}_leave_contributor).

        """
        try:
            permissions = LiveUpdateContributorInvitesByEvent.get(
                c.liveupdate_event, c.user)
        except InviteNotFoundError:
            c.errors.add(errors.LIVEUPDATE_NO_INVITE_FOUND)
            form.set_error(errors.LIVEUPDATE_NO_INVITE_FOUND, None)
            return

        LiveUpdateContributorInvitesByEvent.remove(
            c.liveupdate_event, c.user)

        c.liveupdate_event.add_contributor(c.user, permissions)
        jquery.refresh()

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("manage"),
        VModhash(),
        user=VExistingUname("name"),
        type_and_perms=VLiveUpdatePermissions("type", "permissions"),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_set_contributor_permissions(self, form, jquery, user, type_and_perms):
        """Change a contributor or contributor invite's permissions.

        Requires the `manage` permission for this thread.

        See also: [/api/live/*thread*/invite_contributor]
        (#POST_api_live_{thread}_invite_contributor) and
        [/api/live/*thread*/rm_contributor]
        (#POST_api_live_{thread}_rm_contributor).

        """
        if form.has_errors("name", errors.USER_DOESNT_EXIST,
                                   errors.NO_USER):
            return
        if form.has_errors("type", errors.INVALID_PERMISSION_TYPE):
            return
        if form.has_errors("permissions", errors.INVALID_PERMISSIONS):
            return

        type, permissions = type_and_perms
        if type == "liveupdate_contributor":
            if user._id not in c.liveupdate_event.contributors:
                c.errors.add(errors.LIVEUPDATE_NOT_CONTRIBUTOR, field="user")
                form.has_errors("user", errors.LIVEUPDATE_NOT_CONTRIBUTOR)
                return

            c.liveupdate_event.update_contributor_permissions(user, permissions)
        elif type == "liveupdate_contributor_invite":
            try:
                LiveUpdateContributorInvitesByEvent.get(
                    c.liveupdate_event, user)
            except InviteNotFoundError:
                c.errors.add(errors.LIVEUPDATE_NO_INVITE_FOUND, field="user")
                form.has_errors("user", errors.LIVEUPDATE_NO_INVITE_FOUND)
                return
            else:
                LiveUpdateContributorInvitesByEvent.update_invite_permissions(
                    c.liveupdate_event, user, permissions)

        row = form.closest("tr")
        editor = row.find(".permissions").data("PermissionEditor")
        editor.onCommit(permissions.dumps())

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("manage"),
        VModhash(),
        user=VByName("id", thing_cls=Account),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_rm_contributor(self, form, jquery, user):
        """Revoke another user's contributorship.

        Requires the `manage` permission for this thread.

        See also: [/api/live/*thread*/invite_contributor]
        (#POST_api_live_{thread}_invite_contributor).

        """
        c.liveupdate_event.remove_contributor(user)

    @require_oauth2_scope("submit")
    @validatedForm(
        VLiveUpdateContributorWithPermission("update"),
        VModhash(),
        text=VMarkdownLength("body", max_length=4096),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_update(self, form, jquery, text):
        """Post an update to the thread.

        Requires the `update` permission for this thread.

        See also: [/api/live/*thread*/strike_update]
        (#POST_api_live_{thread}_strike_update), and
        [/api/live/*thread*/delete_update]
        (#POST_api_live_{thread}_delete_update).

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

        # create and store the new update
        update = LiveUpdate(data={
            "author_id": c.user._id,
            "body": text,
            "_spam": c.user._spam,
        })

        hooks.get_hook("liveupdate.update").call(update=update)

        LiveUpdateStream.add_update(c.liveupdate_event, update)

        # tell the world about our new update
        builder = LiveUpdateBuilder(None)
        wrapped = builder.wrap_items([update])[0]
        rendered = wrapped.render(style="api")
        _broadcast(type="update", payload=rendered)

        # Queue up parsing any embeds
        queue_parse_embeds(c.liveupdate_event, update)

        # reset the submission form
        t = form.find("textarea")
        t.attr('rows', 3).html("").val("")

    @require_oauth2_scope("edit")
    @validatedForm(
        VModhash(),
        update=VLiveUpdate("id"),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_delete_update(self, form, jquery, update):
        """Delete an update from the thread.

        Requires that specified update must have been authored by the user or
        that you have the `edit` permission for this thread.

        See also: [/api/live/*thread*/update](#POST_api_live_{thread}_update).

        """
        if form.has_errors("id", errors.NO_THING_ID):
            return

        if not (c.liveupdate_permissions.allow("edit") or
                (c.user_is_loggedin and update.author_id == c.user._id)):
            abort(403)

        update.deleted = True
        LiveUpdateStream.add_update(c.liveupdate_event, update)

        _broadcast(type="delete", payload=update._fullname)

    @require_oauth2_scope("edit")
    @validatedForm(
        VModhash(),
        update=VLiveUpdate("id"),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_strike_update(self, form, jquery, update):
        """Strike (mark incorrect and cross out) the content of an update.

        Requires that specified update must have been authored by the user or
        that you have the `edit` permission for this thread.

        See also: [/api/live/*thread*/update](#POST_api_live_{thread}_update).

        """
        if form.has_errors("id", errors.NO_THING_ID):
            return

        if not (c.liveupdate_permissions.allow("edit") or
                (c.user_is_loggedin and update.author_id == c.user._id)):
            abort(403)

        update.stricken = True
        LiveUpdateStream.add_update(c.liveupdate_event, update)

        _broadcast(type="strike", payload=update._fullname)

    @require_oauth2_scope("livemanage")
    @validatedForm(
        VLiveUpdateContributorWithPermission("close"),
        VModhash(),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_close_thread(self, form, jquery):
        """Permanently close the thread, disallowing future updates.

        Requires the `close` permission for this thread.

        """
        c.liveupdate_event.state = "complete"
        c.liveupdate_event._commit()

        queries.complete_event(c.liveupdate_event)

        _broadcast(type="complete", payload={})

        form.refresh()

    @require_oauth2_scope("report")
    @validatedForm(
        VUser(),
        VModhash(),
        report_type=VOneOf("type", pages.REPORT_TYPES),
    )
    @api_doc(
        section=api_section.live,
    )
    def POST_report(self, form, jquery, report_type):
        """Report the thread for violating the rules of reddit."""
        if form.has_errors("type", errors.INVALID_OPTION):
            return

        if c.user._spam or c.user.ignorereports:
            return

        already_reported = LiveUpdateReportsByAccount.get_report(
            c.user, c.liveupdate_event)
        if already_reported:
            self.abort403()

        LiveUpdateReportsByAccount.create(
            c.user, c.liveupdate_event, type=report_type)
        queries.report_event(c.liveupdate_event)

        try:
            default_subreddit = Subreddit._by_name(g.default_sr)
        except NotFound:
            pass
        else:
            not_yet_reported = g.cache.add(
                "lu_reported_" + str(c.liveupdate_event._id), 1, time=3600)
            if not_yet_reported:
                send_system_message(
                    default_subreddit,
                    subject="live thread reported",
                    body=REPORTED_MESSAGE % {
                        "title": c.liveupdate_event.title,
                        "url": "/live/" + c.liveupdate_event._id,
                        "reason": pages.REPORT_TYPES[report_type],
                    },
                )

    @validatedForm(
        VAdmin(),
        VModhash(),
    )
    def POST_approve(self, form, jquery):
        c.liveupdate_event.banned = False
        c.liveupdate_event._commit()

        queries.unreport_event(c.liveupdate_event)

    @validatedForm(
        VAdmin(),
        VModhash(),
    )
    def POST_ban(self, form, jquery):
        c.liveupdate_event.banned = True
        c.liveupdate_event.banned_by = c.user.name
        c.liveupdate_event._commit()

        queries.unreport_event(c.liveupdate_event)
Ejemplo n.º 7
0
class WikiController(RedditController):
    allow_stylesheets = True

    @require_oauth2_scope("wikiread")
    @api_doc(api_section.wiki, uri='/wiki/{page}', uses_site=True)
    @validate(pv=VWikiPageAndVersion(('page', 'v', 'v2'),
                                     required=False,
                                     restricted=False,
                                     allow_hidden_revision=False),
              page_name=VWikiPageName('page', error_on_name_normalized=True))
    def GET_wiki_page(self, pv, page_name):
        """Return the content of a wiki page

        If `v` is given, show the wiki page as it was at that version
        If both `v` and `v2` are given, show a diff of the two

        """
        message = None

        if c.errors.get(('PAGE_NAME_NORMALIZED', 'page')):
            url = join_urls(c.wiki_base_url, page_name)
            return self.redirect(url)

        page, version, version2 = pv

        if not page:
            is_api = c.render_style in extensions.API_TYPES
            if this_may_revise():
                if is_api:
                    self.handle_error(404, 'PAGE_NOT_CREATED')
                errorpage = WikiNotFound(page=page_name)
                request.environ['usable_error_content'] = errorpage.render()
            elif is_api:
                self.handle_error(404, 'PAGE_NOT_FOUND')
            self.abort404()

        if version:
            edit_by = version.get_author()
            edit_date = version.date
        else:
            edit_by = page.get_author()
            edit_date = page._get('last_edit_date')

        diffcontent = None
        if not version:
            content = page.content
            if c.is_wiki_mod and page.name in page_descriptions:
                message = page_descriptions[page.name]
        else:
            message = _("viewing revision from %s") % timesince(version.date)
            if version2:
                t1 = timesince(version.date)
                t2 = timesince(version2.date)
                timestamp1 = _("%s ago") % t1
                timestamp2 = _("%s ago") % t2
                message = _("comparing revisions from %(date_1)s and %(date_2)s") \
                          % {'date_1': t1, 'date_2': t2}
                diffcontent = make_htmldiff(version.content, version2.content,
                                            timestamp1, timestamp2)
                content = version2.content
            else:
                message = _("viewing revision from %s ago") % timesince(
                    version.date)
                content = version.content

        renderer = RENDERERS_BY_PAGE.get(page.name, 'wiki')

        return WikiPageView(content,
                            alert=message,
                            v=version,
                            diff=diffcontent,
                            may_revise=this_may_revise(page),
                            edit_by=edit_by,
                            edit_date=edit_date,
                            page=page.name,
                            renderer=renderer).render()

    @require_oauth2_scope("wikiread")
    @api_doc(api_section.wiki, uri='/wiki/revisions/{page}', uses_site=True)
    @paginated_listing(max_page_size=100, backend='cassandra')
    @validate(page=VWikiPage(('page'), restricted=False))
    def GET_wiki_revisions(self, num, after, reverse, count, page):
        """Retrieve a list of revisions of this wiki `page`"""
        revisions = page.get_revisions()
        wikiuser = c.user if c.user_is_loggedin else None
        builder = WikiRevisionBuilder(revisions,
                                      user=wikiuser,
                                      sr=c.site,
                                      num=num,
                                      reverse=reverse,
                                      count=count,
                                      after=after,
                                      skip=not c.is_wiki_mod,
                                      wrap=default_thing_wrapper(),
                                      page=page)
        listing = WikiRevisionListing(builder).listing()
        return WikiRevisions(listing,
                             page=page.name,
                             may_revise=this_may_revise(page)).render()

    @validate(wp=VWikiPageRevise('page'), page=VWikiPageName('page'))
    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()

    @validate(wp=VWikiPageRevise('page', restricted=True, required=True))
    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()

    @require_oauth2_scope("wikiread")
    @api_doc(api_section.wiki, uri='/wiki/revisions', uses_site=True)
    @paginated_listing(max_page_size=100, backend='cassandra')
    def GET_wiki_recent(self, num, after, reverse, count):
        """Retrieve a list of recently changed wiki pages in this subreddit"""
        revisions = WikiRevision.get_recent(c.site)
        wikiuser = c.user if c.user_is_loggedin else None
        builder = WikiRecentRevisionBuilder(revisions,
                                            num=num,
                                            count=count,
                                            reverse=reverse,
                                            after=after,
                                            wrap=default_thing_wrapper(),
                                            skip=not c.is_wiki_mod,
                                            user=wikiuser,
                                            sr=c.site)
        listing = WikiRevisionListing(builder).listing()
        return WikiRecent(listing, sr_path=not c.site.is_homepage).render()

    @require_oauth2_scope("wikiread")
    @api_doc(api_section.wiki, uri='/wiki/pages', uses_site=True)
    def GET_wiki_listing(self):
        """Retrieve a list of wiki pages in this subreddit"""
        def check_hidden(page):
            return page.listed and this_may_view(page)

        pages, linear_pages = WikiPage.get_listing(c.site,
                                                   filter_check=check_hidden)
        return WikiListing(pages, linear_pages,
                           sr_path=not c.site.is_homepage).render()

    def GET_wiki_redirect(self, page='index'):
        return self.redirect(str("%s/%s" % (c.wiki_base_url, page)), code=301)

    @require_oauth2_scope("wikiread")
    @api_doc(api_section.wiki, uri='/wiki/discussions/{page}', uses_site=True)
    @base_listing
    @validate(page=VWikiPage('page', restricted=True))
    def GET_wiki_discussions(self, page, num, after, reverse, count):
        """Retrieve a list of discussions about this wiki `page`"""
        page_url = add_sr("%s/%s" % (c.wiki_base_url, page.name))
        builder = url_links_builder(page_url,
                                    num=num,
                                    after=after,
                                    reverse=reverse,
                                    count=count)
        listing = LinkListing(builder).listing()
        return WikiDiscussions(listing,
                               page=page.name,
                               may_revise=this_may_revise(page),
                               sr_path=not c.site.is_homepage).render()

    @require_oauth2_scope("modwiki")
    @api_doc(api_section.wiki, uri='/wiki/settings/{page}', uses_site=True)
    @validate(page=VWikiPage('page', restricted=True, modonly=True))
    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()

    @require_oauth2_scope("modwiki")
    @api_doc(api_section.wiki, uri='/wiki/settings/{page}', uses_site=True)
    @validate(
        VModhash(),
        page=VWikiPage('page', restricted=True, modonly=True),
        permlevel=VInt('permlevel'),
        listed=VBoolean('listed'),
    )
    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)

    def on_validation_error(self, error):
        RedditController.on_validation_error(self, error)
        if error.code:
            self.handle_error(error.code, error.name)

    def handle_error(self, code, reason=None, **data):
        abort(reddit_http_error(code, reason, **data))

    def pre(self):
        RedditController.pre(self)
        if g.disable_wiki and not c.user_is_admin:
            self.handle_error(403, 'WIKI_DOWN')
        if not c.site._should_wiki:
            self.handle_error(404, 'NOT_WIKIABLE')  # /r/mod for an example
        frontpage = c.site.is_homepage
        c.wiki_base_url = join_urls(c.site.path, 'wiki')
        c.wiki_api_url = join_urls(c.site.path, '/api/wiki')

        # CUSTOM
        if frontpage:
            c.wiki_base_url = join_urls('/', 'wiki')
            c.wiki_api_url = join_urls('/', '/api/wiki')

        c.wiki_id = g.default_sr if frontpage else c.site.name

        self.editconflict = False
        c.is_wiki_mod = (c.user_is_admin or c.site.is_moderator_with_perms(
            c.user, 'wiki')) if c.user_is_loggedin else False
        c.wikidisabled = False

        mode = c.site.wikimode
        if not mode or mode == 'disabled':
            if not c.is_wiki_mod:
                self.handle_error(403, 'WIKI_DISABLED')
            else:
                c.wikidisabled = True

    # Redirects from the old wiki
    def GET_faq(self):
        return self.GET_wiki_redirect(page='faq')

    GET_help = GET_wiki_redirect
Ejemplo n.º 8
0
class PromoteApiController(ApiController):
    @json_validate(sr=VSubmitSR('sr', promotion=True),
                   location=VLocation(),
                   start=VDate('startdate'),
                   end=VDate('enddate'))
    def GET_check_inventory(self, responder, sr, location, start, end):
        sr = sr or Frontpage
        if not location or not location.country:
            available = inventory.get_available_pageviews(sr,
                                                          start,
                                                          end,
                                                          datestr=True)
        elif sr == Frontpage or c.user_is_sponsor:
            available = inventory.get_available_pageviews_geotargeted(
                sr, location, start, end, datestr=True)
        else:
            return abort(403, 'forbidden')
        return {'inventory': available}

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink("link_id36"),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_freebie(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        if campaign_has_oversold_error(form, campaign):
            form.set_html(".freebie", "target oversold, can't freebie")
            return

        if promote.is_promo(link) and campaign:
            promote.free_campaign(link, campaign, c.user)
            form.redirect(promote.promo_edit_url(link))

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VByName("link"),
                   note=nop("note"))
    def POST_promote_note(self, form, jquery, link, note):
        if promote.is_promo(link):
            text = PromotionLog.add(link, note)
            form.find(".notes").children(":last").after("<p>" + websafe(text) +
                                                        "</p>")

    @noresponse(VSponsorAdmin(), VModhash(), thing=VByName('id'))
    def POST_promote(self, thing):
        if promote.is_promo(thing):
            promote.accept_promotion(thing)

    @noresponse(VSponsorAdmin(),
                VModhash(),
                thing=VByName('id'),
                reason=nop("reason"))
    def POST_unpromote(self, thing, reason):
        if promote.is_promo(thing):
            promote.reject_promotion(thing, reason=reason)

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink('link'),
                   campaign=VPromoCampaign('campaign'))
    def POST_refund_campaign(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        billable_impressions = promote.get_billable_impressions(campaign)
        billable_amount = promote.get_billable_amount(campaign,
                                                      billable_impressions)
        refund_amount = promote.get_refund_amount(campaign, billable_amount)
        if refund_amount > 0:
            promote.refund_campaign(link, campaign, billable_amount,
                                    billable_impressions)
            form.set_html('.status', _('refund succeeded'))
        else:
            form.set_html('.status', _('refund not needed'))

    @validatedForm(VSponsor('link_id36'),
                   VModhash(),
                   VRatelimit(rate_user=True,
                              rate_ip=True,
                              prefix='create_promo_'),
                   VShamedDomain('url'),
                   username=VLength('username', 100, empty_error=None),
                   l=VLink('link_id36'),
                   title=VTitle('title'),
                   url=VUrl('url', allow_self=False),
                   selftext=VSelfText('text'),
                   kind=VOneOf('kind', ['link', 'self']),
                   ip=ValidIP(),
                   disable_comments=VBoolean("disable_comments"),
                   sendreplies=VBoolean("sendreplies"),
                   media_width=VInt("media-width", min=0),
                   media_height=VInt("media-height", min=0),
                   media_embed=VLength("media-embed", 1000),
                   media_override=VBoolean("media-override"),
                   domain_override=VLength("domain", 100))
    def POST_edit_promo(self, form, jquery, ip, username, l, title, url,
                        selftext, kind, disable_comments, sendreplies,
                        media_height, media_width, media_embed, media_override,
                        domain_override):

        should_ratelimit = False
        if not c.user_is_sponsor:
            should_ratelimit = True

        if not should_ratelimit:
            c.errors.remove((errors.RATELIMIT, 'ratelimit'))

        # check for user override
        if not l and c.user_is_sponsor and username:
            try:
                user = Account._by_name(username)
            except NotFound:
                c.errors.add(errors.USER_DOESNT_EXIST, field="username")
                form.set_error(errors.USER_DOESNT_EXIST, "username")
                return

            if not user.email:
                c.errors.add(errors.NO_EMAIL_FOR_USER, field="username")
                form.set_error(errors.NO_EMAIL_FOR_USER, "username")
                return

            if not user.email_verified:
                c.errors.add(errors.NO_VERIFIED_EMAIL, field="username")
                form.set_error(errors.NO_VERIFIED_EMAIL, "username")
                return
        else:
            user = c.user

        # check for shame banned domains
        if form.has_errors("url", errors.DOMAIN_BANNED):
            g.stats.simple_event('spam.shame.link')
            return

        # demangle URL in canonical way
        if url:
            if isinstance(url, (unicode, str)):
                form.set_inputs(url=url)
            elif isinstance(url, tuple) or isinstance(url[0], Link):
                # there's already one or more links with this URL, but
                # we're allowing mutliple submissions, so we really just
                # want the URL
                url = url[0].url

        if kind == 'link':
            if form.has_errors('url', errors.NO_URL, errors.BAD_URL):
                return

        # users can change the disable_comments on promoted links
        if ((not l or not promote.is_promoted(l))
                and (form.has_errors('title', errors.NO_TEXT, errors.TOO_LONG)
                     or jquery.has_errors('ratelimit', errors.RATELIMIT))):
            return

        if not l:
            l = promote.new_promotion(title, url if kind == 'link' else 'self',
                                      selftext if kind == 'self' else '', user,
                                      ip)

        elif promote.is_promo(l):
            # changing link type is not allowed
            if ((l.is_self and kind == 'link')
                    or (not l.is_self and kind == 'self')):
                c.errors.add(errors.NO_CHANGE_KIND, field="kind")
                form.set_error(errors.NO_CHANGE_KIND, "kind")
                return

            changed = False
            # live items can only be changed by a sponsor, and also
            # pay the cost of de-approving the link
            trusted = c.user_is_sponsor or c.user.trusted_sponsor
            if not promote.is_promoted(l) or trusted:
                if title and title != l.title:
                    l.title = title
                    changed = not trusted

                if kind == 'link' and url and url != l.url:
                    l.url = url
                    changed = not trusted

            # only trips if the title and url are changed by a non-sponsor
            if changed:
                promote.unapprove_promotion(l)

            # selftext can be changed at any time
            if kind == 'self':
                l.selftext = selftext

            # comment disabling and sendreplies is free to be changed any time.
            l.disable_comments = disable_comments
            l.sendreplies = sendreplies
            if c.user_is_sponsor or c.user.trusted_sponsor:
                if media_embed and media_width and media_height:
                    l.media_object = dict(height=media_height,
                                          width=media_width,
                                          content=media_embed,
                                          type='custom')
                else:
                    l.media_object = None

                l.media_override = media_override
                if getattr(l, "domain_override", False) or domain_override:
                    l.domain_override = domain_override
            l._commit()

        form.redirect(promote.promo_edit_url(l))

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_add_roadblock(self, form, jquery, dates, sr):
        if (form.has_errors('startdate', errors.BAD_DATE) or form.has_errors(
                'enddate', errors.BAD_DATE, errors.BAD_DATE_RANGE)):
            return
        if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                           errors.SUBREDDIT_NOTALLOWED,
                           errors.SUBREDDIT_REQUIRED):
            return
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.add(sr, sd, ed)
            jquery.refresh()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_rm_roadblock(self, form, jquery, dates, sr):
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.remove(sr, sd, ed)
            jquery.refresh()

    @validatedForm(VSponsor('link_id36'),
                   VModhash(),
                   dates=VDateRange(
                       ['startdate', 'enddate'],
                       earliest=timedelta(days=g.min_promote_future),
                       latest=timedelta(days=g.max_promote_future),
                       reference_date=promote.promo_datetime_now,
                       business_days=True,
                       sponsor_override=True),
                   link=VLink('link_id36'),
                   bid=VFloat('bid', coerce=False),
                   sr=VSubmitSR('sr', promotion=True),
                   campaign_id36=nop("campaign_id36"),
                   targeting=VLength("targeting", 10),
                   priority=VPriority("priority"),
                   location=VLocation())
    def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid,
                           sr, targeting, priority, location):
        if not link:
            return

        start, end = dates or (None, None)

        if location and sr and not c.user_is_sponsor:
            # only sponsors can geotarget on subreddits
            location = None

        if location and location.metro:
            cpm = g.cpm_selfserve_geotarget_metro.pennies
        elif location:
            cpm = g.cpm_selfserve_geotarget_country.pennies
        else:
            author = Account._byID(link.author_id, data=True)
            cpm = author.cpm_selfserve_pennies

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE,
                                   errors.BAD_DATE_RANGE)):
            return

        # check that start is not so late that authorization hold will expire
        if not c.user_is_sponsor:
            max_start = promote.get_max_startdate()
            if start > max_start:
                c.errors.add(
                    errors.DATE_TOO_LATE,
                    msg_params={'day': max_start.strftime("%m/%d/%Y")},
                    field='startdate')
                form.has_errors('startdate', errors.DATE_TOO_LATE)
                return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        campaign = None
        if campaign_id36:
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
            except NotFound:
                pass

        if campaign and link._id != campaign.link_id:
            return abort(404, 'not found')

        if priority.cpm:
            min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
            max_bid = None if c.user_is_sponsor else g.max_promote_bid

            if bid is None or bid < min_bid or (max_bid and bid > max_bid):
                c.errors.add(errors.BAD_BID,
                             field='bid',
                             msg_params={
                                 'min': min_bid,
                                 'max': max_bid or g.max_promote_bid
                             })
                form.has_errors('bid', errors.BAD_BID)
                return

            # you cannot edit the bid of a live ad unless it's a freebie
            if (campaign and bid != campaign.bid
                    and promote.is_live_promo(link, campaign)
                    and not campaign.is_freebie()):
                c.errors.add(errors.BID_LIVE, field='bid')
                form.has_errors('bid', errors.BID_LIVE)
                return

        else:
            bid = 0.  # Set bid to 0 as dummy value

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return

            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {
                    "start": roadblock[0].strftime('%m/%d/%Y'),
                    "end": roadblock[1].strftime('%m/%d/%Y')
                }
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return

        elif targeting == 'none':
            sr = None

        # Check inventory
        campaign = campaign if campaign_id36 else None
        if not priority.inventory_override:
            oversold = has_oversold_error(form, campaign, start, end, bid, cpm,
                                          sr, location)
            if oversold:
                return

        if campaign:
            promote.edit_campaign(link, campaign, dates, bid, cpm, sr,
                                  priority, location)
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, sr,
                                            priority, location)
        rc = RenderableCampaign.from_campaigns(link, campaign)
        jquery.update_campaign(campaign._fullname, rc.render_html())

    @validatedForm(VSponsor('link_id36'),
                   VModhash(),
                   l=VLink('link_id36'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_delete_campaign(self, form, jquery, l, campaign):
        if not campaign or not l or l._id != campaign.link_id:
            return abort(404, 'not found')

        promote.delete_campaign(l, campaign)

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink('link_id36'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_terminate_campaign(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        promote.terminate_campaign(link, campaign)
        rc = RenderableCampaign.from_campaigns(link, campaign)
        jquery.update_campaign(campaign._fullname, rc.render_html())

    @validatedForm(VSponsor('link'),
                   VModhash(),
                   link=VByName("link"),
                   campaign=VPromoCampaign("campaign"),
                   customer_id=VInt("customer_id", min=0),
                   pay_id=VInt("account", min=0),
                   edit=VBoolean("edit"),
                   address=ValidAddress([
                       "firstName", "lastName", "company", "address", "city",
                       "state", "zip", "country", "phoneNumber"
                   ]),
                   creditcard=ValidCard(
                       ["cardNumber", "expirationDate", "cardCode"]))
    def POST_update_pay(self, form, jquery, link, campaign, customer_id,
                        pay_id, edit, address, creditcard):
        if not g.authorizenetapi:
            return

        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        # Check inventory
        if campaign_has_oversold_error(form, campaign):
            return

        # check that start is not so late that authorization hold will expire
        max_start = promote.get_max_startdate()
        if campaign.start_date > max_start:
            msg = _("please change campaign start date to %(date)s or earlier")
            date = format_date(max_start, format="short", locale=c.locale)
            msg %= {'date': date}
            form.set_html(".status", msg)
            return

        address_modified = not pay_id or edit
        if address_modified:
            address_fields = [
                "firstName", "lastName", "company", "address", "city", "state",
                "zip", "country", "phoneNumber"
            ]
            card_fields = ["cardNumber", "expirationDate", "cardCode"]

            if (form.has_errors(address_fields, errors.BAD_ADDRESS)
                    or form.has_errors(card_fields, errors.BAD_CARD)):
                return

            pay_id = edit_profile(c.user, address, creditcard, pay_id)

        reason = None
        if pay_id:
            success, reason = promote.auth_campaign(link, campaign, c.user,
                                                    pay_id)

            if success:
                form.redirect(promote.promo_edit_url(link))
                return

        msg = reason or _("failed to authenticate card. sorry.")
        form.set_html(".status", msg)

    @validate(VSponsor("link_name"),
              VModhash(),
              link=VByName('link_name'),
              file=VUploadLength('file', 500 * 1024),
              img_type=VImageType('img_type'))
    def POST_link_thumb(self, link=None, file=None, img_type='jpg'):
        if link and (not promote.is_promoted(link) or c.user_is_sponsor
                     or c.user.trusted_sponsor):
            errors = dict(BAD_CSS_NAME="", IMAGE_ERROR="")

            # thumnails for promoted links can change and therefore expire
            force_thumbnail(link, file, file_type=".%s" % img_type)

            if any(errors.values()):
                return UploadedImage("",
                                     "",
                                     "upload",
                                     errors=errors,
                                     form_id="image-upload").render()
            else:
                link._commit()
                return UploadedImage(_('saved'),
                                     thumbnail_url(link),
                                     "",
                                     errors=errors,
                                     form_id="image-upload").render()
Ejemplo n.º 9
0
Archivo: wiki.py Proyecto: wal-f/reddit
class WikiApiController(WikiController):
    @validate(VModhash(),
              pageandprevious=VWikiPageRevise(('page', 'previous'), restricted=True),
              content=VMarkdown(('content'), renderer='wiki'),
              page_name=VWikiPageName('page'),
              reason=VPrintable('reason', 256))
    @api_doc(api_section.wiki, uri='/api/wiki/edit')
    def POST_wiki_edit(self, pageandprevious, content, page_name, reason):
        page, previous = pageandprevious

        if not page:
            error = c.errors.get(('WIKI_CREATE_ERROR', 'page'))
            if error:
                self.handle_error(403, **(error.msg_params or {}))
            if not c.user._spam:
                page = WikiPage.create(c.site, page_name)
        if c.user._spam:
            error = _("You are doing that too much, please try again later.")
            self.handle_error(415, 'SPECIAL_ERRORS', special_errors=[error])
        # 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:
            if page.name == 'config/stylesheet':
                report, parsed = c.site.parse_css(content, verify=False)
                if report is None:  # g.css_killswitch
                    self.handle_error(403, 'STYLESHEET_EDIT_DENIED')
                if report.errors:
                    error_items = [x.message for x in sorted(report.errors)]
                    self.handle_error(415, 'SPECIAL_ERRORS', special_errors=error_items)
                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:
                    setattr(c.site, ATTRIBUTE_BY_PAGE[page.name], content)
                    setattr(c.site, "prev_" + ATTRIBUTE_BY_PAGE[page.name] + "_id", str(page.revision))
                    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)
        except ConflictException as e:
            self.handle_error(409, 'EDIT_CONFLICT', newcontent=e.new, newrevision=page.revision, diffcontent=e.htmldiff)
        return json.dumps({})

    @validate(VModhash(),
              VWikiModerator(),
              page=VWikiPage('page'),
              act=VOneOf('act', ('del', 'add')),
              user=VExistingUname('username'))
    @api_doc(api_section.wiki, uri='/api/wiki/alloweditor/:act')
    def POST_wiki_allow_editor(self, act, page, user):
        if not user:
            self.handle_error(404, 'UNKNOWN_USER')
        elif act == 'del':
            page.remove_editor(user._id36)
        elif act == 'add':
            page.add_editor(user._id36)
        else:
            self.handle_error(400, 'INVALID_ACTION')
        return json.dumps({})

    @validate(VModhash(),
              VWikiModerator(),
              pv=VWikiPageAndVersion(('page', 'revision')))
    @api_doc(api_section.wiki, uri='/api/wiki/hide')
    def POST_wiki_revision_hide(self, pv):
        page, revision = pv
        if not revision:
            self.handle_error(400, 'INVALID_REVISION')
        return json.dumps({'status': revision.toggle_hide()})

    @validate(VModhash(),
              VWikiModerator(),
              pv=VWikiPageAndVersion(('page', 'revision')))
    @api_doc(api_section.wiki, uri='/api/wiki/revert')
    def POST_wiki_revision_revert(self, pv):
        page, revision = pv
        if not revision:
            self.handle_error(400, 'INVALID_REVISION')
        content = revision.content
        reason = 'reverted back %s' % timesince(revision.date)
        if page.name == 'config/stylesheet':
            report, parsed = c.site.parse_css(content)
            if report.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.special:
                    setattr(c.site, ATTRIBUTE_BY_PAGE[page.name], content)
                    setattr(c.site, "prev_" + ATTRIBUTE_BY_PAGE[page.name] + "_id", page.revision)
                    c.site._commit()
            except ContentLengthError as e:
                self.handle_error(403, 'CONTENT_LENGTH_ERROR', max_length=e.max_length)
        return json.dumps({})

    def pre(self):
        WikiController.pre(self)
        c.render_style = 'api'
        set_extension(request.environ, 'json')
Ejemplo n.º 10
0
class OAuth2FrontendController(RedditController):
    def check_for_bearer_token(self):
        pass

    def pre(self):
        RedditController.pre(self)
        require_https()

    def _abort_oauth_error(self, error):
        g.stats.simple_event('oauth2.errors.%s' % error)
        abort(BadRequestError(error))

    def _check_redirect_uri(self, client, redirect_uri):
        if (errors.OAUTH2_INVALID_CLIENT, 'client_id') in c.errors:
            self._abort_oauth_error(errors.OAUTH2_INVALID_CLIENT)

        if not redirect_uri or redirect_uri != client.redirect_uri:
            self._abort_oauth_error(errors.OAUTH2_INVALID_REDIRECT_URI)

    def _check_response_type_and_scope(self, response_type, scope):
        if (errors.INVALID_OPTION, 'response_type') in c.errors:
            self._abort_oauth_error(errors.OAUTH2_INVALID_RESPONSE_TYPE)

        if (errors.OAUTH2_INVALID_SCOPE, 'scope') in c.errors:
            self._abort_oauth_error(errors.OAUTH2_INVALID_SCOPE)

    def _check_client_type_and_duration(self, response_type, client, duration):
        if response_type == "token" and client.is_confidential():
            # Prevent "confidential" clients from distributing tokens
            # in a non-confidential manner
            self._abort_oauth_error(errors.OAUTH2_CONFIDENTIAL_TOKEN)

        if response_type == "token" and duration != "temporary":
            # implicit grant -> No refresh tokens allowed
            self._abort_oauth_error(errors.OAUTH2_NO_REFRESH_TOKENS_ALLOWED)

    def _error_response(self, state, redirect_uri, as_fragment=False):
        """Return an error redirect."""
        resp = {"state": state}

        if (errors.OAUTH2_ACCESS_DENIED, "authorize") in c.errors:
            resp["error"] = "access_denied"
        elif (errors.INVALID_MODHASH, None) in c.errors:
            resp["error"] = "access_denied"
        else:
            resp["error"] = "invalid_request"

        final_redirect = _update_redirect_uri(redirect_uri, resp, as_fragment)
        return self.redirect(final_redirect, code=302)

    def _check_employee_grants(self, client, scope):
        if not c.user.employee or not client or not scope:
            return
        if client._id in g.employee_approved_clients:
            return
        if client._id in g.mobile_auth_allowed_clients:
            return
        # The identity scope doesn't leak much, and we don't mind if employees
        # prove their identity to some external service
        if scope.scopes == {"identity"}:
            return
        error_page = RedditError(
            title=_(
                'this app has not been approved for use with employee accounts'
            ),
            message="",
        )
        request.environ["usable_error_content"] = error_page.render()
        self.abort403()

    @validate(VUser(),
              response_type=VOneOf("response_type", ("code", "token")),
              client=VOAuth2ClientID(),
              redirect_uri=VRequired("redirect_uri",
                                     errors.OAUTH2_INVALID_REDIRECT_URI),
              scope=VOAuth2Scope(),
              state=VRequired("state", errors.NO_TEXT),
              duration=VOneOf("duration", ("temporary", "permanent"),
                              default="temporary"))
    def GET_authorize(self, response_type, client, redirect_uri, scope, state,
                      duration):
        """
        First step in [OAuth 2.0](http://oauth.net/2/) authentication.
        End users will be prompted for their credentials (username/password)
        and asked if they wish to authorize the application identified by
        the **client_id** parameter with the permissions specified by the
        **scope** parameter.  They are then redirected to the endpoint on
        the client application's side specified by **redirect_uri**.

        If the user granted permission to the application, the response will
        contain a **code** parameter with a temporary authorization code
        which can be exchanged for an access token at
        [/api/v1/access_token](#api_method_access_token).

        **redirect_uri** must match the URI configured for the client in the
        [app preferences](/prefs/apps).  All errors will show a 400 error
        page along with some information on what option was wrong.
        """
        self._check_employee_grants(client, scope)

        # Check redirect URI first; it will ensure client exists
        self._check_redirect_uri(client, redirect_uri)

        self._check_response_type_and_scope(response_type, scope)

        self._check_client_type_and_duration(response_type, client, duration)

        if not c.errors:
            return OAuth2AuthorizationPage(client, redirect_uri, scope, state,
                                           duration, response_type).render()
        else:
            self._abort_oauth_error(errors.INVALID_OPTION)

    @validate(VUser(),
              VModhash(fatal=False),
              client=VOAuth2ClientID(),
              redirect_uri=VRequired("redirect_uri",
                                     errors.OAUTH2_INVALID_REDIRECT_URI),
              scope=VOAuth2Scope(),
              state=VRequired("state", errors.NO_TEXT),
              duration=VOneOf("duration", ("temporary", "permanent"),
                              default="temporary"),
              authorize=VRequired("authorize", errors.OAUTH2_ACCESS_DENIED),
              response_type=VOneOf("response_type", ("code", "token"),
                                   default="code"))
    def POST_authorize(self, authorize, client, redirect_uri, scope, state,
                       duration, response_type):
        """Endpoint for OAuth2 authorization."""

        self._check_employee_grants(client, scope)

        self._check_redirect_uri(client, redirect_uri)

        self._check_response_type_and_scope(response_type, scope)

        self._check_client_type_and_duration(response_type, client, duration)

        if c.errors:
            return self._error_response(state,
                                        redirect_uri,
                                        as_fragment=(response_type == "token"))

        if response_type == "code":
            code = OAuth2AuthorizationCode._new(client._id, redirect_uri,
                                                c.user._id36, scope,
                                                duration == "permanent")
            resp = {"code": code._id, "state": state}
            final_redirect = _update_redirect_uri(redirect_uri, resp)
            g.stats.simple_event(
                'oauth2.POST_authorize.authorization_code_create')
        elif response_type == "token":
            device_id = get_device_id(client)
            token = OAuth2AccessToken._new(
                client_id=client._id,
                user_id=c.user._id36,
                scope=scope,
                device_id=device_id,
            )
            resp = OAuth2AccessController._make_new_token_response(token)
            resp["state"] = state
            final_redirect = _update_redirect_uri(redirect_uri,
                                                  resp,
                                                  as_fragment=True)
            g.stats.simple_event('oauth2.POST_authorize.access_token_create')

        # If this is the first time the user is logging in with an official
        # mobile app, gild them
        if (g.live_config.get('mobile_gild_first_login')
                and not c.user.has_used_mobile_app
                and client._id in g.mobile_auth_gild_clients):
            buyer = Account.system_user()
            admintools.adjust_gold_expiration(c.user,
                                              days=g.mobile_auth_gild_time)
            create_gift_gold(buyer._id,
                             c.user._id,
                             g.mobile_auth_gild_time,
                             datetime.now(g.tz),
                             signed=True,
                             note='first_mobile_auth')
            subject = 'Let there be gold! Reddit just sent you Reddit gold!'
            message = (
                "Thank you for using the Reddit mobile app!  As a thank you "
                "for logging in during launch week, you've been gifted %s of "
                "Reddit Gold.\n\n"
                "Reddit Gold is Reddit's premium membership program, which "
                "grants you: \n"
                "An ads-free experience in Reddit's mobile apps, and\n"
                "Extra site features on desktop\n\n"
                "Discuss and get help on the features and perks at " +
                g.brander_community_abbr +
                "/goldbenefits.") % g.mobile_auth_gild_message
            message += '\n\n' + strings.gold_benefits_msg
            send_system_message(c.user, subject, message, add_to_sent=False)
            c.user.has_used_mobile_app = True
            c.user._commit()

        return self.redirect(final_redirect, code=302)
Ejemplo n.º 11
0
class WikiApiController(WikiController):
    @require_oauth2_scope("wikiedit")
    @validate(VModhash(),
              pageandprevious=VWikiPageRevise(('page', 'previous'), restricted=True),
              content=nop(('content')),
              page_name=VWikiPageName('page'),
              reason=VPrintable('reason', 256, empty_error=None))
    @api_doc(api_section.wiki, uri='/api/wiki/edit', uses_site=True)
    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({})

    @require_oauth2_scope("modwiki")
    @validate(VModhash(),
              VWikiModerator(),
              page=VWikiPage('page'),
              act=VOneOf('act', ('del', 'add')),
              user=VExistingUname('username'))
    @api_doc(api_section.wiki, uri='/api/wiki/alloweditor/{act}',
             uses_site=True,
             uri_variants=['/api/wiki/alloweditor/%s' % act for act in ('del', 'add')])
    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({})

    @validate(
        VModhash(),
        VAdmin(),
        pv=VWikiPageAndVersion(('page', 'revision')),
        deleted=VBoolean('deleted'),
    )
    def POST_wiki_revision_delete(self, pv, deleted):
        page, revision = pv
        if not revision:
            self.handle_error(400, 'INVALID_REVISION')
        if deleted and page.revision == str(revision._id):
            self.handle_error(400, 'REVISION_IS_CURRENT')
        revision.admin_deleted = deleted
        revision._commit()
        return json.dumps({'status': revision.admin_deleted})

    @require_oauth2_scope("modwiki")
    @validate(VModhash(),
              VWikiModerator(),
              pv=VWikiPageAndVersion(('page', 'revision')))
    @api_doc(api_section.wiki, uri='/api/wiki/hide', uses_site=True)
    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()})

    @require_oauth2_scope("modwiki")
    @validate(VModhash(),
              VWikiModerator(),
              pv=VWikiPageAndVersion(('page', 'revision')))
    @api_doc(api_section.wiki, uri='/api/wiki/revert', uses_site=True)
    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.special:
                    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({})

    def pre(self):
        WikiController.pre(self)
        c.render_style = 'api'
        set_extension(request.environ, 'json')
Ejemplo n.º 12
0
class PromoteController(ListingController):
    where = 'promoted'
    render_cls = PromotePage

    @property
    def title_text(self):
        return _('promoted by you')

    @classmethod
    @memoize('live_by_subreddit', time=300)
    def live_by_subreddit(cls, sr):
        if sr == Frontpage:
            sr_id = ''
        else:
            sr_id = sr._id
        r = LiveAdWeights.get([sr_id])
        return [i.link for i in r[sr_id]]

    @classmethod
    @memoize('subreddits_with_promos', time=3600)
    def subreddits_with_promos(cls):
        sr_ids = LiveAdWeights.get_live_subreddits()
        srs = Subreddit._byID(sr_ids, return_dict=False)
        sr_names = sorted([sr.name for sr in srs], key=lambda s: s.lower())
        return sr_names

    @classmethod
    @memoize('house_campaigns', time=60)
    def get_house_campaigns(cls):
        now = promote.promo_datetime_now()
        pws = PromotionWeights.get_campaigns(now)
        campaign_ids = {pw.promo_idx for pw in pws}
        campaigns = PromoCampaign._byID(campaign_ids,
                                        data=True,
                                        return_dict=False)
        campaigns = [camp for camp in campaigns if not camp.priority.cpm]
        return campaigns

    @property
    def menus(self):
        filters = [
            NamedButton('all_promos', dest=''),
            NamedButton('future_promos'),
            NamedButton('unpaid_promos'),
            NamedButton('rejected_promos'),
            NamedButton('pending_promos'),
            NamedButton('live_promos'),
        ]
        menus = [
            NavMenu(filters,
                    base_path='/promoted',
                    title='show',
                    type='lightdrop')
        ]

        if self.sort == 'live_promos' and c.user_is_sponsor:
            sr_names = self.subreddits_with_promos()
            buttons = [NavButton(name, name) for name in sr_names]
            frontbutton = NavButton('FRONTPAGE',
                                    Frontpage.name,
                                    aliases=[
                                        '/promoted/live_promos/%s' %
                                        urllib.quote(Frontpage.name)
                                    ])
            buttons.insert(0, frontbutton)
            buttons.insert(0, NavButton('all', ''))
            menus.append(
                NavMenu(buttons,
                        base_path='/promoted/live_promos',
                        title='subreddit',
                        type='lightdrop'))

        return menus

    def keep_fn(self):
        def keep(item):
            if item.promoted and not item._deleted:
                return True
            else:
                return False

        return keep

    def query(self):
        if c.user_is_sponsor:
            if self.sort == "future_promos":
                return queries.get_all_unapproved_links()
            elif self.sort == "pending_promos":
                return queries.get_all_accepted_links()
            elif self.sort == "unpaid_promos":
                return queries.get_all_unpaid_links()
            elif self.sort == "rejected_promos":
                return queries.get_all_rejected_links()
            elif self.sort == "live_promos" and self.sr:
                return self.live_by_subreddit(self.sr)
            elif self.sort == 'live_promos':
                return queries.get_all_live_links()
            elif self.sort == 'underdelivered':
                q = queries.get_underdelivered_campaigns()
                campaigns = PromoCampaign._by_fullname(list(q),
                                                       data=True,
                                                       return_dict=False)
                link_ids = [camp.link_id for camp in campaigns]
                return [Link._fullname_from_id36(to36(id)) for id in link_ids]
            elif self.sort == 'reported':
                return queries.get_reported_links(get_promote_srid())
            elif self.sort == 'house':
                campaigns = self.get_house_campaigns()
                link_ids = {camp.link_id for camp in campaigns}
                return [Link._fullname_from_id36(to36(id)) for id in link_ids]
            return queries.get_all_promoted_links()
        else:
            if self.sort == "future_promos":
                return queries.get_unapproved_links(c.user._id)
            elif self.sort == "pending_promos":
                return queries.get_accepted_links(c.user._id)
            elif self.sort == "unpaid_promos":
                return queries.get_unpaid_links(c.user._id)
            elif self.sort == "rejected_promos":
                return queries.get_rejected_links(c.user._id)
            elif self.sort == "live_promos":
                return queries.get_live_links(c.user._id)
            return queries.get_promoted_links(c.user._id)

    @validate(VSponsor(), sr=nop('sr'))
    def GET_listing(self, sr=None, sort="", **env):
        if not c.user_is_loggedin or not c.user.email_verified:
            # never reached--see MinimalController.on_validation_error
            return self.redirect("/ad_inq")
        self.sort = sort
        self.sr = None
        if sr and sr == Frontpage.name:
            self.sr = Frontpage
        elif sr:
            try:
                self.sr = Subreddit._by_name(sr)
            except NotFound:
                pass
        return ListingController.GET_listing(self, **env)

    GET_index = GET_listing

    @validate(VSponsor())
    def GET_new_promo(self):
        return PromotePage('content', content=PromoteLinkNew()).render()

    @validate(VSponsor('link'), link=VLink('link'))
    def GET_edit_promo(self, link):
        if not link or link.promoted is None:
            return self.abort404()
        rendered = wrap_links(link,
                              wrapper=promote.sponsor_wrapper,
                              skip=False)
        form = PromoteLinkForm(link, rendered)
        page = PromotePage('new_promo', content=form)
        return page.render()

    # admin only because the route might change
    @validate(VSponsorAdmin('campaign'), campaign=VPromoCampaign('campaign'))
    def GET_edit_promo_campaign(self, campaign):
        if not campaign:
            return self.abort404()
        link = Link._byID(campaign.link_id)
        return self.redirect(promote.promo_edit_url(link))

    @json_validate(sr=VSubmitSR('sr', promotion=True),
                   start=VDate('startdate'),
                   end=VDate('enddate'))
    def GET_check_inventory(self, responder, sr, start, end):
        sr = sr or Frontpage
        available_by_datestr = inventory.get_available_pageviews(sr,
                                                                 start,
                                                                 end,
                                                                 datestr=True)
        return {'inventory': available_by_datestr}

    @validate(VSponsor(),
              dates=VDateRange(["startdate", "enddate"],
                               max_range=timedelta(days=28),
                               required=False))
    @validate(VSponsorAdmin(),
              dates=VDateRange(["startdate", "enddate"],
                               max_range=timedelta(days=28),
                               required=False))
    def GET_admingraph(self, dates):
        start, end, bad_dates = _check_dates(dates)
        content = Promote_Graph(start, end, bad_dates=bad_dates)
        if c.render_style == 'csv':
            return content.as_csv()
        return PromotePage("admingraph", content=content).render()

    # ## POST controllers below
    @validatedForm(VSponsorAdmin(),
                   link=VLink("link_id"),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_freebie(self, form, jquery, link, campaign):
        if campaign_has_oversold_error(form, campaign):
            form.set_html(".freebie", "target oversold, can't freebie")
            return

        if promote.is_promo(link) and campaign:
            promote.free_campaign(link, campaign, c.user)
            form.redirect(promote.promo_edit_url(link))

    @validatedForm(VSponsorAdmin(), link=VByName("link"), note=nop("note"))
    def POST_promote_note(self, form, jquery, link, note):
        if promote.is_promo(link):
            text = PromotionLog.add(link, note)
            form.find(".notes").children(":last").after("<p>" + text + "</p>")

    @noresponse(VSponsorAdmin(), thing=VByName('id'))
    def POST_promote(self, thing):
        if promote.is_promo(thing):
            promote.accept_promotion(thing)

    @noresponse(VSponsorAdmin(), thing=VByName('id'), reason=nop("reason"))
    def POST_unpromote(self, thing, reason):
        if promote.is_promo(thing):
            promote.reject_promotion(thing, reason=reason)

    @validate(VSponsorAdmin(),
              link=VLink("link"),
              campaign=VPromoCampaign("campaign"))
    def GET_refund(self, link, campaign):
        if campaign.link_id != link._id:
            return self.abort404()

        content = RefundPage(link, campaign)
        return Reddit("refund", content=content, show_sidebar=False).render()

    @validatedForm(VSponsorAdmin(),
                   link=VLink('link'),
                   campaign=VPromoCampaign('campaign'))
    def POST_refund_campaign(self, form, jquery, link, campaign):
        billable_impressions = promote.get_billable_impressions(campaign)
        billable_amount = promote.get_billable_amount(campaign,
                                                      billable_impressions)
        refund_amount = promote.get_refund_amount(campaign, billable_amount)
        if refund_amount > 0:
            promote.refund_campaign(link, campaign, billable_amount,
                                    billable_impressions)
            form.set_html('.status', _('refund succeeded'))
        else:
            form.set_html('.status', _('refund not needed'))

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   VRatelimit(rate_user=True,
                              rate_ip=True,
                              prefix='create_promo_'),
                   VShamedDomain('url'),
                   username=VLength('username', 100, empty_error=None),
                   l=VLink('link_id'),
                   title=VTitle('title'),
                   url=VUrl('url', allow_self=False, lookup=False),
                   selftext=VSelfText('text'),
                   kind=VOneOf('kind', ['link', 'self']),
                   ip=ValidIP(),
                   disable_comments=VBoolean("disable_comments"),
                   media_width=VInt("media-width", min=0),
                   media_height=VInt("media-height", min=0),
                   media_embed=VLength("media-embed", 1000),
                   media_override=VBoolean("media-override"),
                   domain_override=VLength("domain", 100))
    def POST_edit_promo(self, form, jquery, ip, username, l, title, url,
                        selftext, kind, disable_comments, media_height,
                        media_width, media_embed, media_override,
                        domain_override):

        should_ratelimit = False
        if not c.user_is_sponsor:
            should_ratelimit = True

        if not should_ratelimit:
            c.errors.remove((errors.RATELIMIT, 'ratelimit'))

        # check for user override
        if not l and c.user_is_sponsor and username:
            try:
                user = Account._by_name(username)
            except NotFound:
                c.errors.add(errors.USER_DOESNT_EXIST, field="username")
                form.set_error(errors.USER_DOESNT_EXIST, "username")
                return

            if not user.email:
                c.errors.add(errors.NO_EMAIL_FOR_USER, field="username")
                form.set_error(errors.NO_EMAIL_FOR_USER, "username")
                return

            if not user.email_verified:
                c.errors.add(errors.NO_VERIFIED_EMAIL, field="username")
                form.set_error(errors.NO_VERIFIED_EMAIL, "username")
                return
        else:
            user = c.user

        # check for shame banned domains
        if form.has_errors("url", errors.DOMAIN_BANNED):
            g.stats.simple_event('spam.shame.link')
            return

        # demangle URL in canonical way
        if url:
            if isinstance(url, (unicode, str)):
                form.set_inputs(url=url)
            elif isinstance(url, tuple) or isinstance(url[0], Link):
                # there's already one or more links with this URL, but
                # we're allowing mutliple submissions, so we really just
                # want the URL
                url = url[0].url

        if kind == 'link':
            if form.has_errors('url', errors.NO_URL, errors.BAD_URL):
                return

        # users can change the disable_comments on promoted links
        if ((not l or not promote.is_promoted(l))
                and (form.has_errors('title', errors.NO_TEXT, errors.TOO_LONG)
                     or jquery.has_errors('ratelimit', errors.RATELIMIT))):
            return

        if not l:
            l = promote.new_promotion(title, url if kind == 'link' else 'self',
                                      selftext if kind == 'self' else '', user,
                                      ip)

        elif promote.is_promo(l):
            # changing link type is not allowed
            if ((l.is_self and kind == 'link')
                    or (not l.is_self and kind == 'self')):
                c.errors.add(errors.NO_CHANGE_KIND, field="kind")
                form.set_error(errors.NO_CHANGE_KIND, "kind")
                return

            changed = False
            # live items can only be changed by a sponsor, and also
            # pay the cost of de-approving the link
            trusted = c.user_is_sponsor or c.user.trusted_sponsor
            if not promote.is_promoted(l) or trusted:
                if title and title != l.title:
                    l.title = title
                    changed = not trusted

                if kind == 'link' and url and url != l.url:
                    l.url = url
                    changed = not trusted

            # only trips if the title and url are changed by a non-sponsor
            if changed and not promote.is_unpaid(l):
                promote.unapprove_promotion(l)
            if trusted and promote.is_unapproved(l):
                promote.accept_promotion(l)

            # selftext can be changed at any time
            if kind == 'self':
                l.selftext = selftext

            # comment disabling is free to be changed any time.
            l.disable_comments = disable_comments
            if c.user_is_sponsor or c.user.trusted_sponsor:
                if media_embed and media_width and media_height:
                    l.media_object = dict(height=media_height,
                                          width=media_width,
                                          content=media_embed,
                                          type='custom')
                else:
                    l.media_object = None

                l.media_override = media_override
                if getattr(l, "domain_override", False) or domain_override:
                    l.domain_override = domain_override
            l._commit()

        form.redirect(promote.promo_edit_url(l))

    @validate(VSponsorAdmin())
    def GET_roadblock(self):
        return PromotePage('content', content=Roadblocks()).render()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_add_roadblock(self, form, jquery, dates, sr):
        if (form.has_errors('startdate', errors.BAD_DATE) or form.has_errors(
                'enddate', errors.BAD_DATE, errors.BAD_DATE_RANGE)):
            return
        if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                           errors.SUBREDDIT_NOTALLOWED,
                           errors.SUBREDDIT_REQUIRED):
            return
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.add(sr, sd, ed)
            jquery.refresh()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_rm_roadblock(self, form, jquery, dates, sr):
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.remove(sr, sd, ed)
            jquery.refresh()

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   dates=VDateRange(
                       ['startdate', 'enddate'],
                       earliest=timedelta(days=1),
                       latest=timedelta(days=g.max_promote_future),
                       reference_date=promote.promo_datetime_now,
                       business_days=True,
                       sponsor_override=True),
                   link=VLink('link_id'),
                   bid=VBid('bid',
                            min=0,
                            max=g.max_promote_bid,
                            coerce=False,
                            error=errors.BAD_BID),
                   sr=VSubmitSR('sr', promotion=True),
                   campaign_id36=nop("campaign_id36"),
                   targeting=VLength("targeting", 10),
                   priority=VPriority("priority"))
    def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid,
                           sr, targeting, priority):
        if not link:
            return

        start, end = dates or (None, None)

        author = Account._byID(link.author_id, data=True)
        cpm = author.cpm_selfserve_pennies

        if (start and end and not promote.is_accepted(link)
                and not c.user_is_sponsor):
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now,
                                      g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                day = promote.promo_datetime_now(offset=g.min_promote_future)
                day = day.strftime("%m/%d/%Y")
                c.errors.add(errors.DATE_TOO_EARLY,
                             msg_params=dict(day=day),
                             field="startdate")

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE,
                                   errors.BAD_DATE_RANGE)):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        campaign = None
        if campaign_id36:
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
            except NotFound:
                pass

        if priority.cpm:
            if form.has_errors('bid', errors.BAD_BID):
                return

            # you cannot edit the bid of a live ad unless it's a freebie
            if (campaign and bid != campaign.bid
                    and campaign.start_date < datetime.now(g.tz)
                    and not campaign.is_freebie()):
                c.errors.add(errors.BID_LIVE, field='bid')
                form.has_errors('bid', errors.BID_LIVE)
                return

            min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
            if bid is None or bid < min_bid:
                c.errors.add(errors.BAD_BID,
                             field='bid',
                             msg_params={
                                 'min': min_bid,
                                 'max': g.max_promote_bid
                             })
                form.has_errors('bid', errors.BAD_BID)
                return
        else:
            bid = 0.  # Set bid to 0 as dummy value

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return

            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {
                    "start": roadblock[0].strftime('%m/%d/%Y'),
                    "end": roadblock[1].strftime('%m/%d/%Y')
                }
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return

        elif targeting == 'none':
            sr = None

        # Check inventory
        campaign_id = campaign._id if campaign else None
        if (not priority.inventory_override and has_oversold_error(
                form, campaign_id, start, end, bid, cpm, sr)):
            return

        if campaign:
            promote.edit_campaign(link, campaign, dates, bid, cpm, sr,
                                  priority)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.spent, r.cpm, r.sr,
                                   r.priority_name, r.inventory_override,
                                   r.status)
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, sr,
                                            priority)
            r = promote.get_renderable_campaigns(link, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.spent, r.cpm, r.sr,
                                r.priority_name, r.inventory_override,
                                r.status)

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   l=VLink('link_id'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_delete_campaign(self, form, jquery, l, campaign):
        if l and campaign:
            promote.delete_campaign(l, campaign)

    @validatedForm(VSponsor('container'),
                   VModhash(),
                   user=VExistingUname('name'),
                   thing=VByName('container'))
    def POST_traffic_viewer(self, form, jquery, user, thing):
        """
        Adds a user to the list of users allowed to view a promoted
        link's traffic page.
        """
        if not form.has_errors("name", errors.USER_DOESNT_EXIST,
                               errors.NO_USER):
            form.set_inputs(name="")
            form.set_html(".status:first", _("added"))
            if promote.add_traffic_viewer(thing, user):
                user_row = TrafficViewerList(thing).user_row(
                    'traffic_viewer', user)
                jquery(".traffic_viewer-table").show().find(
                    "table").insert_table_rows(user_row)

                # send the user a message
                msg = user_added_messages['traffic']['pm']['msg']
                subj = user_added_messages['traffic']['pm']['subject']
                if msg and subj:
                    d = dict(url=thing.make_permalink_slow(),
                             traffic_url=promote.promo_traffic_url(thing),
                             title=thing.title)
                    msg = msg % d
                    item, inbox_rel = Message._new(c.user, user, subj, msg,
                                                   request.ip)
                    queries.new_message(item, inbox_rel)

    @validatedForm(VSponsor('container'),
                   VModhash(),
                   iuser=VByName('id'),
                   thing=VByName('container'))
    def POST_rm_traffic_viewer(self, form, jquery, iuser, thing):
        if thing and iuser:
            promote.rm_traffic_viewer(thing, iuser)

    @validatedForm(VSponsor('link'),
                   link=VByName("link"),
                   campaign=VPromoCampaign("campaign"),
                   customer_id=VInt("customer_id", min=0),
                   pay_id=VInt("account", min=0),
                   edit=VBoolean("edit"),
                   address=ValidAddress([
                       "firstName", "lastName", "company", "address", "city",
                       "state", "zip", "country", "phoneNumber"
                   ]),
                   creditcard=ValidCard(
                       ["cardNumber", "expirationDate", "cardCode"]))
    def POST_update_pay(self, form, jquery, link, campaign, customer_id,
                        pay_id, edit, address, creditcard):
        # Check inventory
        if campaign_has_oversold_error(form, campaign):
            return

        address_modified = not pay_id or edit
        form_has_errors = False
        if address_modified:
            if (form.has_errors([
                    "firstName", "lastName", "company", "address", "city",
                    "state", "zip", "country", "phoneNumber"
            ], errors.BAD_ADDRESS) or form.has_errors(
                ["cardNumber", "expirationDate", "cardCode"],
                    errors.BAD_CARD)):
                form_has_errors = True
            elif g.authorizenetapi:
                pay_id = edit_profile(c.user, address, creditcard, pay_id)
            else:
                pay_id = 1
        # if link is in use or finished, don't make a change
        if pay_id and not form_has_errors:
            # valid bid and created or existing bid id.
            # check if already a transaction
            if g.authorizenetapi:
                success, reason = promote.auth_campaign(
                    link, campaign, c.user, pay_id)
            else:
                success = True
            if success:
                form.redirect(promote.promo_edit_url(link))
            else:
                form.set_html(
                    ".status", reason
                    or _("failed to authenticate card.  sorry."))

    @validate(VSponsor("link"),
              link=VLink("link"),
              campaign=VPromoCampaign("campaign"))
    def GET_pay(self, link, campaign):
        # no need for admins to play in the credit card area
        if c.user_is_loggedin and c.user._id != link.author_id:
            return self.abort404()

        if not campaign.link_id == link._id:
            return self.abort404()
        if g.authorizenetapi:
            data = get_account_info(c.user)
            content = PaymentForm(link,
                                  campaign,
                                  customer_id=data.customerProfileId,
                                  profiles=data.paymentProfiles,
                                  max_profiles=PROFILE_LIMIT)
        else:
            content = None
        res = LinkInfoPage(link=link, content=content, show_sidebar=False)
        return res.render()

    def GET_link_thumb(self, *a, **kw):
        """
        See GET_upload_sr_image for rationale
        """
        return "nothing to see here."

    @validate(VSponsor("link_id"),
              link=VByName('link_id'),
              file=VUploadLength('file', 500 * 1024),
              img_type=VImageType('img_type'))
    def POST_link_thumb(self, link=None, file=None, img_type='jpg'):
        if link and (not promote.is_promoted(link) or c.user_is_sponsor
                     or c.user.trusted_sponsor):
            errors = dict(BAD_CSS_NAME="", IMAGE_ERROR="")
            try:
                # thumnails for promoted links can change and therefore expire
                force_thumbnail(link, file, file_type=".%s" % img_type)
            except cssfilter.BadImage:
                # if the image doesn't clean up nicely, abort
                errors["IMAGE_ERROR"] = _("bad image")
            if any(errors.values()):
                return UploadedImage("",
                                     "",
                                     "upload",
                                     errors=errors,
                                     form_id="image-upload").render()
            else:
                link._commit()
                return UploadedImage(_('saved'),
                                     thumbnail_url(link),
                                     "",
                                     errors=errors,
                                     form_id="image-upload").render()

    @validate(VSponsorAdmin(),
              launchdate=VDate('ondate'),
              dates=VDateRange(['startdate', 'enddate']),
              query_type=VOneOf('q', ('started_on', 'between'), default=None))
    def GET_admin(self, launchdate=None, dates=None, query_type=None):
        return PromoAdminTool(query_type=query_type,
                              launchdate=launchdate,
                              start=dates[0],
                              end=dates[1]).render()

    @validate(VSponsorAdminOrAdminSecret('secret'),
              start=VDate('startdate'),
              end=VDate('enddate'),
              link_text=nop('link_text'),
              owner=VAccountByName('owner'))
    def GET_report(self, start, end, link_text=None, owner=None):
        now = datetime.now(g.tz).replace(hour=0,
                                         minute=0,
                                         second=0,
                                         microsecond=0)
        end = end or now - timedelta(days=1)
        start = start or end - timedelta(days=7)

        links = []
        bad_links = []
        owner_name = owner.name if owner else ''

        if owner:
            promo_weights = PromotionWeights.get_campaigns(start,
                                                           end,
                                                           author_id=owner._id)
            campaign_ids = [pw.promo_idx for pw in promo_weights]
            campaigns = PromoCampaign._byID(campaign_ids, data=True)
            link_ids = {camp.link_id for camp in campaigns.itervalues()}
            links.extend(Link._byID(link_ids, data=True, return_dict=False))

        if link_text is not None:
            id36s = link_text.replace(',', ' ').split()
            try:
                links_from_text = Link._byID36(id36s, data=True)
            except NotFound:
                links_from_text = {}

            bad_links = [id36 for id36 in id36s if id36 not in links_from_text]
            links.extend(links_from_text.values())

        content = PromoteReport(links, link_text, owner_name, bad_links, start,
                                end)
        if c.render_style == 'csv':
            return content.as_csv()
        else:
            return PromotePage('report', content=content).render()
Ejemplo n.º 13
0
class GoldApiController(RedditController):
    @validate(
        VUser(),
        VGold(),
    )
    def GET_snoovatar(self):
        snoovatar = SnoovatarsByAccount.load(c.user, "snoo")
        response.content_type = "application/json"
        return json.dumps(snoovatar)

    @validatedForm(
        VUser(),
        VGold(),
        VModhash(),
        public=VBoolean("public"),
        snoo_color=VSnooColor("snoo_color"),
        unvalidated_components=VJSON("components"),
    )
    def POST_snoovatar(self, form, jquery, public, snoo_color,
                       unvalidated_components):
        if form.has_errors(
                "components",
                errors.NO_TEXT,
                errors.TOO_LONG,
                errors.BAD_STRING,
        ):
            return
        if form.has_errors("snoo_color", errors.BAD_CSS_COLOR):
            return

        try:
            tailors = g.plugins["gold"].tailors_data
            validated = {}

            for tailor in tailors:
                tailor_name = tailor["name"]

                component = unvalidated_components.get(tailor_name)

                # if the tailor requires a selection, ensure there is one
                if not tailor["allow_clear"]:
                    require(component)

                # ensure this dressing exists
                dressing = component.get("dressingName")
                if dressing:
                    for d in tailor["dressings"]:
                        if dressing == d["name"]:
                            break
                    else:
                        raise RequirementException

                validated[tailor_name] = component
        except RequirementException:
            c.errors.add(errors.INVALID_SNOOVATAR, field="components")
            form.has_errors("components", errors.INVALID_SNOOVATAR)
            return

        SnoovatarsByAccount.save(
            user=c.user,
            name="snoo",
            public=public,
            snoo_color=snoo_color,
            components=validated,
        )
Ejemplo n.º 14
0
class MultiApiController(RedditController):
    on_validation_error = staticmethod(abort_with_error)

    def pre(self):
        set_extension(request.environ, "json")
        RedditController.pre(self)

    def _format_multi_list(self, multis, viewer, expand_srs):
        templ = LabeledMultiJsonTemplate(expand_srs)
        resp = [
            templ.render(multi).finalize() for multi in multis
            if multi.can_view(viewer)
        ]
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(
        user=VAccountByName("username"),
        expand_srs=VBoolean("expand_srs"),
    )
    @api_doc(api_section.multis, uri="/api/multi/user/{username}")
    def GET_list_multis(self, user, expand_srs):
        """Fetch a list of public multis belonging to `username`"""
        multis = LabeledMulti.by_owner(user)
        return self._format_multi_list(multis, c.user, expand_srs)

    @require_oauth2_scope("read")
    @validate(VUser(), expand_srs=VBoolean("expand_srs"))
    @api_doc(api_section.multis, uri="/api/multi/mine")
    def GET_my_multis(self, expand_srs):
        """Fetch a list of multis belonging to the current user."""
        multis = LabeledMulti.by_owner(c.user)
        return self._format_multi_list(multis, c.user, expand_srs)

    def _format_multi(self, multi, expand_sr_info=False):
        multi_info = LabeledMultiJsonTemplate(expand_sr_info).render(multi)
        return self.api_wrapper(multi_info.finalize())

    @require_oauth2_scope("read")
    @validate(
        multi=VMultiByPath("multipath", require_view=True),
        expand_srs=VBoolean("expand_srs"),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}",
        uri_variants=['/api/filter/{filterpath}'],
    )
    def GET_multi(self, multi, expand_srs):
        """Fetch a multi's data and subreddit list by name."""
        return self._format_multi(multi, expand_srs)

    def _check_new_multi_path(self, path_info):
        if path_info['owner'].lower() != c.user.name.lower():
            raise RedditError('MULTI_CANNOT_EDIT',
                              code=403,
                              fields='multipath')
        return c.user

    def _add_multi_srs(self, multi, sr_datas):
        srs = Subreddit._by_name(sr_data['name'] for sr_data in sr_datas)

        for sr in srs.itervalues():
            if isinstance(sr, FakeSubreddit):
                raise RedditError('MULTI_SPECIAL_SUBREDDIT',
                                  msg_params={'path': sr.path},
                                  code=400)

        sr_props = {}
        for sr_data in sr_datas:
            try:
                sr = srs[sr_data['name']]
            except KeyError:
                raise RedditError('SUBREDDIT_NOEXIST', code=400)
            else:
                # name is passed in via the API data format, but should not be
                # stored on the model.
                del sr_data['name']
                sr_props[sr] = sr_data

        try:
            multi.add_srs(sr_props)
        except TooManySubredditsError as e:
            raise RedditError('MULTI_TOO_MANY_SUBREDDITS', code=409)

        return sr_props

    def _write_multi_data(self, multi, data):
        srs = data.pop('subreddits', None)
        if srs is not None:
            multi.clear_srs()
            try:
                self._add_multi_srs(multi, srs)
            except:
                multi._revert()
                raise

        if 'icon_name' in data:
            try:
                multi.set_icon_by_name(data.pop('icon_name'))
            except:
                multi._revert()
                raise

        for key, val in data.iteritems():
            if key in WRITABLE_MULTI_FIELDS:
                setattr(multi, key, val)

        multi._commit()
        return multi

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        path_info=VMultiPath("multipath", required=False),
        data=VValidatedJSON("model", multi_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def POST_multi(self, path_info, data):
        """Create a multi. Responds with 409 Conflict if it already exists."""

        if not path_info and "path" in data:
            path_info = VMultiPath("").run(data["path"])
        elif 'display_name' in data:
            # if path not provided, create multi for user
            path = LabeledMulti.slugify(c.user, data['display_name'])
            path_info = VMultiPath("").run(path)

        if not path_info:
            raise RedditError('BAD_MULTI_PATH', code=400)

        owner = self._check_new_multi_path(path_info)

        try:
            LabeledMulti._byID(path_info['path'])
        except tdb_cassandra.NotFound:
            multi = LabeledMulti.create(path_info['path'], owner)
            response.status = 201
        else:
            raise RedditError('MULTI_EXISTS', code=409, fields='multipath')

        self._write_multi_data(multi, data)
        return self._format_multi(multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        path_info=VMultiPath("multipath"),
        data=VValidatedJSON("model", multi_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def PUT_multi(self, path_info, data):
        """Create or update a multi."""

        owner = self._check_new_multi_path(path_info)

        try:
            multi = LabeledMulti._byID(path_info['path'])
        except tdb_cassandra.NotFound:
            multi = LabeledMulti.create(path_info['path'], owner)
            response.status = 201

        self._write_multi_data(multi, data)
        return self._format_multi(multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
    )
    @api_doc(api_section.multis, extends=GET_multi)
    def DELETE_multi(self, multi):
        """Delete a multi."""
        multi.delete()

    def _copy_multi(self, from_multi, to_path_info, rename=False):
        """Copy a multi to a user account."""

        to_owner = self._check_new_multi_path(to_path_info)

        # rename requires same owner
        if rename and from_multi.owner != to_owner:
            raise RedditError('MULTI_CANNOT_EDIT', code=400)

        try:
            LabeledMulti._byID(to_path_info['path'])
        except tdb_cassandra.NotFound:
            to_multi = LabeledMulti.copy(to_path_info['path'],
                                         from_multi,
                                         owner=to_owner)
        else:
            raise RedditError('MULTI_EXISTS', code=409, fields='multipath')

        return to_multi

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        from_multi=VMultiByPath("from", require_view=True, kinds='m'),
        to_path_info=VMultiPath(
            "to",
            required=False,
            docs={"to": "destination multireddit url path"},
        ),
        display_name=VLength("display_name",
                             max_length=MAX_DISP_NAME,
                             empty_error=None),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/copy",
    )
    def POST_multi_copy(self, from_multi, to_path_info, display_name):
        """Copy a multi.

        Responds with 409 Conflict if the target already exists.

        A "copied from ..." line will automatically be appended to the
        description.

        """
        if not to_path_info:
            if display_name:
                # if path not provided, copy multi to same owner
                path = LabeledMulti.slugify(from_multi.owner, display_name)
                to_path_info = VMultiPath("").run(path)
            else:
                raise RedditError('BAD_MULTI_PATH', code=400)

        to_multi = self._copy_multi(from_multi, to_path_info)

        from_path = from_multi.path
        to_multi.copied_from = from_path
        if to_multi.description_md:
            to_multi.description_md += '\n\n'
        to_multi.description_md += _('copied from %(source)s') % {
            # force markdown linking since /user/foo is not autolinked
            'source': '[%s](%s)' % (from_path, from_path)
        }
        to_multi.visibility = 'private'
        if display_name:
            to_multi.display_name = display_name
        to_multi._commit()

        return self._format_multi(to_multi)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        from_multi=VMultiByPath("from", require_edit=True, kinds='m'),
        to_path_info=VMultiPath(
            "to",
            required=False,
            docs={"to": "destination multireddit url path"},
        ),
        display_name=VLength("display_name",
                             max_length=MAX_DISP_NAME,
                             empty_error=None),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/rename",
    )
    def POST_multi_rename(self, from_multi, to_path_info, display_name):
        """Rename a multi."""
        if not to_path_info:
            if display_name:
                path = LabeledMulti.slugify(from_multi.owner, display_name)
                to_path_info = VMultiPath("").run(path)
            else:
                raise RedditError('BAD_MULTI_PATH', code=400)

        to_multi = self._copy_multi(from_multi, to_path_info, rename=True)

        if display_name:
            to_multi.display_name = display_name
            to_multi._commit()
        from_multi.delete()

        return self._format_multi(to_multi)

    def _get_multi_subreddit(self, multi, sr):
        resp = LabeledMultiJsonTemplate.sr_props(multi, [sr])[0]
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        multi=VMultiByPath("multipath", require_view=True),
        sr=VSRByName('srname'),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/r/{srname}",
        uri_variants=['/api/filter/{filterpath}/r/{srname}'],
    )
    def GET_multi_subreddit(self, multi, sr):
        """Get data about a subreddit in a multi."""
        return self._get_multi_subreddit(multi, sr)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
        sr_name=VSubredditName('srname', allow_language_srs=True),
        data=VValidatedJSON("model", multi_sr_data_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi_subreddit)
    def PUT_multi_subreddit(self, multi, sr_name, data):
        """Add a subreddit to a multi."""

        new = not any(sr.name.lower() == sr_name.lower() for sr in multi.srs)

        data['name'] = sr_name
        sr_props = self._add_multi_srs(multi, [data])
        sr = sr_props.items()[0][0]
        multi._commit()

        if new:
            response.status = 201

        return self._get_multi_subreddit(multi, sr)

    @require_oauth2_scope("subscribe")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True),
        sr=VSRByName('srname'),
    )
    @api_doc(api_section.multis, extends=GET_multi_subreddit)
    def DELETE_multi_subreddit(self, multi, sr):
        """Remove a subreddit from a multi."""
        multi.del_srs(sr)
        multi._commit()

    def _format_multi_description(self, multi):
        resp = LabeledMultiDescriptionJsonTemplate().render(multi).finalize()
        return self.api_wrapper(resp)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        multi=VMultiByPath("multipath", require_view=True, kinds='m'),
    )
    @api_doc(
        api_section.multis,
        uri="/api/multi/{multipath}/description",
    )
    def GET_multi_description(self, multi):
        """Get a multi's description."""
        return self._format_multi_description(multi)

    @require_oauth2_scope("read")
    @validate(
        VUser(),
        VModhash(),
        multi=VMultiByPath("multipath", require_edit=True, kinds='m'),
        data=VValidatedJSON('model', multi_description_json_spec),
    )
    @api_doc(api_section.multis, extends=GET_multi_description)
    def PUT_multi_description(self, multi, data):
        """Change a multi's markdown description."""
        multi.description_md = data['body_md']
        multi._commit()
        return self._format_multi_description(multi)
Ejemplo n.º 15
0
class PromoteApiController(ApiController):
    @json_validate(sr=VSubmitSR('sr', promotion=True),
                   collection=VCollection('collection'),
                   location=VLocation(),
                   start=VDate('startdate'),
                   end=VDate('enddate'))
    def GET_check_inventory(self, responder, sr, collection, location, start,
                            end):
        if collection:
            target = Target(collection)
            sr = None
        else:
            sr = sr or Frontpage
            target = Target(sr.name)

        if not allowed_location_and_target(location, target):
            return abort(403, 'forbidden')

        available = inventory.get_available_pageviews(target,
                                                      start,
                                                      end,
                                                      location=location,
                                                      datestr=True)

        return {'inventory': available}

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink("link_id36"),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_freebie(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        if campaign_has_oversold_error(form, campaign):
            form.set_text(".freebie", _("target oversold, can't freebie"))
            return

        if promote.is_promo(link) and campaign:
            promote.free_campaign(link, campaign, c.user)
            form.redirect(promote.promo_edit_url(link))

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VByName("link"),
                   note=nop("note"))
    def POST_promote_note(self, form, jquery, link, note):
        if promote.is_promo(link):
            text = PromotionLog.add(link, note)
            form.find(".notes").children(":last").after(
                format_html("<p>%s</p>", text))

    @validatedForm(
        VSponsorAdmin(),
        VModhash(),
        thing=VByName("thing_id"),
        is_fraud=VBoolean("fraud"),
    )
    def POST_review_fraud(self, form, jquery, thing, is_fraud):
        if not promote.is_promo(thing):
            return

        promote.review_fraud(thing, is_fraud)

        button = jquery(".id-%s .fraud-button" % thing._fullname)
        button.text(_("fraud" if is_fraud else "not fraud"))
        form.fadeOut()

    @noresponse(VSponsorAdmin(), VModhash(), thing=VByName('id'))
    def POST_promote(self, thing):
        if promote.is_promo(thing):
            promote.accept_promotion(thing)

    @noresponse(VSponsorAdmin(),
                VModhash(),
                thing=VByName('id'),
                reason=nop("reason"))
    def POST_unpromote(self, thing, reason):
        if promote.is_promo(thing):
            promote.reject_promotion(thing, reason=reason)

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink('link'),
                   campaign=VPromoCampaign('campaign'))
    def POST_refund_campaign(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        billable_impressions = promote.get_billable_impressions(campaign)
        billable_amount = promote.get_billable_amount(campaign,
                                                      billable_impressions)
        refund_amount = promote.get_refund_amount(campaign, billable_amount)
        if refund_amount > 0:
            promote.refund_campaign(link, campaign, billable_amount,
                                    billable_impressions)
            form.set_text('.status', _('refund succeeded'))
        else:
            form.set_text('.status', _('refund not needed'))

    @validatedForm(
        VSponsor('link_id36'),
        VModhash(),
        VRatelimit(rate_user=True, rate_ip=True, prefix='create_promo_'),
        VShamedDomain('url'),
        username=VLength('username', 100, empty_error=None),
        l=VLink('link_id36'),
        title=VTitle('title'),
        url=VUrl('url', allow_self=False),
        selftext=VMarkdownLength('text', max_length=40000),
        kind=VOneOf('kind', ['link', 'self']),
        disable_comments=VBoolean("disable_comments"),
        sendreplies=VBoolean("sendreplies"),
        media_url=VUrl("media_url",
                       allow_self=False,
                       valid_schemes=('http', 'https')),
        gifts_embed_url=VUrl("gifts_embed_url",
                             allow_self=False,
                             valid_schemes=('http', 'https')),
        media_url_type=VOneOf("media_url_type", ("redditgifts", "scrape")),
        media_autoplay=VBoolean("media_autoplay"),
        media_override=VBoolean("media-override"),
        domain_override=VLength("domain", 100),
        is_managed=VBoolean("is_managed"),
    )
    def POST_edit_promo(self, form, jquery, username, l, title, url, selftext,
                        kind, disable_comments, sendreplies, media_url,
                        media_autoplay, media_override, gifts_embed_url,
                        media_url_type, domain_override, is_managed):

        should_ratelimit = False
        if not c.user_is_sponsor:
            should_ratelimit = True

        if not should_ratelimit:
            c.errors.remove((errors.RATELIMIT, 'ratelimit'))

        # check for user override
        if not l and c.user_is_sponsor and username:
            try:
                user = Account._by_name(username)
            except NotFound:
                c.errors.add(errors.USER_DOESNT_EXIST, field="username")
                form.set_error(errors.USER_DOESNT_EXIST, "username")
                return

            if not user.email:
                c.errors.add(errors.NO_EMAIL_FOR_USER, field="username")
                form.set_error(errors.NO_EMAIL_FOR_USER, "username")
                return

            if not user.email_verified:
                c.errors.add(errors.NO_VERIFIED_EMAIL, field="username")
                form.set_error(errors.NO_VERIFIED_EMAIL, "username")
                return
        else:
            user = c.user

        # check for shame banned domains
        if form.has_errors("url", errors.DOMAIN_BANNED):
            g.stats.simple_event('spam.shame.link')
            return

        # demangle URL in canonical way
        if url:
            if isinstance(url, (unicode, str)):
                form.set_inputs(url=url)
            elif isinstance(url, tuple) or isinstance(url[0], Link):
                # there's already one or more links with this URL, but
                # we're allowing mutliple submissions, so we really just
                # want the URL
                url = url[0].url

        if kind == 'link':
            if form.has_errors('url', errors.NO_URL, errors.BAD_URL):
                return

        # users can change the disable_comments on promoted links
        if ((not l or not promote.is_promoted(l))
                and (form.has_errors('title', errors.NO_TEXT, errors.TOO_LONG)
                     or jquery.has_errors('ratelimit', errors.RATELIMIT))):
            return

        if kind == 'self' and form.has_errors('text', errors.TOO_LONG):
            return

        if not l:
            # creating a new promoted link
            l = promote.new_promotion(title, url if kind == 'link' else 'self',
                                      selftext if kind == 'self' else '', user,
                                      request.ip)
            l.domain_override = domain_override or None
            if c.user_is_sponsor:
                l.managed_promo = is_managed
            l._commit()
            form.redirect(promote.promo_edit_url(l))

        elif not promote.is_promo(l):
            return

        # changing link type is not allowed
        if ((l.is_self and kind == 'link')
                or (not l.is_self and kind == 'self')):
            c.errors.add(errors.NO_CHANGE_KIND, field="kind")
            form.set_error(errors.NO_CHANGE_KIND, "kind")
            return

        changed = False
        # live items can only be changed by a sponsor, and also
        # pay the cost of de-approving the link
        if not promote.is_promoted(l) or c.user_is_sponsor:
            if title and title != l.title:
                l.title = title
                changed = not c.user_is_sponsor

            if kind == 'link' and url and url != l.url:
                l.url = url
                changed = not c.user_is_sponsor

        # only trips if the title and url are changed by a non-sponsor
        if changed:
            promote.unapprove_promotion(l)

        # selftext can be changed at any time
        if kind == 'self':
            l.selftext = selftext

        # comment disabling and sendreplies is free to be changed any time.
        l.disable_comments = disable_comments
        l.sendreplies = sendreplies

        if c.user_is_sponsor:
            if (form.has_errors("media_url", errors.BAD_URL)
                    or form.has_errors("gifts_embed_url", errors.BAD_URL)):
                return

        scraper_embed = media_url_type == "scrape"
        media_url = media_url or None
        gifts_embed_url = gifts_embed_url or None

        if c.user_is_sponsor and scraper_embed and media_url != l.media_url:
            if media_url:
                media = _scrape_media(media_url,
                                      autoplay=media_autoplay,
                                      save_thumbnail=False,
                                      use_cache=True)

                if media:
                    l.set_media_object(media.media_object)
                    l.set_secure_media_object(media.secure_media_object)
                    l.media_url = media_url
                    l.gifts_embed_url = None
                    l.media_autoplay = media_autoplay
                else:
                    c.errors.add(errors.SCRAPER_ERROR, field="media_url")
                    form.set_error(errors.SCRAPER_ERROR, "media_url")
                    return
            else:
                l.set_media_object(None)
                l.set_secure_media_object(None)
                l.media_url = None
                l.gifts_embed_url = None
                l.media_autoplay = False

        if (c.user_is_sponsor and not scraper_embed
                and gifts_embed_url != l.gifts_embed_url):
            if gifts_embed_url:
                parsed = UrlParser(gifts_embed_url)
                if not is_subdomain(parsed.hostname, "redditgifts.com"):
                    c.errors.add(errors.BAD_URL, field="gifts_embed_url")
                    form.set_error(errors.BAD_URL, "gifts_embed_url")
                    return

                iframe = """
                    <iframe class="redditgifts-embed"
                            src="%(embed_url)s"
                            width="710" height="500" scrolling="no"
                            frameborder="0" allowfullscreen>
                    </iframe>
                """ % {
                    'embed_url': websafe(gifts_embed_url)
                }
                media_object = {
                    'oembed': {
                        'description': 'redditgifts embed',
                        'height': 500,
                        'html': iframe,
                        'provider_name': 'redditgifts',
                        'provider_url': 'http://www.redditgifts.com/',
                        'title': 'redditgifts secret santa 2014',
                        'type': 'rich',
                        'width': 710
                    },
                    'type': 'redditgifts'
                }
                l.set_media_object(media_object)
                l.set_secure_media_object(media_object)
                l.media_url = None
                l.gifts_embed_url = gifts_embed_url
                l.media_autoplay = False
            else:
                l.set_media_object(None)
                l.set_secure_media_object(None)
                l.media_url = None
                l.gifts_embed_url = None
                l.media_autoplay = False

        if c.user_is_sponsor:
            l.media_override = media_override
            l.domain_override = domain_override or None
            l.managed_promo = is_managed

        l._commit()
        form.redirect(promote.promo_edit_url(l))

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_add_roadblock(self, form, jquery, dates, sr):
        if (form.has_errors('startdate', errors.BAD_DATE) or form.has_errors(
                'enddate', errors.BAD_DATE, errors.BAD_DATE_RANGE)):
            return
        if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                           errors.SUBREDDIT_NOTALLOWED,
                           errors.SUBREDDIT_REQUIRED):
            return
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.add(sr, sd, ed)
            jquery.refresh()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                    reference_date=promote.promo_datetime_now),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_rm_roadblock(self, form, jquery, dates, sr):
        if dates and sr:
            sd, ed = dates
            PromotedLinkRoadblock.remove(sr, sd, ed)
            jquery.refresh()

    @validatedForm(
        VSponsor('link_id36'),
        VModhash(),
        dates=VDateRange(['startdate', 'enddate'],
                         earliest=timedelta(days=g.min_promote_future),
                         latest=timedelta(days=g.max_promote_future),
                         reference_date=promote.promo_datetime_now,
                         business_days=True,
                         sponsor_override=True),
        link=VLink('link_id36'),
        bid=VFloat('bid', coerce=False),
        target=VPromoTarget(),
        campaign_id36=nop("campaign_id36"),
        priority=VPriority("priority"),
        location=VLocation(),
    )
    def POST_edit_campaign(self, form, jquery, link, campaign_id36, dates, bid,
                           target, priority, location):
        if not link:
            return

        if not target:
            # run form.has_errors to populate the errors in the response
            form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                            errors.SUBREDDIT_NOTALLOWED,
                            errors.SUBREDDIT_REQUIRED)
            form.has_errors('collection', errors.COLLECTION_NOEXIST)
            form.has_errors('targeting', errors.INVALID_TARGET)
            return

        start, end = dates or (None, None)

        if not allowed_location_and_target(location, target):
            return abort(403, 'forbidden')

        cpm = PromotionPrices.get_price(c.user, target, location)

        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE)
                or form.has_errors('enddate', errors.BAD_DATE,
                                   errors.DATE_TOO_EARLY, errors.DATE_TOO_LATE,
                                   errors.BAD_DATE_RANGE)):
            return

        # check that start is not so late that authorization hold will expire
        if not c.user_is_sponsor:
            max_start = promote.get_max_startdate()
            if start > max_start:
                c.errors.add(
                    errors.DATE_TOO_LATE,
                    msg_params={'day': max_start.strftime("%m/%d/%Y")},
                    field='startdate')
                form.has_errors('startdate', errors.DATE_TOO_LATE)
                return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(link._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        campaign = None
        if campaign_id36:
            try:
                campaign = PromoCampaign._byID36(campaign_id36, data=True)
            except NotFound:
                pass

            if campaign and (campaign._deleted
                             or link._id != campaign.link_id):
                campaign = None

            if not campaign:
                return abort(404, 'not found')

        if priority.cpm:
            min_bid = 0 if c.user_is_sponsor else g.min_promote_bid
            max_bid = None if c.user_is_sponsor else g.max_promote_bid

            if bid is None or bid < min_bid or (max_bid and bid > max_bid):
                c.errors.add(errors.BAD_BID,
                             field='bid',
                             msg_params={
                                 'min': min_bid,
                                 'max': max_bid or g.max_promote_bid
                             })
                form.has_errors('bid', errors.BAD_BID)
                return

            # you cannot edit the bid of a live ad unless it's a freebie
            if (campaign and bid != campaign.bid
                    and promote.is_live_promo(link, campaign)
                    and not campaign.is_freebie()):
                c.errors.add(errors.BID_LIVE, field='bid')
                form.has_errors('bid', errors.BID_LIVE)
                return

        else:
            bid = 0.  # Set bid to 0 as dummy value

        is_frontpage = (not target.is_collection
                        and target.subreddit_name == Frontpage.name)

        if not target.is_collection and not is_frontpage:
            # targeted to a single subreddit, check roadblock
            sr = target.subreddits_slow[0]
            roadblock = PromotedLinkRoadblock.is_roadblocked(sr, start, end)
            if roadblock and not c.user_is_sponsor:
                msg_params = {
                    "start": roadblock[0].strftime('%m/%d/%Y'),
                    "end": roadblock[1].strftime('%m/%d/%Y')
                }
                c.errors.add(errors.OVERSOLD,
                             field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return

        # Check inventory
        campaign = campaign if campaign_id36 else None
        if not priority.inventory_override:
            oversold = has_oversold_error(form, campaign, start, end, bid, cpm,
                                          target, location)
            if oversold:
                return

        if campaign:
            promote.edit_campaign(link, campaign, dates, bid, cpm, target,
                                  priority, location)
        else:
            campaign = promote.new_campaign(link, dates, bid, cpm, target,
                                            priority, location)
        rc = RenderableCampaign.from_campaigns(link, campaign)
        jquery.update_campaign(campaign._fullname, rc.render_html())

    @validatedForm(VSponsor('link_id36'),
                   VModhash(),
                   l=VLink('link_id36'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_delete_campaign(self, form, jquery, l, campaign):
        if not campaign or not l or l._id != campaign.link_id:
            return abort(404, 'not found')

        promote.delete_campaign(l, campaign)

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   link=VLink('link_id36'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_terminate_campaign(self, form, jquery, link, campaign):
        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        promote.terminate_campaign(link, campaign)
        rc = RenderableCampaign.from_campaigns(link, campaign)
        jquery.update_campaign(campaign._fullname, rc.render_html())

    @validatedForm(
        VSponsor('link'),
        VModhash(),
        link=VByName("link"),
        campaign=VPromoCampaign("campaign"),
        customer_id=VInt("customer_id", min=0),
        pay_id=VInt("account", min=0),
        edit=VBoolean("edit"),
        address=ValidAddress([
            "firstName", "lastName", "company", "address", "city", "state",
            "zip", "country", "phoneNumber"
        ]),
        creditcard=ValidCard(["cardNumber", "expirationDate", "cardCode"]),
    )
    def POST_update_pay(self, form, jquery, link, campaign, customer_id,
                        pay_id, edit, address, creditcard):
        if not g.authorizenetapi:
            return

        if not link or not campaign or link._id != campaign.link_id:
            return abort(404, 'not found')

        # Check inventory
        if campaign_has_oversold_error(form, campaign):
            return

        # check that start is not so late that authorization hold will expire
        max_start = promote.get_max_startdate()
        if campaign.start_date > max_start:
            msg = _("please change campaign start date to %(date)s or earlier")
            date = format_date(max_start, format="short", locale=c.locale)
            msg %= {'date': date}
            form.set_text(".status", msg)
            return

        # check the campaign start date is still valid (user may have created
        # the campaign a few days ago)
        now = promote.promo_datetime_now()
        min_start = now + timedelta(days=g.min_promote_future)
        if campaign.start_date.date() < min_start.date():
            msg = _("please change campaign start date to %(date)s or later")
            date = format_date(min_start, format="short", locale=c.locale)
            msg %= {'date': date}
            form.set_text(".status", msg)
            return

        address_modified = not pay_id or edit
        if address_modified:
            address_fields = [
                "firstName", "lastName", "company", "address", "city", "state",
                "zip", "country", "phoneNumber"
            ]
            card_fields = ["cardNumber", "expirationDate", "cardCode"]

            if (form.has_errors(address_fields, errors.BAD_ADDRESS)
                    or form.has_errors(card_fields, errors.BAD_CARD)):
                return

            pay_id = edit_profile(c.user, address, creditcard, pay_id)

            if pay_id:
                promote.new_payment_method(user=c.user,
                                           ip=request.ip,
                                           address=address,
                                           link=link)

        if pay_id:
            success, reason = promote.auth_campaign(link, campaign, c.user,
                                                    pay_id)

            if success:
                form.redirect(promote.promo_edit_url(link))
                return
            else:
                promote.failed_payment_method(c.user, link)
                msg = reason or _("failed to authenticate card. sorry.")
                form.set_text(".status", msg)
        else:
            promote.failed_payment_method(c.user, link)
            form.set_text(".status", _("failed to authenticate card. sorry."))

    @validate(VSponsor("link_name"),
              VModhash(),
              link=VByName('link_name'),
              file=VUploadLength('file', 500 * 1024),
              img_type=VImageType('img_type'))
    def POST_link_thumb(self, link=None, file=None, img_type='jpg'):
        if link and (not promote.is_promoted(link) or c.user_is_sponsor):
            errors = dict(BAD_CSS_NAME="", IMAGE_ERROR="")

            # thumnails for promoted links can change and therefore expire
            force_thumbnail(link, file, file_type=".%s" % img_type)

            if any(errors.values()):
                return UploadedImage("",
                                     "",
                                     "upload",
                                     errors=errors,
                                     form_id="image-upload").render()
            else:
                link._commit()
                return UploadedImage(_('saved'),
                                     thumbnail_url(link),
                                     "",
                                     errors=errors,
                                     form_id="image-upload").render()
Ejemplo n.º 16
0
Archivo: wiki.py Proyecto: wal-f/reddit
class WikiController(RedditController):
    allow_stylesheets = True

    @validate(pv=VWikiPageAndVersion(('page', 'v', 'v2'),
                                     required=False,
                                     restricted=False,
                                     allow_hidden_revision=False),
              page_name=VWikiPageName('page',
                                      error_on_name_normalized=True))
    def GET_wiki_page(self, pv, page_name):
        message = None

        if c.errors.get(('PAGE_NAME_NORMALIZED', 'page')):
            url = join_urls(c.wiki_base_url, page_name)
            return self.redirect(url)

        page, version, version2 = pv

        if not page:
            is_api = c.render_style in extensions.API_TYPES
            if this_may_revise():
                if is_api:
                    self.handle_error(404, 'PAGE_NOT_CREATED')
                errorpage = WikiNotFound(page=page_name)
                request.environ['usable_error_content'] = errorpage.render()
            elif is_api:
                self.handle_error(404, 'PAGE_NOT_FOUND')
            self.abort404()

        if version:
            edit_by = version.get_author()
            edit_date = version.date
        else:
            edit_by = page.get_author()
            edit_date = page._get('last_edit_date')

        diffcontent = None
        if not version:
            content = page.content
            if c.is_wiki_mod and page.name in page_descriptions:
                message = page_descriptions[page.name]
        else:
            message = _("viewing revision from %s") % timesince(version.date)
            if version2:
                t1 = timesince(version.date)
                t2 = timesince(version2.date)
                timestamp1 = _("%s ago") % t1
                timestamp2 = _("%s ago") % t2
                message = _("comparing revisions from %(date_1)s and %(date_2)s") \
                          % {'date_1': t1, 'date_2': t2}
                diffcontent = make_htmldiff(version.content, version2.content, timestamp1, timestamp2)
                content = version2.content
            else:
                message = _("viewing revision from %s ago") % timesince(version.date)
                content = version.content

        return WikiPageView(content, alert=message, v=version, diff=diffcontent,
                            may_revise=this_may_revise(page), edit_by=edit_by,
                            edit_date=edit_date, page=page.name).render()

    @paginated_listing(max_page_size=100, backend='cassandra')
    @validate(page=VWikiPage(('page'), restricted=False))
    def GET_wiki_revisions(self, num, after, reverse, count, page):
        revisions = page.get_revisions()
        builder = WikiRevisionBuilder(revisions, num=num, reverse=reverse, count=count, after=after, skip=not c.is_wiki_mod, wrap=default_thing_wrapper())
        listing = WikiRevisionListing(builder).listing()
        return WikiRevisions(listing, page=page.name, may_revise=this_may_revise(page)).render()

    @validate(wp=VWikiPageRevise('page'),
              page=VWikiPageName('page'))
    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]:
            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:
            return WikiCreate(page=page, may_revise=True).render()

    @validate(wp=VWikiPageRevise('page', restricted=True, required=True))
    def GET_wiki_revise(self, wp, page, message=None, **kw):
        wp = wp[0]
        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()

    @paginated_listing(max_page_size=100, backend='cassandra')
    def GET_wiki_recent(self, num, after, reverse, count):
        revisions = WikiRevision.get_recent(c.site)
        builder = WikiRecentRevisionBuilder(revisions,  num=num, count=count,
                                            reverse=reverse, after=after,
                                            wrap=default_thing_wrapper(),
                                            skip=not c.is_wiki_mod)
        listing = WikiRevisionListing(builder).listing()
        return WikiRecent(listing).render()

    def GET_wiki_listing(self):
        def check_hidden(page):
            return this_may_view(page)
        pages, linear_pages = WikiPage.get_listing(c.site, filter_check=check_hidden)
        return WikiListing(pages, linear_pages).render()

    def GET_wiki_redirect(self, page='index'):
        return redirect_to(str("%s/%s" % (c.wiki_base_url, page)), _code=301)

    @base_listing
    @validate(page=VWikiPage('page', restricted=True))
    def GET_wiki_discussions(self, page, num, after, reverse, count):
        page_url = add_sr("%s/%s" % (c.wiki_base_url, page.name))
        builder = url_links_builder(page_url)
        listing = LinkListing(builder).listing()
        return WikiDiscussions(listing, page=page.name,
                               may_revise=this_may_revise(page)).render()

    @validate(page=VWikiPage('page', restricted=True, modonly=True))
    def GET_wiki_settings(self, page):
        settings = {'permlevel': page._get('permlevel', 0)}
        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()

    @validate(VModhash(),
              page=VWikiPage('page', restricted=True, modonly=True),
              permlevel=VInt('permlevel'))
    def POST_wiki_settings(self, page, permlevel):
        oldpermlevel = page.permlevel
        try:
            page.change_permlevel(permlevel)
        except ValueError:
            self.handle_error(403, 'INVALID_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)

    def on_validation_error(self, error):
        RedditController.on_validation_error(self, error)
        if error.code:
            self.handle_error(error.code, error.name)

    def handle_error(self, code, reason=None, **data):
        abort(reddit_http_error(code, reason, **data))

    def pre(self):
        RedditController.pre(self)
        if g.disable_wiki and not c.user_is_admin:
            self.handle_error(403, 'WIKI_DOWN')
        if not c.site._should_wiki:
            self.handle_error(404, 'NOT_WIKIABLE')  # /r/mod for an example
        frontpage = isinstance(c.site, DefaultSR)
        c.wiki_base_url = join_urls(c.site.path, 'wiki')
        c.wiki_api_url = join_urls(c.site.path, '/api/wiki')
        c.wiki_id = g.default_sr if frontpage else c.site.name
        self.editconflict = False
        c.is_wiki_mod = (
            c.user_is_admin or c.site.is_moderator_with_perms(c.user, 'wiki')
            ) if c.user_is_loggedin else False
        c.wikidisabled = False

        mode = c.site.wikimode
        if not mode or mode == 'disabled':
            if not c.is_wiki_mod:
                self.handle_error(403, 'WIKI_DISABLED')
            else:
                c.wikidisabled = True

    # Redirects from the old wiki
    def GET_faq(self):
        return self.GET_wiki_redirect(page='faq')

    GET_help = GET_wiki_redirect
Ejemplo n.º 17
0
class GoldApiController(RedditController):
    @json_validate(VUser(), VGold(), VModhash(), deal=VLength('deal', 100))
    def POST_claim_gold_partner_deal_code(self, responder, deal):
        try:
            return {'code': GoldPartnerDealCode.claim_code(c.user, deal)}
        except GoldPartnerCodesExhaustedError:
            return {
                'error': 'GOLD_PARTNER_CODES_EXHAUSTED',
                'explanation': _("sorry, we're out of codes!")
            }

    @validatedForm(
        VUser(),
        VGold(),
        VModhash(),
        public=VBoolean("public"),
        snoo_color=VSnooColor("snoo_color"),
        unvalidated_components=VJSON("components"),
    )
    def POST_snoovatar(self, form, jquery, public, snoo_color,
                       unvalidated_components):
        if not feature.is_enabled('snoovatars'):
            return

        if form.has_errors(
                "components",
                errors.NO_TEXT,
                errors.TOO_LONG,
                errors.BAD_STRING,
        ):
            return
        if form.has_errors("snoo_color", errors.BAD_CSS_COLOR):
            return

        try:
            tailors = g.plugins["gold"].tailors_data
            validated = {}

            for tailor in tailors:
                tailor_name = tailor["name"]
                component = unvalidated_components.get(tailor_name)

                # if the tailor requires a selection, ensure there is one
                if not tailor["allow_clear"]:
                    require(component)

                # ensure this dressing exists
                if component:
                    for dressing in tailor["dressings"]:
                        if component == dressing["name"]:
                            break
                    else:
                        raise RequirementException

                validated[tailor_name] = component
        except RequirementException:
            c.errors.add(errors.INVALID_SNOOVATAR, field="components")
            form.has_errors("components", errors.INVALID_SNOOVATAR)
            return

        SnoovatarsByAccount.save(
            user=c.user,
            name="snoo",
            public=public,
            snoo_color=snoo_color,
            components=validated,
        )
class LiveUpdateEventsController(RedditController):
    def GET_home(self):
        return pages.LiveUpdateMetaPage(
            title=_("reddit live"),
            content=pages.LiveUpdateHome(),
            page_classes=["liveupdate-home"],
        ).render()

    @require_oauth2_scope("read")
    @api_doc(
        section=api_section.live,
        uri="/api/live/happening_now",
    )
    @validate(geo=VCountryCode())
    def GET_happening_now(self, geo):
        """ Get some basic information about the currently featured live thread.

            Returns an empty 204 response for api requests if no thread is currently featured.

            See also: [/api/live/*thread*/about](#GET_api_live_{thread}_about).
        """

        if not is_api() or not feature.is_enabled('live_happening_now'):
            self.abort404()

        featured_event = get_featured_event(geo)
        if not featured_event:
            response.status_code = 204
            return

        c.liveupdate_event = featured_event
        content = Wrapped(featured_event)
        return pages.LiveUpdateEventPage(content).render()

    @validate(
        VEmployee(),
        num=VLimit("limit", default=25, max_limit=100),
        after=VLiveUpdateEvent("after"),
        before=VLiveUpdateEvent("before"),
        count=VCount("count"),
    )
    def GET_listing(self, filter, num, after, before, count):
        reverse = False
        if before:
            after = before
            reverse = True

        builder_cls = LiveUpdateEventBuilder
        wrapper = Wrapped
        listing_cls = Listing

        if filter == "open":
            title = _("live threads")
            query = queries.get_live_events("new", "all")
        elif filter == "closed":
            title = _("closed threads")
            query = queries.get_complete_events("new", "all")
        elif filter == "active":
            title = _("most active threads")
            query = queries.get_active_events()
        elif filter == "reported":
            if not c.user_is_admin:
                self.abort403()

            title = _("reported threads")
            query = queries.get_reported_events()
            builder_cls = LiveUpdateReportedEventBuilder
            wrapper = pages.LiveUpdateReportedEventRow
            listing_cls = pages.LiveUpdateReportedEventListing
        else:
            self.abort404()

        builder = builder_cls(
            query,
            num=num,
            after=after,
            reverse=reverse,
            count=count,
            wrap=wrapper,
            skip=True,
        )

        listing = listing_cls(builder)

        return pages.LiveUpdateMetaPage(
            title=title,
            content=listing.listing(),
        ).render()

    @validate(
        VUser(), )
    def GET_create(self):
        return pages.LiveUpdateMetaPage(
            title=_("create live thread"),
            content=pages.LiveUpdateCreate(),
        ).render()

    @require_oauth2_scope("submit")
    @validatedForm(VUser(), VModhash(),
                   VRatelimit(rate_user=True, prefix="liveupdate_create_"),
                   **EVENT_CONFIGURATION_VALIDATORS)
    @api_doc(
        section=api_section.live,
        uri="/api/live/create",
    )
    def POST_create(self, form, jquery, title, description, resources, nsfw):
        """Create a new live thread.

        Once created, the initial settings can be modified with
        [/api/live/*thread*/edit](#POST_api_live_{thread}_edit) and new updates
        can be posted with
        [/api/live/*thread*/update](#POST_api_live_{thread}_update).

        """
        if not is_event_configuration_valid(form):
            return

        # for simplicity, set the live-thread creation threshold at the
        # subreddit creation threshold
        if not c.user_is_admin and not c.user.can_create_subreddit:
            form.set_error(errors.CANT_CREATE_SR, "")
            c.errors.add(errors.CANT_CREATE_SR, field="")
            return

        if form.has_errors("ratelimit", errors.RATELIMIT):
            return

        VRatelimit.ratelimit(rate_user=True,
                             prefix="liveupdate_create_",
                             seconds=60)

        event = LiveUpdateEvent.new(
            id=None,
            title=title,
            description=description,
            resources=resources,
            banned=c.user._spam,
            nsfw=nsfw,
        )
        event.add_contributor(c.user, ContributorPermissionSet.SUPERUSER)
        queries.create_event(event)

        form.redirect("/live/" + event._id)
        form._send_data(id=event._id)
        liveupdate_events.create_event(event, context=c, request=request)
class LiveUpdateEventsController(RedditController):
    def GET_home(self):
        return pages.LiveUpdateMetaPage(
            title=_("reddit live"),
            content=pages.LiveUpdateHome(),
            page_classes=["liveupdate-home"],
        ).render()

    @validate(
        VEmployee(),
        num=VLimit("limit", default=25, max_limit=100),
        after=VLiveUpdateEvent("after"),
        before=VLiveUpdateEvent("before"),
        count=VCount("count"),
    )
    def GET_listing(self, filter, num, after, before, count):
        reverse = False
        if before:
            after = before
            reverse = True

        builder_cls = LiveUpdateEventBuilder
        wrapper = Wrapped
        listing_cls = Listing

        if filter == "open":
            title = _("live threads")
            query = queries.get_live_events("new", "all")
        elif filter == "closed":
            title = _("closed threads")
            query = queries.get_complete_events("new", "all")
        elif filter == "active":
            title = _("most active threads")
            query = queries.get_active_events()
        elif filter == "reported":
            if not c.user_is_admin:
                self.abort403()

            title = _("reported threads")
            query = queries.get_reported_events()
            builder_cls = LiveUpdateReportedEventBuilder
            wrapper = pages.LiveUpdateReportedEventRow
            listing_cls = pages.LiveUpdateReportedEventListing
        else:
            self.abort404()

        builder = builder_cls(
            query,
            num=num,
            after=after,
            reverse=reverse,
            count=count,
            wrap=wrapper,
            skip=True,
        )

        listing = listing_cls(builder)

        return pages.LiveUpdateMetaPage(
            title=title,
            content=listing.listing(),
        ).render()

    @validate(
        VUser(),
    )
    def GET_create(self):
        return pages.LiveUpdateMetaPage(
            title=_("create live thread"),
            content=pages.LiveUpdateCreate(),
        ).render()

    @require_oauth2_scope("submit")
    @validatedForm(
        VUser(),
        VModhash(),
        VRatelimit(rate_user=True, prefix="liveupdate_create_"),
        **EVENT_CONFIGURATION_VALIDATORS
    )
    @api_doc(
        section=api_section.live,
        uri="/api/live/create",
    )
    def POST_create(self, form, jquery, title, description, resources, nsfw):
        """Create a new live thread.

        Once created, the initial settings can be modified with
        [/api/live/*thread*/edit](#POST_api_live_{thread}_edit) and new updates
        can be posted with
        [/api/live/*thread*/update](#POST_api_live_{thread}_update).

        """
        if not is_event_configuration_valid(form):
            return

        if form.has_errors("ratelimit", errors.RATELIMIT):
            return
        VRatelimit.ratelimit(
            rate_user=True, prefix="liveupdate_create_", seconds=60)

        event = LiveUpdateEvent.new(
            id=None,
            title=title,
            description=description,
            resources=resources,
            banned=c.user._spam,
            nsfw=nsfw,
        )
        event.add_contributor(c.user, ContributorPermissionSet.SUPERUSER)
        queries.create_event(event)

        form.redirect("/live/" + event._id)
        form._send_data(id=event._id)
Ejemplo n.º 20
0
class PromoteController(ListingController):
    where = 'promoted'
    render_cls = PromotePage

    @property
    def title_text(self):
        return _('promoted by you')

    def keep_fn(self):
        def keep(item):
            if item.promoted and not item._deleted:
                return True
            else:
                return False
        return keep

    def query(self):
        if c.user_is_sponsor:
            if self.sort == "future_promos":
                return queries.get_all_unapproved_links()
            elif self.sort == "pending_promos":
                return queries.get_all_accepted_links()
            elif self.sort == "unpaid_promos":
                return queries.get_all_unpaid_links()
            elif self.sort == "rejected_promos":
                return queries.get_all_rejected_links()
            elif self.sort == "live_promos":
                return queries.get_all_live_links()
            return queries.get_all_promoted_links()
        else:
            if self.sort == "future_promos":
                return queries.get_unapproved_links(c.user._id)
            elif self.sort == "pending_promos":
                return queries.get_accepted_links(c.user._id)
            elif self.sort == "unpaid_promos":
                return queries.get_unpaid_links(c.user._id)
            elif self.sort == "rejected_promos":
                return queries.get_rejected_links(c.user._id)
            elif self.sort == "live_promos":
                return queries.get_live_links(c.user._id)
            return queries.get_promoted_links(c.user._id)

    @validate(VSponsor())
    def GET_listing(self, sort="", **env):
        if not c.user_is_loggedin or not c.user.email_verified:
            return self.redirect("/ad_inq")
        self.sort = sort
        return ListingController.GET_listing(self, **env)

    GET_index = GET_listing

    @validate(VSponsor())
    def GET_new_promo(self):
        return PromotePage('content', content=PromoteLinkForm()).render()

    @validate(VSponsor('link'),
              link=VLink('link'))
    def GET_edit_promo(self, link):
        if not link or link.promoted is None:
            return self.abort404()
        rendered = wrap_links(link, wrapper=promote.sponsor_wrapper,
                              skip=False)

        form = PromoteLinkForm(link=link,
                               listing=rendered,
                               timedeltatext="")

        page = PromotePage('new_promo', content=form)

        return page.render()


    # For development. Should eventually replace GET_edit_promo
    @validate(VSponsor('link'),
              link=VLink('link'))
    def GET_edit_promo_cpm(self, link):
        if not link or link.promoted is None:
            return self.abort404()
        rendered = wrap_links(link, wrapper=promote.sponsor_wrapper,
                              skip=False)

        form = PromoteLinkFormCpm(link=link,
                                  listing=rendered,
                                  timedeltatext="")

        page = PromotePage('new_promo', content=form)

        return page.render()


    # admin only because the route might change
    @validate(VSponsorAdmin('campaign'),
              campaign=VPromoCampaign('campaign'))
    def GET_edit_promo_campaign(self, campaign):
        if not campaign:
            return self.abort404()
        link = Link._byID(campaign.link_id)
        return self.redirect(promote.promo_edit_url(link))

    @validate(VSponsor(),
              dates=VDateRange(["startdate", "enddate"],
                               max_range=timedelta(days=28),
                               required=False))
    def GET_graph(self, dates):
        start, end, bad_dates = _check_dates(dates)
        return PromotePage("graph",
                           content=Promote_Graph(
                                start, end, bad_dates=bad_dates)
                           ).render()

    @validate(VSponsorAdmin(),
              dates=VDateRange(["startdate", "enddate"],
                               max_range=timedelta(days=28),
                               required=False))
    def GET_admingraph(self, dates):
        start, end, bad_dates = _check_dates(dates)
        content = Promote_Graph(start, end, bad_dates=bad_dates,
                                admin_view=True)
        if c.render_style == 'csv':
            return content.as_csv()
        return PromotePage("admingraph", content=content).render()

    def GET_inventory(self, sr_name):
        '''
        Return available inventory data as json for use in ajax calls
        '''
        inv_start_date = promote.promo_datetime_now()
        inv_end_date = inv_start_date + timedelta(60)
        inventory = promote.get_available_impressions(
            sr_name,
            inv_start_date,
            inv_end_date,
            fuzzed=(not c.user_is_admin)
        )
        dates = []
        impressions = []
        max_imps = 0
        for date, imps in inventory.iteritems():
            dates.append(date.strftime("%m/%d/%Y"))
            impressions.append(imps)
            max_imps = max(max_imps, imps)
        return json.dumps({'sr':sr_name,
                           'dates': dates,
                           'imps':impressions,
                           'max_imps':max_imps})

    # ## POST controllers below
    @validatedForm(VSponsorAdmin(),
                   link=VLink("link_id"),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_freebie(self, form, jquery, link, campaign):
        if promote.is_promo(link) and campaign:
            promote.free_campaign(link, campaign, c.user)
            form.redirect(promote.promo_edit_url(link))

    @validatedForm(VSponsorAdmin(),
                   link=VByName("link"),
                   note=nop("note"))
    def POST_promote_note(self, form, jquery, link, note):
        if promote.is_promo(link):
            text = PromotionLog.add(link, note)
            form.find(".notes").children(":last").after(
                "<p>" + text + "</p>")


    @noresponse(VSponsorAdmin(),
                thing=VByName('id'))
    def POST_promote(self, thing):
        if promote.is_promo(thing):
            promote.accept_promotion(thing)

    @noresponse(VSponsorAdmin(),
                thing=VByName('id'),
                reason=nop("reason"))
    def POST_unpromote(self, thing, reason):
        if promote.is_promo(thing):
            promote.reject_promotion(thing, reason=reason)

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   VRatelimit(rate_user=True,
                              rate_ip=True,
                              prefix='create_promo_'),
                   l=VLink('link_id'),
                   title=VTitle('title'),
                   url=VUrl('url', allow_self=False, lookup=False),
                   ip=ValidIP(),
                   disable_comments=VBoolean("disable_comments"),
                   set_clicks=VBoolean("set_maximum_clicks"),
                   max_clicks=VInt("maximum_clicks", min=0),
                   set_views=VBoolean("set_maximum_views"),
                   max_views=VInt("maximum_views", min=0),
                   media_width=VInt("media-width", min=0),
                   media_height=VInt("media-height", min=0),
                   media_embed=VLength("media-embed", 1000),
                   media_override=VBoolean("media-override"),
                   domain_override=VLength("domain", 100)
                   )
    def POST_edit_promo(self, form, jquery, ip, l, title, url,
                        disable_comments,
                        set_clicks, max_clicks,
                        set_views, max_views,
                        media_height, media_width, media_embed,
                        media_override, domain_override):

        should_ratelimit = False
        if not c.user_is_sponsor:
            set_clicks = False
            set_views = False
            should_ratelimit = True
        if not set_clicks:
            max_clicks = None
        if not set_views:
            max_views = None

        if not should_ratelimit:
            c.errors.remove((errors.RATELIMIT, 'ratelimit'))

        # demangle URL in canonical way
        if url:
            if isinstance(url, (unicode, str)):
                form.set_inputs(url=url)
            elif isinstance(url, tuple) or isinstance(url[0], Link):
                # there's already one or more links with this URL, but
                # we're allowing mutliple submissions, so we really just
                # want the URL
                url = url[0].url

        # users can change the disable_comments on promoted links
        if ((not l or not promote.is_promoted(l)) and
            (form.has_errors('title', errors.NO_TEXT,
                            errors.TOO_LONG) or
            form.has_errors('url', errors.NO_URL, errors.BAD_URL) or
            jquery.has_errors('ratelimit', errors.RATELIMIT))):
            return

        if not l:
            l = promote.new_promotion(title, url, c.user, ip)
        elif promote.is_promo(l):
            changed = False
            # live items can only be changed by a sponsor, and also
            # pay the cost of de-approving the link
            trusted = c.user_is_sponsor or c.user.trusted_sponsor
            if not promote.is_promoted(l) or trusted:
                if title and title != l.title:
                    l.title = title
                    changed = not trusted
                if url and url != l.url:
                    l.url = url
                    changed = not trusted

            # only trips if the title and url are changed by a non-sponsor
            if changed and not promote.is_unpaid(l):
                promote.unapprove_promotion(l)
            if trusted and promote.is_unapproved(l):
                promote.accept_promotion(l)

            if c.user_is_sponsor:
                l.maximum_clicks = max_clicks
                l.maximum_views = max_views

            # comment disabling is free to be changed any time.
            l.disable_comments = disable_comments
            if c.user_is_sponsor or c.user.trusted_sponsor:
                if media_embed and media_width and media_height:
                    l.media_object = dict(height=media_height,
                                          width=media_width,
                                          content=media_embed,
                                          type='custom')
                else:
                    l.media_object = None

                l.media_override = media_override
                if getattr(l, "domain_override", False) or domain_override:
                    l.domain_override = domain_override
            l._commit()

        form.redirect(promote.promo_edit_url(l))

    @validate(VSponsorAdmin())
    def GET_roadblock(self):
        return PromotePage('content', content=Roadblocks()).render()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                      future=1,
                                      reference_date=promote.promo_datetime_now,
                                      business_days=False,
                                      sponsor_override=True),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_add_roadblock(self, form, jquery, dates, sr):
        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE) or
            form.has_errors('enddate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE)):
            return
        if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                           errors.SUBREDDIT_NOTALLOWED,
                           errors.SUBREDDIT_REQUIRED):
            return
        if dates and sr:
            sd, ed = dates
            promote.roadblock_reddit(sr.name, sd.date(), ed.date())
            jquery.refresh()

    @validatedForm(VSponsorAdmin(),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                      future=1,
                                      reference_date=promote.promo_datetime_now,
                                      business_days=False,
                                      sponsor_override=True),
                   sr=VSubmitSR('sr', promotion=True))
    def POST_rm_roadblock(self, form, jquery, dates, sr):
        if dates and sr:
            sd, ed = dates
            promote.unroadblock_reddit(sr.name, sd.date(), ed.date())
            jquery.refresh()

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   dates=VDateRange(['startdate', 'enddate'],
                                  future=1,
                                  reference_date=promote.promo_datetime_now,
                                  business_days=False,
                                  sponsor_override=True),
                   l=VLink('link_id'),
                   bid=VFloat('bid', min=0, max=g.max_promote_bid,
                                  coerce=False, error=errors.BAD_BID),
                   sr=VSubmitSR('sr', promotion=True),
                   campaign_id36=nop("campaign_id36"),
                   targeting=VLength("targeting", 10))
    def POST_edit_campaign(self, form, jquery, l, campaign_id36,
                          dates, bid, sr, targeting):
        if not l:
            return

        start, end = dates or (None, None)

        if (start and end and not promote.is_accepted(l) and
            not c.user_is_sponsor):
            # if the ad is not approved already, ensure the start date
            # is at least 2 days in the future
            start = start.date()
            end = end.date()
            now = promote.promo_datetime_now()
            future = make_offset_date(now, g.min_promote_future,
                                      business_days=True)
            if start < future.date():
                c.errors.add(errors.BAD_FUTURE_DATE,
                             msg_params=dict(day=g.min_promote_future),
                             field="startdate")


        if (form.has_errors('startdate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE) or
            form.has_errors('enddate', errors.BAD_DATE,
                            errors.BAD_FUTURE_DATE, errors.BAD_DATE_RANGE)):
            return

        # Limit the number of PromoCampaigns a Link can have
        # Note that the front end should prevent the user from getting
        # this far
        existing_campaigns = list(PromoCampaign._by_link(l._id))
        if len(existing_campaigns) > g.MAX_CAMPAIGNS_PER_LINK:
            c.errors.add(errors.TOO_MANY_CAMPAIGNS,
                         msg_params={'count': g.MAX_CAMPAIGNS_PER_LINK},
                         field='title')
            form.has_errors('title', errors.TOO_MANY_CAMPAIGNS)
            return

        duration = max((end - start).days, 1)

        if form.has_errors('bid', errors.BAD_BID):
            return

        # minimum bid depends on user privilege and targeting, checked here
        # instead of in the validator b/c current duration is needed
        if c.user_is_sponsor:
            min_daily_bid = 0
        elif targeting == 'one':
            min_daily_bid = g.min_promote_bid * 1.5
        else:
            min_daily_bid = g.min_promote_bid

        if campaign_id36:
            # you cannot edit the bid of a live ad unless it's a freebie
            try:
                campaign = PromoCampaign._byID36(campaign_id36)
                if (bid != campaign.bid and
                    campaign.start_date < datetime.now(g.tz)
                    and not campaign.is_freebie()):
                    c.errors.add(errors.BID_LIVE, field='bid')
                    form.has_errors('bid', errors.BID_LIVE)
                    return
            except NotFound:
                pass

        if bid is None or bid / duration < min_daily_bid:
            c.errors.add(errors.BAD_BID, field='bid',
                         msg_params={'min': min_daily_bid,
                                       'max': g.max_promote_bid})
            form.has_errors('bid', errors.BAD_BID)
            return

        if targeting == 'one':
            if form.has_errors('sr', errors.SUBREDDIT_NOEXIST,
                               errors.SUBREDDIT_NOTALLOWED,
                               errors.SUBREDDIT_REQUIRED):
                # checking to get the error set in the form, but we can't
                # check for rate-limiting if there's no subreddit
                return
            oversold = promote.is_roadblocked(sr.name, start, end)
            if oversold and not c.user_is_sponsor:
                msg_params = {"start": oversold[0].strftime('%m/%d/%Y'),
                              "end": oversold[1].strftime('%m/%d/%Y')}
                c.errors.add(errors.OVERSOLD, field='sr',
                             msg_params=msg_params)
                form.has_errors('sr', errors.OVERSOLD)
                return
        if targeting == 'none':
            sr = None

        if campaign_id36 is not None:
            campaign = PromoCampaign._byID36(campaign_id36)
            promote.edit_campaign(l, campaign, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.update_campaign(r.campaign_id36, r.start_date, r.end_date,
                                   r.duration, r.bid, r.sr, r.status)
        else:
            campaign = promote.new_campaign(l, dates, bid, sr)
            r = promote.get_renderable_campaigns(l, campaign)
            jquery.new_campaign(r.campaign_id36, r.start_date, r.end_date,
                                r.duration, r.bid, r.sr, r.status)

    @validatedForm(VSponsor('link_id'),
                   VModhash(),
                   l=VLink('link_id'),
                   campaign=VPromoCampaign("campaign_id36"))
    def POST_delete_campaign(self, form, jquery, l, campaign):
        if l and campaign:
            promote.delete_campaign(l, campaign)


    @validatedForm(VSponsor('container'),
                   VModhash(),
                   user=VExistingUname('name'),
                   thing=VByName('container'))
    def POST_traffic_viewer(self, form, jquery, user, thing):
        """
        Adds a user to the list of users allowed to view a promoted
        link's traffic page.
        """
        if not form.has_errors("name",
                               errors.USER_DOESNT_EXIST, errors.NO_USER):
            form.set_inputs(name="")
            form.set_html(".status:first", _("added"))
            if promote.add_traffic_viewer(thing, user):
                user_row = TrafficViewerList(thing).user_row('traffic', user)
                jquery("#traffic-table").show(
                    ).find("table").insert_table_rows(user_row)

                # send the user a message
                msg = user_added_messages['traffic']['pm']['msg']
                subj = user_added_messages['traffic']['pm']['subject']
                if msg and subj:
                    d = dict(url=thing.make_permalink_slow(),
                             traffic_url=promote.promo_traffic_url(thing),
                             title=thing.title)
                    msg = msg % d
                    item, inbox_rel = Message._new(c.user, user,
                                                   subj, msg, request.ip)
                    queries.new_message(item, inbox_rel)


    @validatedForm(VSponsor('container'),
                   VModhash(),
                   iuser=VByName('id'),
                   thing=VByName('container'))
    def POST_rm_traffic_viewer(self, form, jquery, iuser, thing):
        if thing and iuser:
            promote.rm_traffic_viewer(thing, iuser)


    @validatedForm(VSponsor('link'),
                   link=VByName("link"),
                   campaign=VPromoCampaign("campaign"),
                   customer_id=VInt("customer_id", min=0),
                   pay_id=VInt("account", min=0),
                   edit=VBoolean("edit"),
                   address=ValidAddress(
                    ["firstName", "lastName", "company", "address",
                     "city", "state", "zip", "country", "phoneNumber"],
                    allowed_countries=g.allowed_pay_countries),
                   creditcard=ValidCard(["cardNumber", "expirationDate",
                                           "cardCode"]))
    def POST_update_pay(self, form, jquery, link, campaign, customer_id, pay_id,
                        edit, address, creditcard):
        address_modified = not pay_id or edit
        form_has_errors = False
        if address_modified:
            if (form.has_errors(["firstName", "lastName", "company", "address",
                                 "city", "state", "zip",
                                 "country", "phoneNumber"],
                                errors.BAD_ADDRESS) or
                form.has_errors(["cardNumber", "expirationDate", "cardCode"],
                                errors.BAD_CARD)):
                form_has_errors = True
            elif g.authorizenetapi:
                pay_id = edit_profile(c.user, address, creditcard, pay_id)
            else:
                pay_id = 1
        # if link is in use or finished, don't make a change
        if pay_id and not form_has_errors:
            # valid bid and created or existing bid id.
            # check if already a transaction
            if g.authorizenetapi:
                success, reason = promote.auth_campaign(link, campaign, c.user,
                                                        pay_id)
            else:
                success = True
            if success:
                form.redirect(promote.promo_edit_url(link))
            else:
                form.set_html(".status",
                              reason or
                              _("failed to authenticate card.  sorry."))

    @validate(VSponsor("link"),
              link=VLink("link"),
              campaign=VPromoCampaign("campaign"))
    def GET_pay(self, link, campaign):
        # no need for admins to play in the credit card area
        if c.user_is_loggedin and c.user._id != link.author_id:
            return self.abort404()

        if not campaign.link_id == link._id:
            return self.abort404()
        if g.authorizenetapi:
            data = get_account_info(c.user)
            content = PaymentForm(link, campaign,
                                  customer_id=data.customerProfileId,
                                  profiles=data.paymentProfiles,
                                  max_profiles=PROFILE_LIMIT)
        else:
            content = None
        res = LinkInfoPage(link=link,
                            content=content,
                            show_sidebar=False)
        return res.render()

    def GET_link_thumb(self, *a, **kw):
        """
        See GET_upload_sr_image for rationale
        """
        return "nothing to see here."

    @validate(VSponsor("link_id"),
              link=VByName('link_id'),
              file=VLength('file', 500 * 1024))
    def POST_link_thumb(self, link=None, file=None):
        if link and (not promote.is_promoted(link) or
                     c.user_is_sponsor or c.user.trusted_sponsor):
            errors = dict(BAD_CSS_NAME="", IMAGE_ERROR="")
            try:
                # thumnails for promoted links can change and therefore expire
                force_thumbnail(link, file, file_type=".jpg")
            except cssfilter.BadImage:
                # if the image doesn't clean up nicely, abort
                errors["IMAGE_ERROR"] = _("bad image")
            if any(errors.values()):
                return UploadedImage("", "", "upload", errors=errors,
                                     form_id="image-upload").render()
            else:
                link._commit()
                return UploadedImage(_('saved'), thumbnail_url(link), "",
                                     errors=errors,
                                     form_id="image-upload").render()

    @validate(VSponsorAdmin(),
              launchdate=VDate('ondate'),
              dates=VDateRange(['startdate', 'enddate']),
              query_type=VOneOf('q', ('started_on', 'between'), default=None))
    def GET_admin(self, launchdate=None, dates=None, query_type=None):
        return PromoAdminTool(query_type=query_type,
                              launchdate=launchdate,
                              start=dates[0],
                              end=dates[1]).render()