4269478abf1d
#!/usr/bin/env python3
4269478abf1d
# coding=utf-8
4269478abf1d
#
0c2f3413fe8e
# run.py: Run cupola
eae9623f5d29
# Copyright (C) 2015-2017 Sam Black 
4269478abf1d
#
4269478abf1d
# This program is free software: you can redistribute it and/or modify
4269478abf1d
# it under the terms of the GNU Affero General Public License as published by
4269478abf1d
# the Free Software Foundation, either version 3 of the License, or
4269478abf1d
# (at your option) any later version.
4269478abf1d
#
4269478abf1d
# This program is distributed in the hope that it will be useful,
4269478abf1d
# but WITHOUT ANY WARRANTY; without even the implied warranty of
4269478abf1d
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4269478abf1d
# GNU Affero General Public License for more details.
4269478abf1d
#
4269478abf1d
# You should have received a copy of the GNU Affero General Public License
4269478abf1d
# along with this program.  If not, see .
4269478abf1d
#
6e59bba7998a
import sys
eae9623f5d29
import click
6e59bba7998a
from datetime import datetime
6e59bba7998a
6e59bba7998a
from flask import current_app
d312a7c9137b
from flask_babelex import gettext
396ff11353bc
from flask_migrate import upgrade as db_upgrade
eae9623f5d29
from flask_security.utils import hash_password
b43b1f854a98
from sqlalchemy import exc
396ff11353bc
from sqlalchemy_utils.functions import create_database
396ff11353bc
from sqlalchemy_utils.functions import database_exists
6e59bba7998a
from wtforms.validators import Email
6e59bba7998a
from wtforms.validators import ValidationError
4269478abf1d
396ff11353bc
from cupola.models import db
6e59bba7998a
from cupola.models.users import User
6e59bba7998a
from cupola.models.users import user_datastore
473d3c957bd4
from cupola.web import create_app
4269478abf1d
eae9623f5d29
app = create_app()
4269478abf1d
4269478abf1d
6e59bba7998a
def init_db():
6e59bba7998a
    """
6e59bba7998a
    Create the database and populate the required data.
6e59bba7998a
    """
396ff11353bc
    db_url = current_app.config["SQLALCHEMY_DATABASE_URI"]
396ff11353bc
b43b1f854a98
    try:
b43b1f854a98
        if database_exists(db_url):
b43b1f854a98
            if "user" in db.metadata.tables.keys():
f979f34b6ef7
                click.echo(gettext(
f979f34b6ef7
                    "Database already exists, check your installation."))
b43b1f854a98
                sys.exit(1)
b43b1f854a98
        else:
b43b1f854a98
            create_database(db_url)
b43b1f854a98
    except exc.OperationalError:
396ff11353bc
        create_database(db_url)
396ff11353bc
396ff11353bc
    db_upgrade()
6e59bba7998a
    # User roles
6e59bba7998a
    # TODO: This list should probably be in the models file
6e59bba7998a
    user_datastore.create_role(name="Admin", description="Site Administrator")
6e59bba7998a
    user_datastore.create_role(name="Member", description="Member")
6e59bba7998a
396ff11353bc
    db.session.commit()
396ff11353bc
6e59bba7998a
6e59bba7998a
class _DummyEmailField(object):
6e59bba7998a
    """
6e59bba7998a
    This is a dummy WTForms Field class to use with the validator.
6e59bba7998a
    """
6e59bba7998a
    def __init__(self, data):
6e59bba7998a
        self.data = data
6e59bba7998a
6e59bba7998a
    def gettext(self, string):
6e59bba7998a
        return string
6e59bba7998a
6e59bba7998a
b43b1f854a98
class AddUserError(Exception):
b43b1f854a98
    """
b43b1f854a98
    Raise if adding a new user fails.
b43b1f854a98
    """
b43b1f854a98
    pass
b43b1f854a98
b43b1f854a98
6e59bba7998a
def add_user(email, password, roles, admin_first_run=False):
6e59bba7998a
    """
6e59bba7998a
    Add a new user.
6e59bba7998a
6e59bba7998a
    :param email: email of the new user
6e59bba7998a
    :type email: str
6e59bba7998a
    :param password: password of the new user
6e59bba7998a
    :type password: str
6e59bba7998a
    :param roles: site roles the new user should have
6e59bba7998a
    :type roles: list or tuple
6e59bba7998a
    :param admin_first_run: if first run,
6e59bba7998a
        automatically set the user "confirmed_at" field
6e59bba7998a
    :type admin_first_run: bool
6e59bba7998a
    """
6e59bba7998a
    email_test = _DummyEmailField(email)
6e59bba7998a
    email_validator = Email()
6e59bba7998a
    try:
6e59bba7998a
        email_validator(None, email_test)
6e59bba7998a
    except ValidationError:
f979f34b6ef7
        click.echo(gettext("Email is invalid"))
6e59bba7998a
        sys.exit(2)
6e59bba7998a
eae9623f5d29
    if User.query.filter_by(email=email).first():
f979f34b6ef7
        click.echo(gettext("User already exists"))
eae9623f5d29
        sys.exit(2)
6e59bba7998a
6e59bba7998a
    fullname = email.split("@")[0]
6e59bba7998a
    if admin_first_run:
6e59bba7998a
        user_datastore.create_user(email=email,
eae9623f5d29
                                   password=hash_password(password),
6e59bba7998a
                                   fullname=fullname,
6e59bba7998a
                                   confirmed_at=datetime.now())
6e59bba7998a
    else:
6e59bba7998a
        user_datastore.create_user(email=email,
eae9623f5d29
                                   password=hash_password(password),
6e59bba7998a
                                   fullname=fullname)
b43b1f854a98
    try:
b43b1f854a98
        db.session.commit()
b43b1f854a98
    except exc.IntegrityError:
b43b1f854a98
        db.session.rollback()
b43b1f854a98
        raise AddUserError("User already exists")
6e59bba7998a
6e59bba7998a
    for role in roles:
cebb99989793
        role_obj = user_datastore.find_role(role)
cebb99989793
        if role_obj:
cebb99989793
            user_datastore.add_role_to_user(email, role_obj)
6e59bba7998a
b43b1f854a98
    try:
b43b1f854a98
        db.session.commit()
b43b1f854a98
    except exc.IntegrityError:
b43b1f854a98
        db.session.rollback()
b43b1f854a98
        raise AddUserError("User roles not added correctly")
b43b1f854a98
6e59bba7998a
eae9623f5d29
@app.cli.command()
6e59bba7998a
def create_user():
6e59bba7998a
    """
6e59bba7998a
    Create a new user.
6e59bba7998a
    """
6e59bba7998a
    email = None
6e59bba7998a
    while not email:
f979f34b6ef7
        email_tmp = click.prompt(gettext("Email"))
6e59bba7998a
6e59bba7998a
        email_test = _DummyEmailField(email_tmp)
6e59bba7998a
        email_validator = Email()
6e59bba7998a
        try:
6e59bba7998a
            email_validator(None, email_test)
6e59bba7998a
        except ValidationError:
f979f34b6ef7
            click.echo(gettext("Email is invalid"))
6e59bba7998a
        else:
eae9623f5d29
            if User.query.filter_by(email=email_tmp).first():
f979f34b6ef7
                click.echo(gettext("User already exists"))
eae9623f5d29
            else:
eae9623f5d29
                email = email_tmp
6e59bba7998a
6e59bba7998a
    password = None
6e59bba7998a
    while not password:
f979f34b6ef7
        password = click.prompt(gettext("Password"), hide_input=True,
eae9623f5d29
                                confirmation_prompt=True)
6e59bba7998a
f979f34b6ef7
    admin = click.confirm(gettext("Administrator?"))
6e59bba7998a
6e59bba7998a
    roles = ("Admin",) if admin else ()
6e59bba7998a
6e59bba7998a
    add_user(email, password, roles)
6e59bba7998a
f979f34b6ef7
    click.echo(gettext("User added"))
6e59bba7998a
6e59bba7998a
eae9623f5d29
@app.cli.command()
6e59bba7998a
def first_run():
6e59bba7998a
    """
6e59bba7998a
    Init the database and
6e59bba7998a
    query the user for an administrator email/password.
6e59bba7998a
    """
6e59bba7998a
    init_db()
6e59bba7998a
6e59bba7998a
    email = None
6e59bba7998a
    while not email:
f979f34b6ef7
        email_tmp = click.prompt(gettext("Administrator email"))
6e59bba7998a
6e59bba7998a
        email_test = _DummyEmailField(email_tmp)
6e59bba7998a
        email_validator = Email()
6e59bba7998a
        try:
6e59bba7998a
            email_validator(None, email_test)
6e59bba7998a
        except ValidationError:
f979f34b6ef7
            click.echo(gettext("Email is invalid"))
6e59bba7998a
        else:
eae9623f5d29
            if User.query.filter_by(email=email_tmp).first():
f979f34b6ef7
                click.echo(gettext("User already exists"))
eae9623f5d29
            else:
eae9623f5d29
                email = email_tmp
6e59bba7998a
6e59bba7998a
    password = None
6e59bba7998a
    while not password:
f979f34b6ef7
        password = click.prompt(gettext("Administrator Password"),
f979f34b6ef7
                                hide_input=True, confirmation_prompt=True)
6e59bba7998a
6e59bba7998a
    add_user(email, password, ("Admin",), True)
6e59bba7998a
f979f34b6ef7
    click.echo(gettext("Setup complete"))