added python ldap / login pages
This commit is contained in:
@@ -15,7 +15,7 @@ RUN truncate -s0 /tmp/preseed.cfg && \
|
||||
apt-get install -y tzdata
|
||||
## cleanup of files from setup
|
||||
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
RUN apt-get update && apt-get -y install python3-pip python3-psycopg2 libpq-dev gunicorn mediainfo cmake libgl1-mesa-glx libglib2.0-0
|
||||
RUN apt-get update && apt-get -y install python3-pip python3-psycopg2 libpq-dev gunicorn mediainfo cmake libgl1-mesa-glx libglib2.0-0 python3-ldap
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip3 install -r requirements.txt
|
||||
RUN pip3 install --upgrade pillow --user
|
||||
|
||||
3
README
3
README
@@ -13,6 +13,9 @@ pip packages:
|
||||
* datetime
|
||||
* pytz
|
||||
* face_recognition
|
||||
* flask-login
|
||||
* flask_login
|
||||
* flask-ldap3-login
|
||||
#### dlib (might need to install this before face_recognitioin, but it might not be needed, cmake clearly was)
|
||||
|
||||
|
||||
|
||||
2
ai.py
2
ai.py
@@ -8,6 +8,7 @@ from status import st, Status
|
||||
from files import Entry, File, FileRefimgLink
|
||||
from person import Person, PersonRefimgLink
|
||||
from refimg import Refimg
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
@@ -15,6 +16,7 @@ from refimg import Refimg
|
||||
# /aistats -> placholder for some sort of stats
|
||||
################################################################################
|
||||
@app.route("/aistats", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def aistats():
|
||||
tmp=db.session.query(Entry,Person).join(File).join(FileRefimgLink).join(Refimg).join(PersonRefimgLink).join(Person).filter(FileRefimgLink.matched==True).all()
|
||||
entries=[]
|
||||
|
||||
15
files.py
15
files.py
@@ -16,6 +16,7 @@ import numpy
|
||||
import cv2
|
||||
import time
|
||||
import re
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
################################################################################
|
||||
# Local Class imports
|
||||
@@ -225,6 +226,7 @@ def GetEntriesInFolderView( cwd, prefix, noo, offset, how_many ):
|
||||
# /file_list -> show detailed file list of files from import_path(s)
|
||||
################################################################################
|
||||
@app.route("/file_list_ip", methods=["GET","POST"])
|
||||
@login_required
|
||||
def file_list_ip():
|
||||
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
|
||||
entries=[]
|
||||
@@ -240,6 +242,7 @@ def file_list_ip():
|
||||
# /files -> show thumbnail view of files from import_path(s)
|
||||
################################################################################
|
||||
@app.route("/files_ip", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def files_ip():
|
||||
|
||||
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
|
||||
@@ -261,6 +264,7 @@ def files_ip():
|
||||
# /files -> show thumbnail view of files from storage_path
|
||||
################################################################################
|
||||
@app.route("/files_sp", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def files_sp():
|
||||
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
|
||||
entries=[]
|
||||
@@ -282,6 +286,7 @@ def files_sp():
|
||||
# /files -> show thumbnail view of files from storage_path
|
||||
################################################################################
|
||||
@app.route("/files_rbp", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def files_rbp():
|
||||
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
|
||||
entries=[]
|
||||
@@ -303,6 +308,7 @@ def files_rbp():
|
||||
# /search -> show thumbnail view of files from import_path(s)
|
||||
################################################################################
|
||||
@app.route("/search", methods=["GET","POST"])
|
||||
@login_required
|
||||
def search():
|
||||
|
||||
noo, grouping, how_many, offset, size, folders, cwd, root = ViewingOptions( request )
|
||||
@@ -321,6 +327,7 @@ def search():
|
||||
# /files/scannow -> allows us to force a check for new files
|
||||
################################################################################
|
||||
@app.route("/files/scannow", methods=["GET"])
|
||||
@login_required
|
||||
def scannow():
|
||||
job=NewJob("scannow" )
|
||||
st.SetAlert("success")
|
||||
@@ -331,6 +338,7 @@ def scannow():
|
||||
# /files/forcescan -> deletes old data in DB, and does a brand new scan
|
||||
################################################################################
|
||||
@app.route("/files/forcescan", methods=["GET"])
|
||||
@login_required
|
||||
def forcescan():
|
||||
job=NewJob("forcescan" )
|
||||
st.SetAlert("success")
|
||||
@@ -341,6 +349,7 @@ def forcescan():
|
||||
# /files/scan_sp -> allows us to force a check for new files
|
||||
################################################################################
|
||||
@app.route("/files/scan_sp", methods=["GET"])
|
||||
@login_required
|
||||
def scan_sp():
|
||||
job=NewJob("scan_sp" )
|
||||
st.SetAlert("success")
|
||||
@@ -349,6 +358,7 @@ def scan_sp():
|
||||
|
||||
|
||||
@app.route("/fix_dups", methods=["POST"])
|
||||
@login_required
|
||||
def fix_dups():
|
||||
rows = db.engine.execute( "select e1.id as id1, f1.hash, d1.rel_path as rel_path1, d1.eid as did1, e1.name as fname1, p1.id as path1, p1.type_id as path_type1, e2.id as id2, d2.rel_path as rel_path2, d2.eid as did2, e2.name as fname2, p2.id as path2, p2.type_id as path_type2 from entry e1, file f1, dir d1, entry_dir_link edl1, path_dir_link pdl1, path p1, entry e2, file f2, dir d2, entry_dir_link edl2, path_dir_link pdl2, path p2 where e1.id = f1.eid and e2.id = f2.eid and d1.eid = edl1.dir_eid and edl1.entry_id = e1.id and edl2.dir_eid = d2.eid and edl2.entry_id = e2.id and p1.type_id != (select id from path_type where name = 'Bin') and p1.id = pdl1.path_id and pdl1.dir_eid = d1.eid and p2.type_id != (select id from path_type where name = 'Bin') and p2.id = pdl2.path_id and pdl2.dir_eid = d2.eid and f1.hash = f2.hash and e1.id != e2.id and f1.size_mb = f2.size_mb order by path1, rel_path1, fname1");
|
||||
|
||||
@@ -374,6 +384,7 @@ def fix_dups():
|
||||
return render_template("dups.html", DD=DD, pagesize=pagesize )
|
||||
|
||||
@app.route("/rm_dups", methods=["POST"])
|
||||
@login_required
|
||||
def rm_dups():
|
||||
|
||||
jex=[]
|
||||
@@ -398,6 +409,7 @@ def rm_dups():
|
||||
return render_template("base.html")
|
||||
|
||||
@app.route("/restore_files", methods=["POST"])
|
||||
@login_required
|
||||
def restore_files():
|
||||
jex=[]
|
||||
for el in request.form:
|
||||
@@ -409,6 +421,7 @@ def restore_files():
|
||||
return render_template("base.html")
|
||||
|
||||
@app.route("/delete_files", methods=["POST"])
|
||||
@login_required
|
||||
def delete_files():
|
||||
jex=[]
|
||||
for el in request.form:
|
||||
@@ -420,6 +433,7 @@ def delete_files():
|
||||
return render_template("base.html")
|
||||
|
||||
@app.route("/move_files", methods=["POST"])
|
||||
@login_required
|
||||
def move_files():
|
||||
jex=[]
|
||||
for el in request.form:
|
||||
@@ -435,6 +449,7 @@ def move_files():
|
||||
# we create/use symlinks in static/ to reference the images to show
|
||||
################################################################################
|
||||
@app.route("/static/<filename>")
|
||||
@login_required
|
||||
def custom_static(filename):
|
||||
return send_from_directory("static/", filename)
|
||||
|
||||
|
||||
5
job.py
5
job.py
@@ -6,9 +6,11 @@ from sqlalchemy import Sequence
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from status import st, Status
|
||||
from datetime import datetime, timedelta
|
||||
from flask_login import login_required, current_user
|
||||
import pytz
|
||||
import socket
|
||||
from shared import PA_JOB_MANAGER_HOST, PA_JOB_MANAGER_PORT
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
@@ -93,6 +95,7 @@ def NewJob(name, num_files="0", wait_for=None, jex=None ):
|
||||
# /jobs -> show current settings
|
||||
################################################################################
|
||||
@app.route("/jobs", methods=["GET"])
|
||||
@login_required
|
||||
def jobs():
|
||||
page_title='Job list'
|
||||
jobs = Job.query.order_by(Job.id.desc()).all()
|
||||
@@ -103,6 +106,7 @@ def jobs():
|
||||
# /job/<id> -> GET -> shows status/history of jobs
|
||||
################################################################################
|
||||
@app.route("/job/<id>", methods=["GET"])
|
||||
@login_required
|
||||
def joblog(id):
|
||||
page_title='Show Job Details'
|
||||
joblog = Job.query.get(id)
|
||||
@@ -118,6 +122,7 @@ def joblog(id):
|
||||
# /job/<id> -> GET -> shows status/history of jobs
|
||||
################################################################################
|
||||
@app.route("/wakeup", methods=["GET"])
|
||||
@login_required
|
||||
def wakeup():
|
||||
WakePAJobManager()
|
||||
return render_template("base.html")
|
||||
|
||||
89
main.py
89
main.py
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, render_template, request, redirect, jsonify
|
||||
from flask import Flask, render_template, request, redirect, jsonify, url_for, render_template_string
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from flask_marshmallow import Marshmallow
|
||||
@@ -7,6 +7,13 @@ from wtforms import SubmitField, StringField, HiddenField, SelectField, IntegerF
|
||||
from flask_wtf import FlaskForm
|
||||
from status import st, Status
|
||||
from shared import CreateSelect, CreateFoldersSelect, LocationIcon, DB_URL
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
|
||||
# for ldap auth
|
||||
from flask_ldap3_login import LDAP3LoginManager
|
||||
from flask_login import LoginManager, login_user, UserMixin, current_user
|
||||
from flask_ldap3_login.forms import LDAPLoginForm
|
||||
|
||||
import re
|
||||
import socket
|
||||
@@ -23,9 +30,30 @@ app = Flask(__name__)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = DB_URL
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.config.from_mapping( SECRET_KEY=b'\xd6\x04\xbdj\xfe\xed$c\x1e@\xad\x0f\x13,@G')
|
||||
|
||||
# ldap config vars: (the last one is required, or python ldap freaks out)
|
||||
app.config['LDAP_HOST'] = 'mara.ddp.net'
|
||||
app.config['LDAP_BASE_DN'] = 'dc=depaoli,dc=id,dc=au'
|
||||
app.config['LDAP_USER_DN'] = 'ou=users'
|
||||
app.config['LDAP_GROUP_DN'] = 'ou=groups'
|
||||
app.config['LDAP_USER_RDN_ATTR'] = 'cn'
|
||||
app.config['LDAP_USER_LOGIN_ATTR'] = 'uid'
|
||||
app.config['LDAP_BIND_USER_DN'] = None
|
||||
app.config['LDAP_BIND_USER_PASSWORD'] = None
|
||||
app.config['LDAP_GROUP_OBJECT_FILTER'] = '(objectclass=posixGroup)'
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
ma = Marshmallow(app)
|
||||
Bootstrap(app)
|
||||
login_manager = LoginManager(app) # Setup a Flask-Login Manager
|
||||
ldap_manager = LDAP3LoginManager(app) # Setup a LDAP3 Login Manager.
|
||||
login_manager.login_view = "login" # default login route, failed with url_for, so hard-coded
|
||||
|
||||
# Create a dictionary to store the users in when they authenticate
|
||||
# This example stores users in memory.
|
||||
users = {}
|
||||
|
||||
|
||||
|
||||
################################# Now, import non-book classes ###################################
|
||||
from settings import Settings
|
||||
@@ -50,11 +78,70 @@ app.jinja_env.globals['CreateFoldersSelect'] = CreateFoldersSelect
|
||||
app.jinja_env.globals['LocationIcon'] = LocationIcon
|
||||
app.jinja_env.globals['StoragePathNames'] = StoragePathNames
|
||||
|
||||
|
||||
# Declare an Object Model for the user, and make it comply with the
|
||||
# flask-login UserMixin mixin.
|
||||
class User(UserMixin):
|
||||
def __init__(self, dn, username, data):
|
||||
self.dn = dn
|
||||
self.username = username
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
return self.dn
|
||||
|
||||
def get_id(self):
|
||||
return self.dn
|
||||
|
||||
# Declare a User Loader for Flask-Login.
|
||||
# Simply returns the User if it exists in our 'database', otherwise
|
||||
# returns None.
|
||||
@login_manager.user_loader
|
||||
def load_user(id):
|
||||
if id in users:
|
||||
return users[id]
|
||||
return None
|
||||
|
||||
# Declare The User Saver for Flask-Ldap3-Login
|
||||
# This method is called whenever a LDAPLoginForm() successfully validates.
|
||||
# Here you have to save the user, and return it so it can be used in the
|
||||
# login controller.
|
||||
@ldap_manager.save_user
|
||||
def save_user(dn, username, data, memberships):
|
||||
user = User(dn, username, data)
|
||||
users[dn] = user
|
||||
return user
|
||||
|
||||
# default page, just the navbar
|
||||
@app.route("/", methods=["GET"])
|
||||
@login_required
|
||||
def main_page():
|
||||
# Redirect users who are not logged in.
|
||||
if not current_user or current_user.is_anonymous:
|
||||
return redirect(url_for('login'))
|
||||
|
||||
return render_template("base.html")
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
# Instantiate a LDAPLoginForm which has a validator to check if the user
|
||||
# exists in LDAP.
|
||||
form = LDAPLoginForm()
|
||||
form.submit.label.text="Login"
|
||||
|
||||
if form.validate_on_submit():
|
||||
# Successfully logged in, We can now access the saved user object
|
||||
# via form.user.
|
||||
login_user(form.user, remember=True) # Tell flask-login to log them in.
|
||||
next = request.args.get("next")
|
||||
if next:
|
||||
return redirect(next) # Send them back where they came from
|
||||
else:
|
||||
return redirect('/')
|
||||
|
||||
return render_template("login.html", form=form)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if hostname == PROD_HOST:
|
||||
app.run(ssl_context=('/etc/letsencrypt/live/book.depaoli.id.au/cert.pem', '/etc/letsencrypt/live/book.depaoli.id.au/privkey.pem'), host="0.0.0.0", debug=False)
|
||||
|
||||
@@ -6,6 +6,7 @@ from sqlalchemy import Sequence
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from status import st, Status
|
||||
from refimg import Refimg
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
@@ -56,6 +57,7 @@ class PersonForm(FlaskForm):
|
||||
# /persons -> GET only -> prints out list of all persons
|
||||
################################################################################
|
||||
@app.route("/persons", methods=["GET"])
|
||||
@login_required
|
||||
def persons():
|
||||
persons = Person.query.all()
|
||||
return render_template("persons.html", persons=persons)
|
||||
@@ -65,6 +67,7 @@ def persons():
|
||||
# /person -> GET/POST -> creates a new person type and when created, takes you back to /persons
|
||||
################################################################################
|
||||
@app.route("/person", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def new_person():
|
||||
form = PersonForm(request.form)
|
||||
page_title='Create new Person'
|
||||
@@ -90,6 +93,7 @@ def new_person():
|
||||
# person
|
||||
################################################################################
|
||||
@app.route("/person/<id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def person(id):
|
||||
form = PersonForm(request.form)
|
||||
page_title='Edit Person'
|
||||
|
||||
@@ -6,6 +6,7 @@ from sqlalchemy import Sequence
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from status import st, Status
|
||||
import os
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
@@ -51,6 +52,7 @@ class RefimgForm(FlaskForm):
|
||||
# /refimgs -> GET only -> prints out list of all refimgs
|
||||
################################################################################
|
||||
@app.route("/refimgs", methods=["GET"])
|
||||
@login_required
|
||||
def refimgs():
|
||||
refimgs = Refimg.query.all()
|
||||
return render_template("refimgs.html", refimgs=refimgs)
|
||||
@@ -59,6 +61,7 @@ def refimgs():
|
||||
# /refimg -> GET/POST -> creates a new refimg type and when created, takes you back to /refimgs
|
||||
################################################################################
|
||||
@app.route("/refimg", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def new_refimg():
|
||||
form = RefimgForm(request.form)
|
||||
page_title='Create new Reference Image'
|
||||
@@ -88,6 +91,7 @@ def new_refimg():
|
||||
# refimg
|
||||
################################################################################
|
||||
@app.route("/refimg/<id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def refimg(id):
|
||||
form = RefimgForm(request.form)
|
||||
page_title='Edit Reference Image'
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
flask
|
||||
flask_login
|
||||
flask-ldap3-login
|
||||
sqlalchemy
|
||||
flask-sqlalchemy
|
||||
SQLAlchemy-serializer
|
||||
|
||||
@@ -5,6 +5,7 @@ from main import db, app, ma
|
||||
from sqlalchemy import Sequence
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from status import st, Status
|
||||
from flask_login import login_required, current_user
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
@@ -45,6 +46,7 @@ class SettingsForm(FlaskForm):
|
||||
# /settings -> show current settings
|
||||
################################################################################
|
||||
@app.route("/settings", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def settings():
|
||||
form = SettingsForm(request.form)
|
||||
page_title='Settings'
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
{% for job in jobs %}
|
||||
{% if job.state == "Failed" %}
|
||||
row='<tr><td class="bg-danger"><a class="text-white" href="{{url_for('joblog', id=job.id)}}">Job #{{job.id}} - {{job.name}}</a>'
|
||||
row='<tr><td class="table-danger"><a href="{{url_for('joblog', id=job.id)}}">Job #{{job.id}} - {{job.name}}</a>'
|
||||
{% elif job.state == "Withdrawn" %}
|
||||
row='<tr><td class="bg-secondary"><a class="text-white" href="{{url_for('joblog', id=job.id)}}">Job #{{job.id}} - {{job.name}}</a>'
|
||||
row='<tr><td class="table-secondary"><i><a href="{{url_for('joblog', id=job.id)}}">Job #{{job.id}} - {{job.name}}</a>'
|
||||
{% else %}
|
||||
row='<tr><td><a href="{{url_for('joblog', id=job.id)}}">Job #{{job.id}} - {{job.name}}</a>'
|
||||
{% endif %}
|
||||
@@ -21,7 +21,12 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
row+= '</td><td>{{job.start_time}}</td><td>'
|
||||
|
||||
{% if job.state == "Withdrawn" %}
|
||||
row+= '</td><td>{{job.start_time}}</i></td><td>'
|
||||
{% else %}
|
||||
row+= '</td><td>{{job.start_time}}</td><td>'
|
||||
{% endif %}
|
||||
{% if job.pa_job_state != "Completed" %}
|
||||
{% if job.num_files and job.num_files > 0 %}
|
||||
{% set prog=(job.current_file_num/job.num_files*100)|round|int %}
|
||||
|
||||
Reference in New Issue
Block a user