[odrs-web] Add a moderator class that can take over from me



commit 7e7ca6fa3179b3aba2ddf7d59ec0819f8556fc95
Author: Richard Hughes <richard hughsie com>
Date:   Fri Jul 7 16:53:47 2017 +0100

    Add a moderator class that can take over from me

 README.md                   |    2 +
 app/__init__.py             |    2 +-
 app/db.py                   |  148 +++++++++++++++++++++++---
 app/models.py               |   48 +++++---
 app/templates/default.html  |    5 +
 app/templates/modadmin.html |   53 +++++++++
 app/templates/mods.html     |   52 +++++++++
 app/views.py                |    8 +-
 app/views_admin.py          |  248 ++++++++++++++++++++++++++++++++++++++-----
 schema.sql                  |   18 +++-
 10 files changed, 515 insertions(+), 69 deletions(-)
---
diff --git a/README.md b/README.md
index 7461fbb..a1c4032 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@ To set up the database tables do:
     GRANT ALL ON odrs.* TO 'test'@'localhost';
     SOURCE /path/to/schema.sql
 
+The default admin password is `Pa$$w0rd`
+
 ## How do I backup the data ##
 
 You want to save the variable `ODRS_REVIEWS_SECRET` so that old review data
diff --git a/app/__init__.py b/app/__init__.py
index 861a48f..5eb1947 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -35,7 +35,7 @@ def teardown_request(exception):
 @lm.user_loader
 def load_user(user_id):
     db = get_db()
-    user = db.users.get_by_id(user_id)
+    user = db.moderators.get_by_id(user_id)
     return user
 
 @app.errorhandler(404)
diff --git a/app/db.py b/app/db.py
index a4a68ed..aef2df7 100644
--- a/app/db.py
+++ b/app/db.py
@@ -13,7 +13,7 @@ import hashlib
 
 import pymysql as mdb
 
-from .models import User, Event, Review
+from .models import User, Event, Review, Moderator
 
 class CursorError(Exception):
     def __init__(self, cur, e):
@@ -64,6 +64,20 @@ def _create_user(e):
     user.is_banned = int(e[4])
     return user
 
+def _create_moderator(e):
+    """ Parse a user """
+    mod = Moderator()
+    mod.moderator_id = int(e[0])
+    mod.username = e[1]
+    mod.password = e[2]
+    mod.display_name = e[3]
+    mod.email = e[4]
+    mod.is_enabled = int(e[5])
+    mod.is_admin = int(e[6])
+    mod.user_hash = e[7]
+    mod.locales = e[8]
+    return mod
+
 def _password_hash(value):
     """ Generate a salted hash of the password string """
     salt = 'odrs%%%'
@@ -92,6 +106,7 @@ class Database(object):
             print("Error %d: %s" % (e.args[0], e.args[1]))
         assert self._db
         self.users = DatabaseUsers(self._db)
+        self.moderators = DatabaseModerators(self._db)
         self.reviews = DatabaseReviews(self._db)
         self.eventlog = DatabaseEventlog(self._db)
 
@@ -537,6 +552,121 @@ class DatabaseReviews(object):
             data.append(en[0])
         return data
 
+class DatabaseModerators(object):
+
+    def __init__(self, db):
+        """ Constructor for object """
+        self._db = db
+
+    def get_all(self):
+        """ Get all the users on the system """
+        try:
+            cur = self._db.cursor()
+            cur.execute("SELECT moderator_id, username, password, "
+                        "display_name, email, is_enabled, is_admin, "
+                        "user_hash, locales "
+                        "FROM moderators ORDER BY moderator_id ASC;")
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+        res = cur.fetchall()
+        if not res:
+            return []
+        users = []
+        for e in res:
+            users.append(_create_moderator(e))
+        return users
+
+    def get_by_id(self, moderator_id):
+        """ Get information about a specific user """
+        try:
+            cur = self._db.cursor()
+            cur.execute("SELECT moderator_id, username, password, "
+                        "display_name, email, is_enabled, is_admin, "
+                        "user_hash, locales "
+                        "FROM moderators WHERE moderator_id=%s;",
+                        (moderator_id,))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+        res = cur.fetchone()
+        if not res:
+            return None
+        return _create_moderator(res)
+
+    def get_by_username(self, username):
+        """ Get information about a specific user """
+        try:
+            cur = self._db.cursor()
+            cur.execute("SELECT moderator_id, username, password, "
+                        "display_name, email, is_enabled, is_admin, "
+                        "user_hash, locales "
+                        "FROM moderators WHERE username=%s;",
+                        (username,))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+        res = cur.fetchone()
+        if not res:
+            return None
+        return _create_moderator(res)
+
+    def get_by_username_password(self, username, password):
+        """ Get information about a specific login """
+        try:
+            cur = self._db.cursor()
+            cur.execute("SELECT moderator_id, username, password, "
+                        "display_name, email, is_enabled, is_admin, "
+                        "user_hash, locales "
+                        "FROM moderators WHERE username=%s AND password=%s;",
+                        (username,
+                         _password_hash(password),))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+        res = cur.fetchone()
+        if not res:
+            return None
+        return _create_moderator(res)
+
+    def add(self, username, password, display_name, email):
+        """ Adds a moderator """
+        try:
+            cur = self._db.cursor()
+            cur.execute("INSERT INTO moderators (username, password, display_name, "
+                        "email, is_enabled) "
+                        "VALUES (%s, %s, %s, %s, %s);",
+                        (username,
+                         _password_hash(password),
+                         display_name,
+                         email,
+                         True,))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+        res = cur.fetchone()
+        if not res:
+            return None
+        return _create_moderator(res)
+
+    def delete(self, username):
+        """ Deletes a moderator """
+        try:
+            cur = self._db.cursor()
+            cur.execute("DELETE FROM moderators WHERE username = %s;",
+                        (username,))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+
+    def set_property(self, username, key, value):
+        """ Sets some properties on the moderator """
+        assert username
+        assert key
+
+        try:
+            query = "UPDATE moderators SET %s=%%s WHERE username=%%s;" % key
+            if key == 'password':
+                value = _password_hash(value)
+            cur = self._db.cursor()
+            cur.execute(query, (value, username,))
+        except mdb.Error as e:
+            raise CursorError(cur, e)
+
 class DatabaseUsers(object):
 
     def __init__(self, db):
@@ -569,22 +699,6 @@ class DatabaseUsers(object):
             users.append(_create_user(e))
         return users
 
-    def get_with_login(self, username, password):
-        """ Get information about a specific login """
-        try:
-            cur = self._db.cursor()
-            cur.execute("SELECT user_id, date_created, "
-                        "user_hash, karma, is_banned "
-                        "FROM users WHERE user_hash=%s and password=%s;",
-                        (username,
-                         _password_hash(password),))
-        except mdb.Error as e:
-            raise CursorError(cur, e)
-        res = cur.fetchone()
-        if not res:
-            return None
-        return _create_user(res)
-
     def get_by_id(self, user_hash):
         """ Get information about a specific user """
         try:
diff --git a/app/models.py b/app/models.py
index 67a6cb6..a687a82 100644
--- a/app/models.py
+++ b/app/models.py
@@ -14,24 +14,6 @@ class User(object):
         self.user_hash = 0
         self.is_banned = 0
 
-    @property
-    def is_authenticated(self):
-        return True
-
-    @property
-    def is_active(self):
-        return True
-
-    @property
-    def is_anonymous(self):
-        return False
-
-    def get_id(self):
-        return str(self.id)
-
-    def __repr__(self):
-        return '<User %r>' % (self.id)
-
 class Review(object):
     def __init__(self):
         self.review_id = 0
@@ -59,3 +41,33 @@ class Event(object):
         self.message = None
         self.app_id = None
         self.important = False
+
+class Moderator(object):
+    def __init__(self):
+        self.moderator_id = 0
+        self.username = None
+        self.password = None
+        self.display_name = None
+        self.email = None
+        self.is_enabled = False
+        self.is_admin = False
+        self.user_hash = None
+        self.locales = None
+
+    @property
+    def is_authenticated(self):
+        return True
+
+    @property
+    def is_active(self):
+        return True
+
+    @property
+    def is_anonymous(self):
+        return False
+
+    def get_id(self):
+        return str(self.moderator_id)
+
+    def __repr__(self):
+        return '<Moderator %r>' % (self.moderator_id)
diff --git a/app/templates/default.html b/app/templates/default.html
index 9c8db80..6dd525a 100644
--- a/app/templates/default.html
+++ b/app/templates/default.html
@@ -39,10 +39,15 @@
               <li><a href="/admin/show/all">All Reviews</a></li>
 {% if current_user.is_authenticated %}
               <li><a href="/admin/show/reported">Reported</a></li>
+{% endif %}
+{% if current_user.is_admin %}
               <li><a href="/admin/stats">Statistics</a></li>
               <li><a href="/admin/users/all">Users</a></li>
+              <li><a href="/admin/moderators/all">Moderators</a></li>
               <li><a href="/admin/distros">Distributions</a></li>
               <li><a href="/admin/graph_month">Usage</a></li>
+{% else %}
+              <li><a href="/admin/moderator/{{current_user.username}}/admin">Profile</a></li>
 {% endif %}
             </ul>
           </div>
diff --git a/app/templates/modadmin.html b/app/templates/modadmin.html
new file mode 100644
index 0000000..a091215
--- /dev/null
+++ b/app/templates/modadmin.html
@@ -0,0 +1,53 @@
+{% extends "default.html" %}
+{% block title %}Edit Moderator{% endblock %}
+
+{% block content %}
+
+<h1>Details of user ‘{{u.username}}’</h1>
+
+<form method="post" action="/admin/moderator/{{u.username}}/modify_by_admin">
+  <table>
+    <tr>
+      <td>Display Name:</td>
+      <td><input type="text" class="form-control" name="display_name" value="{{u.display_name}}" 
required></td>
+    </tr>
+    <tr>
+      <td>User Hash:</td>
+      <td><input type="text" class="form-control" name="user_hash" value="{{u.user_hash}}" required></td>
+    </tr>
+    <tr>
+      <td>Languages Spoken<br/>(e.g. <code>en,fr,pl</code>):</td>
+      <td><input type="text" class="form-control" name="locales" value="{{u.locales}}" required></td>
+    </tr>
+    <tr>
+      <td>Contact Email:</td>
+      <td><input type="text" class="form-control" name="email" value="{{u.email}}" required></td>
+    </tr>
+    <tr>
+      <td>New Password<br/>(optional):</td>
+      <td><input type="password" class="form-control" name="password"></td>
+    </tr>
+{% if not current_user.is_admin or u.username == 'admin' %}
+     <input type="hidden" name="is_enabled" value="1"/>
+{% else %}
+    <tr>
+      <td>Parameters:</td>
+      <td>
+{% if u.is_enabled %}
+        <input class="checkbox" type="checkbox" name="is_enabled" value="1" checked>Account enabled</input>
+{% else %}
+        <input class="checkbox" type="checkbox" name="is_enabled" value="1"/>Account enabled</input>
+{% endif %}
+      </td>
+    </tr>
+{% endif %}
+  </table>
+  <button type="submit" class="btn btn-primary btn-large" class="submit">Modify</button>
+{% if u.username != 'admin' and current_user.is_admin %}
+  <form method="get" action="/admin/moderator/{{u.username}}/delete">
+{% endif %}
+    <button class="btn btn-danger btn-large">Delete</button>
+  </form>
+</form>
+
+{% endblock %}
diff --git a/app/templates/mods.html b/app/templates/mods.html
new file mode 100644
index 0000000..d96adcc
--- /dev/null
+++ b/app/templates/mods.html
@@ -0,0 +1,52 @@
+{% extends "default.html" %}
+{% block title %}Moderators{% endblock %}
+
+{% block content %}
+
+<h2>Moderators</h2>
+<table>
+  <tr>
+    <th>ID</th>
+    <th>Username</th>
+    <th>Display Name</th>
+    <th>Email</th>
+    <th>Is Enabled</th>
+    <th>Is Admin</th>
+    <th>User Hashes</th>
+  </tr>
+{% for u in mods %}
+  <tr>
+    <td> {{ u.moderator_id }} </td>
+    <td><a href="/admin/moderator/{{u.username}}/admin">{{ u.username }}</a></td>
+    <td> {{ u.display_name }} </td>
+    <td> {{ u.email }} </td>
+    <td> {{ u.is_enabled }} </td>
+    <td> {{ u.is_admin }} </td>
+    <td> {{ u.user_hash }} </td>
+  </tr>
+{% endfor %}
+</table>
+
+<h2>Create new</h2>
+<form method="post" action="/admin/moderator/add" class="form">
+  <div class="form-control">
+    <label for="username_new">Username:</label>
+    <input type="text" class="form-control" name="username_new" required>
+  </div>
+  <div class="form-control">
+    <label for="password_new">Password:</label>
+    <input type="password" class="form-control" name="password_new" required>
+  </div>
+  <div class="form-control">
+    <label for="name">Display Name:</label>
+    <input type="text" class="form-control" name="display_name" required>
+  </div>
+  <div class="form-control">
+    <label for="email">Contact Email:</label>
+    <input type="text" class="form-control" name="email" required>
+  </div>
+  <input type="submit" value="Add">
+</form>
+
+
+{% endblock %}
diff --git a/app/views.py b/app/views.py
index 003e9f7..436d57d 100644
--- a/app/views.py
+++ b/app/views.py
@@ -17,7 +17,7 @@ from flask_login import login_user, logout_user
 from app import app, get_db
 
 from .db import CursorError
-from .models import Review
+from .models import Review, Moderator
 
 def _get_user_key(user_hash, app_id):
     salt = os.environ['ODRS_REVIEWS_SECRET']
@@ -123,8 +123,8 @@ def login():
     password = request.form['password']
     try:
         db = get_db()
-        user = db.users.get_with_login(request.form['username'],
-                                       request.form['password'])
+        user = db.moderators.get_by_username_password(request.form['username'],
+                                                      request.form['password'])
     except CursorError as e:
         flash(str(e))
         return render_template('error.html'), 503
@@ -175,7 +175,7 @@ def json_error(msg=None, errcode=400):
                     mimetype="application/json")
 
 @app.errorhandler(401)
-def error_permission_denied(msg=None):
+def _error_permission_denied(msg=None):
     """ Error handler: Permission Denied """
     return json_error(msg, 401)
 
diff --git a/app/views_admin.py b/app/views_admin.py
index 6f1cfd6..f9c6032 100644
--- a/app/views_admin.py
+++ b/app/views_admin.py
@@ -11,7 +11,7 @@ import calendar
 from math import ceil
 
 from flask import abort, request, flash, render_template, redirect, url_for
-from flask_login import login_required
+from flask_login import login_required, current_user
 
 from app import app, get_db
 from .db import CursorError
@@ -36,6 +36,29 @@ def _get_chart_labels_days():
         labels.append("%02i-%02i-%02i" % (then.year, then.month, then.day))
     return labels
 
+def _password_check(value):
+    """ Check the password for suitability """
+    success = True
+    if len(value) < 8:
+        success = False
+        flash('The password is too short, the minimum is 8 characters', 'warning')
+    if len(value) > 40:
+        success = False
+        flash('The password is too long, the maximum is 40 characters', 'warning')
+    if value.lower() == value:
+        success = False
+        flash('The password requires at least one uppercase character', 'warning')
+    if value.isalnum():
+        success = False
+        flash('The password requires at least one non-alphanumeric character', 'warning')
+    return success
+
+def _email_check(value):
+    """ Do a quick and dirty check on the email address """
+    if len(value) < 5 or value.find('@') == -1 or value.find('.') == -1:
+        return False
+    return True
+
 class Pagination(object):
 
     def __init__(self, page, per_page, total_count):
@@ -69,13 +92,13 @@ class Pagination(object):
                 last = num
 
 @app.errorhandler(400)
-def error_internal(msg=None, errcode=400):
+def _error_internal(msg=None, errcode=400):
     """ Error handler: Internal """
     flash("Internal error: %s" % msg)
     return render_template('error.html'), errcode
 
 @app.errorhandler(401)
-def error_permission_denied(msg=None):
+def _error_permission_denied(msg=None):
     """ Error handler: Permission Denied """
     flash("Permission denied: %s" % msg)
     return render_template('error.html'), 401
@@ -115,11 +138,14 @@ def show_stats():
     """
     Return the statistics page as HTML.
     """
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to show stats as non-admin')
     try:
         db = get_db()
         stats = db.get_stats()
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
 
     # stats
     results_stats = []
@@ -146,11 +172,14 @@ def distros():
     """
     Return the statistics page as HTML.
     """
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to show distros as non-admin')
     try:
         db = get_db()
         stats = db.get_stats_distro(8)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     labels = []
     data = []
     for s in stats:
@@ -202,9 +231,9 @@ def admin_show_review(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     return render_template('show.html', r=review)
 
 @app.route('/admin/modify/<review_id>', methods=['POST'])
@@ -215,9 +244,9 @@ def admin_modify(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     review.distro = request.form['distro']
     review.locale = request.form['locale']
     if len(request.form['user_display']) == 0:
@@ -235,11 +264,14 @@ def admin_modify(review_id):
 @login_required
 def admin_user_ban(user_hash):
     """ Change details about a review """
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to ban user as non-admin')
     try:
         db = get_db()
         db.users.ban(user_hash)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return redirect(url_for('.admin_show_reported'))
 
 @app.route('/admin/unreport/<review_id>')
@@ -250,9 +282,9 @@ def admin_unreport(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     review.reported = 0
     db.reviews.modify(review)
     return redirect(url_for('.admin_show_review', review_id=review_id))
@@ -265,9 +297,9 @@ def admin_unremove(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     review.date_deleted = 0
     db.reviews.modify(review)
     return redirect(url_for('.admin_show_review', review_id=review_id))
@@ -280,9 +312,9 @@ def admin_englishify(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     parts = review.locale.split('_')
     if len(parts) == 1:
         review.locale = 'en'
@@ -299,9 +331,9 @@ def admin_anonify(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     review.user_display = None
     db.reviews.modify(review)
     return redirect(url_for('.admin_show_review', review_id=review_id))
@@ -314,9 +346,9 @@ def admin_delete_force(review_id):
         db = get_db()
         review = db.reviews.get_for_id(review_id)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     if not review:
-        return error_internal('no review with that ID')
+        return _error_internal('no review with that ID')
     db.reviews.delete(review)
     return redirect(url_for('.admin_show_all'))
 
@@ -332,13 +364,26 @@ def admin_show_all(page):
     """
     Return all the reviews on the server as HTML.
     """
+
     try:
         db = get_db()
-        reviews = db.reviews.get_all()
+        reviews_all = db.reviews.get_all()
     except CursorError as e:
-        return error_internal(str(e))
-    if not reviews and page != 1:
+        return _error_internal(str(e))
+    if not reviews_all and page != 1:
         abort(404)
+
+    # filter by the languages the moderator understands
+    reviews = []
+    if not current_user or current_user.locales == None:
+        reviews.extend(reviews_all)
+    else:
+        langs = current_user.locales.split(',')
+        for r in reviews_all:
+            lang = r.locale.split('_')[0]
+            if lang in langs:
+                reviews.append(r)
+
     reviews_per_page = 20
     pagination = Pagination(page, reviews_per_page, len(reviews))
     # FIXME: do this database side...
@@ -348,6 +393,7 @@ def admin_show_all(page):
                            reviews=reviews)
 
 @app.route('/admin/show/reported')
+@login_required
 def admin_show_reported():
     """
     Return all the reported reviews on the server as HTML.
@@ -360,7 +406,7 @@ def admin_show_reported():
             if review.reported > 0:
                 reviews_filtered.append(review)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return render_template('show-all.html', reviews=reviews_filtered)
 
 @app.route('/admin/show/user/<user_hash>')
@@ -376,7 +422,7 @@ def admin_show_user(user_hash):
             if review.user_hash == user_hash:
                 reviews_filtered.append(review)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return render_template('show-all.html', reviews=reviews_filtered)
 
 @app.route('/admin/show/app/<app_id>')
@@ -392,7 +438,7 @@ def admin_show_app(app_id):
             if review.app_id == app_id:
                 reviews_filtered.append(review)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return render_template('show-all.html', reviews=reviews_filtered)
 
 @app.route('/admin/show/lang/<locale>')
@@ -408,7 +454,7 @@ def admin_show_lang(locale):
             if review.locale == locale:
                 reviews_filtered.append(review)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return render_template('show-all.html', reviews=reviews_filtered)
 
 @app.route('/admin/users/all')
@@ -422,5 +468,151 @@ def admin_users_all():
         users_awesome = db.users.get_by_karma(best=True)
         users_haters = db.users.get_by_karma(best=False)
     except CursorError as e:
-        return error_internal(str(e))
+        return _error_internal(str(e))
     return render_template('users.html', users_awesome=users_awesome, users_haters=users_haters)
+
+@app.route('/admin/moderators/all')
+@login_required
+def admin_moderator_show_all():
+    """
+    Return all the moderators as HTML.
+    """
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to show all moderators')
+    try:
+        db = get_db()
+        mods = db.moderators.get_all()
+    except CursorError as e:
+        return _error_internal(str(e))
+    return render_template('mods.html', mods=mods)
+
+@app.route('/admin/moderator/add', methods=['GET', 'POST'])
+@login_required
+def admin_moderator_add():
+    """ Add a moderator [ADMIN ONLY] """
+
+    # only accept form data
+    if request.method != 'POST':
+        return redirect(url_for('.profile'))
+
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to add moderator as non-admin')
+
+    if not 'password_new' in request.form:
+        return _error_permission_denied('Unable to add user as no password_new')
+    if not 'username_new' in request.form:
+        return _error_permission_denied('Unable to add user as no username_new')
+    if not 'display_name' in request.form:
+        return _error_permission_denied('Unable to add user as no display name')
+    if not 'email' in request.form:
+        return _error_permission_denied('Unable to add user as no email')
+    try:
+        db = get_db()
+        auth = db.moderators.get_by_username(request.form['username_new'])
+    except CursorError as e:
+        return _error_internal(str(e))
+    if auth:
+        return _error_internal('Already a entry with that username', 422)
+
+    # verify password
+    password = request.form['password_new']
+    if not _password_check(password):
+        return redirect(url_for('.admin_moderator_show_all'), 422)
+
+    # verify email
+    email = request.form['email']
+    if not _email_check(email):
+        flash('Invalid email address', 'warning')
+        return redirect(url_for('.admin_moderator_show_all'), 422)
+
+    # verify name
+    display_name = request.form['display_name']
+    if len(display_name) < 3:
+        flash('Name invalid', 'warning')
+        return redirect(url_for('.admin_moderator_show_all'), 422)
+
+    # verify username
+    username_new = request.form['username_new']
+    if len(username_new) < 3:
+        flash('Username invalid', 'warning')
+        return redirect(url_for('.admin_moderator_show_all'), 422)
+    try:
+        db.moderators.add(username_new, password, display_name, email)
+    except CursorError as e:
+        return _error_internal(str(e))
+    flash('Added user')
+    return redirect(url_for('.admin_moderator_show_all'), 302)
+
+@app.route('/admin/moderator/<username>/admin')
+@login_required
+def admin_moderator_show(username):
+    """
+    Shows an admin panel for a moderator
+    """
+    if username != current_user.username and current_user.username != 'root':
+        return _error_permission_denied('Unable to show moderator information')
+    try:
+        db = get_db()
+        mod = db.moderators.get_by_username(username)
+    except CursorError as e:
+        return _error_internal(str(e))
+    return render_template('modadmin.html', u=mod)
+
+@app.route('/admin/moderator/<username>/delete')
+@login_required
+def admin_moderate_delete(username):
+    """ Delete a moderator """
+
+    # security check
+    if current_user.username != 'admin':
+        return _error_permission_denied('Unable to delete moderator as not admin')
+
+    # check whether exists in database
+    try:
+        db = get_db()
+        mod = db.moderators.get_by_username(username)
+    except CursorError as e:
+        return _error_internal(str(e))
+    if not mod:
+        flash("No entry with username %s" % username, 'error')
+        return redirect(url_for('.admin_moderator_show_all'), 422)
+    try:
+        db.moderators.remove(username)
+    except CursorError as e:
+        return _error_internal(str(e))
+    flash('Deleted user')
+    return redirect(url_for('.admin_moderator_show_all'), 302)
+
+@app.route('/admin/moderator/<username>/modify_by_admin', methods=['POST'])
+@login_required
+def user_modify_by_admin(username):
+    """ Change details about the any user """
+
+    # security check
+    if username != current_user.username and current_user.username != 'root':
+        return _error_permission_denied('Unable to modify user as non-admin')
+
+    # set each thing in turn
+    for key in ['display_name',
+                'email',
+                'password',
+                'user_hash',
+                'locales',
+                'is_enabled']:
+        # unchecked checkbuttons are not included in the form data
+        if key in request.form:
+            tmp = request.form[key]
+        else:
+            tmp = '0'
+        try:
+            # don't set the optional password
+            if key == 'password' and len(tmp) == 0:
+                continue
+            db = get_db()
+            db.moderators.set_property(username, key, tmp)
+        except CursorError as e:
+            return _error_internal(str(e))
+    flash('Updated profile')
+    return redirect(url_for('.admin_moderator_show', username=username))
diff --git a/schema.sql b/schema.sql
index 5e7e6cc..80191bc 100644
--- a/schema.sql
+++ b/schema.sql
@@ -1,3 +1,6 @@
+ALTER TABLE analytics MODIFY COLUMN app_id VARCHAR(128) DEFAULT NULL;
+
+
 DROP TABLE IF EXISTS reviews;
 CREATE TABLE reviews (
   review_id INT NOT NULL AUTO_INCREMENT,
@@ -34,7 +37,6 @@ CREATE TABLE users (
   user_hash TEXT DEFAULT NULL,
   karma INT DEFAULT 0,
   is_banned INT DEFAULT 0,
-  password TEXT DEFAULT NULL,
   UNIQUE KEY id (user_id)
 ) CHARSET=utf8;
 DROP TABLE IF EXISTS eventlog;
@@ -56,3 +58,17 @@ CREATE TABLE analytics (
   UNIQUE (datestr,app_id)
 ) CHARSET=utf8;
 CREATE INDEX date_created_idx ON eventlog (date_created);
+CREATE TABLE moderators (
+  moderator_id INT NOT NULL AUTO_INCREMENT,
+  username TEXT DEFAULT NULL,
+  password TEXT DEFAULT NULL,
+  display_name TEXT DEFAULT NULL,
+  email TEXT DEFAULT NULL,
+  is_enabled TINYINT DEFAULT 0,
+  is_admin TINYINT DEFAULT 0,
+  user_hash TEXT DEFAULT NULL,
+  locales TEXT DEFAULT NULL,
+  UNIQUE KEY id (moderator_id)
+) CHARSET=utf8;
+INSERT INTO moderators (username, password, display_name, email, is_enabled, is_admin)
+    VALUES ('admin', 'cc492636d799c5509bbd48b27b02986dcf25dfc8', 'Richard Hughes', 'richard hughsie com', 1, 
1);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]