19 changed files with 1061 additions and 8 deletions
@ -1,2 +1,3 @@ |
|||
flask |
|||
redis |
|||
redis |
|||
bcrypt |
|||
|
@ -0,0 +1,209 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""JSON flat file database system.""" |
|||
|
|||
import codecs |
|||
import os |
|||
import os.path |
|||
import glob |
|||
import re |
|||
from fcntl import flock, LOCK_EX, LOCK_SH, LOCK_UN |
|||
import redis |
|||
import json |
|||
import time |
|||
|
|||
import config |
|||
from rophako.log import logger |
|||
|
|||
redis_client = None |
|||
cache_lifetime = 60*60 # 1 hour |
|||
|
|||
|
|||
def get(document): |
|||
"""Get a specific document from the DB.""" |
|||
logger.debug("JsonDB: GET {}".format(document)) |
|||
|
|||
# Exists? |
|||
if not exists(document): |
|||
logger.debug("Requested document doesn't exist") |
|||
return None |
|||
|
|||
path = mkpath(document) |
|||
stat = os.stat(path) |
|||
|
|||
# Do we have it cached? |
|||
data = get_cache(document) |
|||
if data: |
|||
# Check if the cache is fresh. |
|||
if stat.st_mtime > get_cache(document+"_mtime"): |
|||
del_cache(document) |
|||
del_cache(document+"_mtime") |
|||
else: |
|||
return data |
|||
|
|||
# Get the JSON data. |
|||
data = read_json(path) |
|||
|
|||
# Cache and return it. |
|||
set_cache(document, data, expires=cache_lifetime) |
|||
set_cache(document+"_mtime", stat.st_mtime, expires=cache_lifetime) |
|||
return data |
|||
|
|||
|
|||
def commit(document, data): |
|||
"""Insert/update a document in the DB.""" |
|||
|
|||
# Need to create the file? |
|||
path = mkpath(document) |
|||
if not os.path.isfile(path): |
|||
parts = path.split("/") |
|||
parts.pop() # Remove the file part |
|||
directory = list() |
|||
|
|||
# Create all the folders. |
|||
for part in parts: |
|||
directory.append(part) |
|||
segment = "/".join(directory) |
|||
if len(segment) > 0 and not os.path.isdir(segment): |
|||
logger.debug("JsonDB: mkdir {}".format(segment)) |
|||
os.mkdir(segment, 0755) |
|||
|
|||
# Update the cached document. |
|||
set_cache(document, data, expires=cache_lifetime) |
|||
set_cache(document+"_mtime", time.time(), expires=cache_lifetime) |
|||
|
|||
# Write the JSON. |
|||
write_json(path, data) |
|||
|
|||
|
|||
def delete(document): |
|||
"""Delete a document from the DB.""" |
|||
path = mkpath(document) |
|||
if os.path.isfile(path): |
|||
logger.info("Delete DB document: {}".format(path)) |
|||
os.unlink(path) |
|||
|
|||
|
|||
def exists(document): |
|||
"""Query whether a document exists.""" |
|||
path = mkpath(document) |
|||
return os.path.isfile(path) |
|||
|
|||
|
|||
def list_docs(path): |
|||
"""List all the documents at the path.""" |
|||
path = mkpath("{}/*".format(path)) |
|||
docs = list() |
|||
|
|||
for item in glob.glob(path): |
|||
name = re.sub(r'\.json$', '', item) |
|||
name = name.split("/")[-1] |
|||
docs.append(name) |
|||
|
|||
return docs |
|||
|
|||
|
|||
def mkpath(document): |
|||
"""Turn a DB path into a JSON file path.""" |
|||
if document.endswith(".json"): |
|||
# Let's not do that. |
|||
raise Exception("mkpath: document path already includes .json extension!") |
|||
return "{}/{}.json".format(config.DB_ROOT, str(document)) |
|||
|
|||
|
|||
def read_json(path): |
|||
"""Slurp, decode and return the data from a JSON document.""" |
|||
path = str(path) |
|||
if not os.path.isfile(path): |
|||
raise Exception("Can't read JSON file {}: file not found!".format(path)) |
|||
|
|||
# Open and lock the file. |
|||
fh = codecs.open(path, 'r', 'utf-8') |
|||
flock(fh, LOCK_SH) |
|||
text = fh.read() |
|||
flock(fh, LOCK_UN) |
|||
fh.close() |
|||
|
|||
# Decode. |
|||
try: |
|||
data = json.loads(text) |
|||
except: |
|||
logger.error("Couldn't decode JSON data from {}".format(path)) |
|||
data = None |
|||
|
|||
return data |
|||
|
|||
|
|||
def write_json(path, data): |
|||
"""Write a JSON document.""" |
|||
path = str(path) |
|||
|
|||
logger.debug("JsonDB: WRITE > {}".format(path)) |
|||
|
|||
# Open and lock the file. |
|||
fh = None |
|||
if os.path.isfile(path): |
|||
fh = codecs.open(path, 'r+', 'utf-8') |
|||
else: |
|||
fh = codecs.open(path, 'w', 'utf-8') |
|||
flock(fh, LOCK_EX) |
|||
|
|||
# Write it. |
|||
fh.truncate(0) |
|||
fh.write(json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))) |
|||
|
|||
# Unlock and close. |
|||
flock(fh, LOCK_UN) |
|||
fh.close() |
|||
|
|||
|
|||
############################################################################ |
|||
# Redis Caching Functions # |
|||
############################################################################ |
|||
|
|||
def get_redis(): |
|||
"""Connect to Redis or return the existing connection.""" |
|||
global redis_client |
|||
if not redis_client: |
|||
redis_client = redis.StrictRedis( |
|||
host = config.REDIS_HOST, |
|||
port = config.REDIS_PORT, |
|||
db = config.REDIS_DB, |
|||
) |
|||
return redis_client |
|||
|
|||
|
|||
def set_cache(key, value, expires=None): |
|||
"""Set a key in the Redis cache.""" |
|||
key = config.REDIS_PREFIX + key |
|||
try: |
|||
client = get_redis() |
|||
client.set(key, json.dumps(value)) |
|||
|
|||
# Expiration date? |
|||
if expires: |
|||
client.expire(key, expires) |
|||
except: |
|||
logger.error("Redis exception: couldn't set_cache {}".format(key)) |
|||
|
|||
|
|||
def get_cache(key): |
|||
"""Get a cached item.""" |
|||
key = config.REDIS_PREFIX + key |
|||
value = None |
|||
try: |
|||
client = get_redis() |
|||
value = client.get(key) |
|||
if value: |
|||
value = json.loads(value) |
|||
except: |
|||
logger.warning("Redis exception: couldn't get_cache {}".format(key)) |
|||
value = None |
|||
return value |
|||
|
|||
|
|||
def del_cache(key): |
|||
"""Delete a cached item.""" |
|||
key = config.REDIS_PREFIX + key |
|||
client = get_redis() |
|||
client.delete(key) |
@ -0,0 +1,33 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Debug and logging functions.""" |
|||
|
|||
from flask import g, request |
|||
import logging |
|||
|
|||
import config |
|||
|
|||
class LogHandler(logging.Handler): |
|||
"""A custom logging handler.""" |
|||
|
|||
def emit(self, record): |
|||
# The initial log line, which has the $prefix$ in it. |
|||
line = self.format(record) |
|||
|
|||
# Is the user logged in? |
|||
name = "-nobody-" |
|||
|
|||
line = line.replace('$prefix$', '') |
|||
print line |
|||
|
|||
# Set up the logger. |
|||
logger = logging.getLogger("rophako") |
|||
handler = LogHandler() |
|||
handler.setFormatter(logging.Formatter("[%(asctime)s] [%(levelname)s] $prefix$%(message)s")) |
|||
logger.addHandler(handler) |
|||
|
|||
# Log level. |
|||
if config.DEBUG: |
|||
logger.setLevel(logging.DEBUG) |
|||
else: |
|||
logger.setLevel(logging.INFO) |
@ -0,0 +1,3 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Database models.""" |
@ -0,0 +1,162 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""User account models.""" |
|||
|
|||
import bcrypt |
|||
import time |
|||
|
|||
import config |
|||
import rophako.jsondb as JsonDB |
|||
from rophako.log import logger |
|||
|
|||
def create(username, password, name=None, uid=None, role="user"): |
|||
"""Create a new user account. |
|||
|
|||
Returns the user ID number assigned to this user.""" |
|||
# Name defaults to username. |
|||
if name is None: |
|||
name = username |
|||
|
|||
username = username.lower() |
|||
|
|||
# Provided with a user ID? |
|||
if uid is not None: |
|||
# See if it's available. |
|||
if exists(uid=uid): |
|||
logger.warning("Wanted to use UID {} for user {} but it wasn't available.".format(uid, username)) |
|||
uid = None |
|||
|
|||
# Need to generate a UID? |
|||
if uid is None: |
|||
uid = get_next_uid() |
|||
|
|||
uid = int(uid) |
|||
|
|||
# Username musn't exist. |
|||
if exists(username): |
|||
# The front-end shouldn't let this happen. |
|||
raise Exception("Can't create username {}: already exists!".format(username)) |
|||
|
|||
# Crypt their password. |
|||
hashedpass = hash_password(password) |
|||
|
|||
logger.info("Create user {} with username {}".format(uid, username)) |
|||
|
|||
# Create the user file. |
|||
JsonDB.commit("users/by-id/{}".format(uid), dict( |
|||
uid=uid, |
|||
username=username, |
|||
name=name, |
|||
role=role, |
|||
password=hashedpass, |
|||
created=time.time(), |
|||
)) |
|||
|
|||
# And their username to ID map. |
|||
JsonDB.commit("users/by-name/{}".format(username), dict( |
|||
uid=uid, |
|||
)) |
|||
|
|||
return uid |
|||
|
|||
|
|||
def update_user(uid, data): |
|||
"""Update the user's data.""" |
|||
if not exists(uid=uid): |
|||
raise Exception("Can't update user {}: doesn't exist!".format(uid)) |
|||
|
|||
db = get_user(uid=uid) |
|||
|
|||
# Change of username? |
|||
if "username" in data and len(data["username"]) and data["username"] != db["username"]: |
|||
JsonDB.delete("users/by-name/{}".format(db["username"])) |
|||
JsonDB.commit("users/by-name/{}".format(data["username"]), dict( |
|||
uid=int(uid), |
|||
)) |
|||
|
|||
db.update(data) |
|||
JsonDB.commit("users/by-id/{}".format(uid), db) |
|||
|
|||
|
|||
def delete_user(uid): |
|||
"""Delete a user account.""" |
|||
if not exists(uid=uid): |
|||
return |
|||
|
|||
db = get_user(uid=uid) |
|||
username = db["username"] |
|||
|
|||
# Mark the account deleted. |
|||
update_user(uid, dict( |
|||
username="", |
|||
name="", |
|||
role="deleted", |
|||
password="!", |
|||
)) |
|||
|
|||
# Delete their username. |
|||
JsonDB.delete("users/by-name/{}".format(username)) |
|||
|
|||
|
|||
def list_users(): |
|||
"""Get a sorted list of all users.""" |
|||
uids = JsonDB.list_docs("users/by-id") |
|||
users = list() |
|||
for uid in sorted(map(lambda x: int(x), uids)): |
|||
db = get_user(uid=uid) |
|||
if db["role"] == "deleted": continue |
|||
users.append(db) |
|||
return users |
|||
|
|||
|
|||
def get_uid(username): |
|||
"""Turn a username into a user ID.""" |
|||
db = JsonDB.get("users/by-name/{}".format(username)) |
|||
if db: |
|||
return int(db["uid"]) |
|||
return None |
|||
|
|||
|
|||
def get_user(uid=None, username=None): |
|||
"""Get a user's DB file, or None if not found.""" |
|||
if username: |
|||
uid = get_uid(username) |
|||
logger.debug("get_user: resolved username {} to UID {}".format(username, uid)) |
|||
return JsonDB.get("users/by-id/{}".format(uid)) |
|||
|
|||
|
|||
def exists(uid=None, username=None): |
|||
"""Query whether a user ID or name exists.""" |
|||
if uid: |
|||
return JsonDB.exists("users/by-id/{}".format(uid)) |
|||
elif username: |
|||
return JsonDB.exists("users/by-name/{}".format(username.lower())) |
|||
|
|||
|
|||
def hash_password(password): |
|||
return bcrypt.hashpw(str(password), bcrypt.gensalt(config.BCRYPT_ITERATIONS)) |
|||
|
|||
|
|||
def check_auth(username, password): |
|||
"""Check the authentication credentials for the username and password. |
|||
|
|||
Returns a boolean true or false. On error, an error is logged.""" |
|||
# Check if the username exists. |
|||
if not exists(username=username): |
|||
logger.error("User authentication failed: username {} not found!".format(username)) |
|||
return False |
|||
|
|||
# Get the user's file. |
|||
db = get_user(username=username) |
|||
print db |
|||
|
|||
# Check the password. |
|||
return bcrypt.hashpw(str(password), str(db["password"])) == db["password"] |
|||
|
|||
|
|||
def get_next_uid(): |
|||
"""Get the next available user ID.""" |
|||
uid = 1 |
|||
while exists(uid=uid): |
|||
uid += 1 |
|||
return uid |
@ -1,9 +1,129 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from flask import Blueprint |
|||
"""Endpoints for user login and out.""" |
|||
|
|||
from flask import Blueprint, request, redirect, url_for, session, flash |
|||
import re |
|||
|
|||
import rophako.model.user as User |
|||
from rophako.utils import template |
|||
|
|||
mod = Blueprint("account", __name__, url_prefix="/account") |
|||
|
|||
@mod.route("/") |
|||
def index(): |
|||
return "Test" |
|||
return redirect(url_for(".login")) |
|||
|
|||
|
|||
@mod.route("/login", methods=["GET", "POST"]) |
|||
def login(): |
|||
"""Log into an account.""" |
|||
|
|||
if request.method == "POST": |
|||
username = request.form.get("username", "") |
|||
password = request.form.get("password", "") |
|||
|
|||
# Lowercase the username. |
|||
username = username.lower() |
|||
|
|||
if User.check_auth(username, password): |
|||
# OK! |
|||
db = User.get_user(username=username) |
|||
session["login"] = True |
|||
session["username"] = username |
|||
session["uid"] = db["uid"] |
|||
session["name"] = db["name"] |
|||
session["role"] = db["role"] |
|||
return redirect(url_for("index")) |
|||
else: |
|||
flash("Authentication failed.") |
|||
return redirect(url_for(".login")) |
|||
|
|||
return template("account/login.html") |
|||
|
|||
|
|||
@mod.route("/logout") |
|||
def logout(): |
|||
"""Log out the user.""" |
|||
session["login"] = False |
|||
session["username"] = "guest" |
|||
session["uid"] = 0 |
|||
session["name"] = "Guest" |
|||
session["role"] = "user" |
|||
|
|||
flash("You have been signed out.") |
|||
return redirect(url_for(".login")) |
|||
|
|||
|
|||
@mod.route("/setup", methods=["GET", "POST"]) |
|||
def setup(): |
|||
"""Initial setup to create the Admin user account.""" |
|||
|
|||
# This can't be done if users already exist on the CMS! |
|||
if User.exists(uid=1): |
|||
flash("This website has already been configured (users already created).") |
|||
return redirect(url_for("index")) |
|||
|
|||
if request.method == "POST": |
|||
# Submitting the form. |
|||
username = request.form.get("username", "") |
|||
name = request.form.get("name", "") |
|||
pw1 = request.form.get("password1", "") |
|||
pw2 = request.form.get("password2", "") |
|||
|
|||
# Default name = username. |
|||
if name == "": |
|||
name = username |
|||
|
|||
# Lowercase the user. |
|||
username = username.lower() |
|||
if User.exists(username=username): |
|||
flash("That username already exists.") |
|||
return redirect(url_for(".setup")) |
|||
|
|||
# Validate the form. |
|||
errors = validate_create_form(username, pw1, pw2) |
|||
if errors: |
|||
for error in errors: |
|||
flash(error) |
|||
return redirect(url_for(".setup")) |
|||
|
|||
# Create the account. |
|||
uid = User.create( |
|||
username=username, |
|||
password=pw1, |
|||
name=name, |
|||
role="admin", |
|||
) |
|||
|
|||
flash("Admin user created! Please log in now.".format(uid)) |
|||
return redirect(url_for(".login")) |
|||
|
|||
|
|||
return template("account/setup.html") |
|||
|
|||
|
|||
def validate_create_form(username, pw1=None, pw2=None, skip_passwd=False): |
|||
"""Validate the submission of a create-user form. |
|||
|
|||
Returns a list of error messages if there were errors, otherwise |
|||
it returns None.""" |
|||
errors = list() |
|||
|
|||
if len(username) == 0: |
|||
errors.append("You must provide a username.") |
|||
|
|||
if re.search(r'[^A-Za-z0-9-_]', username): |
|||
errors.append("Usernames can only contain letters, numbers, dashes or underscores.") |
|||
|
|||
if not skip_passwd: |
|||
if len(pw1) < 3: |
|||
errors.append("You should use at least 3 characters in your password.") |
|||
|
|||
if pw1 != pw2: |
|||
errors.append("Your passwords don't match.") |
|||
|
|||
if len(errors): |
|||
return errors |
|||
else: |
|||
return None |
@ -0,0 +1,183 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
"""Endpoints for admin functions.""" |
|||
|
|||
from flask import g, Blueprint, request, redirect, url_for, session, flash |
|||
import re |
|||
|
|||
import rophako.model.user as User |
|||
from rophako.modules.account import validate_create_form |
|||
from rophako.utils import template, admin_required |
|||
|
|||
mod = Blueprint("admin", __name__, url_prefix="/admin") |
|||
|
|||
@mod.route("/") |
|||
@admin_required |
|||
def index(): |
|||
return template("admin/index.html") |
|||
|
|||
|
|||
@mod.route("/users") |
|||
@admin_required |
|||
def users(): |
|||
# Get the list of existing users. |
|||
users = User.list_users() |
|||
|
|||
return template("admin/users.html", |
|||
users=users, |
|||
) |
|||
|
|||
|
|||
@mod.route("/users/create", methods=["POST"]) |
|||
@admin_required |
|||
def create_user(): |
|||
# Submitting the form. |
|||
username = request.form.get("username", "") |
|||
name = request.form.get("name", "") |
|||
pw1 = request.form.get("password1", "") |
|||
pw2 = request.form.get("password2", "") |
|||
role = request.form.get("role", "") |
|||
|
|||
# Default name = username. |
|||
if name == "": |
|||
name = username |
|||
|
|||
# Lowercase the user. |
|||
username = username.lower() |
|||
if User.exists(username=username): |
|||
flash("That username already exists.") |
|||
return redirect(url_for(".users")) |
|||
|
|||
# Validate the form. |
|||
errors = validate_create_form(username, pw1, pw2) |
|||
if errors: |
|||
for error in errors: |
|||
flash(error) |
|||
return redirect(url_for(".users")) |
|||
|
|||
# Create the account. |
|||
uid = User.create( |
|||
username=username, |
|||
password=pw1, |
|||
name=name, |
|||
role=role, |
|||
) |
|||
|
|||
flash("User created!") |
|||
return redirect(url_for(".users")) |
|||
|
|||
|
|||
@mod.route("/users/edit/<uid>", methods=["GET", "POST"]) |
|||
@admin_required |
|||
def edit_user(uid): |
|||
uid = int(uid) |
|||
user = User.get_user(uid=uid) |
|||
|
|||
# Submitting? |
|||
if request.method == "POST": |
|||
action = request.form.get("action", "") |
|||
username = request.form.get("username", "") |
|||
name = request.form.get("name", "") |
|||
pw1 = request.form.get("password1", "") |
|||
pw2 = request.form.get("password2", "") |
|||
role = request.form.get("role", "") |
|||
|
|||
username = username.lower() |
|||
|
|||
if action == "save": |
|||
# Validate... |
|||
errors = None |
|||
|
|||
# Don't allow them to change the username to one that exists. |
|||
if username != user["username"]: |
|||
if User.exists(username=username): |
|||
flash("That username already exists.") |
|||
return redirect(url_for(".edit_user", uid=uid)) |
|||
|
|||
# Password provided? |
|||
if len(pw1) > 0: |
|||
errors = validate_create_form(username, pw1, pw2) |
|||
elif username != user["username"]: |
|||
# Just validate the username, then. |
|||
errors = validate_create_form(username, skip_passwd=True) |
|||
|
|||
if errors: |
|||
for error in errors: |
|||
flash(error) |
|||
return redirect(url_for(".edit_user", uid=uid)) |
|||
|
|||
# Update the user. |
|||
user["username"] = username |
|||
user["name"] = name or username |
|||
user["role"] = role |
|||
if len(pw1) > 0: |
|||
user["password"] = User.hash_password(pw1) |
|||
User.update_user(uid, user) |
|||
|
|||
flash("User account updated!") |
|||
return redirect(url_for(".users")) |
|||
|
|||
elif action == "delete": |
|||
# Don't let them delete themself! |
|||
if uid == g.info["session"]["uid"]: |
|||
flash("You shouldn't delete yourself!") |
|||
return redirect(url_for(".edit_user", uid=uid)) |
|||
|
|||
User.delete_user(uid) |
|||
flash("User deleted!") |
|||
return redirect(url_for(".users")) |
|||
|
|||
return template("admin/edit_user.html", |
|||
info=user, |
|||
) |
|||
|
|||
|
|||
@mod.route("/impersonate/<int:uid>") |
|||
@admin_required |
|||
def impersonate(uid): |
|||
"""Impersonate a user.""" |
|||
# Check that they exist. |
|||
if not User.exists(uid=uid): |
|||
flash("That user ID wasn't found.") |
|||
return redirect(url_for(".users")) |
|||
|
|||
db = User.get_user(uid=uid) |
|||
if db["role"] == "deleted": |
|||
flash("That user was deleted!") |
|||
return redirect(url_for(".users")) |
|||
|
|||
# Log them in! |
|||
orig_uid = session["uid"] |
|||
session.update( |
|||
login=True, |
|||
uid=uid, |
|||
username=db["username"], |
|||
name=db["name"], |
|||
role=db["role"], |
|||
impersonator=orig_uid, |
|||
) |
|||
|
|||
flash("Now logged in as {}".format(db["name"])) |
|||
return redirect(url_for("index")) |
|||
|
|||
@mod.route("/unimpersonate") |
|||
def unimpersonate(): |
|||
"""Unimpersonate a user.""" |
|||
|
|||
# Must be impersonating, first! |
|||
if not "impersonator" in session: |
|||
flash("Stop messing around.") |
|||
return redirect(url_for("index")) |
|||
|
|||
uid = session.pop("impersonator") |
|||
db = User.get_user(uid=uid) |
|||
session.update( |
|||
login=True, |
|||
uid=uid, |
|||
username=db["username"], |
|||
name=db["name"], |
|||
role=db["role"], |
|||
) |
|||
|
|||
flash("No longer impersonating.") |
|||
return redirect(url_for("index")) |
@ -0,0 +1,51 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
from flask import g, session, request, render_template |
|||
from functools import wraps |
|||
import uuid |
|||
|
|||
from rophako.log import logger |
|||
|
|||
|
|||
def login_required(f): |
|||
"""Wrapper for pages that require a logged-in user.""" |
|||
@wraps(f) |
|||
def decorated_function(*args, **kwargs): |
|||
if not g.info["session"]["login"]: |
|||
session["redirect_url"] = request.url |
|||
flash("You must be logged in to do that!") |
|||
return redirect(url_for("account.login")) |
|||
return f(*args, **kwargs) |
|||
return decorated_function |
|||
|
|||
|
|||
def admin_required(f): |
|||
"""Wrapper for admin-only pages. Implies login_required.""" |
|||
@wraps(f) |
|||
def decorated_function(*args, **kwargs): |
|||
if not g.info["session"]["login"]: |
|||
# Not even logged in? |
|||
session["redirect_url"] = request.url |
|||
flash("You must be logged in to do that!") |
|||
return redirect(url_for("account.login")) |
|||
|
|||
if g.info["session"]["role"] != "admin": |
|||
logger.warning("User tried to access an Admin page, but wasn't allowed!") |
|||
return redirect(url_for("index")) |
|||
|
|||
return f(*args, **kwargs) |
|||
return decorated_function |
|||
|
|||
|
|||
def template(name, **kwargs): |
|||
"""Render a template to the browser.""" |
|||
|
|||
html = render_template(name, **kwargs) |
|||
return html |
|||
|
|||
|
|||
def generate_csrf_token(): |
|||
"""Generator for CSRF tokens.""" |
|||
if "_csrf" not in session: |
|||
session["_csrf"] = str(uuid.uuid4()) |
|||
return session["_csrf"] |
@ -0,0 +1,23 @@ |
|||
{% extends "layout.html" %} |
|||
{% block title %}Log In{% endblock %} |
|||
|
|||
{% block content %} |
|||
|
|||
<h1>Log In</h1> |
|||
|
|||
<form action="{{ url_for('account.login') }}" method="POST"> |
|||
<input type="hidden" name="token" value="{{ csrf_token() }}"> |
|||
<fieldset> |
|||
<legend>Log In</legend> |
|||
|
|||
<strong>Username:</strong><br> |
|||
<input type="text" size="20" name="username" id="username"><p> |
|||
|
|||
<strong>Passphrase:</strong><br> |
|||
<input type="password" size="20" name="password"><p> |
|||
|
|||
<button type="submit">Log In</button> |
|||
</fieldset> |
|||
</form> |
|||
|
|||
{% endblock %} |
@ -0,0 +1,42 @@ |
|||
{% extends "layout.html" %} |
|||
{% block title %}Initial Setup{% endblock %} |
|||
|
|||
{% block scripts %} |
|||
<script type="text/javascript" src="/rophako/setup.js"></script> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
|
|||
<h1>Welcome to Rophako!</h1> |
|||
|
|||
This is the initial setup for the Rophako CMS. The main purpose of this is |
|||
to create the initial Administrator user account.<p> |
|||
|
|||
<form id="setup_form" action="{{ url_for('account.setup') }}" method="POST"> |
|||
<input type="hidden" name="token" value="{{ csrf_token() }}"> |
|||
<fieldset> |
|||
<legend>Admin User</legend> |
|||
|
|||
Your site needs at least one admin user to log in and manage the site. |
|||
You can use any username/password combination you want, but "admin" is |
|||
a typical username.<p> |
|||
|
|||
<strong>Username:</strong><br> |
|||
<input type="text" size="40" name="username" id="username" placeholder="admin"><p> |
|||
|
|||
<strong>Real name:</strong><br> |
|||
<input type="text" size="40" name="name" placeholder="John Doe"><p> |
|||
|
|||
<strong>Passphrase:</strong><br> |
|||
This can be as long as you want. Pick something |
|||
<a href="http://xkcd.com/936/" target="_blank">secure!</a><br> |
|||
<input type="password" size="40" id="pw1" name="password1" placeholder="correct horse battery staple"><p> |
|||
|
|||
<strong>Confirm Passphrase:</strong><br> |
|||
<input type="password" size="40" id="pw2" name="password2" placeholder="correct horse battery staple"><p> |
|||
|
|||
<button type="submit">Next</button> |
|||
</fieldset> |
|||
</form> |
|||
|
|||
{% endblock %} |
@ -0,0 +1,44 @@ |
|||
{% extends "layout.html" %} |
|||
{% block title %}Admin Center{% endblock %} |
|||
|
|||
{% block scripts %} |
|||
<script type="text/javascript"> |
|||
$(document).ready(function() { |
|||
$("#delete_button").click(function() { |
|||
return window.confirm("Are you sure?"); |
|||
}); |
|||
}); |
|||
</script> |
|||
{% endblock %} |
|||
|
|||
{% block content %} |
|||
|
|||
<h1>Edit User #{{ info["uid"] }}</h1> |
|||
|
|||
<form action="{{ url_for('admin.edit_user', uid=info['uid']) }}" method="POST"> |
|||
<input type="hidden" name="token" value="{{ csrf_token() }}"> |
|||
<fieldset> |
|||
<legend>User Details</legend> |
|||
|
|||
<strong>Username:</strong><br> |
|||
<input type="text" size="20" name="username" value="{{ info['username'] }}"><p> |
|||
|
|||
<strong>Real name:</strong><br> |
|||
<input type="text" size="20" name="name" value="{{ info['name'] }}"><p> |
|||
|
|||
<strong>Reset Password:</strong><br> |
|||
<input type="password" size="20" name="password1"><br> |
|||
<input type="password" size="20" name="password2"><p> |
|||
|
|||
<strong>Role:</strong><br> |
|||
<select name="role"> |
|||
<option value="user"{% if info["role"] == "user" %} selected{% endif %}>User</option> |
|||
<option value="admin"{% if info["role"] == "admin" %} selected{% endif %}>Admin</option> |
|||
</select><p> |
|||
|
|||
<button type="submit" name="action" value="save">Save Changes</button> |
|||
<button type="submit" name="action" id="delete_button" value="delete">Delete User</button> |
|||
</fieldset> |
|||
</form> |
|||
|
|||
{% endblock %} |
@ -0,0 +1,12 @@ |
|||
{% extends "layout.html" %} |
|||
{% block title %}Admin Center{% endblock %} |
|||
|
|||
{% block content %} |
|||
|
|||
<h1>Admin Center</h1> |
|||
|
|||
<ul> |
|||
<li><a href="{{ url_for('admin.users') }}">View and Manage Users</a></li> |
|||
</ul> |
|||
|
|||
{% endblock %} |
@ -0,0 +1,68 @@ |
|||
{% extends "layout.html" %} |
|||
{% block title %}Admin Center{% endblock %} |
|||
|
|||
{% block content %} |
|||
|
|||
<h1>User Management</h1> |
|||
|
|||
<h2>Create New User</h2> |
|||
|
|||
<form action="{{ url_for('admin.create_user') }}" method="POST"> |
|||
<input type="hidden" name="token" value="{{ csrf_token() }}"> |
|||
<fieldset> |
|||
<legend>Create New User</legend> |
|||
|
|||
<strong>Username:</strong><br> |
|||
<input type="text" size="40" name="username" placeholder="soandso"><p> |
|||
|
|||
<strong>Real name:</strong><br> |
|||
<input type="text" size="40" name="name" placeholder="John Smith"><p> |
|||
|
|||
<strong>Passphrase:</strong><br> |
|||
<input type="password" size="40" name="password1" placeholder="correct horse battery staple"><p> |
|||
|
|||
<strong>Confirm:</strong><br> |
|||
<input type="password" size="40" name="password2" placeholder="correct horse battery staple"><p> |
|||
|
|||
<strong>Role:</strong><br> |
|||
<select name="role"> |
|||
<option value="user" selected>User</option> |
|||
<option value="admin">Admin</option> |
|||
</select> |
|||
|
|||
<button type="submit">Create</button> |
|||
</fieldset> |
|||
|
|||
<h2>User List</h2> |
|||
|
|||
<table class="table table-wide"> |
|||
<thead> |
|||
<tr> |
|||
<th width="100">User ID</th> |
|||
<th width="300">Username</th> |
|||
<th>Real name</th> |
|||
<th width="100">Role</th> |
|||
<th width="100">Log in</th> |
|||
</tr> |
|||
</thead> |
|||
|
|||
<tbody> |
|||
{% for user in users %} |
|||
<tr> |
|||
<td>{{ user["uid"] }}</td> |
|||
<td><a href="{{ url_for('admin.edit_user', uid=user['uid']) }}">{{ user["username"] }}</a></td> |
|||
<td>{{ user["name"] }}</td> |
|||
<td>{{ user["role"] }}</td> |
|||
<td> |
|||
{% if user["role"] != "admin" %} |
|||
<a href="{{ url_for('admin.impersonate', uid=user['uid']) }}">Log in as</a> |
|||
{% else %} |
|||
n/a |
|||
{% endif %} |
|||
</td> |
|||
</tr> |
|||
{% endfor %} |
|||
</tbody> |
|||
</table> |
|||
|
|||
{% endblock %} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,31 @@ |
|||
/* rophako cms |
|||
----------- |
|||
Script for /account/setup |
|||
*/ |
|||
|
|||
$(document).ready(function() { |
|||
$("#setup_form").submit(function() { |
|||
var username = $("#username").val(), |
|||
pw1 = $("#pw1").val(), |
|||
pw2 = $("#pw2").val(); |
|||
|
|||
if (username.length === 0) { |
|||
window.alert("The username is required."); |
|||
} |
|||
else if (username.match(/[^A-Za-z0-9_-]+/)) { |
|||
window.alert("The username should only contain numbers, letters, underscores or dashes."); |
|||
} |
|||
else if (pw1.length < 3) { |
|||
window.alert("Your password should have at least three characters."); |
|||
} |
|||
else if (pw1 !== pw2) { |
|||
window.alert("Your passwords don't match."); |
|||
} |
|||
else { |
|||
// All good!
|
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
}); |
|||
}) |
Loading…
Reference in new issue