def edit_campaign(link, campaign, dates, bid, cpm, sr, priority): sr_name = sr.name if sr else '' # empty string means target to all # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, priority, commit=True) if campaign.priority.cpm: # record the transaction text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def edit_campaign(link, campaign, dates, bid, cpm, sr, priority): sr_name = sr.name if sr else "" # empty string means target to all try: # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, priority, commit=True) if campaign.priority.cpm: # record the transaction text = "updated campaign %s. (bid: %0.2f)" % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) except Exception, e: # record error and rethrow g.log.error("Failed to update PromoCampaign %s on link %d. Error was: %r" % (campaign._id, link._id, e)) try: # wrapped in try/except so orig error won't be lost if commit fails text = "update FAILED. (campaign: %s, bid: %.2f)" % (campaign._id, bid) PromotionLog.add(link, text) except: pass raise e
def void_campaign(link, campaign): transactions = get_transactions(link, [campaign]) bid_record = transactions.get(campaign._id) if bid_record: a = Account._byID(link.author_id) authorize.void_transaction(a, bid_record.transaction, campaign._id) hooks.get_hook('campaign.void').call(link=link, campaign=campaign)
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) promotion_weights = PromotionWeights.get_campaigns(now) live_campaigns = {pw.promo_idx for pw in promotion_weights if pw.thing_name == link._fullname} if live_campaigns: campaigns = PromoCampaign._byID(live_campaigns, data=True, return_dict=False) PromotionLog.add(link, "has live campaigns, forcing live") charge_pending(0) # campaign must be charged before it will go live for campaign in campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [ camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged()) ] for campaign in charged_campaigns: hooks.get_hook("campaign.edit").call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def POST_update(self, form, jquery, text): 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("")
def cast_vote(user, thing, direction, **data): """Register a vote and queue it for processing.""" update_vote_lookups(user, thing, direction) vote_data = { "user_id": user._id, "thing_fullname": thing._fullname, "direction": direction, "date": int(epoch_timestamp(datetime.now(g.tz))), } data['ip'] = getattr(request, "ip", None) if data['ip'] is not None: data['org'] = organization_by_ips(data['ip']) vote_data['data'] = data hooks.get_hook("vote.get_vote_data").call( data=vote_data["data"], user=user, thing=thing, request=request, context=c, ) # The vote event will actually be sent from an async queue processor, so # we need to pull out the context data at this point if not g.running_as_script: vote_data["event_data"] = { "context": Event.get_context_data(request, c), "sensitive": Event.get_sensitive_context_data(request, c), } amqp.add_item(thing.vote_queue_name, json.dumps(vote_data))
def accept(cls, things, correct=True): from r2.lib.db import queries things = tup(things) things_by_cls = {} for thing in things: things_by_cls.setdefault(thing.__class__, []).append(thing) for thing_cls, cls_things in things_by_cls.iteritems(): to_clear = [] # look up all of the reports for each thing rel_cls = cls.rel(Account, thing_cls) thing_ids = [t._id for t in cls_things] rels = list(rel_cls._query(rel_cls.c._thing2_id == thing_ids)) for r in rels: if r._name == '0': r._name = '1' if correct else '-1' r._commit() for thing in cls_things: if thing.reported > 0: thing.reported = 0 thing._commit() to_clear.append(thing) queries.clear_reports(to_clear, rels) if correct: hooks.get_hook("report.accept").call(reports=rels) else: hooks.get_hook("report.reject").call(reports=rels)
def charge_campaign(link, campaign): if charged_or_not_needed(campaign): return user = Account._byID(link.author_id) success, reason = authorize.charge_transaction(user, campaign.trans_id, campaign._id) if not success: if reason == authorize.TRANSACTION_NOT_FOUND: # authorization hold has expired original_trans_id = campaign.trans_id campaign.trans_id = NO_TRANSACTION campaign._commit() text = ('voided expired transaction for %s: (trans_id: %d)' % (campaign, original_trans_id)) PromotionLog.add(link, text) return hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign) if not is_promoted(link): update_promote_status(link, PROMOTE_STATUS.pending) emailer.queue_promo(link, campaign.bid, campaign.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (campaign._id, campaign.trans_id)) PromotionLog.add(link, text)
def charge_pending(offset=1): for l, camp, weight in accepted_campaigns(offset=offset): user = Account._byID(l.author_id) try: if charged_or_not_needed(camp): continue charge_succeeded = authorize.charge_transaction(user, camp.trans_id, camp._id) if not charge_succeeded: continue hooks.get_hook('promote.new_charge').call(link=l, campaign=camp) if is_promoted(l): emailer.queue_promo(l, camp.bid, camp.trans_id) else: set_promote_status(l, PROMOTE_STATUS.pending) emailer.queue_promo(l, camp.bid, camp.trans_id) text = ('auth charge for campaign %s, trans_id: %d' % (camp._id, camp.trans_id)) PromotionLog.add(l, text) except: print "Error on %s, campaign %s" % (l, camp._id)
def use(self): hooks.get_hook("js_preload.use").call(js_preload=self) if self.data: return js.DataSource.use(self) else: return ''
def apply_effects(self): """Apply the effects of the vote to the thing that was voted on.""" # remove the old vote if self.previous_vote and self.previous_vote.affected_thing_attr: self.thing._incr(self.previous_vote.affected_thing_attr, -1) if (self.previous_vote and not self.previous_vote.effects.affects_score and self.previous_vote.is_self_vote): self.thing._incr('_ups', -1) # add the new vote if self.affected_thing_attr: self.thing._incr(self.affected_thing_attr, 1) if self.effects.affects_karma: change = self.effects.karma_change if self.previous_vote: change -= self.previous_vote.effects.karma_change if change: self.thing.author_slow.incr_karma( kind=self.thing.affects_karma_type, sr=self.thing.subreddit_slow, amt=change, ) hooks.get_hook("vote.apply_effects").call(vote=self)
def accept_promotion(link): """ Accepting is campaign agnostic. Accepting the ad just means that it is allowed to run if payment has been processed. If a campagn is able to run, this also requeues it. """ PromotionLog.add(link, 'status update: accepted') # update the query queue set_promote_status(link, PROMOTE_STATUS.accepted) # campaigns that should be live now must be updated now = promo_datetime_now(0) if link._fullname in set(l.thing_name for l in PromotionWeights.get_campaigns(now)): PromotionLog.add(link, 'Marked promotion for acceptance') charge_pending(0) # campaign must be charged before it will go live queue_changed_promo(link, "accepted") # campaigns that were charged and will go live in the future must be updated future_campaigns = [camp for camp in PromoCampaign._by_link(link._id) if camp.start_date > now] transactions = get_transactions(link, future_campaigns) charged_campaigns = [camp for camp in future_campaigns if (transactions.get(camp._id) and transactions.get(camp._id).is_charged())] for campaign in charged_campaigns: hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) if link._spam: link._spam = False link._commit() emailer.accept_promo(link)
def _on_create(self): hooks.get_hook("oauth2.create_token").call(token=self) # update the by-user view if self.user_id: self._by_user_view()._set_values(str(self.user_id), {self._id: ''}) return super(OAuth2AccessToken, self)._on_create()
def toggle_pause_campaign(link, campaign, should_pause): campaign.paused = should_pause campaign._commit() action = "paused" if should_pause else "resumed" PromotionLog.add(link, "%s campaign %s" % (action, campaign._id)) hooks.get_hook("promote.edit_campaign").call(link=link, campaign=campaign)
def js_config(extra_config=None): logged = c.user_is_loggedin and c.user.name gold = bool(logged and c.user.gold) config = { # is the user logged in? "logged": logged, # the subreddit's name (for posts) "post_site": c.site.name if not c.default_sr else "", # the user's voting hash "modhash": c.modhash or False, # the current rendering style "renderstyle": c.render_style, # they're welcome to try to override this in the DOM because we just # disable the features server-side if applicable 'store_visits': gold and c.user.pref_store_visits, # current domain "cur_domain": get_domain(cname=c.frameless_cname, subreddit=False, no_www=True), # where do ajax requests go? "ajax_domain": get_domain(cname=c.authorized_cname, subreddit=False), "extension": c.extension, "https_endpoint": is_subdomain(request.host, g.domain) and g.https_endpoint, # does the client only want to communicate over HTTPS? "https_forced": c.user.https_forced, # debugging? "debug": g.debug, "send_logs": g.live_config["frontend_logging"], "server_time": math.floor(time.time()), "status_msg": { "fetching": _("fetching title..."), "submitting": _("submitting..."), "loading": _("loading...") }, "is_fake": isinstance(c.site, FakeSubreddit), "fetch_trackers_url": g.fetch_trackers_url, "adtracker_url": g.adtracker_url, "clicktracker_url": g.clicktracker_url, "uitracker_url": g.uitracker_url, "static_root": static(''), "over_18": bool(c.over18), "new_window": bool(c.user.pref_newwindow), "vote_hash": c.vote_hash, "gold": gold, "has_subscribed": logged and c.user.has_subscribed, } if g.uncompressedJS: config["uncompressedJS"] = True if extra_config: config.update(extra_config) hooks.get_hook("js_config").call(config=config) return config
def toggle_pause_campaign(link, campaign, should_pause): campaign.paused = should_pause campaign._commit() action = 'paused' if should_pause else 'resumed' PromotionLog.add(link, '%s campaign %s' % (action, campaign._id)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def review_fraud(link, is_fraud): link.fraud = is_fraud link._commit() PromotionLog.add(link, "marked as fraud" if is_fraud else "resolved as not fraud") queries.unset_payment_flagged_link(link) if is_fraud: reject_promotion(link, "fraud") hooks.get_hook("promote.fraud_identified").call(link=link, sponsor=c.user)
def run_trylater(): our_hooks = (key[len(PREFIX):] for key in all_hooks().keys() if key.startswith(PREFIX)) with TryLater.multi_handle(our_hooks) as handleable: for system, data in handleable.iteritems(): hook_name = "trylater.%s" % system g.log.info("Trying %s", system) get_hook(hook_name).call(data=data) amqp.worker.join()
def edit_campaign(link, campaign, dates, bid, cpm, target, priority, location, platform='desktop', mobile_os=None): changed = {} if bid != campaign.bid: # if the bid amount changed, cancel any pending transactions void_campaign(link, campaign, reason='changed_bid') changed['bid'] = ("$%0.2f" % campaign.bid, "$%0.2f" % bid) hooks.get_hook('promote.edit_bid').call( link=link,campaign=campaign, previous=campaign.bid, current=bid) campaign.bid = bid if dates[0] != campaign.start_date or dates[1] != campaign.end_date: original = '%s to %s' % (campaign.start_date, campaign.end_date) edited = '%s to %s' % (dates[0], dates[1]) changed['dates'] = (original, edited) campaign.start_date = dates[0] campaign.end_date = dates[1] if cpm != campaign.cpm: changed['cpm'] = (campaign.cpm, cpm) campaign.cpm = cpm if target != campaign.target: changed['target'] = (campaign.target, target) campaign.target = target if priority != campaign.priority: changed['priority'] = (campaign.priority.name, priority.name) campaign.priority = priority if location != campaign.location: changed['location'] = (campaign.location, location) campaign.location = location if platform != campaign.platform: changed["platform"] = (campaign.platform, platform) campaign.platform = platform if mobile_os != campaign.mobile_os: changed["mobile_os"] = (campaign.mobile_os, mobile_os) campaign.mobile_os = mobile_os change_strs = map(lambda t: '%s: %s -> %s' % (t[0], t[1][0], t[1][1]), changed.iteritems()) change_text = ', '.join(change_strs) campaign._commit() # update the index PromotionWeights.reschedule(link, campaign) if campaign.priority.cpm: # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) # record the changes if change_text: PromotionLog.add(link, 'edited %s: %s' % (campaign, change_text)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def new_campaign(link, dates, bid, cpm, target, priority, location, platform, mobile_os): campaign = PromoCampaign.create(link, target, bid, cpm, dates[0], dates[1], priority, location, platform, mobile_os) PromotionWeights.add(link, campaign) PromotionLog.add(link, "campaign %s created" % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook("promote.new_campaign").call(link=link, campaign=campaign) return campaign
def valid_login(name, password): try: a = Account._by_name(name) except NotFound: return False if not a._loaded: a._load() hooks.get_hook("account.spotcheck").call(account=a) if a._banned: return False return valid_password(a, password)
def pre(self): action = request.environ["pylons.routes_dict"].get("action") if action: if not self._get_action_handler(): action = 'invalid' controller = request.environ["pylons.routes_dict"]["controller"] key = "{}.{}".format(controller, action) c.request_timer = g.stats.get_timer(request_timer_name(key)) else: c.request_timer = SimpleSillyStub() c.response_wrapper = None c.start_time = datetime.now(g.tz) c.request_timer.start() g.reset_caches() c.domain_prefix = request.environ.get("reddit-domain-prefix", g.domain_prefix) c.secure = request.environ["wsgi.url_scheme"] == "https" c.request_origin = request.host_url #check if user-agent needs a dose of rate-limiting if not c.error_page: ratelimit_throttled() ratelimit_agents() c.allow_loggedin_cache = False c.allow_framing = False c.cdn_cacheable = (request.via_cdn and g.login_cookie not in request.cookies) # the domain has to be set before Cookies get initialized set_subreddit() c.errors = ErrorSet() c.cookies = Cookies() # if an rss feed, this will also log the user in if a feed= # GET param is included set_content_type() c.request_timer.intermediate("minimal-pre") # True/False forces. None updates for most non-POST requests c.update_last_visit = None g.stats.count_string('user_agents', request.user_agent) if not self.defer_ratelimiting: self.run_sitewide_ratelimits() c.request_timer.intermediate("minimal-ratelimits") hooks.get_hook("reddit.request.minimal_begin").call()
def new_campaign(link, dates, bid, cpm, target, priority, location): campaign = PromoCampaign.create(link, target, bid, cpm, dates[0], dates[1], priority, location) PromotionWeights.add(link, campaign._id, target.subreddit_names, dates[0], dates[1], bid) PromotionLog.add(link, 'campaign %s created' % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('promote.new_campaign').call(link=link, campaign=campaign) return campaign
def new_campaign(link, dates, bid, cpm, sr, priority, location): # empty string for sr_name means target to all sr_name = sr.name if sr else "" campaign = PromoCampaign._new(link, sr_name, bid, cpm, dates[0], dates[1], priority, location) PromotionWeights.add(link, campaign._id, sr_name, dates[0], dates[1], bid) PromotionLog.add(link, "campaign %s created" % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook("promote.new_campaign").call(link=link, campaign=campaign) return campaign
def pre(self): action = request.environ["pylons.routes_dict"].get("action") if action: if not self._get_action_handler(): action = 'invalid' c.request_timer = g.stats.get_timer(request_timer_name(action)) else: c.request_timer = SimpleSillyStub() c.response_wrapper = None c.start_time = datetime.now(g.tz) c.request_timer.start() g.reset_caches() c.domain_prefix = request.environ.get("reddit-domain-prefix", g.domain_prefix) c.secure = request.host in g.secure_domains # wsgi.url_scheme is used in generating absolute urls, such as by webob # for translating some of our relative-url redirects to rfc compliant # absolute-url ones. TODO: consider using one of webob's methods of # setting wsgi.url_scheme based on incoming request headers added by # upstream things like stunnel/haproxy. if c.secure: request.environ["wsgi.url_scheme"] = "https" c.request_origin = request.host_url #check if user-agent needs a dose of rate-limiting if not c.error_page: ratelimit_throttled() ratelimit_agents() c.allow_loggedin_cache = False # the domain has to be set before Cookies get initialized set_subreddit() c.errors = ErrorSet() c.cookies = Cookies() # if an rss feed, this will also log the user in if a feed= # GET param is included set_content_type() c.request_timer.intermediate("minimal-pre") # True/False forces. None updates for most non-POST requests c.update_last_visit = None g.stats.count_string('user_agents', request.user_agent) hooks.get_hook("reddit.request.minimal_begin").call()
def revoke(self): self.revoked = True self._commit() if self.user_id: try: user_tokens = self._by_user_view()._byID(self.user_id) del user_tokens[self._id] except (tdb_cassandra.NotFound, KeyError): pass else: user_tokens._commit() hooks.get_hook("oauth2.revoke_token").call(token=self)
def add_target_fields(self, target): if not target: return from r2.models import Comment, Link, Message self.add("target_id", target._id) self.add("target_fullname", target._fullname) self.add("target_age_seconds", target._age.total_seconds()) target_type = target.__class__.__name__.lower() if target_type == "link" and target.is_self: target_type = "self" self.add("target_type", target_type) # If the target is an Account or Subreddit (or has a "name" attr), # add the target_name if hasattr(target, "name"): self.add("target_name", target.name) # Add info about the target's author for comments, links, & messages if isinstance(target, (Comment, Link, Message)): author = target.author_slow if target._deleted or author._deleted: self.add("target_author_id", 0) self.add("target_author_name", "[deleted]") else: self.add("target_author_id", author._id) self.add("target_author_name", author.name) # Add info about the url being linked to for link posts if isinstance(target, Link): self.add("target_title", target.title) if not target.is_self: self.add("target_url", target.url) self.add("target_url_domain", target.link_domain()) # Add info about the link being commented on for comments if isinstance(target, Comment): link_fullname = Link._fullname_from_id36(to36(target.link_id)) self.add("link_id", target.link_id) self.add("link_fullname", link_fullname) # Add info about when target was originally posted for links/comments if isinstance(target, (Comment, Link)): self.add("target_created_ts", _datetime_to_millis(target._date)) hooks.get_hook("eventcollector.add_target_fields").call( event=self, target=target, )
def new(cls, user, thing, reason=None, ip=None): from r2.lib.db import queries # check if this report exists already! rel = cls.rel(user, thing) q = rel._fast_query(user, thing, ['-1', '0', '1']) q = [ report for (tupl, report) in q.iteritems() if report ] if q: # stop if we've seen this before, so that we never get the # same report from the same user twice oldreport = q[0] g.log.debug("Ignoring duplicate report %s" % oldreport) return oldreport kw = {} if reason: kw['reason'] = reason r = Report(user, thing, '0', **kw) if ip: r.ip = ip r._commit() # mark item as reported try: thing._incr(cls._field) except (ValueError, TypeError): g.log.error("%r has bad field %r = %r" % (thing, cls._field, getattr(thing, cls._field, "(nonexistent)"))) raise if hasattr(thing, 'author_id'): author = Account._byID(thing.author_id, data=True) author._incr('reported') if not getattr(thing, "ignore_reports", False): # update the reports queue if it exists queries.new_report(thing, r) # if the thing is already marked as spam, accept the report if thing._spam: cls.accept(thing) hooks.get_hook("report.new").call(report=r) return r
def which_emails_are_banned(cls, canons): banned = hooks.get_hook("email.get_banned").call(canons=canons) # Create a dictionary like: # d["abc.def.com"] = [ "*****@*****.**", "*****@*****.**" ] rv = {} canons_by_domain = {} # email.get_banned will return a list of lists (one layer from the # hooks system, the second from the function itself); chain them # together for easy processing for canon in itertools.chain(*banned): rv[canon] = None at_sign = canon.find("@") domain = canon[at_sign + 1 :] canons_by_domain.setdefault(domain, []) canons_by_domain[domain].append(canon) # Hand off to the domain ban system; it knows in the case of # [email protected] to check foo.bar.com, bar.com, and .com from r2.models.admintools import bans_for_domain_parts for domain, canons in canons_by_domain.iteritems(): for d in bans_for_domain_parts(domain): if d.no_email: rv[canon] = "domain" return rv
def new_campaign(link, dates, bid, cpm, sr, priority): # empty string for sr_name means target to all sr_name = sr.name if sr else "" campaign = PromoCampaign._new(link, sr_name, bid, cpm, dates[0], dates[1], priority) PromotionWeights.add(link, campaign._id, sr_name, dates[0], dates[1], bid) PromotionLog.add(link, 'campaign %s created' % campaign._id) if campaign.priority.cpm: author = Account._byID(link.author_id, data=True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) else: # non-cpm campaigns are never charged, so we need to fire the hook now hooks.get_hook('promote.new_charge').call(link=link, campaign=campaign) return campaign
def for_url(cls, url, autoplay=False, maxwidth=600): scraper = hooks.get_hook("scraper.factory").call_until_return(url=url) if scraper: return scraper embedly_services = _fetch_embedly_services() for service_re, service_secure in embedly_services: if service_re.match(url): return _EmbedlyScraper(url, service_secure, autoplay=autoplay, maxwidth=maxwidth) return _ThumbnailOnlyScraper(url)
def get_media_embed(media_object): if not isinstance(media_object, dict): return embed_hook = hooks.get_hook("scraper.media_embed") media_embed = embed_hook.call_until_return(media_object=media_object) if media_embed: return media_embed if media_object.get("type") == "custom": return _make_custom_media_embed(media_object) if "oembed" in media_object: return _EmbedlyScraper.media_embed(media_object)
def apply_effects(self): """Apply the effects of the vote to the thing that was voted on.""" # remove the old vote if self.previous_vote and self.previous_vote.affected_thing_attr: self.thing._incr(self.previous_vote.affected_thing_attr, -1) # add the new vote if self.affected_thing_attr: self.thing._incr(self.affected_thing_attr, 1) if self.effects.affects_karma: change = self.effects.karma_change if self.previous_vote: change -= self.previous_vote.effects.karma_change if change: self.thing.author_slow.incr_karma( kind=self.thing.affects_karma_type, sr=self.thing.subreddit_slow, amt=change, ) hooks.get_hook("vote.apply_effects").call(vote=self)
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("")
def edit_campaign(link, campaign, dates, bid, cpm, sr): sr_name = sr.name if sr else '' # empty string means target to all try: # if the bid amount changed, cancel any pending transactions if campaign.bid != bid: void_campaign(link, campaign) # update the schedule PromotionWeights.reschedule(link, campaign._id, sr_name, dates[0], dates[1], bid) # update values in the db campaign.update(dates[0], dates[1], bid, cpm, sr_name, campaign.trans_id, commit=True) # record the transaction text = 'updated campaign %s. (bid: %0.2f)' % (campaign._id, bid) PromotionLog.add(link, text) # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) hooks.get_hook('campaign.edit').call(link=link, campaign=campaign) except Exception, e: # record error and rethrow g.log.error("Failed to update PromoCampaign %s on link %d. Error was: %r" % (campaign._id, link._id, e)) try: # wrapped in try/except so orig error won't be lost if commit fails text = 'update FAILED. (campaign: %s, bid: %.2f)' % (campaign._id, bid) PromotionLog.add(link, text) except: pass raise e
def make_daily_promotions(): # charge campaigns so they can go live charge_pending(offset=0) charge_pending(offset=1) # promote links and record ids of promoted links link_ids = set() for campaign, link in get_scheduled_promos(offset=0): link_ids.add(link._id) promote_link(link, campaign) # expire finished links q = Link._query(Link.c.promote_status == PROMOTE_STATUS.promoted, data=True) q = q._filter(not_(Link.c._id.in_(link_ids))) for link in q: update_promote_status(link, PROMOTE_STATUS.finished) emailer.finished_promo(link) # update subreddits with promos all_live_promo_srnames(_update=True) _mark_promos_updated() finalize_completed_campaigns(daysago=1) hooks.get_hook('promote.make_daily_promotions').call(offset=0)
def add_props(cls, user, wrapped): from r2.lib.wrapped import CachedVariable for item in wrapped: # insert replacement variable for timesince to allow for # caching of thing templates item.display = CachedVariable("display") item.timesince = CachedVariable("timesince") item.childlisting = CachedVariable("childlisting") score_fmt = getattr(item, "score_fmt", Score.number_only) # CUSTOM: voting model if score_fmt == Score.points_dual: item.display_score = map(score_fmt, item.voting_score_ups, item.voting_score_downs) else: item.display_score = map(score_fmt, item.voting_score) if item.cachable: item.render_score = item.display_score item.display_score = map(CachedVariable, ["scoredislikes", "scoreunvoted", "scorelikes", "scorelikesdislikes"]) hooks.get_hook("add_props").call(items=wrapped)
def can_create_subreddit(self): hook = hooks.get_hook("account.can_create_subreddit") can_create = hook.call_until_return(account=self) if can_create is not None: return can_create min_age = timedelta(days=g.live_config["create_sr_account_age_days"]) if self._age < min_age: return False if (self.link_karma < g.live_config["create_sr_link_karma"] and self.comment_karma < g.live_config["create_sr_comment_karma"]): return False return True
def needs_captcha(self): if g.disable_captcha: return False hook = hooks.get_hook("account.is_captcha_exempt") captcha_exempt = hook.call_until_return(account=self) if captcha_exempt: return False if self.link_karma >= g.live_config["captcha_exempt_link_karma"]: return False if self.comment_karma >= g.live_config["captcha_exempt_comment_karma"]: return False return True
def handle_login(controller, form, responder, user, rem=None, signature=None, **kwargs): def _event(error): g.events.login_event('login_attempt', error_msg=error, user_name=request.urlvars.get('url_user'), remember_me=rem, signature=signature, request=request, context=c) hook_error = hooks.get_hook("account.login").call_until_return( responder=responder, request=request, context=c, ) # if any of the hooks returned an error, abort the login. The # set_error in this case also needs to exist in the hook. if hook_error: _event(error=hook_error) return exempt_ua = (request.user_agent and any( ua in request.user_agent for ua in g.config.get('exempt_login_user_agents', ()))) if (errors.LOGGED_IN, None) in c.errors: if user == c.user or exempt_ua: # Allow funky clients to re-login as the current user. c.errors.remove((errors.LOGGED_IN, None)) else: _event(error='LOGGED_IN') abort(reddit_http_error(409, errors.LOGGED_IN)) if responder.has_errors("ratelimit", errors.RATELIMIT): _event(error='RATELIMIT') elif responder.has_errors("passwd", errors.WRONG_PASSWORD): _event(error='WRONG_PASSWORD') else: controller._login(responder, user, rem) _event(error=None)
def process_message(msg): timer = g.stats.get_timer("new_voting.%s" % queue) timer.start() vote_data = json.loads(msg.body) hook = hooks.get_hook('vote.validate_vote_data') if hook.call_until_return(msg=msg, vote_data=vote_data) is False: # Corrupt records in the queue. Ignore them. print "Ignoring invalid vote by %s on %s %s" % ( vote_data.get('user_id', '<unknown>'), vote_data.get('thing_fullname', '<unknown>'), vote_data) return # if it's an old-style vote, convert to the new format if "uid" in vote_data: vote_data = convert_old_vote_data(vote_data, msg.timestamp) user = Account._byID(vote_data.pop("user_id"), data=True) thing = Thing._by_fullname(vote_data.pop("thing_fullname"), data=True) timer.intermediate("preamble") lock_key = "vote-%s-%s" % (user._id36, thing._fullname) with g.make_lock("voting", lock_key, timeout=5): print "Processing vote by %s on %s %s" % (user, thing, vote_data) try: vote = Vote( user, thing, direction=vote_data["direction"], date=datetime.utcfromtimestamp(vote_data["date"]), data=vote_data["data"], event_data=vote_data.get("event_data"), ) except TypeError as e: # a vote on an invalid type got in the queue, just skip it g.log.exception("Invalid type: %r", e.message) return timer.intermediate("create_vote_obj") vote.commit() timer.flush()
def for_url(cls, url, autoplay=False, maxwidth=600, use_youtube_scraper=False): scraper = hooks.get_hook("scraper.factory").call_until_return(url=url) if scraper: return scraper if use_youtube_scraper and _YouTubeScraper.matches(url): return _YouTubeScraper(url, maxwidth=maxwidth) embedly_services = _fetch_embedly_services() for service_re in embedly_services: if service_re.match(url): return _EmbedlyScraper(url, autoplay=autoplay, maxwidth=maxwidth) return _ThumbnailOnlyScraper(url)
def can_create_subreddit(self): hook = hooks.get_hook("account.can_create_subreddit") can_create = hook.call_until_return(account=self) if can_create is not None: return can_create min_age = timedelta(days=g.live_config["create_sr_account_age_days"]) if self._age < min_age: return False if (self.link_karma < g.live_config["create_sr_link_karma"] and self.comment_karma < g.live_config["create_sr_comment_karma"]): return False # CUSTOM - allow subreddit creation once every X days # To avoid checking all subs, we get a list of user's contributed to subs, # and then check the sub author and sub creation date. Works even if they # create a sub then quit as moderator. # NOTE: user_subreddits() safely covers subs returned by special_reddits() with "contributor" and "moderator" # TODO: Use the can_create_subreddit hook to do this stuff elsewhere if g.live_config["create_sr_ratelimit_once_per_days"] > 0: from r2.models import Subreddit user_sr_ids = Subreddit.user_subreddits(self) if user_sr_ids: min_last_created = datetime.today() - timedelta(days=int(g.live_config["create_sr_ratelimit_once_per_days"])) srs = Subreddit._byID(user_sr_ids) for sr in srs.itervalues(): if sr.author_id == self._id and sr._date > min_last_created.replace(tzinfo=g.tz) and not c.user_is_admin and not self.employee: # g.log.warning("!!! dbg: user %s cannot create sub, created %s at %s, once every %s days max" % (self.name, sr.name, sr._date, g.live_config["create_sr_ratelimit_once_per_days"])) return False # CUSTOM - Global Ban enforcement # TODO: Use the can_create_subreddit hook to do this stuff elsewhere from r2.models.globalban import GlobalBan if GlobalBan._user_banned(self._id): # g.log.warning("!!! dbg: user %s cannot create sub, is globally banned" % self.name) return False return True
def watcher(data, stat): self.data = json.loads(data) hooks.get_hook("worker.live_config.update").call()
def process_message(msg): from r2.lib.db.queries import ( add_queries, add_to_commentstree_q, get_comments, ) vote_data = json.loads(msg.body) hook = hooks.get_hook('vote.validate_vote_data') if hook.call_until_return(msg=msg, vote_data=vote_data) is False: # Corrupt records in the queue. Ignore them. print "Ignoring invalid vote by %s on %s %s" % ( vote_data.get('user_id', '<unknown>'), vote_data.get('thing_fullname', '<unknown>'), vote_data) return timer = g.stats.get_timer("comment_vote_processor") timer.start() user = Account._byID(vote_data.pop("user_id")) comment = Comment._by_fullname(vote_data.pop("thing_fullname")) print "Processing vote by %s on %s %s" % (user, comment, vote_data) try: vote = Vote( user, comment, direction=vote_data["direction"], date=datetime.utcfromtimestamp(vote_data["date"]), data=vote_data["data"], event_data=vote_data.get("event_data"), ) except TypeError as e: # a vote on an invalid type got in the queue, just skip it g.log.exception("Invalid type: %r", e.message) return vote.commit() timer.intermediate("create_vote_object") vote_valid = vote.is_automatic_initial_vote or vote.effects.affects_score comment_valid = not (comment._spam or comment._deleted) if vote_valid and comment_valid: author = Account._byID(comment.author_id) add_queries( queries=[get_comments(author, sort, 'all') for sort in SORTS], insert_items=comment, ) timer.intermediate("author_queries") # update the score periodically when a comment has many votes update_threshold = g.live_config['comment_vote_update_threshold'] update_period = g.live_config['comment_vote_update_period'] num_votes = comment.num_votes if num_votes <= update_threshold or num_votes % update_period == 0: add_to_commentstree_q(comment) timer.stop() timer.flush()
def cast_vote(user, thing, direction, **data): """Register a vote and queue it for processing.""" if not isinstance(thing, (Link, Comment)): return # CUSTOM: voting model, validate direction if direction not in (Vote.DIRECTIONS.up, Vote.DIRECTIONS.down, Vote.DIRECTIONS.unup, Vote.DIRECTIONS.undown): g.log.warning("!!! cast_vote() discarding vote with dir: %s" % direction) return # CUSTOM: voting model, use direction as state # NOTE: vote_direction is tracked in addition to direction for easy updating of _likes, _dislikes, and karma in Vote._commit() vote_direction = direction previous_vote = VoteDetailsByThing.get_vote(user, thing) if previous_vote: if direction == Vote.DIRECTIONS.up: # interesting/liked if previous_vote.is_offonvote: direction = Vote.DIRECTIONS.onon elif previous_vote.is_offoffvote: direction = Vote.DIRECTIONS.onoff # backward compatibility elif previous_vote.is_downvote: direction = Vote.DIRECTIONS.onon else: g.log.warning("!!! cast_vote() up, discarding vote with dir: %s prev dir: %s" % (direction, previous_vote.direction)) return elif direction == Vote.DIRECTIONS.down: # funny/disliked if previous_vote.is_onoffvote: direction = Vote.DIRECTIONS.onon elif previous_vote.is_offoffvote: direction = Vote.DIRECTIONS.offon elif previous_vote.is_offoffvote: direction = Vote.DIRECTIONS.offon # backward compatibility elif previous_vote.is_upvote: direction = Vote.DIRECTIONS.onon else: g.log.warning("!!! cast_vote() down, discarding vote with dir: %s prev dir: %s" % (direction, previous_vote.direction)) return elif direction == Vote.DIRECTIONS.unup: # un-interesting / unliked if previous_vote.is_ononvote: direction = Vote.DIRECTIONS.offon elif previous_vote.is_onoffvote: direction = Vote.DIRECTIONS.offoff # backward compatibility elif previous_vote.is_upvote: direction = Vote.DIRECTIONS.offoff else: g.log.warning("!!! cast_vote() unup, discarding vote with dir: %s prev dir: %s" % (direction, previous_vote.direction)) return elif direction == Vote.DIRECTIONS.undown: # un-funny / undisliked if previous_vote.is_ononvote: direction = Vote.DIRECTIONS.onoff elif previous_vote.is_offonvote: direction = Vote.DIRECTIONS.offoff # backward compatibility elif previous_vote.is_downvote: direction = Vote.DIRECTIONS.offoff else: g.log.warning("!!! cast_vote() undown, discarding vote with dir: %s prev dir: %s" % (direction, previous_vote.direction)) return # first vote else: if direction == Vote.DIRECTIONS.up: direction = Vote.DIRECTIONS.onoff elif direction == Vote.DIRECTIONS.down: direction = Vote.DIRECTIONS.offon else: return # g.log.warning("!!! cast_vote() new Vote with dir: %s vote_dir: %s" % (direction, vote_direction)) update_vote_lookups(user, thing, direction) vote_data = { "user_id": user._id, "thing_fullname": thing._fullname, "direction": direction, "date": int(epoch_timestamp(datetime.now(g.tz))), # CUSTOM: voting model "vote_direction": vote_direction, } data['ip'] = getattr(request, "ip", None) if data['ip'] is not None: data['org'] = organization_by_ips(data['ip']) vote_data['data'] = data hooks.get_hook("vote.get_vote_data").call( data=vote_data["data"], user=user, thing=thing, request=request, context=c, ) # The vote event will actually be sent from an async queue processor, so # we need to pull out the context data at this point if not g.running_as_script: vote_data["event_data"] = { "context": Event.get_context_data(request, c), "sensitive": Event.get_sensitive_context_data(request, c), } try: vote_dump = json.dumps(vote_data) except UnicodeDecodeError: g.log.error("Got weird unicode in the vote data: %r", vote_data) return if isinstance(thing, Link): queue = "vote_link_q" elif isinstance(thing, Comment): queue = "vote_comment_q" amqp.add_item(queue, vote_dump)
def process_message(msg): from r2.lib.comment_tree import write_comment_scores from r2.lib.db.queries import ( add_queries, add_to_commentstree_q, get_comments, ) from r2.models.builder import get_active_sort_orders_for_link vote_data = json.loads(msg.body) hook = hooks.get_hook('vote.validate_vote_data') if hook.call_until_return(msg=msg, vote_data=vote_data) is False: # Corrupt records in the queue. Ignore them. print "Ignoring invalid vote by %s on %s %s" % ( vote_data.get('user_id', '<unknown>'), vote_data.get('thing_fullname', '<unknown>'), vote_data) return timer = g.stats.get_timer("comment_vote_processor") timer.start() user = Account._byID(vote_data.pop("user_id")) comment = Comment._by_fullname(vote_data.pop("thing_fullname")) print "Processing vote by %s on %s %s" % (user, comment, vote_data) try: vote = Vote( user, comment, direction=vote_data["direction"], date=datetime.utcfromtimestamp(vote_data["date"]), data=vote_data["data"], event_data=vote_data.get("event_data"), ) except TypeError as e: # a vote on an invalid type got in the queue, just skip it g.log.exception("Invalid type: %r", e.message) return vote.commit() timer.intermediate("create_vote_object") vote_invalid = (not vote.effects.affects_score and not vote.is_automatic_initial_vote) comment_invalid = comment._spam or comment._deleted if vote_invalid or comment_invalid: timer.stop() timer.flush() return author = Account._byID(comment.author_id) add_queries( queries=[get_comments(author, sort, 'all') for sort in SORTS], insert_items=comment, ) timer.intermediate("author_queries") update_threshold = g.live_config['comment_vote_update_threshold'] update_period = g.live_config['comment_vote_update_period'] skip_score_update = (comment.num_votes > update_threshold and comment.num_votes % update_period != 0) # skip updating scores if this was the automatic initial vote. those # updates will be handled by new_comment. Also only update scores # periodically once a comment has many votes. if not vote.is_automatic_initial_vote and not skip_score_update: # check whether this link is using precomputed sorts, if it is # we'll need to push an update to commentstree_q link = Link._byID(comment.link_id) if get_active_sort_orders_for_link(link): # send this comment to commentstree_q where we will update # CommentScoresByLink, CommentTree (noop), and CommentOrderer add_to_commentstree_q(comment) else: # the link isn't using precomputed sorts, so just update the # scores write_comment_scores(link, [comment]) timer.intermediate("update_scores") timer.stop() timer.flush()
def edit_campaign(link, campaign, dates, bid, cpm, target, frequency_cap, frequency_cap_duration, priority, location, platform='desktop', mobile_os=None): changed = {} if bid != campaign.bid: # if the bid amount changed, cancel any pending transactions void_campaign(link, campaign, reason='changed_bid') changed['bid'] = ("$%0.2f" % campaign.bid, "$%0.2f" % bid) hooks.get_hook('promote.edit_bid').call(link=link, campaign=campaign, previous=campaign.bid, current=bid) campaign.bid = bid if dates[0] != campaign.start_date or dates[1] != campaign.end_date: original = '%s to %s' % (campaign.start_date, campaign.end_date) edited = '%s to %s' % (dates[0], dates[1]) changed['dates'] = (original, edited) campaign.start_date = dates[0] campaign.end_date = dates[1] if cpm != campaign.cpm: changed['cpm'] = (campaign.cpm, cpm) campaign.cpm = cpm if target != campaign.target: changed['target'] = (campaign.target, target) campaign.target = target if frequency_cap != campaign.frequency_cap: changed['frequency_cap'] = (campaign.frequency_cap, frequency_cap) campaign.frequency_cap = frequency_cap if frequency_cap_duration != campaign.frequency_cap_duration: changed['frequency_cap_duration'] = (campaign.frequency_cap_duration, frequency_cap_duration) campaign.frequency_cap_duration = frequency_cap_duration if priority != campaign.priority: changed['priority'] = (campaign.priority.name, priority.name) campaign.priority = priority if location != campaign.location: changed['location'] = (campaign.location, location) campaign.location = location if platform != campaign.platform: changed["platform"] = (campaign.platform, platform) campaign.platform = platform if mobile_os != campaign.mobile_os: changed["mobile_os"] = (campaign.mobile_os, mobile_os) campaign.mobile_os = mobile_os change_strs = map(lambda t: '%s: %s -> %s' % (t[0], t[1][0], t[1][1]), changed.iteritems()) change_text = ', '.join(change_strs) campaign._commit() # update the index PromotionWeights.reschedule(link, campaign) if campaign.priority.cpm: # make it a freebie, if applicable author = Account._byID(link.author_id, True) if getattr(author, "complimentary_promos", False): free_campaign(link, campaign, c.user) # record the changes if change_text: PromotionLog.add(link, 'edited %s: %s' % (campaign, change_text)) hooks.get_hook('promote.edit_campaign').call(link=link, campaign=campaign)
def js_config(extra_config=None): logged = c.user_is_loggedin and c.user.name user_id = c.user_is_loggedin and c.user._id gold = bool(logged and c.user.gold) controller_name = request.environ['pylons.routes_dict']['controller'] action_name = request.environ['pylons.routes_dict']['action'] mac = hmac.new(g.secrets["action_name"], controller_name + '.' + action_name, hashlib.sha1) verification = mac.hexdigest() cur_subreddit = "" if isinstance(c.site, Subreddit) and not c.default_sr: cur_subreddit = c.site.name config = { # is the user logged in? "logged": logged, # logged in user's id "user_id": user_id, # the subreddit's name (for posts) "post_site": cur_subreddit, # the user's voting hash "modhash": c.modhash or False, # the current rendering style "renderstyle": c.render_style, # they're welcome to try to override this in the DOM because we just # disable the features server-side if applicable 'store_visits': gold and c.user.pref_store_visits, # current domain "cur_domain": get_domain(cname=c.frameless_cname, subreddit=False, no_www=True), # where do ajax requests go? "ajax_domain": get_domain(cname=c.authorized_cname, subreddit=False), "stats_domain": g.stats_domain or '', "stats_sample_rate": g.stats_sample_rate or 0, "extension": c.extension, "https_endpoint": is_subdomain(request.host, g.domain) and g.https_endpoint, # does the client only want to communicate over HTTPS? "https_forced": c.user.https_forced, # debugging? "debug": g.debug, "send_logs": g.live_config["frontend_logging"], "server_time": math.floor(time.time()), "status_msg": { "fetching": _("fetching title..."), "submitting": _("submitting..."), "loading": _("loading...") }, "is_fake": isinstance(c.site, FakeSubreddit), "tracker_url": tracking.get_pageview_pixel_url() or '', "adtracker_url": g.adtracker_url, "clicktracker_url": g.clicktracker_url, "uitracker_url": g.uitracker_url, "eventtracker_url": g.eventtracker_url, "anon_eventtracker_url": g.anon_eventtracker_url, "static_root": static(''), "over_18": bool(c.over18), "new_window": bool(c.user.pref_newwindow), "mweb_blacklist_expressions": g.live_config['mweb_blacklist_expressions'], "vote_hash": c.vote_hash, "gold": gold, "has_subscribed": logged and c.user.has_subscribed, "is_sponsor": logged and c.user_is_sponsor, "pageInfo": { "verification": verification, "actionName": controller_name + '.' + action_name, }, "facebook_app_id": g.live_config["facebook_app_id"], } if g.uncompressedJS: config["uncompressedJS"] = True if extra_config: config.update(extra_config) hooks.get_hook("js_config").call(config=config) return config
def new_payment_method(user, ip, address, link): user._incr('num_payment_methods') hooks.get_hook('promote.new_payment_method').call(user=user, ip=ip, address=address, link=link)
def pre(self): record_timings = g.admin_cookie in request.cookies or g.debug admin_bar_eligible = response.content_type == 'text/html' if admin_bar_eligible and record_timings: g.stats.start_logging_timings() # set up stuff needed in base templates at error time here. c.js_preload = JSPreload() MinimalController.pre(self) set_cnameframe() # populate c.cookies unless we're on the unsafe media_domain if request.host != g.media_domain or g.media_domain == g.domain: cookie_counts = collections.Counter() try: for k, v in request.cookies.iteritems(): # minimalcontroller can still set cookies if k not in c.cookies: # we can unquote even if it's not quoted c.cookies[k] = Cookie(value=unquote(v), dirty=False) cookie_counts[Cookie.classify(k)] += 1 except CookieError: #pylons or one of the associated retarded libraries #can't handle broken cookies request.environ['HTTP_COOKIE'] = '' for cookietype, count in cookie_counts.iteritems(): g.stats.simple_event("cookie.%s" % cookietype, count) delete_obsolete_cookies() # the user could have been logged in via one of the feeds maybe_admin = False is_otpcookie_valid = False # no logins for RSS feed unless valid_feed has already been called if not c.user: if c.extension != "rss": authenticate_user() admin_cookie = c.cookies.get(g.admin_cookie) if c.user_is_loggedin and admin_cookie: maybe_admin, first_login = valid_admin_cookie( admin_cookie.value) if maybe_admin: self.enable_admin_mode(c.user, first_login=first_login) else: self.disable_admin_mode(c.user) otp_cookie = read_user_cookie(g.otp_cookie) if c.user_is_loggedin and otp_cookie: is_otpcookie_valid = valid_otp_cookie(otp_cookie) if not c.user: c.user = UnloggedUser(get_browser_langs()) # patch for fixing mangled language preferences if (not isinstance(c.user.pref_lang, basestring) or not all( isinstance(x, basestring) for x in c.user.pref_content_langs)): c.user.pref_lang = g.lang c.user.pref_content_langs = [g.lang] c.user._commit() if c.user_is_loggedin: if not c.user._loaded: c.user._load() c.modhash = c.user.modhash() if hasattr(c.user, 'msgtime') and c.user.msgtime: c.have_messages = c.user.msgtime c.show_mod_mail = Subreddit.reverse_moderator_ids(c.user) c.have_mod_messages = getattr(c.user, "modmsgtime", False) c.user_is_admin = maybe_admin and c.user.name in g.admins c.user_special_distinguish = c.user.special_distinguish() c.user_is_sponsor = c.user_is_admin or c.user.name in g.sponsors c.otp_cached = is_otpcookie_valid if not isinstance(c.site, FakeSubreddit) and not g.disallow_db_writes: c.user.update_sr_activity(c.site) c.over18 = over18() set_obey_over18() # looking up the multireddit requires c.user. set_multireddit() #set_browser_langs() set_host_lang() set_iface_lang() set_content_lang() set_recent_clicks() # used for HTML-lite templates set_colors() # set some environmental variables in case we hit an abort if not isinstance(c.site, FakeSubreddit): request.environ['REDDIT_NAME'] = c.site.name # random reddit trickery -- have to do this after the content lang is set if c.site == Random: c.site = Subreddit.random_reddit(user=c.user) redirect_to("/" + c.site.path.strip('/') + request.path_qs) elif c.site == RandomSubscription: if c.user.gold: c.site = Subreddit.random_subscription(c.user) redirect_to('/' + c.site.path.strip('/') + request.path_qs) else: redirect_to('/gold/about') elif c.site == RandomNSFW: c.site = Subreddit.random_reddit(over18=True, user=c.user) redirect_to("/" + c.site.path.strip('/') + request.path_qs) if not request.path.startswith("/api/login/"): # is the subreddit banned? if c.site.spammy() and not c.user_is_admin and not c.error_page: ban_info = getattr(c.site, "ban_info", {}) if "message" in ban_info: message = ban_info['message'] else: sitelink = url_escape(add_sr("/")) subject = ("/r/%s has been incorrectly banned" % c.site.name) link = ("/r/redditrequest/submit?url=%s&title=%s" % (sitelink, subject)) message = strings.banned_subreddit_message % dict( link=link) errpage = pages.RedditError(strings.banned_subreddit_title, message, image="subreddit-banned.png") request.environ['usable_error_content'] = errpage.render() self.abort404() # check if the user has access to this subreddit if not c.site.can_view(c.user) and not c.error_page: if isinstance(c.site, LabeledMulti): # do not leak the existence of multis via 403. self.abort404() else: public_description = c.site.public_description errpage = pages.RedditError( strings.private_subreddit_title, strings.private_subreddit_message, image="subreddit-private.png", sr_description=public_description, ) request.environ['usable_error_content'] = errpage.render() self.abort403() #check over 18 if (c.site.over_18 and not c.over18 and request.path not in ("/frame", "/over18") and c.render_style == 'html'): return self.intermediate_redirect("/over18") #check whether to allow custom styles c.allow_styles = True c.can_apply_styles = self.allow_stylesheets if g.css_killswitch: c.can_apply_styles = False #if the preference is set and we're not at a cname elif not c.user.pref_show_stylesheets and not c.cname: c.can_apply_styles = False #if the site has a cname, but we're not using it elif c.site.domain and c.site.css_on_cname and not c.cname: c.can_apply_styles = False c.bare_content = request.GET.pop('bare', False) c.show_admin_bar = admin_bar_eligible and (c.user_is_admin or g.debug) if not c.show_admin_bar: g.stats.end_logging_timings() hooks.get_hook("reddit.request.begin").call() c.request_timer.intermediate("base-pre")
def delete_campaign(link, campaign): PromotionWeights.delete(link, campaign) void_campaign(link, campaign, reason='deleted_campaign') campaign.delete() PromotionLog.add(link, 'deleted campaign %s' % campaign._id) hooks.get_hook('promote.delete_campaign').call(link=link, campaign=campaign)
def js_config(extra_config=None): logged = c.user_is_loggedin and c.user.name user_id = c.user_is_loggedin and c.user._id user_in_timeout = c.user_is_loggedin and c.user.in_timeout gold = bool(logged and c.user.gold) controller_name = request.environ['pylons.routes_dict']['controller'] action_name = request.environ['pylons.routes_dict']['action'] route_name = controller_name + '.' + action_name cache_policy = "loggedout_www" if c.user_is_loggedin: cache_policy = "loggedin_www_new" # Canary for detecting cache poisoning poisoning_canary = None poisoning_report_mac = None if logged: if "pc" in c.cookies and len(c.cookies["pc"].value) == 2: poisoning_canary = c.cookies["pc"].value poisoning_report_mac = make_poisoning_report_mac( poisoner_canary=poisoning_canary, poisoner_name=logged, poisoner_id=user_id, cache_policy=cache_policy, source="web", route_name=route_name, ) mac = hmac.new(g.secrets["action_name"], route_name, hashlib.sha1) verification = mac.hexdigest() cur_subreddit = "" cur_sr_fullname = "" cur_listing = "" if isinstance(c.site, Subreddit) and not c.default_sr: cur_subreddit = c.site.name cur_sr_fullname = c.site._fullname cur_listing = cur_subreddit elif isinstance(c.site, DefaultSR): cur_listing = "frontpage" elif isinstance(c.site, FakeSubreddit): cur_listing = c.site.name if g.debug: events_collector_url = g.events_collector_test_url events_collector_key = g.secrets['events_collector_test_js_key'] events_collector_secret = g.secrets['events_collector_test_js_secret'] else: events_collector_url = g.events_collector_url events_collector_key = g.secrets['events_collector_js_key'] events_collector_secret = g.secrets['events_collector_js_secret'] config = { # is the user logged in? "logged": logged, # logged in user's id "user_id": user_id, # is user in timeout? "user_in_timeout": user_in_timeout, # the subreddit's name (for posts) "post_site": cur_subreddit, "cur_site": cur_sr_fullname, "cur_listing": cur_listing, # the user's voting hash "modhash": c.modhash or False, # the current rendering style "renderstyle": c.render_style, # they're welcome to try to override this in the DOM because we just # disable the features server-side if applicable 'store_visits': gold and c.user.pref_store_visits, # current domain "cur_domain": get_domain(subreddit=False, no_www=True), # where do ajax requests go? "ajax_domain": get_domain(subreddit=False), "stats_domain": g.stats_domain or '', "stats_sample_rate": g.stats_sample_rate or 0, "extension": c.extension, "https_endpoint": is_subdomain(request.host, g.domain) and g.https_endpoint, "media_domain": g.media_domain, # does the client only want to communicate over HTTPS? "https_forced": feature.is_enabled("force_https"), # debugging? "debug": g.debug, "poisoning_canary": poisoning_canary, "poisoning_report_mac": poisoning_report_mac, "cache_policy": cache_policy, "send_logs": g.live_config["frontend_logging"], "server_time": math.floor(time.time()), "status_msg": { "fetching": _("fetching title..."), "submitting": _("submitting..."), "loading": _("loading...") }, "is_fake": isinstance(c.site, FakeSubreddit), "tracker_url": "", # overridden below if configured "adtracker_url": g.adtracker_url, "clicktracker_url": g.clicktracker_url, "uitracker_url": g.uitracker_url, "eventtracker_url": g.eventtracker_url, "anon_eventtracker_url": g.anon_eventtracker_url, "events_collector_url": events_collector_url, "events_collector_key": events_collector_key, "events_collector_secret": events_collector_secret, "feature_screenview_events": feature.is_enabled('screenview_events'), "static_root": static(''), "over_18": bool(c.over18), "new_window": logged and bool(c.user.pref_newwindow), "mweb_blacklist_expressions": g.live_config['mweb_blacklist_expressions'], "gold": gold, "has_subscribed": logged and c.user.has_subscribed, "is_sponsor": logged and c.user_is_sponsor, "pageInfo": { "verification": verification, "actionName": route_name, }, "facebook_app_id": g.live_config["facebook_app_id"], "feature_new_report_dialog": feature.is_enabled('new_report_dialog'), "email_verified": logged and c.user.email and c.user.email_verified, } if g.tracker_url: config["tracker_url"] = tracking.get_pageview_pixel_url() if g.uncompressedJS: config["uncompressedJS"] = True if extra_config: config.update(extra_config) hooks.get_hook("js_config").call(config=config) return config
def post(self): c.request_timer.intermediate("action") # if the action raised an HTTPException (i.e. it aborted) then pylons # will have replaced response with the exception itself. c.is_exception_response = getattr(response, "_exception", False) if c.response_wrapper and not c.is_exception_response: content = flatten_response(response.content) wrapped_content = c.response_wrapper(content) response.content = wrapped_content if c.user_is_loggedin and not c.allow_loggedin_cache: response.headers['Cache-Control'] = 'no-cache' response.headers['Pragma'] = 'no-cache' if c.deny_frames: response.headers["X-Frame-Options"] = "DENY" # save the result of this page to the pagecache if possible. we # mustn't cache things that rely on state not tracked by request_key # such as If-Modified-Since headers for 304s or requesting IP for 429s. if (g.page_cache_time and request.method.upper() == 'GET' and c.can_use_pagecache and not c.used_cache and response.status_int not in (304, 429) and not response.status.startswith("5") and not c.is_exception_response): try: g.pagecache.set(self.request_key(), (response._current_obj(), c.cookies), g.page_cache_time) except MemcachedError as e: # this codepath will actually never be hit as long as # the pagecache memcached client is in no_reply mode. g.log.warning( "Ignored exception (%r) on pagecache " "write for %r", e, request.path) pragmas = [ p.strip() for p in request.headers.get("Pragma", "").split(",") ] if g.debug or "x-reddit-pagecache" in pragmas: if c.can_use_pagecache: pagecache_state = "hit" if c.used_cache else "miss" else: pagecache_state = "disallowed" response.headers["X-Reddit-Pagecache"] = pagecache_state # send cookies for k, v in c.cookies.iteritems(): if v.dirty: response.set_cookie(key=k, value=quote(v.value), domain=v.domain, expires=v.expires, secure=getattr(v, 'secure', False), httponly=getattr(v, 'httponly', False)) if self.should_update_last_visit(): c.user.update_last_visit(c.start_time) hooks.get_hook("reddit.request.end").call() # this thread is probably going to be reused, but it could be # a while before it is. So we might as well dump the cache in # the mean time so that we don't have dead objects hanging # around taking up memory g.reset_caches() c.request_timer.intermediate("post") # push data to statsd c.request_timer.stop() g.stats.flush()
def _commit(self, keys=None): lock = None try: if not self._created: begin() self._create() just_created = True self.record_cache_write(event="create") else: just_created = False lock = g.make_lock("thing_commit", 'commit_' + self._fullname) lock.acquire() if not just_created and not self._sync_latest(): #sync'd and we have nothing to do now, but we still cache anyway self._cache_myself() return if not just_created: self.record_cache_write(event="modify") # begin is a no-op if already done, but in the not-just-created # case we need to do this here because the else block is not # executed when the try block is exited prematurely in any way # (including the return in the above branch) begin() to_set = self._dirties.copy() if keys: keys = tup(keys) for key in to_set.keys(): if key not in keys: del to_set[key] data_props = {} thing_props = {} for k, (old_value, new_value) in to_set.iteritems(): if k.startswith('_'): thing_props[k[1:]] = new_value else: data_props[k] = new_value if data_props: self._set_data(self._type_id, self._id, just_created, **data_props) if thing_props: self._set_props(self._type_id, self._id, **thing_props) if keys: for k in keys: if self._dirties.has_key(k): del self._dirties[k] else: self._dirties.clear() except: rollback() raise else: commit() self._cache_myself() finally: if lock: lock.release() hooks.get_hook("thing.commit").call(thing=self, changes=to_set)
def cache_poisoning_event(self, poison_info, event_base=None, request=None, context=None): """Create a 'cache_poisoning_server' event for event-collector poison_info: Details from the client about the poisoning event event_base: The base fields for an Event. If not given, caller MUST supply a pylons.request and pylons.c object to build a base from request, context: Should be pylons.request & pylons.c respectively; used to build the base Event if event_base is not given """ if event_base is None: event_base = Event.base_from_request(request, context) event_base["event_name"] = "cache_poisoning_server" event_base["event_topic"] = "cache_poisoning" submit_ts = epoch_timestamp(datetime.datetime.now(pytz.UTC)) event_base["event_ts"] = _epoch_to_millis(submit_ts) poisoner_name = poison_info.pop("poisoner_name") event_base.update(**poison_info) event_base["poison_blame_guess"] = "proxy" resp_headers = poison_info["resp_headers"] if resp_headers: # Check if the caching headers we got back match the current policy cache_policy = poison_info["cache_policy"] headers_valid = cache_headers_valid(cache_policy, resp_headers) event_base["cache_headers_valid"] = headers_valid # try to determine what kind of poisoning we're dealing with if poison_info["source"] == "web": # Do we think they logged in the usual way, or do we think they # got poisoned with someone else's session cookie? valid_login_hook = hooks.get_hook("poisoning.guess_valid_login") if valid_login_hook.call_until_return(poisoner_name=poisoner_name): # Maybe a misconfigured local Squid proxy + multiple # clients? event_base["poison_blame_guess"] = "local_proxy" event_base["poison_credentialed_guess"] = False elif (context.user_is_loggedin and context.user.name == poisoner_name): # Guess we got poisoned with a cookie-bearing response. event_base["poison_credentialed_guess"] = True else: event_base["poison_credentialed_guess"] = False elif poison_info["source"] == "mweb": # All mweb responses contain an OAuth token, so we have to assume # whoever got this response can perform actions as the poisoner event_base["poison_credentialed_guess"] = True else: raise Exception("Unsupported source in cache_poisoning_event") # Check if the CF-Cache-Status header is present (this header is not # present if caching is disallowed.) If it is, the CDN caching rules # are all jacked up. if resp_headers and "cf-cache-status" in resp_headers: event_base["poison_blame_guess"] = "cdn" size_so_far = len(json.dumps(event_base)) oversize = size_so_far - MAX_EVENT_SIZE if oversize > 0: # It's almost definitely the headers that are too large event_base["resp_headers"] = {} # No JSON support in the DBs we target event_base["resp_headers"] = json.dumps(event_base["resp_headers"]) self.save_event(event_base)
def update_promote_status(link, status): queries.set_promote_status(link, status) hooks.get_hook('promote.edit_promotion').call(link=link)
def js_config(extra_config=None): logged = c.user_is_loggedin and c.user.name gold = bool(logged and c.user.gold) config = { # is the user logged in? "logged": logged, # the subreddit's name (for posts) "post_site": c.site.name if not c.default_sr else "", # the user's voting hash "modhash": c.modhash or False, # the current rendering style "renderstyle": c.render_style, # they're welcome to try to override this in the DOM because we just # disable the features server-side if applicable 'store_visits': gold and c.user.pref_store_visits, # current domain "cur_domain": get_domain(cname=c.frameless_cname, subreddit=False, no_www=True), # where do ajax requests go? "ajax_domain": get_domain(cname=c.authorized_cname, subreddit=False), "extension": c.extension, "https_endpoint": is_subdomain(request.host, g.domain) and g.https_endpoint, # debugging? "debug": g.debug, "send_logs": g.live_config["frontend_logging"], "server_time": math.floor(time.time()), "status_msg": { "fetching": _("fetching title..."), "submitting": _("submitting..."), "loading": _("loading...") }, "is_fake": isinstance(c.site, FakeSubreddit), "fetch_trackers_url": g.fetch_trackers_url, "adtracker_url": g.adtracker_url, "clicktracker_url": g.clicktracker_url, "uitracker_url": g.uitracker_url, "static_root": static(''), "over_18": bool(c.over18), "new_window": bool(c.user.pref_newwindow), "vote_hash": c.vote_hash, "gold": gold, "has_subscribed": logged and c.user.has_subscribed, } if extra_config: config.update(extra_config) hooks.get_hook("js_config").call(config=config) return config
def failed_payment_method(user, link): user._incr('num_failed_payments') hooks.get_hook('promote.failed_payment').call(user=user, link=link)
def post(self): c.request_timer.intermediate("action") # if the action raised an HTTPException (i.e. it aborted) then pylons # will have replaced response with the exception itself. c.is_exception_response = getattr(response, "_exception", False) if c.response_wrapper and not c.is_exception_response: content = flatten_response(response.content) wrapped_content = c.response_wrapper(content) response.content = wrapped_content if c.user_is_loggedin and not c.allow_loggedin_cache: response.headers['Cache-Control'] = 'no-cache' response.headers['Pragma'] = 'no-cache' if c.deny_frames: response.headers["X-Frame-Options"] = "DENY" #set content cache if (g.page_cache_time and request.method.upper() == 'GET' and (not c.user_is_loggedin or c.allow_loggedin_cache) and not c.used_cache and response.status_int != 429 and not response.status.startswith("5") and not c.is_exception_response): try: g.pagecache.set(self.request_key(), (response._current_obj(), c.cookies), g.page_cache_time) except MemcachedError as e: # this codepath will actually never be hit as long as # the pagecache memcached client is in no_reply mode. g.log.warning("Ignored exception (%r) on pagecache " "write for %r", e, request.path) # send cookies for k, v in c.cookies.iteritems(): if v.dirty: response.set_cookie(key=k, value=quote(v.value), domain=v.domain, expires=v.expires, secure=getattr(v, 'secure', False), httponly=getattr(v, 'httponly', False)) if self.should_update_last_visit(): c.user.update_last_visit(c.start_time) hooks.get_hook("reddit.request.end").call() # this thread is probably going to be reused, but it could be # a while before it is. So we might as well dump the cache in # the mean time so that we don't have dead objects hanging # around taking up memory g.reset_caches() c.request_timer.intermediate("post") # push data to statsd c.request_timer.stop() g.stats.flush()