Prepare snappass for distribution.
This commit is contained in:
parent
812539fc06
commit
eefe2bdc76
27 changed files with 384 additions and 17 deletions
5
.bumpversion.cfg
Normal file
5
.bumpversion.cfg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
[bumpversion]
|
||||||
|
files = setup.py
|
||||||
|
commit = True
|
||||||
|
tag = True
|
||||||
|
current_version = 0.1.0
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.tox
|
||||||
|
.coverage
|
||||||
|
.project
|
||||||
|
*.rdb
|
||||||
|
junit*xml
|
8
.travis.yml
Normal file
8
.travis.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
language: python
|
||||||
|
python: 2.7
|
||||||
|
install:
|
||||||
|
- pip install tox
|
||||||
|
script:
|
||||||
|
- tox
|
||||||
|
services:
|
||||||
|
- redis-server
|
6
AUTHORS.rst
Normal file
6
AUTHORS.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Credits
|
||||||
|
=======
|
||||||
|
|
||||||
|
"snappass" is originally written and by Owen Coutts and Ryan Park.
|
||||||
|
|
||||||
|
It is currently maintained by Dave Dash and Pinterest.
|
126
CONTRIBUTING.rst
Normal file
126
CONTRIBUTING.rst
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
============
|
||||||
|
Contributing
|
||||||
|
============
|
||||||
|
|
||||||
|
Contributions are welcome, and they are greatly appreciated! Every
|
||||||
|
little bit helps, and credit will always be given.
|
||||||
|
|
||||||
|
You can contribute in many ways:
|
||||||
|
|
||||||
|
Types of Contributions
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
Report Bugs
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Report bugs at https://github.com/pinterest/snappass/issues.
|
||||||
|
|
||||||
|
If you are reporting a bug, please include:
|
||||||
|
|
||||||
|
* Your operating system name and version (if relevant).
|
||||||
|
* Any details about your local setup that might be helpful in troubleshooting.
|
||||||
|
* If you can, provide detailed steps to reproduce the bug.
|
||||||
|
* If you don't have steps to reproduce the bug, just note your observations in
|
||||||
|
as much detail as you can. Questions to start a discussion about the issue
|
||||||
|
are welcome.
|
||||||
|
|
||||||
|
Python 3.3 Support
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
We'd love for ``tox -e py33`` to work and would welcome anybody who can help
|
||||||
|
make that a reality.
|
||||||
|
|
||||||
|
Fix Bugs
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Look through the GitHub issues for bugs. Anything tagged with "bug"
|
||||||
|
is open to whoever wants to implement it.
|
||||||
|
|
||||||
|
Implement Features
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Look through the GitHub issues for features. Anything tagged with "enhancement"
|
||||||
|
is open to whoever wants to implement it.
|
||||||
|
|
||||||
|
|
||||||
|
Write Documentation
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Snappass could always use better documentation, whether as part of the
|
||||||
|
official docs, in docstrings, or even on the web in blog posts, articles, and
|
||||||
|
such.
|
||||||
|
|
||||||
|
Submit Feedback
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The best way to send feedback is to file an issue at
|
||||||
|
https://github.com/pinterest/snappass/issues.
|
||||||
|
|
||||||
|
If you are proposing a feature:
|
||||||
|
|
||||||
|
* Explain in detail how it would work.
|
||||||
|
* Note that this project has an intentionally narrow scope.
|
||||||
|
Our target users are small organizations that really need a
|
||||||
|
quick and dirty way to exchange secrets.
|
||||||
|
* Remember that this is a volunteer-driven project, and that contributions
|
||||||
|
are welcome :)
|
||||||
|
|
||||||
|
|
||||||
|
Setting Up the Code for Local Development
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
Here's how to set up `snappass` for local development.
|
||||||
|
|
||||||
|
1. Fork the `snappass` repo on GitHub.
|
||||||
|
2. Clone your fork locally::
|
||||||
|
|
||||||
|
$ git clone git@github.com:your_name_here/snappass.git
|
||||||
|
|
||||||
|
3. Install your local copy into a ``virtualenv``. Assuming you have
|
||||||
|
``virtualenvwrapper`` installed, this is how you set up your fork for local
|
||||||
|
development::
|
||||||
|
|
||||||
|
$ mkvirtualenv snappass
|
||||||
|
$ cd snappass/
|
||||||
|
$ python setup.py develop
|
||||||
|
|
||||||
|
4. Create a branch for local development::
|
||||||
|
|
||||||
|
$ git checkout -b name-of-your-bugfix-or-feature
|
||||||
|
|
||||||
|
Now you can make your changes locally.
|
||||||
|
|
||||||
|
5. When you're done making changes, check that your changes pass the tests and
|
||||||
|
flake8::
|
||||||
|
|
||||||
|
$ flake8 snappass tests
|
||||||
|
$ tox
|
||||||
|
|
||||||
|
6. Commit your changes and push your branch to GitHub::
|
||||||
|
|
||||||
|
$ git add .
|
||||||
|
$ git commit -m "Your detailed description of your changes."
|
||||||
|
$ git push origin name-of-your-bugfix-or-feature
|
||||||
|
|
||||||
|
7. Check that the test coverage hasn't dropped::
|
||||||
|
|
||||||
|
coverage run --source snappass setup.py tests
|
||||||
|
coverage report -m
|
||||||
|
coverage html
|
||||||
|
|
||||||
|
8. Submit a pull request through the GitHub website.
|
||||||
|
|
||||||
|
Pull Request Guidelines
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Before you submit a pull request, check that it meets these guidelines:
|
||||||
|
|
||||||
|
1. The pull request should include tests.
|
||||||
|
2. If the pull request adds functionality, the docs should be updated. Put
|
||||||
|
your new functionality into a function with a docstring, and add the
|
||||||
|
feature to the list in README.rst.
|
||||||
|
3. The pull request should work for Python 2.7 and ideally 3.3. Check
|
||||||
|
`Travis`_ and make sure that
|
||||||
|
the tests pass for all supported Python versions.
|
||||||
|
|
||||||
|
.. _Travis: https://travis-ci.org/pinterest/snappass/pull_requests
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2012-2013 Pinterest
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
3
MANIFEST.in
Normal file
3
MANIFEST.in
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
include *.rst LICENSE
|
||||||
|
recursive-include snappass/static *
|
||||||
|
recursive-include snappass/templates *
|
1
README
1
README
|
@ -1 +0,0 @@
|
||||||
Redis frontend to securley share passwords
|
|
61
README.rst
Normal file
61
README.rst
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
========
|
||||||
|
SnapPass
|
||||||
|
========
|
||||||
|
|
||||||
|
.. image:: https://travis-ci.org/pinterest/snappass.png
|
||||||
|
|
||||||
|
|
||||||
|
It's like SnapChat... for Passwords.
|
||||||
|
|
||||||
|
This is a webapp that lets you share passwords securely.
|
||||||
|
|
||||||
|
Let's say you have a password. You want to give it to your coworker, Jane.
|
||||||
|
You could email it to her, but then it's in her email, which might be backed up,
|
||||||
|
and probably is in some storage device controlled by the NSA.
|
||||||
|
|
||||||
|
You could send it to her over chat, but chances are Jane logs all her messages
|
||||||
|
because she uses Google Talk, and Google Talk logs everything.
|
||||||
|
|
||||||
|
You could write it down, but you can't find a pen, and there's way too many
|
||||||
|
characters because your Security Person, Paul, is paranoid.
|
||||||
|
|
||||||
|
So we build SnapPass. It's not that complicated, it does one thing. If
|
||||||
|
Jane gets a link to the password and never looks at it, the password goes away.
|
||||||
|
If the NSA gets a hold of the link, and they look at the password... well they
|
||||||
|
have the password. Also, Jane can't get the password, but now Jane knows that
|
||||||
|
not only is someone looking in her email, they are clicking on links.
|
||||||
|
|
||||||
|
Anyway, this took us very little time to write, but we figure we'd save you the
|
||||||
|
trouble of writing it yourself, because maybe you are busy and have other things
|
||||||
|
to do. Enjoy.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Redis.
|
||||||
|
* Python 2.6, 2.7 or 3.3.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
$ pip install snappass
|
||||||
|
$ snappass
|
||||||
|
* Running on http://0.0.0.0:5000/
|
||||||
|
* Restarting with reloader
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
|
||||||
|
You can configure the following via environment variables.
|
||||||
|
|
||||||
|
`SECRET_KEY` this should be a unique key that's used to sign key. This should
|
||||||
|
be kept secret. See the `Flask Documentation`_ for more information.
|
||||||
|
|
||||||
|
.. _Flask Documentation: http://flask.pocoo.org/docs/quickstart/#sessions
|
||||||
|
|
||||||
|
`STATIC_URL` this should be the location of your static assets. You might not
|
||||||
|
need to change this.
|
||||||
|
|
||||||
|
`NO_SSL` if you are not using SSL.
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Flask==0.10.1
|
||||||
|
Jinja2==2.7.1
|
||||||
|
MarkupSafe==0.18
|
||||||
|
Werkzeug==0.9.4
|
||||||
|
itsdangerous==0.23
|
||||||
|
redis==2.8.0
|
36
setup.py
Normal file
36
setup.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='snappass',
|
||||||
|
version='0.1.0',
|
||||||
|
description="It's like SnapChat... for Passwords.",
|
||||||
|
long_description=(open('README.rst').read() + '\n\n' +
|
||||||
|
open('AUTHORS.rst').read()),
|
||||||
|
url='http://github.com/Pinterest/snappass/',
|
||||||
|
install_requires=['Flask', 'redis'],
|
||||||
|
license='MIT',
|
||||||
|
author='Dave Dash',
|
||||||
|
author_email='dd+github@davedash.com',
|
||||||
|
packages=['snappass'],
|
||||||
|
entry_points={
|
||||||
|
'console_scripts': [
|
||||||
|
'snappass = snappass.main:main',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
include_package_data=True,
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Natural Language :: English',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.6',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
1
snappass/__init__.py
Normal file
1
snappass/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
__author__ = 'davedash'
|
|
@ -6,13 +6,15 @@ import redis
|
||||||
from flask import abort, Flask, render_template, request
|
from flask import abort, Flask, render_template, request
|
||||||
|
|
||||||
|
|
||||||
application = Flask(__name__)
|
NO_SSL = os.environ.get('NO_SSL', False)
|
||||||
application.secret_key = os.environ.get('SECRET_KEY', 'Secret Key')
|
app = Flask(__name__)
|
||||||
application.config.update(dict(STATIC_URL=os.environ.get('STATIC_URL', 'static')))
|
app.secret_key = os.environ.get('SECRET_KEY', 'Secret Key')
|
||||||
|
app.config.update(
|
||||||
|
dict(STATIC_URL=os.environ.get('STATIC_URL', 'static')))
|
||||||
|
|
||||||
id = lambda: uuid.uuid4().get_hex()
|
id_ = lambda: uuid.uuid4().hex
|
||||||
redis_host = os.environ.get('REDIS_HOST', 'localhost')
|
redis_host = os.environ.get('REDIS_HOST', 'localhost')
|
||||||
r = redis.StrictRedis(host=redis_host, port=6379, db=0)
|
redis_client = redis.StrictRedis(host=redis_host, port=6379, db=0)
|
||||||
|
|
||||||
time_conversion = {
|
time_conversion = {
|
||||||
'week': 604800,
|
'week': 604800,
|
||||||
|
@ -20,17 +22,20 @@ time_conversion = {
|
||||||
'hour': 3600
|
'hour': 3600
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_password(password, ttl):
|
def set_password(password, ttl):
|
||||||
key = id()
|
key = id_()
|
||||||
r.set(key, password)
|
redis_client.set(key, password)
|
||||||
r.expire(key, ttl)
|
redis_client.expire(key, ttl)
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def get_password(key):
|
def get_password(key):
|
||||||
password = r.get(key)
|
password = redis_client.get(key)
|
||||||
r.delete(key)
|
redis_client.delete(key)
|
||||||
return password
|
return password
|
||||||
|
|
||||||
|
|
||||||
def clean_input():
|
def clean_input():
|
||||||
"""
|
"""
|
||||||
Make sure we're not getting bad data from the front end,
|
Make sure we're not getting bad data from the front end,
|
||||||
|
@ -47,19 +52,27 @@ def clean_input():
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
return time_conversion[time_period], request.form['password']
|
return time_conversion[time_period], request.form['password']
|
||||||
|
|
||||||
@application.route('/', methods=['GET'])
|
|
||||||
|
@app.route('/', methods=['GET'])
|
||||||
def index():
|
def index():
|
||||||
return render_template('set_password.html')
|
return render_template('set_password.html')
|
||||||
|
|
||||||
@application.route('/', methods=['POST'])
|
|
||||||
|
@app.route('/', methods=['POST'])
|
||||||
def handle_password():
|
def handle_password():
|
||||||
ttl, password = clean_input()
|
ttl, password = clean_input()
|
||||||
key = set_password(password, ttl)
|
key = set_password(password, ttl)
|
||||||
link = request.url_root.replace("http://", "https://") + key
|
|
||||||
|
if NO_SSL:
|
||||||
|
base_url = request.url_root
|
||||||
|
else:
|
||||||
|
base_url = request.url_root.replace("http://", "https://")
|
||||||
|
link = base_url + key
|
||||||
return render_template('confirm.html', password_link=link)
|
return render_template('confirm.html', password_link=link)
|
||||||
|
|
||||||
@application.route('/<password_key>', methods=['GET'])
|
|
||||||
|
@app.route('/<password_key>', methods=['GET'])
|
||||||
def show_password(password_key):
|
def show_password(password_key):
|
||||||
password = get_password(password_key)
|
password = get_password(password_key)
|
||||||
if not password:
|
if not password:
|
||||||
|
@ -67,5 +80,10 @@ def show_password(password_key):
|
||||||
|
|
||||||
return render_template('password.html', password=password)
|
return render_template('password.html', password=password)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
app.run(host='0.0.0.0', debug=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
application.run(host='0.0.0.0', debug=True)
|
main()
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
62
tests.py
Normal file
62
tests.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import unittest
|
||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from werkzeug.exceptions import ClientDisconnected
|
||||||
|
|
||||||
|
#noinspection PyPep8Naming
|
||||||
|
import snappass.main as snappass
|
||||||
|
|
||||||
|
__author__ = 'davedash'
|
||||||
|
|
||||||
|
|
||||||
|
class SnapPassTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_set_password(self):
|
||||||
|
"""Ensure we return a 32-bit key."""
|
||||||
|
key = snappass.set_password("foo", 30)
|
||||||
|
self.assertEqual(32, len(key))
|
||||||
|
|
||||||
|
def test_get_password(self):
|
||||||
|
password = "melatonin overdose 1337!$"
|
||||||
|
key = snappass.set_password(password, 30)
|
||||||
|
self.assertEqual(password, snappass.get_password(key))
|
||||||
|
# Assert that we can't look this up a second time.
|
||||||
|
self.assertEqual(None, snappass.get_password(key))
|
||||||
|
|
||||||
|
def test_clean_input(self):
|
||||||
|
# Test Bad Data
|
||||||
|
with snappass.app.test_request_context(
|
||||||
|
"/", data={'password': 'foo', 'ttl': 'bar'}, method='POST'):
|
||||||
|
self.assertRaises(ClientDisconnected, snappass.clean_input)
|
||||||
|
|
||||||
|
# No Password
|
||||||
|
with snappass.app.test_request_context(
|
||||||
|
"/", method='POST'):
|
||||||
|
self.assertRaises(ClientDisconnected, snappass.clean_input)
|
||||||
|
|
||||||
|
# No TTL
|
||||||
|
with snappass.app.test_request_context(
|
||||||
|
"/", data={'password': 'foo'}, method='POST'):
|
||||||
|
self.assertRaises(ClientDisconnected, snappass.clean_input)
|
||||||
|
|
||||||
|
with snappass.app.test_request_context(
|
||||||
|
"/", data={'password': 'foo', 'ttl': 'hour'}, method='POST'):
|
||||||
|
self.assertEqual((3600, 'foo'), snappass.clean_input())
|
||||||
|
|
||||||
|
|
||||||
|
class SnapPassRoutesTestCase(TestCase):
|
||||||
|
#noinspection PyPep8Naming
|
||||||
|
def setUp(self):
|
||||||
|
snappass.app.config['TESTING'] = True
|
||||||
|
self.app = snappass.app.test_client()
|
||||||
|
|
||||||
|
def test_show_password(self):
|
||||||
|
password = "I like novelty kitten statues!"
|
||||||
|
key = snappass.set_password(password, 30)
|
||||||
|
rv = self.app.get('/{}'.format(key))
|
||||||
|
self.assertIn(password, rv.data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
|
|
10
tox.ini
Normal file
10
tox.ini
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
[tox]
|
||||||
|
envlist = py27
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps =
|
||||||
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
commands =
|
||||||
|
pip install -r requirements.txt --use-wheel
|
||||||
|
py.test --junitxml=junit-{envname}.xml --cov-report xml tests.py
|
Loading…
Reference in a new issue