Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ls/latest updates #45

Merged
merged 95 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
1e1b189
Remove py3.7 (#234)
yurushao Jun 9, 2023
6f02f6e
Bump cryptography from 39.0.2 to 41.0.1 (#260)
dependabot[bot] Jun 9, 2023
013c0d1
Bump tox from 3.25.0 to 4.6.0 (#262)
dependabot[bot] Jun 9, 2023
95f5c35
Bump fakeredis from 1.7.5 to 2.14.1 (#263)
dependabot[bot] Jun 9, 2023
147bdf3
Bump flask from 2.1.2 to 2.3.2 (#250)
dependabot[bot] Jun 9, 2023
6fec10e
Bump pytest from 7.1.2 to 7.3.1 (#243)
dependabot[bot] Jun 9, 2023
a34aaf8
Bump redis from 4.5.3 to 4.5.5 (#253)
dependabot[bot] Jun 9, 2023
5725b0d
Bump coverage from 6.4.1 to 7.2.7 (#267)
dependabot[bot] Jun 12, 2023
4c118cf
Bump pytest-cov from 3.0.0 to 4.1.0 (#266)
dependabot[bot] Jun 12, 2023
9d68d6b
Bump actions/checkout from 3 to 4 (#282)
dependabot[bot] Sep 25, 2023
31ae18d
[Snyk] Security upgrade cryptography from 41.0.1 to 41.0.4 (#284)
devinlundberg Sep 25, 2023
f3edccd
Bump tox from 4.6.0 to 4.11.3 (#287)
dependabot[bot] Sep 26, 2023
03bf76f
Bump fakeredis from 2.14.1 to 2.20.0
dependabot[bot] Oct 23, 2023
c4d6074
Merge pull request #292 from pinterest/dependabot/pip/fakeredis-2.20.0
xia0pin9 Oct 23, 2023
1a9824d
Bump redis from 4.5.5 to 5.0.1
dependabot[bot] Oct 23, 2023
6a10fd3
Merge pull request #289 from pinterest/dependabot/pip/redis-5.0.1
xia0pin9 Oct 23, 2023
99028bf
Install deps from requirements.txt (#303)
yurushao Dec 1, 2023
baa921f
Prepare 1.6.1 release (#304)
yurushao Dec 2, 2023
8103cb4
Bump version: 1.6.0 → 1.6.1 (#305)
yurushao Dec 2, 2023
ae27473
Use urllib.parse for quoting/unquoting plus instead of deprecated wer…
vin01 Dec 2, 2023
fd27ab7
Bump actions/setup-python from 4 to 5 (#306)
dependabot[bot] Dec 22, 2023
b53ceed
Bump github/codeql-action from 2 to 3 (#309)
dependabot[bot] Dec 22, 2023
b66b1e1
Bump werkzeug from 2.3.3 to 3.0.1 (#295)
dependabot[bot] Dec 22, 2023
7db0be7
Bump flask from 2.3.2 to 3.0.0 (#294)
dependabot[bot] Dec 22, 2023
3871c39
Bump pytest from 7.3.1 to 7.4.4
dependabot[bot] Jan 1, 2024
a8e4312
Bump version: 1.6.1 → 1.6.2 (#311)
yurushao Jan 3, 2024
29ce62b
Merge pull request #314 from pinterest/dependabot/pip/pytest-7.4.4
xia0pin9 Jan 3, 2024
6798a26
Bump freezegun from 1.2.1 to 1.4.0
dependabot[bot] Jan 3, 2024
564a29d
Merge pull request #312 from pinterest/dependabot/pip/freezegun-1.4.0
xia0pin9 Jan 3, 2024
d8c05a9
Bump flake8 from 6.0.0 to 7.0.0
dependabot[bot] Jan 5, 2024
74ded41
Merge pull request #315 from pinterest/dependabot/pip/flake8-7.0.0
xia0pin9 Jan 5, 2024
455db36
Add health check endpoint (#329)
yurushao Feb 3, 2024
6d294c6
add i18n to Snappass
systeembeheerder Feb 14, 2024
62a6290
Bump fakeredis from 2.20.0 to 2.21.1
dependabot[bot] Feb 16, 2024
6d17603
remove import of flask, g
systeembeheerder Feb 16, 2024
d178664
Add empty translations for de and es
systeembeheerder Feb 16, 2024
49de2bc
Bump cryptography from 41.0.4 to 42.0.3
dependabot[bot] Feb 19, 2024
28c396e
Add German Translation
systeembeheerder Feb 19, 2024
fcfc1b0
Merge pull request #331 from pinterest/dependabot/pip/fakeredis-2.21.1
xia0pin9 Feb 20, 2024
e0b8245
Bump pytest from 7.4.4 to 8.0.1
dependabot[bot] Feb 20, 2024
0084d85
Merge pull request #332 from pinterest/dependabot/pip/cryptography-42…
xia0pin9 Feb 20, 2024
f551b73
Merge pull request #334 from pinterest/dependabot/pip/pytest-8.0.1
xia0pin9 Feb 20, 2024
3cba966
Bump coverage from 7.2.7 to 7.4.2
dependabot[bot] Feb 20, 2024
82c345c
Merge pull request #335 from pinterest/dependabot/pip/coverage-7.4.2
xia0pin9 Feb 20, 2024
ba67b42
Bump tox from 4.11.3 to 4.13.0
dependabot[bot] Feb 20, 2024
415d5ee
Merge pull request #333 from pinterest/dependabot/pip/tox-4.13.0
xia0pin9 Feb 21, 2024
9fdddab
fix missing bracket
systeembeheerder Feb 21, 2024
4fffb9c
restore extra spaces
systeembeheerder Feb 22, 2024
114b5af
Add Spanish and fixup NL&DE
systeembeheerder Feb 22, 2024
106ac26
TIL flake8 :)
systeembeheerder Feb 23, 2024
2b108d3
Merge pull request #330 from systeembeheerder/i18n
xia0pin9 Feb 23, 2024
5d37e45
Bump actions/cache from 3 to 4 (#320)
dependabot[bot] Feb 23, 2024
04f9402
Bump jinja2 from 3.1.2 to 3.1.3 (#336)
dependabot[bot] Feb 23, 2024
dc321ef
add /api endpoint for automated flows (#316)
reinoud Feb 26, 2024
838cdf6
Bump pytest from 8.0.1 to 8.1.0
dependabot[bot] Mar 4, 2024
9c233c0
Merge pull request #339 from pinterest/dependabot/pip/pytest-8.1.0
xia0pin9 Mar 4, 2024
7607822
Bump pytest-cov from 4.1.0 to 5.0.0
dependabot[bot] Mar 25, 2024
05cd81c
:construction: Add a 'modern' REST API
XREvo Mar 29, 2024
ed9e715
:construction: Add RFC7807 response type
XREvo Mar 29, 2024
ff35bb6
:construction: Import missing parts
XREvo Mar 29, 2024
ad5a7de
:art: Cleanup
XREvo Mar 29, 2024
82d3a61
:recycle: Use token as name for password_key
XREvo Mar 29, 2024
2023c9d
:technologist: Use HATEHOAS style
XREvo Mar 29, 2024
3cfd5f0
:white_check_mark: Finish test suite implementation
XREvo Mar 29, 2024
26b26f9
:memo: Add documentation about new APIs
XREvo Mar 29, 2024
04235c1
:art: flake8
XREvo Mar 29, 2024
b692904
:children_crossing: Remove URL encoding from token
XREvo Mar 30, 2024
e480397
:children_crossing: Add a link to web view
XREvo Mar 30, 2024
20136d9
Merge pull request #346 from XREvo/master
xia0pin9 Apr 8, 2024
eaf96ef
Merge pull request #345 from pinterest/dependabot/pip/pytest-cov-5.0.0
xia0pin9 Apr 10, 2024
cbeb611
Bump wheel from 0.42.0 to 0.43.0
dependabot[bot] Apr 10, 2024
7475a98
Merge pull request #343 from pinterest/dependabot/pip/wheel-0.43.0
xia0pin9 Apr 23, 2024
e2ca2fa
fix: requirements.txt to reduce vulnerabilities
snyk-bot May 6, 2024
95b7573
Merge pull request #358 from pinterest/snyk-fix-f2c8e3c8682180c20af41…
xia0pin9 May 6, 2024
e247322
Bump tox from 4.13.0 to 4.16.0 (#376)
dependabot[bot] Aug 2, 2024
4c5f63b
Bump coverage from 7.4.2 to 7.6.0 (#379)
dependabot[bot] Aug 2, 2024
91dd677
Bump fakeredis from 2.21.1 to 2.23.4 (#381)
dependabot[bot] Aug 2, 2024
f6ad788
[Snyk] Security upgrade cryptography from 42.0.3 to 42.0.8 (#371)
devinlundberg Aug 2, 2024
d3ffeac
Bump freezegun from 1.4.0 to 1.5.1 (#362)
dependabot[bot] Aug 2, 2024
57ec624
[Snyk] Security upgrade jinja2 from 3.1.3 to 3.1.4 (#359)
devinlundberg Aug 2, 2024
4e5fc2e
Bump itsdangerous from 2.1.2 to 2.2.0 (#347)
dependabot[bot] Aug 2, 2024
9e43578
Environment variables for default port and bind address (#342)
Radical-Egg Aug 2, 2024
6370c04
Bump cryptography from 42.0.8 to 43.0.0 (#382)
dependabot[bot] Aug 5, 2024
7af9712
Bump pytest from 8.1.0 to 8.3.2 (#385)
dependabot[bot] Aug 5, 2024
63f7d8f
Bump flake8 from 7.0.0 to 7.1.1 (#383)
dependabot[bot] Aug 5, 2024
eea66f4
Bump wheel from 0.43.0 to 0.44.0 (#384)
dependabot[bot] Aug 5, 2024
ce7994e
Bump tox from 4.16.0 to 4.17.0 (#386)
dependabot[bot] Aug 6, 2024
f3af080
Bump tox from 4.17.0 to 4.18.0 (#388)
dependabot[bot] Aug 27, 2024
5745dd4
Bump fakeredis from 2.23.4 to 2.24.1 (#390)
dependabot[bot] Aug 27, 2024
c13e80b
fix i18n (#375)
viktorhaid Sep 11, 2024
86e7e2e
[Snyk] Security upgrade cryptography from 43.0.0 to 43.0.1 (#391)
devinlundberg Sep 11, 2024
cf81a01
Bump tox from 4.18.0 to 4.23.0 (#405)
dependabot[bot] Oct 18, 2024
5e9054b
Bump fakeredis from 2.24.1 to 2.25.1 (#397)
dependabot[bot] Oct 18, 2024
b78280a
Bump redis from 5.0.1 to 5.1.1 (#401)
dependabot[bot] Oct 18, 2024
fd04960
Merge branch 'master' of https://github.com/pinterest/snappass into l…
silverl Nov 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ WORKDIR $APP_DIR
COPY ["setup.py", "requirements.txt", "MANIFEST.in", "README.rst", "AUTHORS.rst", "$APP_DIR/"]
COPY ["./snappass", "$APP_DIR/snappass"]

RUN pip install -r requirements.txt

RUN pybabel compile -d snappass/translations

RUN python setup.py install && \
chown -R snappass $APP_DIR && \
chgrp -R snappass $APP_DIR
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include *.rst LICENSE
recursive-include snappass/static *
recursive-include snappass/templates *
recursive-include snappass/translations *
158 changes: 152 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,21 @@ need to change this.

``HOST_OVERRIDE``: (optional) Used to override the base URL if the app is unaware. Useful when running behind reverse proxies like an identity-aware SSO. Example: ``sub.domain.com``

API
---
``SNAPPASS_BIND_ADDRESS``: (optional) Used to override the default bind address of 0.0.0.0 for flask app Example: ``127.0.0.1``

SnapPass also has a simple API that can be used to create passwords links. The advantage of using the API is that
you can create a password and retrieve the link without having to open the web interface. This is useful if you want to
embed it in a script or use it in a CI/CD pipeline.
``SNAPPASS_PORT``: (optional) Used to override the default port of 5000 Example: ``6000``

APIs
----

SnapPass has 2 APIs :
1. A simple API : That can be used to create passwords links, and then share them with users
2. A more REST-y API : Which facilitate programmatic interactions with SnapPass, without having to parse HTML content when retrieving the password

Simple API
^^^^^^^^^^

The advantage of using the simple API is that you can create a password and retrieve the link without having to open the web interface. This is useful if you want to embed it in a script or use it in a CI/CD pipeline.

To create a password, send a POST request to ``/api/set_password`` like so:

Expand All @@ -124,12 +133,149 @@ the default TTL is 2 weeks (1209600 seconds), but you can override it by adding

$ curl -X POST -H "Content-Type: application/json" -d '{"password": "foobar", "ttl": 3600 }' http://localhost:5000/api/set_password/


REST API
^^^^^^^^

The advantage of using the REST API is that you can fully manage the lifecycle of the password stored in SnapPass without having to interact with any web user interface.

This is useful if you want to embed it in a script, use it in a CI/CD pipeline or share it between multiple client applications.

Create a password
"""""""""""""""""

To create a password, send a POST request to ``/api/v2/passwords`` like so:

::

$ curl -X POST -H "Content-Type: application/json" -d '{"password": "foobar"}' http://localhost:5000/api/v2/passwords

This will return a JSON response with a token and the password link:

::

{
"token": "snappassbedf19b161794fd288faec3eba15fa41~hHnILpQ50ZfJc3nurDfHCb_22rBr5gGEya68e_cZOrY=",
"links": [{
"rel": "self",
"href": "http://127.0.0.1:5000/api/v2/passwords/snappassbedf19b161794fd288faec3eba15fa41~hHnILpQ50ZfJc3nurDfHCb_22rBr5gGEya68e_cZOrY%3D",
},{
"rel": "web-view",
"href": "http://127.0.0.1:5000/snappassbedf19b161794fd288faec3eba15fa41~hHnILpQ50ZfJc3nurDfHCb_22rBr5gGEya68e_cZOrY%3D",
}],
"ttl":1209600
}

The default TTL is 2 weeks (1209600 seconds), but you can override it by adding a expiration parameter:

::

$ curl -X POST -H "Content-Type: application/json" -d '{"password": "foobar", "ttl": 3600 }' http://localhost:5000/api/v2/passwords

If the password is null or empty, and the TTL is larger than the max TTL of the application, the API will return an error like this:


Otherwise, the API will return a 404 (Not Found) response like so:

::

{
"invalid-params": [{
"name": "password",
"reason": "The password is required and should not be null or empty."
}, {
"name": "ttl",
"reason": "The specified TTL is longer than the maximum supported."
}],
"title": "The password and/or the TTL are invalid.",
"type": "https://127.0.0.1:5000/set-password-validation-error"
}

Check if a password exists
""""""""""""""""""""""""""

To check if a password exists, send a HEAD request to ``/api/v2/passwords/<token>``, where ``<token>`` is the token of the API response when a password is created (url encoded), or simply use the `self` link:

::

$ curl --head http://localhost:5000/api/v2/passwords/snappassbedf19b161794fd288faec3eba15fa41~hHnILpQ50ZfJc3nurDfHCb_22rBr5gGEya68e_cZOrY%3D

If :
- the passwork_key is valid
- the password :
- exists,
- has not been read
- is not expired

Then the API will return a 200 (OK) response like so:

::

HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Fri, 29 Mar 2024 22:15:54 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close

Otherwise, the API will return a 404 (Not Found) response like so:

::

HTTP/1.1 404 NOT FOUND
Server: Werkzeug/3.0.1 Python/3.12.2
Date: Fri, 29 Mar 2024 22:19:29 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 0
Connection: close


Read a password
"""""""""""""""

To read a password, send a GET request to ``/api/v2/passwords/<password_key>``, where ``<password_key>`` is the token of the API response when a password is created, or simply use the `self` link:

::

$ curl -X GET http://localhost:5000/api/v2/passwords/snappassbedf19b161794fd288faec3eba15fa41~hHnILpQ50ZfJc3nurDfHCb_22rBr5gGEya68e_cZOrY%3D

If :
- the token is valid
- the password :
- exists
- has not been read
- is not expired

Then the API will return a 200 (OK) with a JSON response containing the password :

::

{
"password": "foobar"
}

Otherwise, the API will return a 404 (Not Found) response like so:

::

{
"invalid-params": [{
"name": "token"
}],
"title": "The password doesn't exist.",
"type": "https://127.0.0.1:5000/get-password-error"
}

Notes on APIs
^^^^^^^^^^^^^

Notes:

- When using the API, you can specify any ttl, as long as it is lower than the default.
- When using the APIs, you can specify any ttl, as long as it is lower than the default.
- The password is passed in the body of the request rather than in the URL. This is to prevent the password from being logged in the server logs.
- Depending on the environment you are running it, you might want to expose the ``/api`` endpoint to your internal network only, and put the web interface behind authentication.


Docker
------

Expand Down
16 changes: 8 additions & 8 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
coverage==7.4.2
fakeredis==2.21.1
flake8==7.0.0
freezegun==1.4.0
pytest==8.1.0
pytest-cov==4.1.0
tox==4.13.0
coverage==7.6.0
fakeredis==2.25.1
flake8==7.1.1
freezegun==1.5.1
pytest==8.3.2
pytest-cov==5.0.0
tox==4.23.0
bumpversion==0.6.0
wheel==0.42.0
wheel==0.44.0
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: '3'

services:
snappass:
image: snappass:v1.6.2
image: trackabout/snappass:main
ports:
- "5000:5000"
stop_signal: SIGINT
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
cryptography==42.0.4
cryptography==43.0.1
Flask==3.0.0
itsdangerous==2.1.2
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.1
redis==5.0.1
redis==5.1.1
Werkzeug==3.0.3
flask-babel
115 changes: 112 additions & 3 deletions snappass/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
import redis

from cryptography.fernet import Fernet
from flask import abort, Flask, render_template, request, jsonify
from flask import abort, Flask, render_template, request, jsonify, make_response
from redis.exceptions import ConnectionError
from urllib.parse import quote_plus
from urllib.parse import unquote_plus
from urllib.parse import urljoin
from distutils.util import strtobool
from flask_babel import Babel
# _ is required to get the Jinja templates translated
from flask_babel import Babel, _ # noqa: F401

NO_SSL = bool(strtobool(os.environ.get('NO_SSL', 'False')))
URL_PREFIX = os.environ.get('URL_PREFIX', None)
Expand Down Expand Up @@ -102,6 +104,37 @@ def parse_token(token):
return storage_key, decryption_key


def as_validation_problem(request, problem_type, problem_title, invalid_params):
base_url = set_base_url(request)

problem = {
"type": base_url + problem_type,
"title": problem_title,
"invalid-params": invalid_params
}
return as_problem_response(problem)


def as_not_found_problem(request, problem_type, problem_title, invalid_params):
base_url = set_base_url(request)

problem = {
"type": base_url + problem_type,
"title": problem_title,
"invalid-params": invalid_params
}
return as_problem_response(problem, 404)


def as_problem_response(problem, status_code=None):
if not isinstance(status_code, int) or not status_code:
status_code = 400

response = make_response(jsonify(problem), status_code)
response.headers['Content-Type'] = 'application/problem+json'
return response


@check_redis_alive
def set_password(password, ttl):
"""
Expand Down Expand Up @@ -219,6 +252,81 @@ def api_handle_password():
abort(500)


@app.route('/api/v2/passwords', methods=['POST'])
def api_v2_set_password():
password = request.json.get('password')
ttl = int(request.json.get('ttl', DEFAULT_API_TTL))

invalid_params = []

if not password:
invalid_params.append({
"name": "password",
"reason": "The password is required and should not be null or empty."
})

if not isinstance(ttl, int) or ttl > MAX_TTL:
invalid_params.append({
"name": "ttl",
"reason": "The specified TTL is longer than the maximum supported."
})

if len(invalid_params) > 0:
# Return a ProblemDetails expliciting issue with Password and/or TTL
return as_validation_problem(
request,
"set-password-validation-error",
"The password and/or the TTL are invalid.",
invalid_params
)

token = set_password(password, ttl)
url_token = quote_plus(token)
base_url = set_base_url(request)
api_link = urljoin(base_url, request.path + "/" + url_token)
web_link = urljoin(base_url, url_token)
response_content = {
"token": token,
"links": [{
"rel": "self",
"href": api_link
}, {
"rel": "web-view",
"href": web_link
}],
"ttl": ttl
}
return jsonify(response_content)


@app.route('/api/v2/passwords/<token>', methods=['HEAD'])
def api_v2_check_password(token):
token = unquote_plus(token)
if not password_exists(token):
# Return NotFound, to indicate that password does not exists (anymore or at all)
return ('', 404)
else:
# Return OK, to indicate that password still exists
return ('', 200)


@app.route('/api/v2/passwords/<token>', methods=['GET'])
def api_v2_retrieve_password(token):
token = unquote_plus(token)
password = get_password(token)
if not password:
# Return NotFound, to indicate that password does not exists (anymore or at all)
return as_not_found_problem(
request,
"get-password-error",
"The password doesn't exist.",
[{"name": "token"}]
)
else:
# Return OK and the password in JSON message
return jsonify(password=password)


@app.route('/<password_key>', methods=['GET'])
def preview_password(password_key):
password_key = unquote_plus(password_key)
Expand Down Expand Up @@ -246,7 +354,8 @@ def health_check():

@check_redis_alive
def main():
app.run(host='0.0.0.0')
app.run(host=os.environ.get('SNAPPASS_BIND_ADDRESS', '0.0.0.0'),
port=os.environ.get('SNAPPASS_PORT', 5000))


if __name__ == '__main__':
Expand Down
Loading
Loading