Roles en Flask

- Andrés Cruz

In english
Roles en Flask

En los sistemas típicos que se requiere proteger los recursos de una aplicación, usualmente se utilizan los roles para manejar el acceso controlado a cada uno de los recursos de nuestra aplicación, en el caso de nuestra aplicación sería el módulo de dashboard; los roles son un mecanismo para controlar y restringir el acceso a diferentes partes de una aplicación web.

Implementemos los siguientes modelos y relación foránea en la tabla de usuarios:

my_app\auth\models.py

class User(db.Model):
    ***
    roles = db.relationship('Roles', secondary='user_roles')

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)

class UserRoles(db.Model):
    __tablename__ = 'user_roles'
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))

Y aplicamos los cambios a la base de datos con:

$ python -m flask db migrate -m "User roles" 
$ python -m flask db upgrade

Como puedes deducir de los modelos anteriores, un usuario puede tener múltiples roles, por lo tanto, dependiendo de la complejidad de la aplicación, puede tener más de un rol asignado; por ejemplo, supongamos que tenemos una aplicación tipo blog con un módulo de gestión o dashboard y otro módulo de cara al usuario final, la aplicación tipo blog consiste en posts y categorías asignadas a estos posts.

Para los posts/tareas en un rol administrador:

  • Crear post.
  • Actualizar post.
  • Eliminar post.
  • Detalle/Listado post.

Para las categorías en un rol administrador:

  • Crear categoría.
  • Actualizar categoría.
  • Eliminar categoría.
  • Detalle/Listado categoría.

Para los posts en un rol editor:

  • Crear post/tareas.
  • Actualizar post/tareas (solamente las suyas).
  • Eliminar post/tareas (solamente las suyas).
  • Detalle/Listado post/tareas.

Para los posts en un rol lector:

  • Detalle/Listado post.

Para las categorías en un rol lector:

  • Detalle/Listado categoría.

En nuestro caso sería para administrar tareas en vez de posts.

Como puedes apreciar en el ejemplo anterior, tendríamos tres roles:

  1. Administrador, que puede ingresar a todos los módulos de la aplicación, específicamente los CRUDs para los posts y categorías.
  2. Editor, que solamente puede gestionar post y categorías que fueran creadas por su usuario y de lectura.
  3. Lector, solo lectura.

Para la aplicación que tenemos actualmente que es de solo tareas, no sería necesario implementar dicha lógica empleando múltiples roles.

Este es solamente un posible esquema que pudieras implementar pero puedes personalizar a gusto, por ejemplo, crear un rol super administrador o solo un rol de administrador u otros tipos de roles.

Puedes obtener más información en:

https://flask-user.readthedocs.io/en/latest/basic_app.html

Generar datos de prueba

Para la aplicación de tareas, vamos a tener los siguientes roles

  1. Lector de tareas
  2. Gestión de tareas
  3. Lector de categorías
  4. Gestión de categorías
  5. Administrador, acceso a sus recursos y el de los demás
  6. Editor, acceso solamente a sus recursos

Crearemos un usuario de prueba:

INSERT INTO `users` (`id`, `username`, `pwdhash`, `email`, `first_name`, `last_name`, `avatar_id`, `address_id`, `lang`) VALUES (2, 'editor', 'scrypt:32768:8:1$sLDyIDXY0csuow8Y$cada365071c5b0c6ece9c3e684f37e781092acfcf391ecda21914ee759e3cab5c275124e67463a177543ea888792b75995fad1d4b978a85236c5367616e0c34f', 'editor@admin.com', 'Editor', 'Cruz', 32, 4, 'EN');

Recuerda que ya tenemos un usuario creado en el capítulo 9 con ID de 1.

Crearemos los roles correspondientes:

INSERT INTO `roles` (`name`) VALUES ('READ_TASK');
INSERT INTO `roles` (`name`) VALUES ('READ_CATEGORY');
INSERT INTO `roles` (`name`) VALUES ('SAVE_TASK');
INSERT INTO `roles` (`name`) VALUES ('SAVE_CATEGORY');
INSERT INTO `roles` (`name`) VALUES ('ADMIN');
INSERT INTO `roles` (`name`) VALUES ('EDITOR');

Asignamos los roles correspondientes:

INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 3);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 4);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 5);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 1);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (1, 2);

INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (2, 3);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (2, 2);
INSERT INTO `user_roles` (`user_id`, `role_id`) VALUES (2, 6);

Decorador para verificar los roles

En ambos casos, necesitaremos un método que verifique si el usuario tiene el acceso a los roles creados anteriormente; primero, asignamos los roles en la sesión:

my_app\auth\models.py

class User(db.Model):
   ***
    @property
    def serialize(self):

        ***
        roles = ''
        if len(self.roles) > 0:
            for r in self.roles:
                roles += r.name+','

        return {
            ***
            'roles' : roles
        }

Ya que los roles son un array:

[SAVE_TASK,SAVE_CATEGORY,ADMIN,READ_TASK,READ_CATEGORY]

Para que puedan ser serializables y con esto, establecerlo en la sesión, lo convertimos a un String de roles separados por comas como hicimos anteriormente:

SAVE_TASK,SAVE_CATEGORY,ADMIN,READ_TASK,READ_CATEGORY

Creamos un decorador para verificar los roles, el cual verifica si el token suministrado desde la vista/controlador existe en el String de tokens en cargado en la sesión:

my_app\__init__.py
def roles_required(*role_names):
    def wrapper(f):
        @wraps(f)   
        def wrap(*args, **kwargs):
            for r in role_names:
                if session['user']['roles'].find(r) < 0:
                    return "You do not have the role to perform this operation", 401
                
            return f(*args, **kwargs)
        return wrap
    return wrapper
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.