Browse Source

implemented authentication to access admin pages

master
Youen 2 years ago
parent
commit
3a392135d1
  1. 2
      .gitignore
  2. 4
      src/api/api_document.py
  3. 15
      src/app.py
  4. 1
      src/app_globals.py
  5. 9
      src/data/document.py
  6. 3
      src/templates/admin/document/manage.html
  7. 16
      src/templates/admin/login.html
  8. 42
      src/web/admin/admin.py
  9. 2
      src/web/admin/admin_document.py

2
.gitignore vendored

@ -1,3 +1,3 @@
/venv
__pycache__
/data/doc
/data

4
src/api/api_document.py

@ -12,8 +12,12 @@ bp = Blueprint('api_document', __name__, url_prefix='/api/doc')
def build():
doc_name = get_arg('doc')
branch = get_arg('branch', 'master')
apikey = get_arg('apikey')
doc = Document(doc_name, branch)
if doc.get_api_key() != apikey:
raise Exception("Invalid API key")
output = ""
output += "\n# Pulling source...\n"
output += doc.pull()

15
src/app.py

@ -1,13 +1,28 @@
import os
import random
import string
from flask import Flask
import data.document
import app_globals
def create_app():
app = Flask(__name__)
src_path = os.path.dirname(os.path.realpath(__file__))
data.document.set_document_root(os.path.realpath(src_path+'/../data/doc'))
app_globals.data_root_dir = os.path.realpath(src_path+'/../data')
secret_key_path = app_globals.data_root_dir + '/flask-secret-key'
if not os.path.isfile(secret_key_path):
new_secret_key = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(20))
with open(secret_key_path, 'wb') as f:
f.write(new_secret_key.encode())
with open(secret_key_path, 'rb') as f:
secret_key = f.read().decode().replace('\n', '').replace('\r', '')
if len(secret_key) < 20:
raise Exception("Internal error: insecure secret key")
app.secret_key = secret_key
from api import api_document
app.register_blueprint(api_document.bp)

1
src/app_globals.py

@ -0,0 +1 @@
admin_password = None

9
src/data/document.py

@ -1,4 +1,5 @@
import os
import uuid
from types import SimpleNamespace
from web_utils.run import run
import shutil
@ -62,6 +63,10 @@ class Document:
def get_url(self):
return "/doc/" + sanitize_name(self.doc_name)+'/'+sanitize_name(self.branch) + "/index.html"
def get_api_key(self):
with open(self.doc_path + "/apikey") as f:
return f.read().replace('\n', '')
@staticmethod
def make_doc_path(doc_name, branch):
doc_path = os.path.realpath(get_document_root()+'/'+sanitize_name(doc_name)+'/'+sanitize_name(branch))
@ -82,11 +87,15 @@ class Document:
# we have potentially serious security issues related to cloning anything. For example cloning from SSH may use a pre-configured server identity, etc.
if not repo.startswith("https://"):
raise Exception("Only HTTPS repositories are allowed in current implementation")
# Generate an API key
apikey = str(uuid.uuid4())
target_dir = doc_path + "/repo"
cmd = ""
cmd += "mkdir -p \"" + target_dir + "\"\n"
cmd += "echo \""+apikey+"\" > \"" + doc_path + "/apikey\"\n"
cmd += "cd \"" + target_dir + "\"\n"
cmd += "git init \"--initial-branch=" + branch + "\"\n"
cmd += "git remote add -f origin \"" + repo + "\"\n"

3
src/templates/admin/document/manage.html

@ -4,8 +4,9 @@
{% block content %}
<h1>Gestion de {{ doc.doc_name }} / {{ doc.branch }}</h1>
<p>URL permettant de déclencher la compilation : {{ url_for('api_document.build', doc = doc.doc_name, branch = doc.branch, apikey = doc.get_api_key(), _external = True) }}</p>
<a href="{{doc.get_url()}}" class="button">Consulter</a><br/>
<a href="{{ url_for('api_document.build', doc = doc.doc_name, branch = doc.branch) }}" class="button">Compiler</a><br/>
<a href="{{ url_for('api_document.build', doc = doc.doc_name, branch = doc.branch, apikey = doc.get_api_key()) }}" class="button">Compiler</a><br/>
<br/>
<a href="{{ url_for('admin_document.delete', doc_name = doc.doc_name, branch = doc.branch) }}" class="button danger" data-confirm="Êtes-vous sûr de vouloir supprimer le document {{ doc.doc_name }} / {{ doc.branch }} ?">Supprimer</a>
{% endblock %}

16
src/templates/admin/login.html

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Documentation (admin)</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<form method="POST">
<label for="password">Mot de passe admin :</label> <input type="password" id="password" name="password"><br/>
<input type="submit"/>
</form>
</body>

42
src/web/admin/admin.py

@ -1,9 +1,49 @@
from flask import Blueprint, render_template
import os
import random
import string
from flask import Blueprint, render_template, session, redirect, url_for, request
from data.document import Document
import app_globals
bp = Blueprint('admin', __name__, url_prefix='/admin')
def get_admin_password():
password_path = app_globals.data_root_dir + '/admin-password'
if not os.path.isfile(password_path):
new_password = ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(12))
with open(password_path, 'wb') as f:
f.write(new_password.encode())
with open(password_path, 'rb') as f:
result = f.read().decode().replace('\n', '').replace('\r', '')
if len(result) < 12:
raise Exception("Internal error: insecure password")
return result
@bp.before_app_request
def authenticate():
print(request.path)
if request.path == '/admin/login' or request.path.startswith('/doc/') or request.path == '/api/doc/build':
return
authenticated = session.get('authenticated')
if not authenticated:
return redirect(url_for('admin.login'), code=302)
@bp.route('/')
def index():
return render_template("admin/index.html", documents=Document.list())
@bp.route('/login', methods=['GET', 'POST'])
def login():
correct_password = get_admin_password()
if request.method == 'POST':
password = request.form.get('password')
if password == correct_password:
session.clear()
session['authenticated'] = True
return redirect(url_for('admin.index'), code=302)
else:
raise Exception("Incorrect password")
else:
return render_template("admin/login.html")

2
src/web/admin/admin_document.py

@ -1,5 +1,5 @@
import os
from flask import Blueprint, render_template, redirect, url_for, request
from flask import Blueprint, render_template, redirect, url_for, request, session
from web_utils.get_arg import get_arg

Loading…
Cancel
Save