Bots that prefetch should not destroy the secret (#100)
* Create preview view, remove sneaky-user-agents logic * unit tests * rename openSecret to viewSecret * code clean-up and style * rename view secret to reveal secret * update authors list * bump version to 1.5.0
This commit is contained in:
parent
d4c96cf58a
commit
1ac262e34e
9 changed files with 59 additions and 39 deletions
|
@ -2,4 +2,5 @@
|
||||||
files = setup.py
|
files = setup.py
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
current_version = 0.1.0
|
current_version = 1.5.0
|
||||||
|
|
||||||
|
|
|
@ -19,3 +19,4 @@ Thanks a lot for the contributions of:
|
||||||
* Donny Winston
|
* Donny Winston
|
||||||
* James Barclay
|
* James Barclay
|
||||||
* Thomas Decaux
|
* Thomas Decaux
|
||||||
|
* Lauri Lubi
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 1.4.0
|
current_version = 1.5.0
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
files = setup.py snappass/__init__.py
|
files = setup.py snappass/__init__.py
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -2,7 +2,7 @@ from setuptools import setup
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='snappass',
|
name='snappass',
|
||||||
version='1.4.1',
|
version='1.5.0',
|
||||||
description="It's like SnapChat... for Passwords.",
|
description="It's like SnapChat... for Passwords.",
|
||||||
long_description=(open('README.rst').read() + '\n\n' +
|
long_description=(open('README.rst').read() + '\n\n' +
|
||||||
open('AUTHORS.rst').read()),
|
open('AUTHORS.rst').read()),
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
__version__ = '1.4.1'
|
__version__ = '1.5.0'
|
||||||
|
|
|
@ -11,10 +11,6 @@ from redis.exceptions import ConnectionError
|
||||||
from werkzeug.urls import url_quote_plus
|
from werkzeug.urls import url_quote_plus
|
||||||
from werkzeug.urls import url_unquote_plus
|
from werkzeug.urls import url_unquote_plus
|
||||||
|
|
||||||
|
|
||||||
SNEAKY_USER_AGENTS = ('Slackbot', 'facebookexternalhit', 'Twitterbot',
|
|
||||||
'Facebot', 'WhatsApp', 'SkypeUriPreview', 'Iframely')
|
|
||||||
SNEAKY_USER_AGENTS_RE = re.compile('|'.join(SNEAKY_USER_AGENTS))
|
|
||||||
NO_SSL = os.environ.get('NO_SSL', False)
|
NO_SSL = os.environ.get('NO_SSL', False)
|
||||||
TOKEN_SEPARATOR = '~'
|
TOKEN_SEPARATOR = '~'
|
||||||
|
|
||||||
|
@ -127,6 +123,11 @@ def get_password(token):
|
||||||
return password.decode('utf-8')
|
return password.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
|
@check_redis_alive
|
||||||
|
def password_exists(token):
|
||||||
|
storage_key, decryption_key = parse_token(token)
|
||||||
|
return redis_client.exists(storage_key)
|
||||||
|
|
||||||
def empty(value):
|
def empty(value):
|
||||||
if not value:
|
if not value:
|
||||||
return True
|
return True
|
||||||
|
@ -150,14 +151,6 @@ def clean_input():
|
||||||
return TIME_CONVERSION[time_period], request.form['password']
|
return TIME_CONVERSION[time_period], request.form['password']
|
||||||
|
|
||||||
|
|
||||||
def request_is_valid(request):
|
|
||||||
"""
|
|
||||||
Ensure the request validates the following:
|
|
||||||
- not made by some specific User-Agents (to avoid chat's preview feature issue)
|
|
||||||
"""
|
|
||||||
return not SNEAKY_USER_AGENTS_RE.search(request.headers.get('User-Agent', ''))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET'])
|
@app.route('/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
return render_template('set_password.html')
|
return render_template('set_password.html')
|
||||||
|
@ -177,9 +170,16 @@ def handle_password():
|
||||||
|
|
||||||
|
|
||||||
@app.route('/<password_key>', methods=['GET'])
|
@app.route('/<password_key>', methods=['GET'])
|
||||||
def show_password(password_key):
|
def preview_password(password_key):
|
||||||
if not request_is_valid(request):
|
password_key = url_unquote_plus(password_key)
|
||||||
|
if not password_exists(password_key):
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
return render_template('preview.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/<password_key>', methods=['POST'])
|
||||||
|
def show_password(password_key):
|
||||||
password_key = url_unquote_plus(password_key)
|
password_key = url_unquote_plus(password_key)
|
||||||
password = get_password(password_key)
|
password = get_password(password_key)
|
||||||
if not password:
|
if not password:
|
||||||
|
|
10
snappass/static/snappass/scripts/preview.js
Normal file
10
snappass/static/snappass/scripts/preview.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
$('#revealSecret').click(function () {
|
||||||
|
var form = $('<form/>')
|
||||||
|
.attr('id', 'revealSecretForm')
|
||||||
|
.attr('method', 'post');
|
||||||
|
form.appendTo($('body'));
|
||||||
|
form.submit();
|
||||||
|
});
|
||||||
|
})();
|
23
snappass/templates/preview.html
Normal file
23
snappass/templates/preview.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<section>
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Secret</h1>
|
||||||
|
</div>
|
||||||
|
<p class="lead">You can only reveal the secret once!</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6 margin-bottom-10">
|
||||||
|
<button id="revealSecret" type="button" class="btn-lg btn-primary">Reveal secret</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script src="{{ config.STATIC_URL }}/clipboardjs/clipboard.min.js"></script>
|
||||||
|
<script src="{{ config.STATIC_URL }}/snappass/scripts/clipboard_button.js"></script>
|
||||||
|
<script src="{{ config.STATIC_URL }}/snappass/scripts/preview.js"></script>
|
||||||
|
{% endblock %}
|
27
tests.py
27
tests.py
|
@ -106,32 +106,17 @@ class SnapPassRoutesTestCase(TestCase):
|
||||||
snappass.app.config['TESTING'] = True
|
snappass.app.config['TESTING'] = True
|
||||||
self.app = snappass.app.test_client()
|
self.app = snappass.app.test_client()
|
||||||
|
|
||||||
def test_show_password(self):
|
def test_preview_password(self):
|
||||||
password = "I like novelty kitten statues!"
|
password = "I like novelty kitten statues!"
|
||||||
key = snappass.set_password(password, 30)
|
key = snappass.set_password(password, 30)
|
||||||
rv = self.app.get('/{0}'.format(key))
|
rv = self.app.get('/{0}'.format(key))
|
||||||
self.assertIn(password, rv.get_data(as_text=True))
|
self.assertNotIn(password, rv.get_data(as_text=True))
|
||||||
|
|
||||||
def test_bots_denial(self):
|
def test_show_password(self):
|
||||||
"""
|
password = "I like novelty kitten statues!"
|
||||||
Main known bots User-Agent should be denied access
|
|
||||||
"""
|
|
||||||
password = "Bots can't access this"
|
|
||||||
key = snappass.set_password(password, 30)
|
key = snappass.set_password(password, 30)
|
||||||
a_few_sneaky_bots = [
|
rv = self.app.post('/{0}'.format(key))
|
||||||
"Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)",
|
self.assertIn(password, rv.get_data(as_text=True))
|
||||||
"facebookexternalhit/1.1",
|
|
||||||
"Facebot/1.0",
|
|
||||||
"Twitterbot/1.0",
|
|
||||||
"_WhatsApp/2.12.81 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00",
|
|
||||||
"WhatsApp/2.16.6/i",
|
|
||||||
"SkypeUriPreview Preview/0.5",
|
|
||||||
"Iframely/0.8.5 (+http://iframely.com/;)",
|
|
||||||
]
|
|
||||||
|
|
||||||
for ua in a_few_sneaky_bots:
|
|
||||||
rv = self.app.get('/{0}'.format(key), headers={'User-Agent': ua})
|
|
||||||
self.assertEqual(404, rv.status_code)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in a new issue