I have a complete Login and Registration system in my Django app, that lets me register users, log in users, logout users, change passwords, reset passwords, invite users. I have all the basic functionality.
I want now to have Workspaces for users, as per following points:
There will be a workspace admin of each workspace
Workspace admins can only add/remove (register/delete) users to his own workspaces.
Users can react to only those workspaces to which they have been added.
A superuser (main admin) can manage all workspaces, all workspaces-admins and all users.
How do I accomplish this? You can say this is similar to a Slack thing. I just need a guidelines/roadmap and I will implement it myself.
I am thinking to create a Workspace Model and have workspaces attribute in User Model as ManyToManyField, like below:
class User(AbstractBaseUser, PermissionsMixin):
workspaces = models.ManyToManyField(Workspace)
Is this approach correct? If not please guide me to the proper way. BTW, using this approach, I can add/assign workspaces to any user, but how will I be able to manage the users logging in their own workspaces and reacting with only their workspaces to which they have been assigned. And also workspaces admins controlling their workspacesâ users etc?
Has anyone ever build this kind of thing in Django? Is their source code available? Or is there any Django official documentation on this? Or is there any free/paid course on building this kind of system in Django?
I shall be very grateful for the proper guideline or roadmap.
I would build this using the standard Django permission system with Group objects.
Iâd create a Group for each Workspace and assign users to the Group objects as appropriate.
(Effectively, this is very similar to how we manage âdepartmentsâ within our system. Thereâs a âsupervisorâ for each department with specific rights to âstaffâ in that department. The âstaffâ can modify data for their department only. The views are structured such that lists only display data to a âstaffâ where that data is associated to a department that the staff is a member of. In our case, a staff member could be a member of multiple departments.)
Itâs less an issue of how do design such a structure, and more an issue of writing the various permissions tests to ensure they work as intended. But this can all be done using Djangoâs built-in facilities.
So, if I use Djangoâs built-in functionality. Do I need to create some kind of dashboard page, where a user, after signing in, reaches after various checks like:
if a user is super user, he/she will view the dashboard that allows him to manage all workspaces (groups as you mentioned), workspace users (group members), workspace admins (how do I get this functionality if I follow Djangoâs built-in method?)
if a user is workspace admin (again how do I implement this?), he could be shown the dashboard that only allows him/her to manage those workspaces on which he/she is admin and users of those workspaces (members of those groups)
if a user is normal user, he can only be in or react with the workspace (group) he/she is assigned to (member of)
Can I have all above functionalit(ies)y using Django built-in method? Also please guide me on the questions I asked above related to implementing workspace admins.
Unfortunately, âdashboardâ is a distressingly vague term. If you want to get more specific about details, then we need to use more precise terminology.
A Django page displays content.
Views generate that content.
Views can determine what content is to be displayed on a page based on a variety of data. (That content may include links to other pages.)
Access to the view can be controlled using an implementation of the user_passes_test method in the general case, or the permission_required method is more limited situations.
You can also make this as general as needed by implementing the has_permission method on your views to allow for a very granular row-level security model for the data.
To address your primary question - Yes. I have yet to encounter any security requirement that canât be addressed by the features built in to Django - including an implementation of row-level security on a data model that depends upon data within the model to determine whether or not a user has access to that model at that specific point in time.
What you want to do is to think about these requirements in the right terms.
What views do you have?
What data is displayed in those views?
What users are accessing those views?
How do you determine what data is accessible by those users?
@KenWhitesell well, I got the workflow of using Groups for this purpose. But I still have my question unanswered. Let me ask again in other words. How do I make a user manage only few specific groups? Also I thought about the questions you shared in your reply. So let me answer them here.
Right now, I just have simple Invitation, Registration and Login views, no other data is being passed back and forth
Simple user details
Only admins can access Invitation Views, and anyone can access other views
By using user.is_admin attribute
OK these were answers to the questions you told me to think about. Now let me explain my question. Suppose I have created 10 groups viz. group_a, group_b, group_c and so on, each have different unique name.
And I have 5 users viz. user_a, user_b and so on.
How would I accomplish that user_c get to manage (change group member details, add members to group and remove members from group) only group_b and not other groups?
First, my apologies - I should have been more clear. Those questions I posed at the end of my previous response are items you need to be thinking about when youâre building this out - they werenât intended to elicit answers from you here.
You write code that tests or limits that at the appropriate points.
For example, say you have a âdetails pageâ for a user, and you want to provide a widget allowing you to add that user to a group.
Your code to generate the list of groups able to be added would be filtered to only be the groups that the current user can add to the selected user.
Or if youâre looking at it from the other direction, say you have a page that lets you select a group and add or remove users.
Again, your code would filter that group selection list to those groups that the current user can manage.
This might sound like a lot of work to do this, but in reality it turns out to be not that bad. The key is to not code these individually in the views, but to construct generic methods that can be used across views.
We did it by implementing a custom Manager class with the basic functionality to perform these tests, and then had our individual models (about 50 of them being managed this way) use that custom manager. The forms creating the select widgets use those manager functions to get the valid choices for that user at that point in time.
Which group? Did you consider âdetails pageâ a group?
Can you please demonstrate this with a short and simple example of code/pseudo-code?
OK, I understood this. But it is only possible to filter those groups, if we know that which groups are being managed by the user or in other words say if we implicitly make the user to manage some specific groups only then we can filter-out those specific groups. So, filtering the groups is the second step, first step is to make user manage some specific group rather than managing all groups (like admin/superuser) and my question was exactly this, that how can we make user to manage only some specific groups?
I donât know what youâre trying to ask here. Iâm talking about a Detail View for a User - a page on which you can add / remove that User to groups.
The view that generates the edit page for âgroup_aâ then has a get_permission_required method that checks the url to identify that the user is trying to edit âgroup_aâ, and returns ['manage_group_a']
If that user does not have that permission, the has_permission test fails and the user can be redirected elsewhere.
Thank you for detailed explanation. I understood the terminology now. But by calling get_permission_required() method, how will the method recognize that the user trying to edit group_a should have manage_group_a permissions? Do I need to add such permissions? If yes, then where should I add and how should I add and how should I get those permissions?
You could either construct the permission name (e.g. return ['manage_'+group_name]) or, you could add an attribute to your Workspaces model (e.g. having something like management_permission = CharField(...) in the model. Or you could come up with something else entirely.
Looking at this, if I want to create a permission for Workspace admin to remove the other users in the same workspace, should I do something like below?
Close - since youâre managing this on a per-workspace instance, and need to create these permissions for each Workspace, it should more be something like:
permission = Permission.objects.create(
codename='can_remove_from_workspace_%s' % workspace_name,
name='Can Remove User From Workspace %s' % workspace_name,
content_type=content_type,
)
(Values selected for discussion purposes only. Should be changed as you see fit.)
OK, but I am still confused. How will the django or more specifically the Workspace will know that the permission that has name "can_remove_from_workspace_a", is used to remove the users from Workspace? I mean how does it know that this specific functionality (removing users from workspace) should be allowed? Because looking at the permission variable, it just has a name, a codename and content_type, nothing else specific that could tell the Workspace Model what is this permission of.
Thatâs what ties back to the earlier references to the get_permission_required and has_permission methods referenced in message #8 above.
You use these permissions in your methods to determine whether or not a user is allowed to perform a certain action. (Which may include retrieving lists, or accessing views, etc.)
Someone going to a view allowing for a user to be removed from âworkspace_aâ needs to have the permission can_remove_from_workspace_a.
and I have the following permission for the Workspace model:
content_type = ContentType.objects.get_for_model(Workspace)
permission = Permission.objects.create(
codename='can_remove_from_workspace_%s' % workspace_name,
name='Can Remove User From Workspace %s' % workspace_name,
content_type=content_type,
)
and in the views.py, I have:
class WorkspaceView(PermissionRequiredMixin, View):
permission_required = 'can_remove_from_workspace_%s' % workspace_name
So I have few questions related to above supposition:
Do I need to create separate View for each permission, e.g. AddWorkspaceUser(PermissionRequiredMixin, View) for adding user in the same Workspace instance, RemoveWorkspaceUser(PermissionRequiredMixin, View) for removing user from Workspace instance and so on for each Workspace use case?
How do I add the permission to user? For example, a user is going to view WorkspaceView, he/she can only see the webpage if he/she has the required permissions and I know that can be checked using has_permission method. But how do I add the permission to user so he/she gets able to view the page in WorkspaceView?
Secondly, as you can see from the code above, I have is_workspace_admin attribute in my User Model. So how can I get this functionality that if user has is_workspace_admin=True, then automatically adds the Workspace admin permissions in the User instance, and if it has is_workspace_admin=False, then automatically remove the related permissions, the same/similar functionality that we have with is_admin attribute.
Where do I add the code in which I am adding permissions? Do I need to create a separate file permissions.py or can I add it in models.py?
That is not going to work because you donât know what the workspace_name is going to be at the time the instance of the class is created. That is why you must implement this in the get_permissions_required method.
Wouldnât you have separate views for those functions anyway?
Even if not, no you donât. If youâve got separate functionality within a view to perform those disparate functions in the same view, you can still check permissions within that âsubfunctionâ.
You donât.
The Permission is assigned to a Django Group, e.g. workspace_a_admin.
You then add that user to that group. That user then inherits all the permissions granted to that group.
Your model design for this is wrong. There is no reason to have a is_workspace_admin attribute on the user. The userâs status regarding their ability to administer a group is defined by their Group membership.
In the view where youâre creating the Workspace. (And likewise, if you allow for a Workspace to be deleted, youâll want to remove these permissions as well.)
Youâll probably want to create the necessary Group objects in this same location as well as assigning the permissions to the Group.
OK, sure. But how would the Djangoâs Group know that what functionality the permission that is being added in Django Group, should allow?
Fine, but I also want the user that has is_admin=True be able to add the Workspace Admin Permissions to any other user. How would I achieve this if I remove is_workspace_admin attribute?
âItâ doesnât. Nor does it care. The Group doesnât perform any validation, the view does.
If you need a Group to identify who can create âWorkspace Adminsâ, then create another group with the related permissions, and protect those views accordingly.
This all is a traditional âRole-Based Securityâ structure.
You define the Roles that people have.
Those Roles are represented within Django as Group objects.
Those Roles enumerate the Permissions that the members of that Role possess.
It doesnât matter what the functionality being protected is. The Permissions define what Roles are allowed to perform that function. Users made members of those Roles then are allowed to perform those functions.
Sorry apologies, I meant is_supeuser=True, not is_admin.
So, considering above correction, I guess I would not need to create a separate group, as Django already have permissions for superuser. So, I now need a functionality as the Django default User Model has, like when we make any user have is_superuser=True, then all the Superuser Permissions automatically get attached to that user and if we make it is_supeuser=False, then it takes away the Superuser Permissions. So I want a similar functionality that when a "User A" that has is_superuser=True, makes the is_workspace_admin=True of "User B", then "User B" should automatically gets all the Workspace Admin Permissions. In other words, I want something like this:
workspace_user = User.objects.get(email="some-email-address")
if request.user.is_superuser:
wordspace_user.permissions.add([Workspace Admin Permissions])
There are a handful of options here, depending upon the precise implications of what youâre trying to achieve.
For example, if you want âUser Bâ to always have all rights to all views controlled by any *_admin group, you have at least two choices for this option.
You can create a Group named all_admin, and assign the necessary permissions to this group in addition to assigning them to their specific groups when those other groups are created.
Or, you could override the has_permission method to check for membership in that group directly. Check to see if the user is a member of all_admin first.
For example, the standard has_permission method looks like this: (copied from Django 3.2)
def has_permission(self):
"""
Override this method to customize the way permissions are checked.
"""
perms = self.get_permission_required()
return self.request.user.has_perms(perms)
Your overriding method could look something like:
def has_permission(self):
if self.request.user.groups.filter(name='all_admin').exists():
return True
return super().has_permission()