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
|
||||
|
||||
|
||||
application = Flask(__name__)
|
||||
application.secret_key = os.environ.get('SECRET_KEY', 'Secret Key')
|
||||
application.config.update(dict(STATIC_URL=os.environ.get('STATIC_URL', 'static')))
|
||||
NO_SSL = os.environ.get('NO_SSL', False)
|
||||
app = Flask(__name__)
|
||||
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')
|
||||
r = redis.StrictRedis(host=redis_host, port=6379, db=0)
|
||||
redis_client = redis.StrictRedis(host=redis_host, port=6379, db=0)
|
||||
|
||||
time_conversion = {
|
||||
'week': 604800,
|
||||
|
@ -20,17 +22,20 @@ time_conversion = {
|
|||
'hour': 3600
|
||||
}
|
||||
|
||||
|
||||
def set_password(password, ttl):
|
||||
key = id()
|
||||
r.set(key, password)
|
||||
r.expire(key, ttl)
|
||||
key = id_()
|
||||
redis_client.set(key, password)
|
||||
redis_client.expire(key, ttl)
|
||||
return key
|
||||
|
||||
|
||||
def get_password(key):
|
||||
password = r.get(key)
|
||||
r.delete(key)
|
||||
password = redis_client.get(key)
|
||||
redis_client.delete(key)
|
||||
return password
|
||||
|
||||
|
||||
def clean_input():
|
||||
"""
|
||||
Make sure we're not getting bad data from the front end,
|
||||
|
@ -47,19 +52,27 @@ def clean_input():
|
|||
abort(400)
|
||||
|
||||
return time_conversion[time_period], request.form['password']
|
||||
|
||||
@application.route('/', methods=['GET'])
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return render_template('set_password.html')
|
||||
|
||||
@application.route('/', methods=['POST'])
|
||||
|
||||
@app.route('/', methods=['POST'])
|
||||
def handle_password():
|
||||
ttl, password = clean_input()
|
||||
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)
|
||||
|
||||
@application.route('/<password_key>', methods=['GET'])
|
||||
|
||||
@app.route('/<password_key>', methods=['GET'])
|
||||
def show_password(password_key):
|
||||
password = get_password(password_key)
|
||||
if not password:
|
||||
|
@ -67,5 +80,10 @@ def show_password(password_key):
|
|||
|
||||
return render_template('password.html', password=password)
|
||||
|
||||
|
||||
def main():
|
||||
app.run(host='0.0.0.0', debug=True)
|
||||
|
||||
|
||||
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