Merge pull request 'Move to JSON controlled server settings' (#3) from inex/selfprivacy-rest-api:json-manipulations into master

Reviewed-on: https://git.selfprivacy.org/ilchub/selfprivacy-rest-api/pulls/3
This commit is contained in:
Illia Chub 2021-11-16 09:42:52 +02:00
commit a86f2fe2bb
10 changed files with 226 additions and 237 deletions

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"python.formatting.provider": "black"
}

View file

@ -1,3 +1,3 @@
[build-system]
requires = ["setuptools", "wheel"]
requires = ["setuptools", "wheel", "portalocker"]
build-backend = "setuptools.build_meta"

View file

@ -3,3 +3,4 @@ flask
flask_restful
flask_socketio
setuptools
portalocker

View file

@ -1,22 +1,28 @@
#!/usr/bin/env python3
from flask_restful import Resource
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable Bitwarden
class EnableBitwarden(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/passmgr/bitwarden.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/passmgr/bitwarden.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "bitwarden" not in data:
data["bitwarden"] = {}
data["bitwarden"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Bitwarden enabled",
}
@ -24,17 +30,21 @@ class EnableBitwarden(Resource):
# Disable Bitwarden
class DisableBitwarden(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/passmgr/bitwarden.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = true;", "enable = false;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/passmgr/bitwarden.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "bitwarden" not in data:
data["bitwarden"] = {}
data["bitwarden"]["enable"] = False
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Bitwarden disabled",
}

View file

@ -1,22 +1,28 @@
#!/usr/bin/env python3
from flask_restful import Resource
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable Gitea
class EnableGitea(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/git/gitea.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/git/gitea.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "gitea" not in data:
data["gitea"] = {}
data["gitea"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Gitea enabled",
}
@ -24,17 +30,21 @@ class EnableGitea(Resource):
# Disable Gitea
class DisableGitea(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/git/gitea.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = true;", "enable = false;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/git/gitea.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "gitea" not in data:
data["gitea"] = {}
data["gitea"]["enable"] = False
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Gitea disabled",
}

View file

@ -1,22 +1,28 @@
#!/usr/bin/env python3
from flask_restful import Resource
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable Nextcloud
class EnableNextcloud(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/nextcloud/nextcloud.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/nextcloud/nextcloud.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "nextcloud" not in data:
data["nextcloud"] = {}
data["nextcloud"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Nextcloud enabled",
}
@ -24,17 +30,21 @@ class EnableNextcloud(Resource):
# Disable Nextcloud
class DisableNextcloud(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/nextcloud/nextcloud.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = true;", "enable = false;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/nextcloud/nextcloud.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "nextcloud" not in data:
data["nextcloud"] = {}
data["nextcloud"]["enable"] = False
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Nextcloud disabled",
}

View file

@ -1,22 +1,28 @@
#!/usr/bin/env python3
from flask_restful import Resource
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable OpenConnect VPN server
class EnableOcserv(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/vpn/ocserv.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/vpn/ocserv.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "ocserv" not in data:
data["ocserv"] = {}
data["ocserv"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "OpenConnect VPN server enabled",
}
@ -24,17 +30,21 @@ class EnableOcserv(Resource):
# Disable OpenConnect VPN server
class DisableOcserv(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/vpn/ocserv.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = true;", "enable = false;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/vpn/ocserv.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "ocserv" not in data:
data["ocserv"] = {}
data["ocserv"]["enable"] = False
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "OpenConnect VPN server disabled",
}

View file

@ -1,22 +1,28 @@
#!/usr/bin/env python3
from flask_restful import Resource
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable Pleroma
class EnablePleroma(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/social/pleroma.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/social/pleroma.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "pleroma" not in data:
data["pleroma"] = {}
data["pleroma"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Pleroma enabled",
}
@ -24,17 +30,21 @@ class EnablePleroma(Resource):
# Disable Pleroma
class DisablePleroma(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/social/pleroma.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = true;", "enable = false;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/social/pleroma.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "pleroma" not in data:
data["pleroma"] = {}
data["pleroma"]["enable"] = False
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "Pleroma disabled",
}

View file

@ -1,23 +1,29 @@
#!/usr/bin/env python3
from flask import Blueprint, request
from flask_restful import Resource, reqparse
import portalocker
import json
from selfprivacy_api.resources.services import api
# Enable SSH
class EnableSSH(Resource):
def post(self):
readOnlyFileDescriptor = open("/etc/nixos/configuration.nix", "rt")
fileContent = readOnlyFileDescriptor.read()
fileContent = fileContent.replace("enable = false;", "enable = true;")
readOnlyFileDescriptor.close()
readWriteFileDescriptor = open("/etc/nixos/configuration.nix", "wt")
writeOperationDescriptor = readWriteFileDescriptor.write(fileContent)
readWriteFileDescriptor.close()
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "ssh" not in data:
data["ssh"] = {}
data["ssh"]["enable"] = True
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": writeOperationDescriptor,
"message": "SSH enabled",
}
@ -33,41 +39,28 @@ class WriteSSHKey(Resource):
publicKey = args["public_key"]
print("[INFO] Opening /etc/nixos/configuration.nix...", sep="")
readOnlyFileDescriptor = open("/etc/nixos/configuration.nix", "r")
print("done")
fileContent = list()
index = int(0)
print("[INFO] Reading file content...", sep="")
while True:
line = readOnlyFileDescriptor.readline()
if not line:
break
else:
fileContent.append(line)
print("[DEBUG] Read line!")
for line in fileContent:
index += 1
if "openssh.authorizedKeys.keys = [" in line:
print("[DEBUG] Found SSH key configuration snippet match!")
print("[INFO] Writing new SSH key", sep="")
fileContent.append('\n "' + publicKey + '"')
print("done")
break
print("[INFO] Writing data from memory to file...", sep="")
readWriteFileDescriptor = open("/etc/nixos/configuration.nix", "w")
print("done")
operationResult = readWriteFileDescriptor.writelines(fileContent)
with portalocker.Lock("/etc/nixos/userdata/userdata.json", "r+") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
if "ssh" not in data:
data["ssh"] = {}
# Return 400 if key already in array
for key in data["ssh"]["rootSshKeys"]:
if key == publicKey:
return {
"error": "Key already exists",
}, 400
data["ssh"]["rootSshKeys"].append(publicKey)
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {
"status": 0,
"descriptor": operationResult,
"message": "New SSH key successfully written to /etc/nixos/configuration.nix",
"message": "New SSH key successfully written",
}

View file

@ -2,6 +2,9 @@
from flask import Blueprint, jsonify, request
from flask_restful import Resource, Api
import subprocess
import portalocker
import json
import re
from selfprivacy_api import resources
@ -24,123 +27,62 @@ class Users(Resource):
hashedPassword = hashedPassword.decode("ascii")
hashedPassword = hashedPassword.rstrip()
print("[TRACE] {0}".format(hashedPassword))
print("[INFO] Opening /etc/nixos/users.nix...", sep="")
readOnlyFileDescriptor = open("/etc/nixos/users.nix", "r")
print("done")
fileContent = list()
index = int(0)
print("[INFO] Reading file content...", sep="")
while True:
line = readOnlyFileDescriptor.readline()
if not line:
break
else:
fileContent.append(line)
print("[DEBUG] Read line!")
userTemplate = """
#begin
\"{0}\" = {{
isNormalUser = true;
hashedPassword = \"{1}\";
}};
#end
""".format(
request.headers.get("X-User"), hashedPassword
)
mailUserTemplate = """
\"{0}@{2}\" = {{
hashedPassword =
\"{1}\";
catchAll = [ \"{2}\" ];
sieveScript = ''
require [\"fileinto\", \"mailbox\"];
if header :contains \"Chat-Version\" \"1.0\"
{{
fileinto :create \"DeltaChat\";
stop;
}}
'';
}};""".format(
request.headers.get("X-User"),
hashedPassword,
request.headers.get("X-Domain"),
)
for line in fileContent:
index += 1
if line.startswith(" #begin"):
print("[DEBUG] Found user configuration snippet match!")
print(
"[INFO] Writing new user configuration snippet to memory...", sep=""
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
# Return 400 if username is not provided
if request.headers.get("X-User") is None:
return {"error": "username is required"}, 400
# Return 400 if password is not provided
if request.headers.get("X-Password") is None:
return {"error": "password is required"}, 400
# Check is username passes regex
if not re.match(r"^[a-z_][a-z0-9_]+$", request.headers.get("X-User")):
return {"error": "username must be alphanumeric"}, 400
# Check if username less than 32 characters
if len(request.headers.get("X-User")) > 32:
return {"error": "username must be less than 32 characters"}, 400
# Return 400 if user already exists
for user in data["users"]:
if user["username"] == request.headers.get("X-User"):
return {"error": "User already exists"}, 400
if "users" not in data:
data["users"] = []
data["users"].append(
{
"username": request.headers.get("X-User"),
"hashedPassword": hashedPassword,
}
)
fileContent.insert(index - 1, userTemplate)
print("done")
break
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
print("[INFO] Writing data from memory to file...", sep="")
readWriteFileDescriptor = open("/etc/nixos/users.nix", "w")
userConfigurationWriteOperationResult = readWriteFileDescriptor.writelines(
fileContent
)
print("done")
readOnlyFileDescriptor.close()
readWriteFileDescriptor.close()
print(
"[INFO] Opening /etc/nixos/mailserver/system/mailserver.nix.nix for reading...",
sep="",
)
readOnlyFileDescriptor = open("/etc/nixos/mailserver/system/mailserver.nix")
print("done")
fileContent = list()
index = int(0)
while True:
line = readOnlyFileDescriptor.readline()
if not line:
break
else:
fileContent.append(line)
print("[DEBUG] Read line!")
for line in fileContent:
if line.startswith(" loginAccounts = {"):
print("[DEBUG] Found mailuser configuration snippet match!")
print(
"[INFO] Writing new user configuration snippet to memory...", sep=""
)
fileContent.insert(index + 1, mailUserTemplate)
print("done")
break
index += 1
readWriteFileDescriptor = open(
"/etc/nixos/mailserver/system/mailserver.nix", "w"
)
mailUserConfigurationWriteOperationResult = readWriteFileDescriptor.writelines(
fileContent
)
return {
"result": 0,
"descriptor0": userConfigurationWriteOperationResult,
"descriptor1": mailUserConfigurationWriteOperationResult,
}
return {"result": 0}
def delete(self):
user = subprocess.Popen(["userdel", request.headers.get("X-User")])
user.communicate()[0]
return user.returncode
with open("/etc/nixos/userdata/userdata.json", "r+", encoding="utf8") as f:
portalocker.lock(f, portalocker.LOCK_EX)
try:
data = json.load(f)
# Return 400 if username is not provided
if request.headers.get("X-User") is None:
return {"error": "username is required"}, 400
# Return 400 if user does not exist
for user in data["users"]:
if user["username"] == request.headers.get("X-User"):
data["users"].remove(user)
break
else:
return {"error": "User does not exist"}, 400
f.seek(0)
json.dump(data, f, indent=4)
f.truncate()
finally:
portalocker.unlock(f)
return {"result": 0}