|
|
|
|
@@ -30,20 +30,25 @@ from click import progressbar
|
|
|
|
|
|
|
|
|
|
from sigal import signals
|
|
|
|
|
from sigal.settings import get_thumb
|
|
|
|
|
from sigal.utils import copy, url_from_path
|
|
|
|
|
from sigal.utils import copy
|
|
|
|
|
|
|
|
|
|
from .endec import encrypt, kdf_gen_key
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
ASSETS_PATH = os.path.normpath(os.path.join(
|
|
|
|
|
os.path.abspath(os.path.dirname(__file__)), 'static'))
|
|
|
|
|
ASSETS_PATH = os.path.normpath(
|
|
|
|
|
os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Abort(Exception):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gen_rand_string(length=16):
|
|
|
|
|
return "".join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=length))
|
|
|
|
|
return "".join(random.SystemRandom().choices(string.ascii_letters +
|
|
|
|
|
string.digits,
|
|
|
|
|
k=length))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_options(settings, cache):
|
|
|
|
|
if "encrypt_options" not in settings:
|
|
|
|
|
@@ -58,7 +63,7 @@ def get_options(settings, cache):
|
|
|
|
|
|
|
|
|
|
table = str.maketrans({'"': r'\"', '\\': r'\\'})
|
|
|
|
|
if "password" not in settings["encrypt_options"] \
|
|
|
|
|
or len(settings["encrypt_options"]["password"]) == 0:
|
|
|
|
|
or len(settings["encrypt_options"]["password"]) == 0:
|
|
|
|
|
logger.error("Encrypt: no password provided")
|
|
|
|
|
raise ValueError("no password provided")
|
|
|
|
|
else:
|
|
|
|
|
@@ -66,8 +71,10 @@ def get_options(settings, cache):
|
|
|
|
|
options["escaped_password"] = options["password"].translate(table)
|
|
|
|
|
|
|
|
|
|
if "ask_password" not in options:
|
|
|
|
|
options["ask_password"] = settings["encrypt_options"].get("ask_password", False)
|
|
|
|
|
options["filtered_password"] = "" if options["ask_password"] else options["escaped_password"]
|
|
|
|
|
options["ask_password"] = settings["encrypt_options"].get(
|
|
|
|
|
"ask_password", False)
|
|
|
|
|
options["filtered_password"] = "" if options["ask_password"] else options[
|
|
|
|
|
"escaped_password"]
|
|
|
|
|
|
|
|
|
|
if "gcm_tag" not in options:
|
|
|
|
|
options["gcm_tag"] = gen_rand_string()
|
|
|
|
|
@@ -83,7 +90,8 @@ def get_options(settings, cache):
|
|
|
|
|
if "kdf_iters" not in options:
|
|
|
|
|
options["kdf_iters"] = 10000
|
|
|
|
|
|
|
|
|
|
# in case any of the credentials are newly generated, write them back to cache
|
|
|
|
|
# in case any of the credentials are newly generated, write them back
|
|
|
|
|
# to cache
|
|
|
|
|
cache["credentials"] = {
|
|
|
|
|
"gcm_tag": options["gcm_tag"],
|
|
|
|
|
"kdf_salt": options["kdf_salt"],
|
|
|
|
|
@@ -93,9 +101,11 @@ def get_options(settings, cache):
|
|
|
|
|
|
|
|
|
|
return options
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cache_key(media):
|
|
|
|
|
return os.path.join(media.path, media.filename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_property(cache, media):
|
|
|
|
|
key = cache_key(media)
|
|
|
|
|
if key not in cache:
|
|
|
|
|
@@ -104,16 +114,20 @@ def save_property(cache, media):
|
|
|
|
|
cache[key]["thumb_size"] = media.thumb_size
|
|
|
|
|
cache[key]["encrypted"] = set()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_encrypt_list(settings, media):
|
|
|
|
|
to_encrypt = []
|
|
|
|
|
to_encrypt.append(media.filename) #resized image or in case of "use_orig", the original
|
|
|
|
|
# resized image or in case of "use_orig", the original
|
|
|
|
|
to_encrypt.append(media.filename)
|
|
|
|
|
if settings["make_thumbs"]:
|
|
|
|
|
to_encrypt.append(get_thumb(settings, media.filename)) #thumbnail
|
|
|
|
|
to_encrypt.append(get_thumb(settings, media.filename)) # thumbnail
|
|
|
|
|
if media.big is not None and not settings["use_orig"]:
|
|
|
|
|
to_encrypt.append(media.big) #original image
|
|
|
|
|
to_encrypt = list(map(lambda path: os.path.join(media.path, path), to_encrypt))
|
|
|
|
|
to_encrypt.append(media.big) # original image
|
|
|
|
|
to_encrypt = list(
|
|
|
|
|
map(lambda path: os.path.join(media.path, path), to_encrypt))
|
|
|
|
|
return to_encrypt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_property(album):
|
|
|
|
|
gallery = album.gallery
|
|
|
|
|
cache = load_cache(gallery.settings)
|
|
|
|
|
@@ -125,21 +139,25 @@ def load_property(album):
|
|
|
|
|
media.size = cache[key]["size"]
|
|
|
|
|
media.thumb_size = cache[key]["thumb_size"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_cache(settings):
|
|
|
|
|
cachePath = os.path.join(settings["destination"], ".encryptCache")
|
|
|
|
|
try:
|
|
|
|
|
with open(cachePath, "rb") as cacheFile:
|
|
|
|
|
encryptCache = pickle.load(cacheFile)
|
|
|
|
|
logger.debug("Loaded encryption cache with %d entries", len(encryptCache))
|
|
|
|
|
logger.debug("Loaded encryption cache with %d entries",
|
|
|
|
|
len(encryptCache))
|
|
|
|
|
return encryptCache
|
|
|
|
|
except FileNotFoundError:
|
|
|
|
|
encryptCache = {}
|
|
|
|
|
return encryptCache
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("Could not load encryption cache: %s", e)
|
|
|
|
|
logger.error("Giving up encryption. You may have to delete and rebuild the entire gallery.")
|
|
|
|
|
logger.error("Giving up encryption. You may have to delete and "
|
|
|
|
|
"rebuild the entire gallery.")
|
|
|
|
|
raise Abort
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_cache(settings, cache):
|
|
|
|
|
cachePath = os.path.join(settings["destination"], ".encryptCache")
|
|
|
|
|
try:
|
|
|
|
|
@@ -150,6 +168,7 @@ def save_cache(settings, cache):
|
|
|
|
|
logger.warning("Could not store encryption cache: %s", e)
|
|
|
|
|
logger.warning("Next build of the gallery is likely to fail!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def encrypt_gallery(gallery):
|
|
|
|
|
albums = gallery.albums
|
|
|
|
|
settings = gallery.settings
|
|
|
|
|
@@ -163,19 +182,26 @@ def encrypt_gallery(gallery):
|
|
|
|
|
encrypt_files(settings, config, cache, albums, gallery.progressbar_target)
|
|
|
|
|
save_cache(settings, cache)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def encrypt_files(settings, config, cache, albums, progressbar_target):
|
|
|
|
|
if settings["keep_orig"] and settings["orig_link"]:
|
|
|
|
|
logger.warning("Original images are symlinked! Encryption is aborted. Please set \"orig_link\" to False and restart gallery build.")
|
|
|
|
|
logger.warning(
|
|
|
|
|
"Original images are symlinked! Encryption is aborted. "
|
|
|
|
|
"Please set 'orig_link' to False and restart gallery build.")
|
|
|
|
|
raise Abort
|
|
|
|
|
|
|
|
|
|
key = kdf_gen_key(config["password"], config["kdf_salt"], config["kdf_iters"])
|
|
|
|
|
key = kdf_gen_key(config["password"], config["kdf_salt"],
|
|
|
|
|
config["kdf_iters"])
|
|
|
|
|
gcm_tag = config["gcm_tag"].encode("utf-8")
|
|
|
|
|
|
|
|
|
|
medias = list(chain.from_iterable(albums.values()))
|
|
|
|
|
with progressbar(medias, label="%16s" % "Encrypting files", file=progressbar_target, show_eta=True) as medias:
|
|
|
|
|
with progressbar(medias,
|
|
|
|
|
label="%16s" % "Encrypting files",
|
|
|
|
|
file=progressbar_target,
|
|
|
|
|
show_eta=True) as medias:
|
|
|
|
|
for media in medias:
|
|
|
|
|
if media.type != "image":
|
|
|
|
|
logger.info("Skipping non-image file %s", media.filename)
|
|
|
|
|
logger.info("Skipping non-image file %s", media.src_filename)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
save_property(cache, media)
|
|
|
|
|
@@ -196,9 +222,11 @@ def encrypt_files(settings, config, cache, albums, progressbar_target):
|
|
|
|
|
save_cache(settings, cache)
|
|
|
|
|
raise Abort
|
|
|
|
|
|
|
|
|
|
key_check_path = os.path.join(settings["destination"], 'static', 'keycheck.txt')
|
|
|
|
|
key_check_path = os.path.join(settings["destination"], 'static',
|
|
|
|
|
'keycheck.txt')
|
|
|
|
|
encrypt_file("keycheck.txt", key_check_path, key, gcm_tag)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def encrypt_file(filename, full_path, key, gcm_tag):
|
|
|
|
|
with BytesIO() as outBuffer:
|
|
|
|
|
try:
|
|
|
|
|
@@ -217,16 +245,28 @@ def encrypt_file(filename, full_path, key, gcm_tag):
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def copy_assets(settings):
|
|
|
|
|
theme_path = os.path.join(settings["destination"], 'static')
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "decrypt.js"), theme_path, symlink=False, rellink=False)
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "keycheck.txt"), theme_path, symlink=False, rellink=False)
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "sw.js"), settings["destination"], symlink=False, rellink=False)
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "decrypt.js"),
|
|
|
|
|
theme_path,
|
|
|
|
|
symlink=False,
|
|
|
|
|
rellink=False)
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "keycheck.txt"),
|
|
|
|
|
theme_path,
|
|
|
|
|
symlink=False,
|
|
|
|
|
rellink=False)
|
|
|
|
|
copy(os.path.join(ASSETS_PATH, "sw.js"),
|
|
|
|
|
settings["destination"],
|
|
|
|
|
symlink=False,
|
|
|
|
|
rellink=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def inject_scripts(context):
|
|
|
|
|
cache = load_cache(context['settings'])
|
|
|
|
|
context["encrypt_options"] = get_options(context['settings'], cache)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def register(settings):
|
|
|
|
|
signals.gallery_build.connect(encrypt_gallery)
|
|
|
|
|
signals.album_initialized.connect(load_property)
|
|
|
|
|
|