Ejemplo n.º 1
0
def delete_user(username):
    """Delete a user, and return HTML success/failure message."""
    user = User(username, False)
    if not user.user:
        return user_management_failure_message("No such user: "******"user", user.user)
    audit("User deleted: {}".format(user.user))
    return user_management_success_message("User " + user.user + " deleted")
Ejemplo n.º 2
0
def set_password_directly(username, password):
    """If the user exists, set its password. Returns Boolean success."""
    user = User(username, False)
    if not user.user:
        return False
    user.set_password(password)
    user.save()
    user.enable()
    audit("Password changed for user " + user.user, from_console=True)
    return True
Ejemplo n.º 3
0
def act_on_login_failure(username):
    """Record login failure and lock out user if necessary."""
    audit("Failed login as user: {}".format(username))
    record_login_failure(username)
    nfailures = how_many_login_failures(username)
    nlockouts = nfailures // pls.LOCKOUT_THRESHOLD
    nfailures_since_last_lockout = nfailures % pls.LOCKOUT_THRESHOLD
    if nlockouts >= 1 and nfailures_since_last_lockout == 0:
        # new lockout required
        lockout_minutes = nlockouts * pls.LOCKOUT_DURATION_INCREMENT_MINUTES
        lock_user_out(username, lockout_minutes)
Ejemplo n.º 4
0
def lock_user_out(username, lockout_minutes):
    """Lock user out for a specified number of minutes."""
    lock_until = pls.NOW_UTC_NO_TZ + datetime.timedelta(
        minutes=lockout_minutes)
    pls.db.db_exec(
        "INSERT INTO " + SECURITY_ACCOUNT_LOCKOUT_TABLENAME +
        " (user, locked_until) VALUES (?, ?)",
        username,
        lock_until)
    audit("Account {} locked out for {} minutes".format(username,
                                                        lockout_minutes))
Ejemplo n.º 5
0
def get_multiple_views_data_as_tsv_zip(tables):
    """Returns the data from multiple views, as multiple TSV files in a ZIP."""
    tables = validate_table_list(tables)
    if not tables:
        return None
    memfile = io.BytesIO()
    z = zipfile.ZipFile(memfile, "w")
    for t in tables:
        result = get_view_data_as_tsv(t, prevalidated=True,
                                      audit_individually=False)
        z.writestr(t + ".tsv", result.encode("utf-8"))
    z.close()
    audit("dump as TSV ZIP: " + " ".join(tables))
    return memfile.getvalue()
Ejemplo n.º 6
0
def change_password(username, form, as_manager=False):
    """Change password, and return success/failure HTML."""
    user = User(username, False)
    if not user.user:
        return user_management_failure_message(
            "Problem: can't find user " + username, as_manager)
    old_password = ws.get_cgi_parameter_str(form, PARAM.OLD_PASSWORD)
    new_password_1 = ws.get_cgi_parameter_str(form, PARAM.NEW_PASSWORD_1)
    new_password_2 = ws.get_cgi_parameter_str(form, PARAM.NEW_PASSWORD_2)
    must_change_password = ws.get_cgi_parameter_bool(
        form, PARAM.MUST_CHANGE_PASSWORD)
    if new_password_1 != new_password_2:
        return user_management_failure_message("New passwords don't match",
                                               as_manager)
    if len(new_password_1) < MINIMUM_PASSWORD_LENGTH:
        return user_management_failure_message(
            "New password must be at least {} characters; not changed.".format(
                MINIMUM_PASSWORD_LENGTH
            ),
            as_manager
        )
    if old_password == new_password_1 and not as_manager:
        return user_management_failure_message(
            "Old/new passwords are the same",
            as_manager
        )
    if (not as_manager) and (not user.is_password_valid(old_password)):
        return user_management_failure_message("Old password incorrect",
                                               as_manager)

    # OK
    user.set_password(new_password_1)
    user.save()

    if not as_manager:
        must_change_password = False
    if must_change_password:
        user.force_password_change()

    audit("Password changed for user " + user.user)
    return user_management_success_message(
        "Password updated for user {}.".format(user.user),
        as_manager,
        """<div class="important">
            If you store your password in your CamCOPS tablet application,
            remember to change it there as well.
        </div>"""
    )
Ejemplo n.º 7
0
def get_database_dump_as_sql(tables=[]):
    """Returns a database dump of all the tables requested, in SQL format."""
    tables = validate_table_list(tables)
    if not tables:
        return NOTHING_VALID_SPECIFIED

    # We'll need to re-fetch the database password,
    # since we don't store it (for security reasons).
    config = ConfigParser.ConfigParser()
    config.read(pls.CAMCOPS_CONFIG_FILE)

    # -------------------------------------------------------------------------
    # SECURITY: from this point onwards, consider the possibility of a
    # password leaking via a debugging exception handler
    # -------------------------------------------------------------------------
    try:
        DB_PASSWORD = config.get(CONFIG_FILE_MAIN_SECTION, "DB_PASSWORD")
    except Exception as e:  # deliberately conceal details for security
        raise RuntimeError(
            "Problem reading DB_PASSWORD from config: {}".format(e))
    if DB_PASSWORD is None:
        raise RuntimeError("No database password specified")
        # OK from a security perspective: if there's no password, there's no
        # password to leak via a debugging exception handler

    # Database:
    try:
        audit("dump as SQL: " + " ".join(tables))
        return subprocess.check_output([
            pls.MYSQLDUMP,
            "-h", pls.DB_SERVER,  # rather than --host=X
            "-P", str(pls.DB_PORT),  # rather than --port=X
            "-u", pls.DB_USER,  # rather than --user=X
            "-p{}".format(DB_PASSWORD),
            # neither -pPASSWORD nor --password=PASSWORD accept spaces
            "--opt",
            "--hex-blob",
            "--default-character-set=utf8",
            pls.DB_NAME,
        ] + tables).decode('utf8')
    except:  # deliberately conceal details for security
        raise RuntimeError("Problem opening or reading from database; "
                           "details concealed for security reasons")
    finally:
        # Executed whether an exception is raised or not.
        DB_PASSWORD = None
Ejemplo n.º 8
0
def get_view_data_as_tsv(view, prevalidated=False, audit_individually=True):
    """Returns the data from the view specified, in TSV format."""
    # Views need special handling: mysqldump will provide the view-generating
    # SQL, not the contents. If the output is saved as .XLS, Excel will open it
    # without prompting for conversion.
    if not prevalidated:
        view = validate_single_table(view)
        if not view:
            return "Invalid table or view"
    # Special blob handling...
    if view == cc_blob.Blob.TABLENAME:
        query = (
            "SELECT "
            + ",".join(cc_blob.Blob.FIELDS_WITHOUT_BLOB)
            + ",HEX(theblob) FROM " + cc_blob.Blob.TABLENAME
        )
    else:
        query = "SELECT * FROM " + view
    if audit_individually:
        audit("dump as TSV: " + view)
    return get_query_as_tsv(query)
Ejemplo n.º 9
0
def create_superuser(username, password):
    """Create a superuser."""
    user = User(username, False)
    if user.user:
        # already exists!
        return False
    user = User(username, True)

    user.may_upload = True
    user.may_register_devices = True
    user.may_use_webstorage = True
    user.may_use_webviewer = True
    user.may_view_other_users_records = True
    user.view_all_patients_when_unfiltered = True
    user.superuser = True
    user.may_dump_data = True
    user.may_run_reports = True
    user.may_add_notes = True

    user.set_password(password)
    user.save()
    audit("SUPERUSER CREATED: " + user.user, from_console=True)
    return True
Ejemplo n.º 10
0
def add_user(form):
    """Add a user, and return HTML success/failure message."""
    username = ws.get_cgi_parameter_str(form, PARAM.USERNAME)
    password_1 = ws.get_cgi_parameter_str(form, PARAM.PASSWORD_1)
    password_2 = ws.get_cgi_parameter_str(form, PARAM.PASSWORD_2)
    must_change_password = ws.get_cgi_parameter_bool(
        form, PARAM.MUST_CHANGE_PASSWORD)

    may_use_webviewer = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_USE_WEBVIEWER)
    may_view_other_users_records = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_VIEW_OTHER_USERS_RECORDS)
    view_all_patients_when_unfiltered = ws.get_cgi_parameter_bool(
        form, PARAM.VIEW_ALL_PTS_WHEN_UNFILTERED)
    may_upload = ws.get_cgi_parameter_bool(form, PARAM.MAY_UPLOAD)
    superuser = ws.get_cgi_parameter_bool(form, PARAM.SUPERUSER)
    may_register_devices = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_REGISTER_DEVICES)
    may_use_webstorage = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_USE_WEBSTORAGE)
    may_dump_data = ws.get_cgi_parameter_bool(form, PARAM.MAY_DUMP_DATA)
    may_run_reports = ws.get_cgi_parameter_bool(form, PARAM.MAY_RUN_REPORTS)
    may_add_notes = ws.get_cgi_parameter_bool(form, PARAM.MAY_ADD_NOTES)

    user = User(username, False)
    if user.user:
        return user_management_failure_message(
            "User already exists: " + username)
    if not is_username_permissible(username):
        return user_management_failure_message(
            "Invalid username: "******"Passwords don't mach")
    if len(password_1) < MINIMUM_PASSWORD_LENGTH:
        return user_management_failure_message(
            "Password must be at least {} characters".format(
                MINIMUM_PASSWORD_LENGTH
            ))

    user = User(username, True)
    user.set_password(password_1)

    user.may_use_webviewer = may_use_webviewer
    user.may_view_other_users_records = may_view_other_users_records
    user.view_all_patients_when_unfiltered = view_all_patients_when_unfiltered
    user.may_upload = may_upload
    user.superuser = superuser
    user.may_register_devices = may_register_devices
    user.may_use_webstorage = may_use_webstorage
    user.may_dump_data = may_dump_data
    user.may_run_reports = may_run_reports
    user.may_add_notes = may_add_notes

    user.save()
    if must_change_password:
        user.force_password_change()

    audit(
        (
            "User created: {}: "
            "may_use_webviewer={}, "
            "may_view_other_users_records={}, "
            "view_all_patients_when_unfiltered={}, "
            "may_upload={}, "
            "superuser={}, "
            "may_register_devices={}, "
            "may_use_webstorage={}, "
            "may_dump_data={}, "
            "may_run_reports={}, "
            "may_add_notes={}, "
            "must_change_password={}"
        ).format(
            user.user,
            may_use_webviewer,
            may_view_other_users_records,
            view_all_patients_when_unfiltered,
            may_upload,
            superuser,
            may_register_devices,
            may_use_webstorage,
            may_dump_data,
            may_run_reports,
            may_add_notes,
            must_change_password
        )
    )
    return user_management_success_message("User " + user.user + " created")
Ejemplo n.º 11
0
def change_user(form):
    """Apply changes to a user, and return success/failure HTML."""
    username = ws.get_cgi_parameter_str(form, PARAM.USERNAME)
    may_use_webviewer = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_USE_WEBVIEWER)
    may_view_other_users_records = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_VIEW_OTHER_USERS_RECORDS)
    view_all_patients_when_unfiltered = ws.get_cgi_parameter_bool(
        form, PARAM.VIEW_ALL_PTS_WHEN_UNFILTERED)
    may_upload = ws.get_cgi_parameter_bool(form, PARAM.MAY_UPLOAD)
    superuser = ws.get_cgi_parameter_bool(form, PARAM.SUPERUSER)
    may_register_devices = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_REGISTER_DEVICES)
    may_use_webstorage = ws.get_cgi_parameter_bool(
        form, PARAM.MAY_USE_WEBSTORAGE)
    may_dump_data = ws.get_cgi_parameter_bool(form, PARAM.MAY_DUMP_DATA)
    may_run_reports = ws.get_cgi_parameter_bool(form, PARAM.MAY_RUN_REPORTS)
    may_add_notes = ws.get_cgi_parameter_bool(form, PARAM.MAY_ADD_NOTES)

    user = User(username, False)
    if not user.user:
        return user_management_failure_message("Invalid user: "******"User permissions edited for user {}: "
            "may_use_webviewer={}, "
            "may_view_other_users_records={}, "
            "view_all_patients_when_unfiltered={}, "
            "may_upload={}, "
            "superuser={}, "
            "may_register_devices={}, "
            "may_use_webstorage={}, "
            "may_dump_data={}, "
            "may_run_reports={}, "
            "may_add_notes={} "
        ).format(
            user.user,
            may_use_webviewer,
            may_view_other_users_records,
            view_all_patients_when_unfiltered,
            may_upload,
            superuser,
            may_register_devices,
            may_use_webstorage,
            may_dump_data,
            may_run_reports,
            may_add_notes,
        )
    )
    return user_management_success_message(
        "Details updated for user " + user.user)
Ejemplo n.º 12
0
def enable_user(username):
    """Unlock user and clear login failures."""
    unlock_user(username)
    clear_login_failures(username)
    audit("User {} re-enabled".format(username))