def update_clicks(domainname): """ Updates the memcached total clickcount value """ domain = Domain.query(Domain.name == str(domainname)).get() if domain: memcache.set(get_cache_key(domain), domain.clickcount)
def auth(self): """Loads the source and token and checks that they're valid. Expects token in the `token` query param, source in `key` or `username`. Raises: :class:`HTTPException` with HTTP 400 if the token or source are missing or invalid Returns: BrowserSource or None """ # Load source source = util.load_source(self, param='key') if not source: self.abort( 404, f'No account found for {self.gr_source().NAME} user {key or username}' ) # Load and check token token = util.get_required_param(self, 'token') for domain in Domain.query(Domain.tokens == token): if domain.key.id() in source.domains: return source self.abort( 403, f'Token {token} is not authorized for any of: {source.domains}')
def list_redirects_json(self): redirects = [] domains = Domain.query(Domain.redirect_enabled == True) for domain in domains.iter(): host = urlparse(domain.redirect_url) rule = dict(hosts=[host.netloc]) rule["rules"] = [ {"from": "^" + re.escape(domain.redirect_url), "to": "http://" + domain.name + "/"}] if re.match('^www\.', host.netloc): # Add rule without www rule["hosts"].append(host.netloc[4:]) nowww_redirect_url = re.sub('//www\.', '//', domain.redirect_url) rule["rules"].append({"from": "^" + re.escape(nowww_redirect_url), "to": "http://" + domain.name + "/"}) else: # Add rule with www rule["hosts"].append("www." + host.netloc) www_redirect_url = re.sub('(https*://)', '\g<1>www.', domain.redirect_url) rule["rules"].append({"from": "^" + re.escape(www_redirect_url), "to": "http://" + domain.name + "/"}) rule["exceptions"] = [] redirects.append(rule) self.response.headers['Content-Type'] = 'application/json' self.response.write(json.encode(redirects))
def update_total_clicks(): """ Updates the memcached total clickcount value """ clicks = 0 for domain in Domain.query(): clicks = clicks + domain.clickcount memcache.set('clicks_total', clicks)
def post(self): token = util.get_required_param(self, 'token') domains = [d.key.id() for d in Domain.query(Domain.tokens == token)] if not domains: self.abort(404, f'No registered domains for token {token}') self.output(domains)
def get_domain_or_404(name, allow_none=False): if not name: webapp2.abort(404) domain = Domain.query(Domain.name == name).get() if not domain and not allow_none: webapp2.abort(404) return domain
def list_redirects_csv(self): self.response.headers['Content-Type'] = 'text/csv' writer = csv.writer(self.response, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) domains = Domain.query(Domain.redirect_enabled == True) for domain in domains.iter(): writer.writerow([domain.name, domain.redirect_url])
def dispatch_request(self): token = request.values['token'] domains = [d.key.id() for d in Domain.query(Domain.tokens == token)] if not domains: indieauth_start = util.host_url(f'/indieauth/start?token={token}') self.error( f'Not connected to Bridgy. <a href="{indieauth_start}" target="_blank"">Connect now!</a>', 404) return jsonify(domains)
def get_domain_or_404(name, allow_none=False): """Gets a domain entity for the given name. Aborts with 404 if entity doesn't exist and if that is not allowed. """ if not name: webapp2.abort(404) domain = Domain.query(Domain.name==name).get() if not domain and not allow_none: webapp2.abort(404) return domain
def get(self): if self.request.headers.get('X-AppEngine-Cron') != "true": self.error(403) return total = 0 for domain in Domain.query(): mem_count = memcache.get(get_cache_key(domain)) if mem_count: total = total + mem_count else: total = total + domain.clickcount ct = models.ClickcountDate() ct.clickcount = total ct.put()
def test_callback_new_domain(self): self.expect_indieauth_check() self.expect_site_fetch() self.mox.ReplayAll() resp = self.callback() self.assertEqual('http://localhost/#!Authorized you for snarfed.org.', urllib.parse.unquote_plus(resp.headers['Location'])) self.assert_entities_equal([ Domain(id='snarfed.org', tokens=['towkin'], auth=self.auth_entity.key), ], Domain.query().fetch(), ignore=('created', 'updated'))
def get(self): if self.request.headers.get('X-AppEngine-Cron') != "true": self.error(403) return for domain in Domain.query(): # TODO: Maybe maintain a list of domains which do have a memcache counter? k = get_cache_key(domain) new_count = memcache.get(k) if not new_count: logging.info("No clickcount in memcache for: %s", domain.name) elif new_count < domain.clickcount: logging.error("New clickcount for %s would be %d, is %d", domain.name, new_count, domain.clickcount) memcache.delete(k) elif new_count == domain.clickcount: logging.info("clickcount for %s unchanged: %d", domain.name, new_count) else: old_count = domain.clickcount domain.clickcount = new_count domain.put() logging.info("clickcount for %s: %d -> %d", domain.name, old_count, new_count)
def auth(self): """Loads the source and token and checks that they're valid. Expects token in the `token` query param, source in `key` or `username`. Raises: :class:`HTTPException` with HTTP 400 if the token or source are missing or invalid Returns: BrowserSource or None """ # Load source source = util.load_source(error_fn=self.error) # Load and check token token = request.values['token'] for domain in Domain.query(Domain.tokens == token): if domain.key.id() in source.domains: return source self.error( f'Token {token} is not authorized for any of: {source.domains}', 403)
def post(self): params = self.request.params if not 'domain' in params: self.abort(404) cache_key_domain = 'domain:d:' + params['domain'] domain = memcache.get(cache_key_domain) if domain == 0: self.abort(404) return if not domain: domain = Domain.query(Domain.name == params['domain']).get() if not domain: # store for 60 seconds memcache.set(cache_key_domain, 0, 60) self.abort(404) return else: memcache.set(cache_key_domain, domain, 60) # count clicks from outside the iframe that have a previous (pt) and current (ct) visit timestamp # previous timestamp may be empty if the client has no record of this user having visited the domain before) # current timestamp may not be empty # for simplicity timestamps are expressed as integers (unix timestamps) in the users timezone try: if 'from' in params and 'pt' in params and 'ct' in params: if params['from'] == 'outside': current_visit = int(params['ct']) if params['ct'] != "" else 0 if current_visit < 1397340000000: raise Error("Invalid value for 'ct': '%s'" % params['ct']) previous_visit = int(params['pt']) if params['pt'] != "" else current_visit - COUNT_THRESHOLD if previous_visit < 1397340000000: raise Error("Invalid value for 'pt': '%s'" % params['pt']) if current_visit < previous_visit: raise Error("Value '%s' for 'ct' must be greater or equal than value '%s' for 'pt'" % (params['ct'], params['pt'])) if current_visit - previous_visit >= COUNT_THRESHOLD: if memcache.incr(jobs.get_cache_key(domain)) is None: jobs.init_clicks(domain) if memcache.incr("clicks_total") is None: jobs.init_clicks_total() except Error as e: self.response.headers['Content-Type'] = 'application/api-problem+json' self.response.set_status(403) self.response.write( json.encode( { "problemType": "https://github.com/dothiv/clickcounter-backend/wiki/problem-invalid-request", "title": "%s" % e.message } ) ) return # explicit request to have content-type application/json self.response.headers['Content-Type'] = 'application/json' # allow origin if 'Origin' in self.request.headers: self.response.headers['Access-Control-Allow-Origin'] = self.request.headers['Origin'] config = createDomainConfig(domain, getClientLocales(self.request.headers.get('accept-language'))) self.response.write(json.encode(config))
def delete(self, domain_name): domain = Domain.query(Domain.name == domain_name).get() if domain: domain.key.delete() self.response.headers['Content-Type'] = 'text/plain' self.response.set_status(204)
def delete(self, domain_name): domain = Domain.query(Domain.name==domain_name).get() if domain: domain.key.delete() self.response.headers['Content-Type'] = 'text/plain' self.response.set_status(204)