Commit 9e0fd190 authored by Jacotsu's avatar Jacotsu
Browse files

Fixed decodings errors

Added secure referrals management
Added TODO.md
parent 21a8be00
- [X] Url referrals should save only the hostname and the counter
- [X] Change referrals data structure to 'HSET referrers.<short_url> <referrer_url> <count>'
- [ ] Fix javascript
- [ ] Implement refererrers pagination
- [ ] Write tests
...@@ -7,10 +7,12 @@ import crypt ...@@ -7,10 +7,12 @@ import crypt
import flask import flask
from flask_httpauth import HTTPBasicAuth from flask_httpauth import HTTPBasicAuth
from flask_redis import FlaskRedis from flask_redis import FlaskRedis
from urllib.parse import urlparse
app = flask.Flask(__name__) app = flask.Flask(__name__)
auth = HTTPBasicAuth() auth = HTTPBasicAuth()
app.config['REDIS_URL'] = os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379/0') app.config['REDIS_URL'] = os.environ.get('REDIS_URL',
'redis://127.0.0.1:6379/0')
redis_store = FlaskRedis() redis_store = FlaskRedis()
redis_store.init_app(app) redis_store.init_app(app)
pure_string = re.compile(r'^\w+$') pure_string = re.compile(r'^\w+$')
...@@ -29,8 +31,8 @@ def redis_key_for_url(url: str) -> str: ...@@ -29,8 +31,8 @@ def redis_key_for_url(url: str) -> str:
return "url.{}".format(url) return "url.{}".format(url)
def redis_key_for_referrer_count(url: str, referrer: str) -> str: def redis_key_for_url_referrers(url: str) -> str:
return "url.{}.referrers.{}.count".format(url, referrer) return "referrers.{}".format(url)
def redis_key_for_count(url: str) -> str: def redis_key_for_count(url: str) -> str:
...@@ -42,7 +44,7 @@ def redis_key_for_useragent(url: str) -> str: ...@@ -42,7 +44,7 @@ def redis_key_for_useragent(url: str) -> str:
def redirect_useragent(path: str): def redirect_useragent(path: str):
useragent = flask.headers.get('User-Agent') useragent = flask.request.headers.get('User-Agent')
for agent, match in useragent_match.items(): for agent, match in useragent_match.items():
if match.match(useragent): if match.match(useragent):
redis_store.hincrby(redis_key_for_useragent(path), redis_store.hincrby(redis_key_for_useragent(path),
...@@ -50,7 +52,22 @@ def redirect_useragent(path: str): ...@@ -50,7 +52,22 @@ def redirect_useragent(path: str):
def redirect_referrer(path: str): def redirect_referrer(path: str):
pass raw_referrer = flask.request.headers.get('referer')
if raw_referrer:
safe_referrer = flask.escape(raw_referrer)
if safe_referrer != raw_referrer:
app.logger.warning('XSS injection attempt from {}'
.format(flask.request.remote_addr))
return flask.abort(400)
else:
# The hostname property is empty
referrer_hostname = urlparse(safe_referrer).hostname
if referrer_hostname:
redis_store.hincrby(redis_key_for_url_referrers(path),
referrer_hostname)
app.logger.debug('Increased counter for {}'
.format(referrer_hostname))
def handle_redirect(path): def handle_redirect(path):
...@@ -91,9 +108,8 @@ def add_url(): ...@@ -91,9 +108,8 @@ def add_url():
long_url = payload['url'] long_url = payload['url']
if not pure_string.match(short_url): if not pure_string.match(short_url):
return flask.abort(400) return flask.abort(400)
if not redis_store.exists(redis_key_for_url(short_url)): redis_store.set(redis_key_for_url(short_url),
redis_store.set(redis_key_for_count(short_url), 0) long_url)
redis_store.set(redis_key_for_url(short_url), long_url)
return '' return ''
except KeyError: except KeyError:
return flask.abort(400) return flask.abort(400)
...@@ -109,6 +125,7 @@ def del_url(): ...@@ -109,6 +125,7 @@ def del_url():
try: try:
redis_store.delete(redis_key_for_url(payload['name'])) redis_store.delete(redis_key_for_url(payload['name']))
redis_store.delete(redis_key_for_count(payload['name'])) redis_store.delete(redis_key_for_count(payload['name']))
redis_store.delete(redis_key_for_url_referrers(payload['name']))
return '' return ''
except KeyError: except KeyError:
return flask.abort(400) return flask.abort(400)
...@@ -122,15 +139,24 @@ def list_urls(): ...@@ -122,15 +139,24 @@ def list_urls():
return_dict = {} return_dict = {}
for key in redis_store.keys('url.*'): for key in redis_store.keys('url.*'):
app.logger.debug(key) app.logger.debug(key)
real_key = key.replace('url.', '', 1) real_key = key.decode().replace('url.', '', 1)
click_count = redis_store.get(redis_key_for_count(real_key))
return_dict[real_key] = {'url': redis_store.get(key).decode(), return_dict[real_key] = {'url': redis_store.get(key).decode(),
'count': int(redis_store.get(redis_key_for_count(real_key)).decode())} 'count': int(click_count.decode())
return flask.jsonify(return_dict) if click_count else 0}
return flask.jsonify(return_dict)
@app.route('/api/v1/listreferrers/<shortedurl>') @app.route('/api/v1/listreferrers/<shortedurl>')
@auth.login_required @auth.login_required
def list_referrers(shortedurl=None): def list_referrers(shortedurl=None):
if shortedurl:
referers_dict = redis_store\
.hgetall(redis_key_for_url_referrers(shortedurl))
decoded_dict = {key.decode(): value.decode()
for key, value in referers_dict.items()}
app.logger.debug(decoded_dict)
return flask.jsonify(decoded_dict)
return '' return ''
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment