Compare commits

..

2 Commits

Author SHA1 Message Date
0b22731076
Remove a few leftover lines 2020-09-03 21:28:14 -05:00
9f2c7c2b88
Clean up and reorganize keygen.py
This makes it easier to add new features
- Clearer variable names
- Split up large functions into smaller ones for each action
- Help make it easier to add new features
2020-09-03 20:20:22 -05:00
11 changed files with 187 additions and 389 deletions

7
.github/README.md vendored
View File

@ -1,7 +0,0 @@
# imgupload
## Moving from GitHub to Gitea
**TL;DR: Please go to my Gitea instance instead of GitHub for anything related to imgupload. [https://git.bbaovanc.com/bbaovanc/imgupload](https://git.bbaovanc.com/bbaovanc/imgupload)**
This repository might not exist on GitHub in the future! Releases will not be released here in the future. Instead, they will be released on the repository on my Gitea instance, which you can find [here](https://git.bbaovanc.com/bbaovanc/imgupload). Issues and pull requests should also be created on Gitea. For now, commits will still be pushed to this repository, but that may change in the future.

28
.gitignore vendored
View File

@ -1,8 +1,3 @@
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python
### Python ###
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
@ -55,7 +50,6 @@ coverage.xml
*.py,cover *.py,cover
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/
pytestdebug.log
# Translations # Translations
*.mo *.mo
@ -76,7 +70,6 @@ instance/
# Sphinx documentation # Sphinx documentation
docs/_build/ docs/_build/
doc/_build/
# PyBuilder # PyBuilder
target/ target/
@ -116,7 +109,6 @@ venv/
ENV/ ENV/
env.bak/ env.bak/
venv.bak/ venv.bak/
pythonenv*
# Spyder project settings # Spyder project settings
.spyderproject .spyderproject
@ -136,28 +128,10 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# pytype static type analyzer
.pytype/
# profiling data
.prof
### VisualStudioCode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
*.code-workspace
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python
# imgupload custom # imgupload custom
uploadkeys uploadkeys
savelog.log savelog.log
uwsgi.log uwsgi.log
settings.py settings.py
functions.py functions.py
secret.key

View File

@ -1,88 +1,26 @@
# imgupload # imgupload
![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/BBaoVanC/imgupload/master?color=purple) ![GitHub repo size](https://img.shields.io/github/repo-size/bbaovanc/imgupload?color=purple) ![GitHub All Releases](https://img.shields.io/github/downloads/bbaovanc/imgupload/total?color=purple) ![GitHub issues](https://img.shields.io/github/issues/bbaovanc/imgupload?color=purple) ![GitHub closed issues](https://img.shields.io/github/issues-closed/bbaovanc/imgupload?color=purple) ![GitHub](https://img.shields.io/github/license/bbaovanc/imgupload?color=purple)
<!---![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/BBaoVanC/imgupload/master?color=purple) ### What is imgupload?
![GitHub repo size](https://img.shields.io/github/repo-size/bbaovanc/imgupload?color=purple)
![GitHub All Releases](https://img.shields.io/github/downloads/bbaovanc/imgupload/total?color=purple)
![GitHub issues](https://img.shields.io/github/issues/bbaovanc/imgupload?color=purple)
![GitHub closed issues](https://img.shields.io/github/issues-closed/bbaovanc/imgupload?color=purple)
![GitHub](https://img.shields.io/github/license/bbaovanc/imgupload?color=purple)-->
## What is imgupload?
imgupload is a Flask + uWSGI application to serve as an all-purpose image/file uploader over POST requests. imgupload is a Flask + uWSGI application to serve as an all-purpose image/file uploader over POST requests.
--- ### Installation
1. Clone the repository: `git clone https://github.com/BBaoVanC/imgupload.git`
## FAQ 2. Enter the imgupload directory: `cd imgupload`
3. Create a virtualenv: `python3 -m venv env`
**Where can I send bug reports and feature requests?** 4. Enter the virtualenv: `source env/bin/activate`
5. Install dependencies: `python3 -m pip install -r requirements.txt`
You can create an issue [here](https://git.bbaovanc.com/bbaovanc/imgupload/issues). 6. Run the Flask app
**How do I use this program?**
See [Installation](#installation)
**I want to make a pull request. Where should I do that?**
First, fork [this repository](https://git.bbaovanc.com/bbaovanc/imgupload). If you don't have an account on my Gitea site yet, you can either create one, or sign in using your GitHub account. Commit your changes to your fork, and then create a pull request.
---
## Installation
## Running the Flask app
### Using uWSGI ### Using uWSGI
[https://uwsgi-docs.readthedocs.io/en/latest/Configuration.html](https://uwsgi-docs.readthedocs.io/en/latest/Configuration.html)
Note: replace `www-data` with whatever user your webserver runs as. Instructions specific to imgupload are coming soon
1. Go to /srv: `cd /srv`
2. Clone the repository: `git clone https://git.bbaovanc.com/bbaovanc/imgupload.git`
3. Change ownership of /srv/imgupload: `sudo chown www-data:www-data /srv/imgupload`
4. Enter www-data user: `sudo su www-data`
5. Change directories to /srv/imgupload: `cd /srv/imgupload`
6. Checkout the version you want (replace [version] with desired version tag: `git checkout [version]`
7. Enter the imgupload directory: `cd imgupload`
8. Create a virtualenv: `python3 -m venv env`
9. Enter the virtualenv: `source env/bin/activate`
10. Install dependencies: `python3 -m pip install -r requirements.txt`
11. Leave the www-data user: `exit`
12. Copy the default uWSGI configuration: `sudo cp /srv/imgupload/uwsgi.ini.default /etc/uwsgi/apps-available/imgupload.ini`
13. Modify `/etc/uwsgi/apps-available/imgupload.ini` to your preferences
14. Enable imgupload: `sudo ln -s /etc/uwsgi/apps-available/imgupload.ini /etc/uwsgi/apps-enabled/`
15. Restart uWSGI: `sudo systemctl restart uwsgi`
16. Set up your webserver to proxy the uwsgi.sock
Example NGINX location block:
```nginx
location /upload {
include uwsgi_params;
uwsgi_pass unix:/srv/imgupload/uwsgi.sock;
client_max_body_size 25M;
}
```
### Using Flask development server ### Using Flask development server
#### Setup
```shell ```shell
git clone https://git.bbaovanc.com/bbaovanc/imgupload.git $ source env/bin/activate # if you haven't already entered the virtualenv
cd imgupload $ export FLASK_APP=imgupload.py
python3 -m venv env $ flask run
source env/bin/activate
pip3 install -r requirements.txt
``` ```
#### Run
```shell
export FLASK_APP=imgupload.py
flask run
```
---
## License
_imgupload_ is licensed under the GPLv3 license. For more information, please refer to [`LICENSE`](https://git.bbaovanc.com/bbaovanc/imgupload/src/branch/master/LICENSE)

View File

@ -1,10 +1,3 @@
#!/usr/bin/env python3
"""
configtest.py
Tests the validity of your configuration in settings.py.
"""
import os import os
import settings as settings import settings as settings
@ -17,6 +10,7 @@ defaults = {
"SAVELOG": "savelog.log", "SAVELOG": "savelog.log",
"SAVELOG_CHMOD": "0o644", "SAVELOG_CHMOD": "0o644",
"SAVELOG_KEYPREFIX": 4, "SAVELOG_KEYPREFIX": 4,
"ENCKEY_PATH": "secret.key"
} }
deftypes = { deftypes = {
@ -26,6 +20,7 @@ deftypes = {
"SAVELOG": str, "SAVELOG": str,
"SAVELOG_CHMOD": int, "SAVELOG_CHMOD": int,
"SAVELOG_KEYPREFIX": int, "SAVELOG_KEYPREFIX": int,
"ENCKEY_PATH": str,
} }
@ -35,7 +30,7 @@ unset_settings = [i for i in defaults.keys() if i not in dir(settings)]
if len(unset_settings) > 0: if len(unset_settings) > 0:
for unset in unset_settings: for unset in unset_settings:
checksettings.remove(unset) checksettings.remove(unset)
print(f"[!] {unset} is unset. The default value is type {deftypes[unset].__name__} with value {defaults[unset]}") print("[!] {0} is unset. The default value is type {1} with value {2}".format(unset, deftypes[unset].__name__, defaults[unset]))
else: else:
print("[" + u"\u2713" + "] Found all required settings!") print("[" + u"\u2713" + "] Found all required settings!")
@ -45,7 +40,7 @@ typesgood = True
typeswrong = [] typeswrong = []
for testtype in checksettings: for testtype in checksettings:
if type(getattr(settings, testtype)) is not deftypes[testtype]: if type(getattr(settings, testtype)) is not deftypes[testtype]:
print(f"[!] {testtype} requires {deftypes[testtype].__name__}, but is {type(getattr(settings, testtype)).__name__}") print("[!] {0} requires {1}, but is {2}".format(testtype, deftypes[testtype].__name__, type(getattr(settings, testtype)).__name__))
typeswrong.append(testtype) typeswrong.append(testtype)
typesgood = False typesgood = False
@ -63,7 +58,7 @@ if "ALLOWED_EXTENSIONS" in checksettings:
if len(invalid_exts) > 0: if len(invalid_exts) > 0:
print("[!] The following extensions listed in ALLOWED_EXTENSIONS are invalid:") print("[!] The following extensions listed in ALLOWED_EXTENSIONS are invalid:")
for e in invalid_exts: for e in invalid_exts:
print(f" {e} is listed in ALLOWED_EXTENSIONS, but doesn't start with a .") print(" {0} is listed in ALLOWED_EXTENSIONS, but doesn't start with a .".format(e))
else: else:
print("[" + u"\u2713" + "] ALLOWED_EXTENSIONS is good!") print("[" + u"\u2713" + "] ALLOWED_EXTENSIONS is good!")
@ -73,7 +68,7 @@ uploadfolder_exists = True
if "UPLOAD_FOLDER" in checksettings: if "UPLOAD_FOLDER" in checksettings:
if not os.path.isdir(settings.UPLOAD_FOLDER): if not os.path.isdir(settings.UPLOAD_FOLDER):
uploadfolder_exists = False uploadfolder_exists = False
print(f"[!] The directory set in UPLOAD_FOLDER ('{settings.UPLOAD_FOLDER}') doesn't exist!") print("[!] The directory set in UPLOAD_FOLDER ('{0}') doesn't exist!".format(settings.UPLOAD_FOLDER))
else: else:
print("[" + u"\u2713" + "] UPLOAD_FOLDER exists!") print("[" + u"\u2713" + "] UPLOAD_FOLDER exists!")
@ -85,6 +80,8 @@ if "ROOTURL" in checksettings:
pass pass
else: else:
rooturl_good = False rooturl_good = False
print(settings.ROOTURL)
print(settings.ROOTURL.startswith("https://"))
print("[!] ROOTURL does not start with `http://` or `https://`! This may cause issues!") print("[!] ROOTURL does not start with `http://` or `https://`! This may cause issues!")
if not settings.ROOTURL.endswith("/"): if not settings.ROOTURL.endswith("/"):
rooturl_good = False rooturl_good = False
@ -92,14 +89,24 @@ if "ROOTURL" in checksettings:
if not rooturl_good: if not rooturl_good:
print(" With your current settings, this is what a generated url would look like:") print(" With your current settings, this is what a generated url would look like:")
print(f" {settings.ROOTURL}example.png") print(" {0}example.png".format(settings.ROOTURL))
else: else:
print("[" + u"\u2713" + "] ROOTURL is good!") print("[" + u"\u2713" + "] ROOTURL is good!")
# Check if ENCKEY_PATH exists
enckey_exists = True
if "ENCKEY_PATH" in checksettings:
if not os.path.isfile(settings.ENCKEY_PATH):
enckey_exists = False
print("[!] The path set in ENCKEY_PATH ('{0}') doesn't exist!".format(settings.ENCKEY_PATH))
else:
print("[" + u"\u2713" + "] ENCKEY_PATH exists!")
# Ask the user if SAVELOG is the intended filename # Ask the user if SAVELOG is the intended filename
if "SAVELOG" in checksettings: if "SAVELOG" in checksettings:
print(f"[*] SAVELOG was interpreted to be {settings.SAVELOG}") print("[*] SAVELOG was interpreted to be {0}".format(settings.SAVELOG))
print("[*] If this is not the intended filename, please fix it.") print("[*] If this is not the intended filename, please fix it.")
@ -111,32 +118,36 @@ if len(unset_settings) > 0:
summarygood = False summarygood = False
print("Unset settings:") print("Unset settings:")
for unset in unset_settings: for unset in unset_settings:
print(f" {unset}") print(" {0}".format(unset))
if len(typeswrong) > 0: if len(typeswrong) > 0:
summarygood = False summarygood = False
print("Incorrect types:") print("Incorrect types:")
for wtype in typeswrong: for wtype in typeswrong:
print(f" {wtype}") print(" {0}".format(wtype))
if len(invalid_exts) > 0: if len(invalid_exts) > 0:
summarygood = False summarygood = False
print("Invalid extensions:") print("Invalid extensions:")
for wext in invalid_exts: for wext in invalid_exts:
print(f" '{wext}'") print(" '{0}'".format(wext))
if not uploadfolder_exists: if not uploadfolder_exists:
summarygood = False summarygood = False
print(f"UPLOAD_FOLDER ({settings.UPLOAD_FOLDER}) does not exist!") print("UPLOAD_FOLDER ({0}) does not exist!".format(settings.UPLOAD_FOLDER))
if not enckey_exists:
summarygood = False
print("ENCKEY_PATH ({0}) does not exist!".format(settings.ENCKEY_PATH))
if not rooturl_good: if not rooturl_good:
summarygood = False summarygood = False
print("ROOTURL may cause issues!") print("ROOTURL may cause issues!")
print("With current settings, this is what a generated URL would look like:") print("With current settings, this is what a generated URL would look like:")
print(f"{settings.ROOTURL}example.png") print("{0}example.png".format(settings.ROOTURL))
if "SAVELOG" in checksettings: if "SAVELOG" in checksettings:
print(f"[*] SAVELOG is {settings.SAVELOG}") print("[*] SAVELOG is {0}".format(settings.SAVELOG))
if summarygood: if summarygood:
print("[" + u"\u2713" + "] This configuration passes all tests!") print("[" + u"\u2713" + "] This configuration passes all tests!")

View File

@ -1,10 +1,3 @@
#!/usr/bin/env python3
"""
functions.py
Functions used by imgupload which can be easily customized.
"""
import string import string
import random import random

View File

@ -1,16 +1,10 @@
#!/usr/bin/env python3 from flask import Flask, request, jsonify, abort, Response
""" from cryptography.fernet import Fernet
imgupload.py
Flask application for processing images uploaded through POST requests.
"""
from flask import Flask, request, jsonify
from flask_api import status from flask_api import status
from pathlib import Path from pathlib import Path
import random
import os import os
import datetime import datetime
from PIL import Image
import settings # app settings (such as allowed extensions) import settings # app settings (such as allowed extensions)
import functions # custom functions import functions # custom functions
@ -19,7 +13,7 @@ app = Flask(__name__) # app is the app
def allowed_extension(testext): def allowed_extension(testext):
if testext.lower() in settings.ALLOWED_EXTENSIONS: if testext in settings.ALLOWED_EXTENSIONS:
return True return True
else: else:
return False return False
@ -28,11 +22,11 @@ def allowed_extension(testext):
def log_savelog(key, ip, savedname): def log_savelog(key, ip, savedname):
if settings.SAVELOG_KEYPREFIX > 0: if settings.SAVELOG_KEYPREFIX > 0:
with open(settings.SAVELOG, "a+") as slogf: with open(settings.SAVELOG, "a+") as slogf:
slogf.write(f"[{datetime.datetime.now()}] {key[:settings.SAVELOG_KEYPREFIX]}: {ip} - {savedname}\n") slogf.write("[{0}] {1}: {2} - {3}\n".format(datetime.datetime.now(), key[:settings.SAVELOG_KEYPREFIX], ip, savedname))
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD) os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
else: else:
with open(settings.SAVELOG, "a+") as slogf: with open(settings.SAVELOG, "a+") as slogf:
slogf.write(f"[{datetime.datetime.now()}] {ip} - {savedname}\n") slogf.write("[{0}] {1} - {2}\n".format(datetime.datetime.now(), ip, savedname))
os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD) os.chmod(settings.SAVELOG, settings.SAVELOG_CHMOD)
@app.route("/upload", methods = ["POST"]) @app.route("/upload", methods = ["POST"])
@ -40,22 +34,24 @@ def upload():
if request.method == "POST": # sanity check: make sure it's a POST request if request.method == "POST": # sanity check: make sure it's a POST request
print("Request method was POST!") print("Request method was POST!")
with open("uploadkeys", "r") as keyfile: # load valid keys with open(settings.ENCKEY_PATH,"rb") as enckey: # load encryption key
validkeys = keyfile.readlines() key = enckey.read()
validkeys = [x.strip("\n") for x in validkeys] f = Fernet(key)
with open("uploadkeys", "rb") as keyfile:
encrypted_data = keyfile.read()
decrypted_data = str(f.decrypt(encrypted_data).decode('utf-8'))
decrypted_data = decrypted_data.splitlines()
validkeys = [x.strip("\n") for x in decrypted_data]
while "" in validkeys: while "" in validkeys:
validkeys.remove("") validkeys.remove("")
print("Removed blank key(s)")
print("Loaded validkeys") print("Loaded validkeys")
if "uploadKey" in request.form: # if an uploadKey was provided if "uploadKey" in request.form: # if an uploadKey was provided
if request.form["uploadKey"] in validkeys: # check if uploadKey is valid if request.form["uploadKey"] in validkeys: # check if uploadKey is valid
print("Key is valid!") print("Key is valid!")
if "verify" in request.form.keys():
if request.form["verify"] == "true":
print("Request is asking if key is valid (it is)")
return jsonify({'status': 'key_valid'})
if "imageUpload" in request.files: # check if image to upload was provided if "imageUpload" in request.files: # check if image to upload was provided
f = request.files["imageUpload"] # f is the image to upload f = request.files["imageUpload"] # f is the image to upload
else: else:
@ -68,36 +64,14 @@ def upload():
fext = Path(f.filename).suffix # get the uploaded extension fext = Path(f.filename).suffix # get the uploaded extension
if allowed_extension(fext): # if the extension is allowed if allowed_extension(fext): # if the extension is allowed
if not "imageName" in request.form.keys(): print("Generating file with extension {0}".format(fext))
print(f"Generating file with extension {fext}")
fname = functions.generate_name() + fext # generate file name fname = functions.generate_name() + fext # generate file name
print(f"Generated name: {fname}") print("Generated name: {0}".format(fname))
else:
fname = request.form["imageName"]
if len(fname) > 0:
print(f"Request imageName: {fname}")
if not fname.lower().endswith(fext.lower()): # if requested name doesn't have the correct extension
fname += fext # add the extension
print(f"Added extension; new filename: {fname}")
else:
print("Requested filename is blank!")
fname = functions.generate_name() + fext # generate a valid filename
print(f"Generated name: {fname}")
if f: # if the uploaded image exists if f: # if the uploaded image exists
print("Uploaded image exists") print("Uploaded image exists")
if Path(os.path.join(settings.UPLOAD_FOLDER, fname)).is_file(): f.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image
print("Requested filename already exists!") print("Saved to {0}".format(fname))
return jsonify({'status': 'error', 'error': 'FILENAME_TAKEN'}), status.HTTP_409_CONFLICT
f.save(f"/tmp/{fname}") # save the image temporarily (before removing EXIF)
image = Image.open(f"/tmp/{fname}")
data = list(image.getdata())
stripped = Image.new(image.mode, image.size)
stripped.putdata(data)
stripped.save(os.path.join(settings.UPLOAD_FOLDER, fname)) # save the image without EXIF
print(f"Saved to {fname}")
url = settings.ROOTURL + fname # construct the url to the image url = settings.ROOTURL + fname # construct the url to the image
if settings.SAVELOG != "/dev/null": if settings.SAVELOG != "/dev/null":
print("Saving to savelog") print("Saving to savelog")
@ -110,16 +84,21 @@ def upload():
else: # if the extension was invalid else: # if the extension was invalid
print("Uploaded extension is invalid!") print("Uploaded extension is invalid!")
return jsonify({'status': 'error', 'error': 'INVALID_EXTENSION'}), status.HTTP_415_UNSUPPORTED_MEDIA_TYPE abort(415)
else: # if the key was not valid else: # if the key was not valid
print("Key is invalid!") print("Key is invalid!")
print(f"Request key: {request.form['uploadKey']}") print("Request key: {0}".format(request.form["uploadKey"]))
return jsonify({'status': 'error', 'error': 'UNAUTHORIZED'}), status.HTTP_401_UNAUTHORIZED abort(401)
else: # if uploadKey was not found in request body else: # if uploadKey was not found in request body
print("No uploadKey found in request!") print("No uploadKey found in request!")
return jsonify({'status': 'error', 'error': 'UNAUTHORIZED'}), status.HTTP_401_UNAUTHORIZED abort(401)
else: # if the request method wasn't post
print("Request method was not POST!")
abort(405)
if __name__ == "__main__": if __name__ == "__main__":
print("Run with `flask` or a WSGI server!") print("Run with `flask` or a WSGI server!")

180
keyctl.py
View File

@ -1,180 +0,0 @@
#!/usr/bin/env python3
"""
keyctl.py
Command-line utility for easy management of the uploadkeys file.
"""
from pathlib import Path
import argparse
import logging
import secrets
import string
def read_keyfile():
with open("uploadkeys", "r") as keyfile: # open uploadkeys
keys = keyfile.readlines() # read all the keys
logging.debug("Read uploadkeys")
keys = [x.strip("\n") for x in keys] # strip newlines from keys
logging.debug("Stripped newlines from keys")
return keys
def genkey(length):
key = ''.join(secrets.choice(string.ascii_letters + string.digits) for x in range(length))
return key
def savekey(key):
if not Path("uploadkeys").is_file(): # if uploadkeys doesn't exist, log an info message
logging.info("uploadkeys file doesn't exist, it will be created.")
with open("uploadkeys", "a+") as keyfile:
keyfile.write(str(key) + "\n") # add the key
logging.debug(f"Saved a key to uploadkeys: {key}")
def rmkey(delkey):
removedkey = False
allkeys = read_keyfile()
if delkey in allkeys: # if the key to remove exists
allkeys.remove(delkey) # remove the first instance of the key
removedkey = True
logging.debug("Removed one instance of the key")
with open("uploadkeys", "w") as keyfile:
for k in allkeys:
keyfile.write(k + "\n") # write the remaining keys
if removedkey:
return True
else:
return False
def find_duplicates():
allkeys = read_keyfile()
seen = set()
ukeys = []
dupkeys = []
for x in allkeys:
if x not in seen:
ukeys.append(x)
seen.add(x)
else:
dupkeys.append(x)
return dupkeys
def get_keys():
validkeys = read_keyfile()
while "" in validkeys:
validkeys.remove("")
logging.debug("Removed blank keys")
return validkeys
def cmd_list(args):
validkeys = get_keys()
print("List of upload keys:")
for i in range(len(validkeys)):
showkey = validkeys[i][:6]
if len(validkeys[i]) > 6:
showkey += "..." # add ellipses since the key was shortened in list
print(f" [{i+1}] {showkey}")
def cmd_generate(args):
k = genkey(args.length)
logging.debug(f"Generated a new key: {k}")
savekey(k)
print(f"Your new key is: {k}")
def cmd_add(args):
print("Please type/paste the key you would like to add.")
akr = input("> ")
ak = akr.strip()
print()
logging.debug("Ran strip() on key")
print(ak)
if input("Is the above key correct? [y/N] ").lower() == "y":
logging.debug("Interpreted as yes")
savekey(ak)
logging.info("Added.")
else:
logging.debug("Interpreted as no")
print("No key has been saved.")
def cmd_remove(args):
if rmkey(args.key):
logging.debug("Successfully removed the requested key")
else:
logging.info("No key was removed.")
def cmd_dedupe(args):
dupes = find_duplicates()
if len(dupes) > 0:
for d in dupes:
r = rmkey(d)
logging.debug(r)
logging.info(f"Removed duplicate key: {d}")
else:
logging.info("[" + u"\u2713" + "] No duplicate keys found!")
def cmd_show(args):
for k in get_keys():
if k[:6] == args.prefix:
print(f"Key: {k}")
break
parser = argparse.ArgumentParser() # create instance of argument parser class
parlog = parser.add_mutually_exclusive_group()
parlog.add_argument("-v", "--verbose", help="show debugging messages", action="store_true")
parlog.add_argument("-q", "--quiet", help="show only warning messages and up", action="store_true")
subparsers = parser.add_subparsers(help="sub-commands")
parser_list = subparsers.add_parser("list", help="list the beginning of each key")
parser_list.set_defaults(func=cmd_list)
parser_gen = subparsers.add_parser("generate", help="generate a key and save it to uploadkeys")
parser_gen.add_argument("length", help="length of key to generate", default=64, type=int, nargs="?")
parser_gen.set_defaults(func=cmd_generate)
parser_add = subparsers.add_parser("add", help="prompts for a key to add to uploadkeys")
parser_add.set_defaults(func=cmd_add)
parser_remove = subparsers.add_parser("remove", help="remove (one instance of) a key from uploadkeys")
parser_remove.add_argument("key", help="key to remove")
parser_remove.set_defaults(func=cmd_remove)
parser_dedupe = subparsers.add_parser("dedupe", help="remove duplicate keys")
parser_dedupe.set_defaults(func=cmd_dedupe)
parser_show = subparsers.add_parser("show", help="show the full key based on the first 6 characters")
parser_show.add_argument("prefix", help="first 6 characters of key (shown by `python3 keyctl.py list`)")
parser_show.set_defaults(func=cmd_show)
args = parser.parse_args() # parse the arguments
if args.verbose:
loglevel = logging.DEBUG
elif args.quiet:
loglevel = logging.WARNING
else:
loglevel = logging.INFO
logging.basicConfig(level=loglevel, format="%(levelname)s: %(message)s")
try:
args.func(args)
except AttributeError:
logging.error("AttributeError")
parser.print_help()

108
keygen.py Normal file
View File

@ -0,0 +1,108 @@
from cryptography.fernet import Fernet
from cryptography.fernet import InvalidToken
from pathlib import Path
import settings
import string
import secrets
import sys
import os
# Load secret
def load_secret():
with open(settings.ENCKEY_PATH, "rb") as sf:
secret = sf.read()
return secret
# Encrypting and storing of key
def append_uploadkey(akey):
with open('uploadkeys', 'a+') as uploadkeysf:
print(str(akey), file=uploadkeysf)
def decrypt_uploadkeys():
with open("uploadkeys", "rb") as uploadkeysf:
uploadkeys_data = uploadkeysf.read()
try:
secret = load_secret()
secretf = Fernet(secret)
decrypted_data = secretf.decrypt(uploadkeys_data) # decrypt data
with open("uploadkeys", "wb") as ukf:
ukf.write(decrypted_data) # write the original file
print("Done decrypting") # debug
return True
except InvalidToken:
print("InvalidToken") # debug
print("The encrypted key data is invalid and cannot be read.")
print("It may be necessary to clear the file entirely, which will invalidate all tokens.")
proceed = ask_yn("Do you wish to proceed to clearing the uploadkeys file? [y/n] ")
if proceed:
os.remove("uploadkeys")
print("Removed uploadkeys file.")
proceed2 = ask_yn("Would you like to continue and generate a new key? [y/n] ")
if not proceed2:
return False
else:
return True
else:
return False
def encrypt_uploadkeys():
with open("uploadkeys", "rb") as uploadkeysf:
uploadkeys_data = uploadkeysf.read()
secret = load_secret()
secretf = Fernet(secret)
encrypted_data = secretf.encrypt(uploadkeys_data)
with open("uploadkeys", "wb") as uploadkeysf:
uploadkeysf.write(encrypted_data)
def ask_yn(msg):
resps = {"y": True, "n": False}
ask = True
while ask:
proceedraw = input(msg)
if proceedraw.lower() in resps.keys():
proceed = resps[proceedraw]
ask = False
else:
print("Invalid response.")
return proceed
# Check if encryption secret already exists
if Path(settings.ENCKEY_PATH).is_file():
print("Encryption secret found.")
else:
print("Encryption secret not found.")
print("Generating secret...")
newsecret = Fernet.generate_key()
with open(settings.ENCKEY_PATH, "wb") as secret_file:
secret_file.write(newsecret)
print("Encryption secret generated and stored in {0}".format(settings.ENCKEY_PATH))
if __name__ == "__main__":
start = ask_yn("Have you run this program as the correct user (for example, nginx uses www-data)? [y/n] ")
if not start:
print("Please run this as the correct user with: sudo su [user] -s /bin/sh -c 'python3 keygen.py'")
else:
uploadkeysp = Path("uploadkeys")
if not uploadkeysp.is_file():
uploadkeysp.touch()
if decrypt_uploadkeys(): # Decrypt the file
N = 64 # Size of key
key = ''.join(secrets.choice(string.ascii_letters + string.digits) for i in range(N))
print("Your new key is: " + str(key)) # Print key
append_uploadkey(key) # Save the new key to file unencrypted
encrypt_uploadkeys() # Encrypt the uploadkeys file
else:
print("Exiting.")

View File

@ -1,3 +1,3 @@
Flask_API==2.0 Flask_API==2.0
cryptography==3.1
Flask==1.1.2 Flask==1.1.2
Pillow==8.0.1

View File

@ -1,13 +1,7 @@
#!/usr/bin/env python3
"""
settings.py
User-defined settings used by imgupload.py.
"""
UPLOAD_FOLDER = "/path/to/images" UPLOAD_FOLDER = "/path/to/images"
ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"] ALLOWED_EXTENSIONS = [".png", ".jpg", ".jpeg", ".svg", ".bmp", ".gif", ".ico", ".webp"]
ROOTURL = "https://example.com/" ROOTURL = "https://example.com/"
SAVELOG = "savelog.log" SAVELOG = "savelog.log"
SAVELOG_CHMOD = 0o644 SAVELOG_CHMOD = 0o644
SAVELOG_KEYPREFIX = 4 SAVELOG_KEYPREFIX = 4
ENCKEY_PATH = "secret.key"

View File

@ -1,12 +0,0 @@
[uwsgi]
socket = /srv/imgupload/uwsgi.sock
chmod-socket = 755
chdir = /srv/imgupload
venv = /srv/imgupload/env
master = true
module = imgupload:app
processes = 10
threads = 1
uid = www-data
gid = www-data
plugins = python3,logfile