Prepare snappass for distribution.

This commit is contained in:
Dave Dash 2013-10-05 14:12:31 -07:00
parent 812539fc06
commit eefe2bdc76
27 changed files with 384 additions and 17 deletions

5
.bumpversion.cfg Normal file
View file

@ -0,0 +1,5 @@
[bumpversion]
files = setup.py
commit = True
tag = True
current_version = 0.1.0

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.tox
.coverage
.project
*.rdb
junit*xml

8
.travis.yml Normal file
View file

@ -0,0 +1,8 @@
language: python
python: 2.7
install:
- pip install tox
script:
- tox
services:
- redis-server

6
AUTHORS.rst Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
include *.rst LICENSE
recursive-include snappass/static *
recursive-include snappass/templates *

1
README
View file

@ -1 +0,0 @@
Redis frontend to securley share passwords

61
README.rst Normal file
View 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
View 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
View 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
View file

@ -0,0 +1 @@
__author__ = 'davedash'

View file

@ -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,
@ -48,18 +53,26 @@ def clean_input():
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()

View file

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

62
tests.py Normal file
View 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
View 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