4

I would like to use Flask-Security with RethinkDB, but I cannot get my custom RethinkDBUserDatastore to work properly. Below is the code I've created for the custom data store, plus the adapted example app from the Flask-Security documentation.

rethinkdb_security.py file is the implementation of the Flask-Security classes necessary to create a relevant datastore objects, based on the examples provided for SQLAlchemy and Mongo. Below is the current source code I'm using:

from flask_security import UserMixin, RoleMixin
from flask_security.datastore import Datastore
from flask_security.datastore import UserDatastore
import rethinkdb as r

class Bunch(object):
    def __init__(self, obj, **kws):
        self.__dict__.update(obj)
        self.__dict__.update(kws)

    def __repr__(self):
        return 'Bunch({})'.format(repr(self.__dict__))


class User(Bunch, UserMixin):
    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)

        if not hasattr(self, 'active'):
            self.active = True

        if not hasattr(self, 'roles'):
            self.roles = []


class Role(Bunch, RoleMixin):
    def __init__(self, *args, **kwargs):
        super(Role, self).__init__(*args, **kwargs)

class RethinkDBDatastore(Datastore):
    def commit(self):
        pass

    def put(self, model):
        return model

    def delete(self, model):
        pass

class RethinkDBUserDatastore(UserDatastore):
    def __init__(self, db_conn, user_table='users', role_table='roles', user_pk='id', role_pk='id'):
        self.db_conn = db_conn
        self.user_table = user_table
        self.role_table = role_table
        self.user_pk = user_pk
        self.role_pk = role_pk
        RethinkDBDatastore.__init__(self, db=db_conn)

    def commit(self):
        pass

    def get_user(self, id_or_email):
        obj = r.table(self.user_table).get(id_or_email).run(self.db_conn)
        return User(obj)

    def find_user(self, **query):
        return User(r.table(self.user_table).filter(query)[0].run(self.db_conn))

    def find_role(self, **query):
        return Role(r.table(self.role_table).filter(query)[0].run(self.db_conn))

    def add_role_to_user(self, user, role):
        return r.table(self.user_table) \
                .get(user[self.user_pk]) \
                .update({'roles': r.row['roles'].default([]).set_insert(role.name)}) \
                .run(self.db_conn)

    def remove_role_from_user(self, user, role):
        return r.table(self.user_table) \
                .get(user[self.user_pk]) \
                .update({'roles': r.row['roles'].default([]).set_difference([role.name])}) \
                .run(self.db_conn)

    def toggle_active(self, user):
        user['active'] = not user.get('active', True)
        return True

    def deactivate_user(self, user):
        active = user.get('active', True)
        if active:
            user['active'] = False
            return True
        return False

    def activate_user(self, user):
        active = user.get('active', True)
        if not active:
            user['active'] = True
            return True
        return False

    def create_role(self, **role):
        result = r.table(self.role_table).insert(role, return_changes='always').run(self.db_conn)
        return result['changes'][0]['new_val']

    def create_user(self, **user):
        if 'roles' in user:
            user['roles'] = [role['name'] for role in roles]

        if 'id' not in user:
            user['id'] = user['email'] or user['username']

        result = r.table(self.user_table).insert(user, return_changes='always').run(self.db_conn)
        return result['changes'][0]['new_val']

    def delete_user(self, user):
        r.table(self.user_table).get(user[self.user_pk]).delete().run(self.db_conn)

flask_security_example.py file is an example app I've created to test this datastore adapter. It runs, but throws an error when attempting to authenticate with the example user created in the create_user function.

import bcrypt
import rethinkdb as r
from flask import Flask, render_template
from flask_security import Security, login_required
from rethinkdb import ReqlRuntimeError
from rethinkdb import ReqlOpFailedError
from rethinkdb_security import RethinkDBUserDatastore

# Get database connection information
DB_HOST = os.environ.get('DB_HOST', 'localhost')
DB_PORT = os.environ.get('DB_PORT', 28015)
DB_NAME = os.environ.get('DB_NAME', 'flask_security')
DB_TABLE = os.environ.get('DB_TABLE', 'users')

# Create app
APP = Flask(__name__)
APP.config['DEBUG'] = True
APP.config['SECRET_KEY'] = 'super-secret'
APP.config['SECURITY_PASSWORD_SALT'] = bcrypt.gensalt()

# Create the database connection
DB_CONN = r.connect(host=DB_HOST, port=DB_PORT, db=DB_NAME)

# Setup Flask-Security
USER_DATASTORE = RethinkDBUserDatastore(DB_CONN, user_table=DB_TABLE)
SECURITY = Security(APP, USER_DATASTORE)

# Create a user to test with
@APP.before_first_request
def create_user():
    try:
        r.db_create(DB_NAME).run(DB_CONN)
    except ReqlRuntimeError:
        pass
    try:
        r.db(DB_NAME).table_create(DB_TABLE).run(DB_CONN)
    except ReqlOpFailedError:
        pass
    USER_DATASTORE.create_user(email='test@test.com', password='password')

# Views
@APP.route('/')
@login_required
def home():
    return "Hello, world."

if __name__ == '__main__':
    APP.run()

Is there something in particular that I'm doing wrong or some guidance that can be given as to how to create custom datastore adapters for Flask-Security?

Nicholas Tulach
  • 1,023
  • 3
  • 12
  • 35

0 Answers0