Roles in Flask

- Andrés Cruz

En español
Roles in Flask

In typical systems that require protecting the resources of an application, roles are usually used to manage controlled access to each of the resources of our application, in the case of our application it would be the dashboard module; roles are a mechanism to control and restrict access to different parts of a web application.

Let's implement the following models and foreign relationship in the users table:

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'))

And we apply the changes to the database with:

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

As you can deduce from the previous models, a user can have multiple roles, therefore, depending on the complexity of the application, they can have more than one role assigned; for example, suppose we have a blog application with a management or dashboard module and another module facing the end user, the blog application consists of posts and categories assigned to these posts.

For posts/tasks in an admin role:

  • Create post.
  • Update post.
  • Delete post.
  • Detail/Post List.

For categories in an administrator role:

  • Create category.
  • Update category.
  • Delete category.
  • Detail/Category List.

For posts in an editor role:

  • Create post/tasks.
  • Update posts/tasks (only yours).
  • Delete posts/tasks (only yours).
  • Detail/List post/tasks.

For posts in a reader role:

  • Detail/Post List.

For categories in a reader role:

  • Detail/Category List.

In our case it would be to manage tasks instead of posts.

As you can see in the previous example, we would have three roles:

  1. Administrator, who can access all modules of the application, specifically the CRUDs for posts and categories.
  2. Editor, which can only manage posts and categories that were created by its user and read.
  3. Reader, read only.

For the application we currently have that is task-only, it would not be necessary to implement such logic using multiple roles.

This is just a possible scheme that you could implement but you can customize as you wish, for example, creating a super administrator role or just an administrator role or other types of roles.

You can get more information at:

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

Generate test data

For the task application, we will have the following roles

  1. Task reader
  2. Task management
  3. Category Reader
  4. Category management
  5. Administrator, access to your resources and those of others
  6. Editor, access only to your resources

We will create a test user:

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');

Remember that we already have a user created in chapter 9 with ID of 1.

We will create the corresponding roles:

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');

We assign the corresponding roles:

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);

Decorator to verify roles

In both cases, we will need a method that checks if the user has access to the roles created previously; first, we assign the roles in the session:

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
        }

Since the roles are an array:

[SAVE_TASK,SAVE_CATEGORY,ADMIN,READ_TASK,READ_CATEGORY]

So that they can be serializable and with this, establish it in the session, we convert it to a String of roles separated by commas as we did before:

SAVE_TASK,SAVE_CATEGORY,ADMIN,READ_TASK,READ_CATEGORY

We create a decorator to verify the roles, which checks if the token supplied from the view/controller exists in the token String loaded in the session:

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.