"""Role Based Access Control Core
This handles the core functions for role based security for Cobalt.
See `RBAC Overview`_ for more details.
.. _RBAC Overview:
./rbac_overview.html
"""
from django.db.models import Q
from .models import (
RBACGroup,
RBACUserGroup,
RBACGroupRole,
RBACAdminUserGroup,
RBACAdminGroupRole,
RBACModelDefault,
RBACAdminTree,
RBACAdminGroup,
)
from cobalt.settings import RBAC_EVERYONE
from accounts.models import User
from organisations.models import Organisation
[docs]
def rbac_create_group(name_qualifier, name_item, description):
"""Create an RBAC group
Args:
name_qualifier(str): where in the tree the group will go
name_item(str): name
description(str): free format description
Returns:
RBACGroup
"""
group = RBACGroup.objects.filter(
name_qualifier=name_qualifier, name_item=name_item
).first()
if not group:
group = RBACGroup(
name_qualifier=name_qualifier, name_item=name_item, description=description
)
group.save()
return group
[docs]
def rbac_create_admin_group(name_qualifier, name_item, description):
"""create an admin group
Args:
name_qualifier(str): where in the tree the group will go
name_item(str): name
description(str): free format description
Returns:
RBACAdminGroup
"""
group = RBACAdminGroup.objects.filter(
name_qualifier=name_qualifier, name_item=name_item
).first()
if not group:
group = RBACAdminGroup(
name_qualifier=name_qualifier, name_item=name_item, description=description
)
group.save()
return group
[docs]
def rbac_delete_group(group):
"""Delete an RBAC group. Cascade takes care of related objects.
Args:
group(RBACGroup): Group to delete
Returns:
bool
"""
try:
group.delete()
return True
except RBACGroup.DoesNotExist:
return False
[docs]
def rbac_get_group_by_name(group_name):
"""Get an RBAC group by name
Args:
group_name(str): group name to find
Returns:
RBACGroup
"""
name_parts = group_name.split(".")
group_name_item = name_parts[-1]
group_name_qualifier = ".".join(name_parts[:-1])
try:
return RBACGroup.objects.get(
name_qualifier=group_name_qualifier, name_item=group_name_item
)
except RBACGroup.DoesNotExist:
return False
[docs]
def rbac_get_admin_group_by_name(group_name):
"""Get an RBAC Admin group by name
Args:
group_name(str): group name to find
Returns:
RBACGroup
"""
name_parts = group_name.split(".")
group_name_item = name_parts[-1]
group_name_qualifier = ".".join(name_parts[:-1])
try:
return RBACAdminGroup.objects.get(
name_qualifier=group_name_qualifier, name_item=group_name_item
)
except RBACAdminGroup.DoesNotExist:
return False
[docs]
def rbac_delete_group_by_name(group_name):
"""Delete an RBAC group by name
Args:
group_name(str): group name to delete
Returns:
bool
"""
rbac_get_group_by_name(group_name).delete()
[docs]
def rbac_delete_admin_group_by_name(group_name):
"""Delete an RBAC Admin group by name
Args:
group_name(str): group name to delete
Returns:
bool
"""
rbac_get_admin_group_by_name(group_name).delete()
[docs]
def rbac_add_user_to_group(member: User, group: RBACGroup) -> RBACGroup:
"""Adds a user to an RBAC group
Args:
member(User): standard user object
group(RBACGroup): group to add to
Returns:
RBACUserGroup
"""
user_group = RBACUserGroup.objects.filter(member=member, group=group).first()
if not user_group:
user_group = RBACUserGroup(member=member, group=group)
user_group.save()
return user_group
[docs]
def rbac_add_user_to_admin_group(member, admin_group):
"""Adds a user to an RBAC admin group
Args:
member(User): standard user object
group(RBACAdminGroup): group to add to
Returns:
Nothing
"""
if not RBACAdminUserGroup.objects.filter(group=admin_group, member=member).exists():
RBACAdminUserGroup(group=admin_group, member=member).save()
[docs]
def rbac_remove_user_from_group(member, group):
"""Removes a user from an RBAC group
Args:
member(User): standard user object
group(RBACGroup): group to remove user from
Returns:
bool
"""
try:
user_group = RBACUserGroup.objects.filter(member=member, group=group)
user_group.delete()
return True
except RBACUserGroup.DoesNotExist:
return False
[docs]
def rbac_remove_admin_user_from_group(member, group):
"""Removes a user from an RBAC admin group
Args:
member(User): standard user object
group(RBACAdminGroup): group to remove user from
Returns:
bool
"""
try:
user_group = RBACAdminUserGroup.objects.filter(member=member, group=group)
user_group.delete()
return True
except RBACAdminUserGroup.DoesNotExist:
return False
[docs]
def rbac_add_role_to_group(group, app, model, action, rule_type, model_id=None):
"""Adds a role to an RBAC group
Args:
group(RBACGroup): group
app(str): name of the app
model(str): name of the model
action(str): action
rule_type(str): Allow or Block
model_id(int): model instance (Optional)
Returns:
RBACGroupRole
"""
group_role = RBACGroupRole.objects.filter(
group=group,
app=app,
model=model,
model_id=model_id,
action=action,
rule_type=rule_type,
).first()
if not group_role:
group_role = RBACGroupRole()
group_role.group = group
group_role.app = app
group_role.model = model
group_role.action = action
group_role.rule_type = rule_type
group_role.model_id = model_id
group_role.save()
return group_role
[docs]
def rbac_user_has_role_exact(member, role):
"""check if a user has an exact role
This is called by rbac_user_has_role to check exact roles. The process
for checking an exact role is always the same. rbac_user_has_role has
the logic to put this together at a higher level and to use defaults
in order to work out if the combination of rules allows a user to do
something. This function only checks at the most specific level.
Args:
member(User): standard user object
role(str): role to check
Returns:
string: "Allow", "Block", or None for no match
"""
# If user isn't logged in, they have no access
if member.is_anonymous:
return
(app, model, model_instance, action) = role_to_parts(role)
# we also match against an action of all. e.g. if the role is:
# forums.forum.5.create then we will also accept finding:
# forums.forum.5.all.
if model_instance:
all_role = "%s.%s.%s.all" % (app, model, model_instance)
else:
all_role = "%s.%s.all" % (app, model)
groups = RBACUserGroup.objects.filter(member=member).values_list("group")
matches = RBACGroupRole.objects.filter(group__in=groups)
for m in matches:
if m.role == role or m.role == all_role:
return m.rule_type
# no match
return None
[docs]
def rbac_user_has_role_exact_explain(member, role):
"""check if a user has an exact role and explain why
Args:
member(User): standard user object
role(str): role to check
Returns:
string: "Allow", "Block", or None for no match
string: Role that matched
group: The group that matched
"""
(app, model, model_instance, action) = role_to_parts(role)
# we also match against an action of all. e.g. if the role is:
# forums.forum.5.create then we will also accept finding:
# forums.forum.5.all.
if model_instance:
all_role = "%s.%s.%s.all" % (app, model, model_instance)
else:
all_role = "%s.%s.all" % (app, model)
groups = RBACUserGroup.objects.filter(member=member).values_list("group")
matches = RBACGroupRole.objects.filter(group__in=groups)
for m in matches:
if m.role == role:
return m.rule_type, m, m.group
if m.role == all_role:
return m.rule_type, m, m.group
# no match
return (None, None, None)
[docs]
def rbac_user_has_role(member, role, debug=False):
"""check if a user has a specific role
Args:
member(User): standard user object
role(str): role to check
debug(bool): print debug info
Returns:
bool: True or False for user role
"""
# Is there a specific rule for this user and role
return_code = rbac_user_has_role_exact(member, role)
if return_code:
return allow_to_boolean(return_code)
# Is there a specific rule for Everyone and this role
everyone = User.objects.get(pk=RBAC_EVERYONE)
return_code = rbac_user_has_role_exact(everyone, role)
if return_code:
return allow_to_boolean(return_code)
# Is there a higher role. eg. for forums.forum.5 if no match then is there
# a rule for forums.forum. We only go one level up for performance reasons.
if role.count(".") == 3: # 3 levels plus action
parts = role.split(".")
role = "%s.%s.%s" % (parts[0], parts[1], parts[3]) # f.f.5.create -> f.f.create
# next level rule for this user
return_code = rbac_user_has_role_exact(member, role)
if return_code:
return allow_to_boolean(return_code)
# next level rule for everyone
return_code = rbac_user_has_role_exact(everyone, role)
if return_code:
return allow_to_boolean(return_code)
# No match or no higher rule - use default
(app, model, model_instance, action) = role_to_parts(role)
try:
default = (
RBACModelDefault.objects.filter(app=app, model=model)
.values_list("default_behaviour")
.first()[0]
)
except TypeError:
raise TypeError(
"It looks like there is no default set up for app=%s model=%s"
% (app, model)
)
return allow_to_boolean(default)
[docs]
def rbac_user_has_role_explain(member, role):
"""check if a user has a specific role and explains why
Args:
member(User): standard user object
role(str): role to check
Returns:
bool: True or False for user role
"""
log = f"Checking Member: {member} Role: {role}\n\n"
# Is there a specific rule for this user and role
(return_code, role_match, group) = rbac_user_has_role_exact_explain(member, role)
if return_code:
log += "There is a specific rule for this user\n"
log += "GroupRole: [id=%s] %s %s\n" % (
role_match.id,
role_match.role,
role_match.rule_type,
)
log += "Group: [%s] %s\n" % (group.id, group)
ret = f"{return_code}\n\n{log}"
return ret
log += "No specific rule for user\n"
# Is there a specific rule for Everyone and this role
everyone = User.objects.get(pk=RBAC_EVERYONE)
(return_code, role_match, group) = rbac_user_has_role_exact_explain(everyone, role)
if return_code:
log += "There is a specific rule for EVERYONE\n"
log += "GroupRole: [id=%s] %s %s\n" % (
role_match.id,
role_match.role,
role_match.rule_type,
)
log += "Group: [%s] %s\n" % (group.id, group)
ret = f"{return_code}\n\n{log}"
return ret
log += "No specific rule for EVERYONE\n"
# Is there a higher role. eg. for forums.forum.5 if no match then is there
# a rule for forums.forum. We only go one level up for performance reasons.
if role.count(".") == 3: # 3 levels plus action
parts = role.split(".")
role = "%s.%s.%s" % (parts[0], parts[1], parts[3]) # f.f.5.create -> f.f.create
# next level rule for this user
(return_code, role_match, group) = rbac_user_has_role_exact_explain(
member, role
)
if return_code:
log += "There is a higher level rule for this user\n"
log += "GroupRole: [id=%s] %s %s\n" % (
role_match.id,
role_match.role,
role_match.rule_type,
)
log += "Group: [%s] %s\n" % (group.id, group)
ret = f"{return_code}\n\n{log}"
return ret
log += "No higher level rule for this user\n"
# next level rule for everyone
(return_code, role_match, group) = rbac_user_has_role_exact_explain(
everyone, role
)
if return_code:
log += "There is a higher level rule for EVERYONE\n"
log += "GroupRole: [id=%s] %s %s\n" % (
role_match.id,
role_match.role,
role_match.rule_type,
)
log += "Group: [%s] %s\n" % (group.id, group)
ret = f"{return_code}\n\n{log}"
return ret
log += "No higher level rule for EVERYONE\n"
# No match or no higher rule - use default
(app, model, model_instance, action) = role_to_parts(role)
try:
default = (
RBACModelDefault.objects.filter(app=app, model=model)
.values_list("default_behaviour")
.first()[0]
)
except TypeError:
raise TypeError(
"It looks like there is no default set up for app=%s model=%s"
% (app, model)
)
log += "Using default: %s" % default
ret = f"{default}\n\n{log}"
return ret
[docs]
def allow_to_boolean(test_string):
"""takes a string and returns True if it is "Allow" """
return test_string == "Allow"
[docs]
def role_to_parts(role):
"""take a role string and return it in parts
Args:
role(str): string in format e.g. forums.forum.5.view
Returns:
tuple: (app, model, model_instance, action)
"""
parts = role.split(".")
app = parts[0]
model = parts[1]
action = parts[-1]
model_instance = parts[2] if len(parts) == 4 else None
return app, model, model_instance, action
[docs]
def rbac_user_blocked_for_model(user, app, model, action):
"""returns a list of model instances which the user cannot view
Args:
user(User): standard user object
app(str): application name
model(str): model name
action(str): action required
Returns:
list: list of model_instances explicitly block
"""
default = RBACModelDefault.objects.filter(app=app, model=model).first()
if not default:
raise ReferenceError("%s.%s not set up in RBACModelDefault" % (app, model))
if default.default_behaviour == "Block":
raise ReferenceError("Only supported for default Allow models")
# get block rules first for both this user and everyone
groups = RBACUserGroup.objects.filter(
member__in=[user.id, RBAC_EVERYONE]
).values_list("group")
everyone_matches = RBACGroupRole.objects.filter(
group__in=groups, rule_type="Block", action__in=[action, "all"]
).values_list("model_id")
# get rules for this user that allow
user_groups = RBACUserGroup.objects.filter(member=user).values_list("group")
user_matches = RBACGroupRole.objects.filter(
group__in=user_groups, rule_type="Allow", action__in=[action, "all"]
).values_list("model_id")
# allow rules for this user override block rules for everyone
ret = []
for m in everyone_matches:
if m not in user_matches:
ret.append(m[0])
return ret
[docs]
def rbac_user_allowed_for_model(user, app, model, action):
"""returns a tuple.
Args:
user(User): standard user object
app(str): application name
model(str): model name
action(str): action required
Returns:
tuple: boolean - allowed for all, list - list of model_instances explicitly allowed
"""
default = RBACModelDefault.objects.filter(app=app, model=model).first()
if not default:
raise ReferenceError("%s.%s not set up in RBACModelDefault" % (app, model))
if default.default_behaviour == "Allow":
raise ReferenceError("Only supported for default Block models")
# See if user has everything and return now if true
if rbac_user_has_role(user, "%s.%s.%s" % (app, model, action)):
return ("True", [])
# get all rules first for both this user and everyone
groups = RBACUserGroup.objects.filter(
member__in=[user.id, RBAC_EVERYONE]
).values_list("group")
everyone_matches = RBACGroupRole.objects.filter(
group__in=groups, rule_type="Allow", action__in=[action, "all"]
).values_list("model_id")
everyone_matches = [em[0] for em in everyone_matches] # strip tuple noise
# get rules for this user that block
user_groups = RBACUserGroup.objects.filter(member=user).values_list("group")
user_matches = RBACGroupRole.objects.filter(
group__in=user_groups, rule_type="Block", action__in=[action, "all"]
).values_list("model_id")
user_matches = [um[0] for um in user_matches] # strip tuple noise
# allow rules for this user override block rules for everyone
ret = [m for m in everyone_matches if m and m not in user_matches]
return False, ret
[docs]
def rbac_admin_all_rights(user):
"""returns a list of which apps, models and model_ids a user is an admin for
Args:
user(User): standard user object
Returns:
list: list of App, model, model_id
"""
groups = (
RBACAdminUserGroup.objects.filter(member=user).values_list("group").distinct()
)
matches = RBACAdminGroupRole.objects.filter(group__in=groups).distinct()
ret = []
for m in matches:
if m.model_id:
ret_str = "%s.%s.%s" % (m.app, m.model, m.model_id)
else:
ret_str = "%s.%s" % (m.app, m.model)
if ret_str not in ret:
ret.append(ret_str)
# Sort and strip any unnecessary e.g. if org.orgs is in here we don't need org.orgs.4
pretty = []
for item in ret:
if item.count(".") == 2:
higher = ".".join(item.split(".")[2])
if higher not in ret:
pretty.append(item)
else:
pretty.append(item)
pretty.sort()
return pretty
[docs]
def rbac_user_is_group_admin(member, group):
"""check if a user has admin rights to a group based upon their rights
in the tree. Note - they also need admin rights to the objects if they are
intending to change the group. This only checks for the ability to change
group membership or delete the group.
Args:
member(User): standard user object
group(RBACGroup): group to check
Returns:
bool: True of False for user role
"""
path = group.name
group_list = RBACAdminUserGroup.objects.filter(member=member).values_list("group")
trees = RBACAdminTree.objects.filter(group__in=group_list)
for tree in trees:
if tree.tree == path:
return True
# check for match on aaaaa.bbbbb.
partial = tree.tree + "."
if path.find(partial) == 0:
return True
return False
[docs]
def rbac_user_is_role_admin(member, role):
"""check if a user is an admin for a specific role
Args:
member(User): standard user object
role(str): role to check. should be from role.path e.g. forums.forum.3. No action in string.
Returns:
bool: True of False for user role
"""
groups = RBACAdminUserGroup.objects.filter(member=member).values_list("group")
matches = RBACAdminGroupRole.objects.filter(group__in=groups)
# look for specific rule
for m in matches:
# compare strings not objects
role_str = "%s" % role
if m.model_id:
m_str = "%s.%s.%s" % (m.app, m.model, m.model_id)
else:
m_str = "%s.%s" % (m.app, m.model)
if m_str == role_str:
return True
# change role org.org.15 --> org.org
parts = role.split(".")
if len(parts) == 3:
role = ".".join(parts[:-1])
# look for general rule
for m in matches:
# compare strings not objects
role_str = "%s" % role
m_str = "%s.%s" % (m.app, m.model)
if m_str == role_str:
return True
# No match
return False
[docs]
def rbac_user_is_admin_for_admin_group(member, group):
"""check if a user is an admin for an admin group. Any member of an
admin group is automatically an administrator for that group.
Args:
member(User): standard user object
group(RBACAdminGroup): admin group to check
Returns:
bool: True of False for user role
"""
users = RBACAdminUserGroup.objects.filter(group=group)
user_list = users.values_list("member", flat=True)
if member.id in user_list:
return True
else:
return False
[docs]
def rbac_access_in_english(user):
"""returns what access a user has in plain English
Args:
user(User): a standard User object
Returns:
list: list of strings with user access explained
"""
return rbac_access_in_english_sub(
RBAC_EVERYONE, "Everyone"
) + rbac_access_in_english_sub(user, user.first_name)
[docs]
def rbac_access_in_english_sub(user, this_name):
"""returns what access a user has in plain English
Args:
this_name(str): Will be Everyone or the user's first name
user(User): a standard User object
Returns:
list: list of tuples - role, strings with user access explained
"""
groups = RBACUserGroup.objects.filter(member=user).values_list("group")
roles = RBACGroupRole.objects.filter(group__in=groups).order_by("app", "model")
english = []
for role in roles:
# Generic messages first
if role.rule_type == "Allow":
verb = "can"
else:
verb = "cannot"
if role.action == "all":
action_word = "do everything"
else:
action_word = role.action
if role.model_id:
desc = f"{this_name} {verb} {action_word} in {role.model} no. {role.model_id} in the application '{role.app}'."
else:
desc = f"{this_name} {verb} {action_word} in every {role.model} in the application '{role.app}'."
# some specific hard coding
if role.app == "payments":
if role.model == "global":
if role.action in ["all", "edit"]:
desc = f"{this_name} is a global admin for the Payments module."
elif role.action == "view":
desc = (
f"{this_name} has global view access for the Payments module."
)
if role.model == "manage":
# normal users have model_ids
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if role.action in ["all", "edit"]:
desc = f"{this_name} {verb} manage payments for {org}"
elif role.action == "view":
desc = f"{this_name} {verb} view payments for {org}"
elif role.action in ["all", "edit"]:
desc = f"{this_name} {verb} manage payments for any organisation"
elif role.action == "view":
desc = f"{this_name} {verb} view payments for any organisation"
if (
role.app == "support"
and role.model == "helpdesk"
and role.action in ["all", "edit"]
):
desc = f"{this_name} {verb} use the Helpdesk module"
if (
role.app == "system"
and role.model == "admin"
and role.action in ["all", "edit"]
):
desc = f"{this_name} {verb} manage global system settings"
if role.app == "events":
if role.model == "org":
# normal users have model_ids
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if org:
desc = f"{this_name} {verb} create and run congresses for {org}"
else:
desc = f"{this_name} {verb} create and run congresses for org_id={role.model_id} (org not found)."
else:
desc = f"{this_name} {verb} create and run congresses for all organisations."
if role.model == "global":
desc = f"{this_name} {verb} manage global settings for Events such as creating new types of congress."
if role.app == "club_sessions" and role.model == "sessions":
# Normal users have a model_id
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if role.action in ["edit", "all"]:
desc = f"{this_name} can run sessions for {org}"
elif role.action == "view":
desc = f"{this_name} can view sessions for {org}"
# admin user don't have a model_id
elif role.action in ["edit", "all"]:
desc = f"{this_name} can run sessions for any club"
elif role.action == "view":
desc = f"{this_name} can view sessions for any club"
if role.app == "notifications":
if role.model == "admin" and role.action in ["all", "view"]:
desc = f"{this_name} is a global administrator for the Notifications module"
if role.model == "orgcomms":
# normal users have a model_id
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if role.action in ["all", "edit"]:
desc = f"{this_name} can administer comms for {org}"
elif role.action in ["all", "edit"]:
desc = f"{this_name} can administer comms for all organisations"
if role.model == "realtime_send":
desc = f"{this_name} can send real time messages to members such as SMS"
if role.app == "orgs":
if role.model == "admin":
desc = f"{this_name} is a global admin for Orgs. They can add or delete clubs etc."
if role.model == "members":
# normal users have a model_id
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if role.action in ["edit", "all"]:
desc = f"{this_name} can add, remove and edit members for {org}"
elif role.action in ["edit", "all"]:
desc = f"{this_name} can add, remove and edit members for any organisation"
if role.model == "org":
# normal users have a model_id
if role.model_id:
org = Organisation.objects.get(pk=role.model_id)
if role.action in ["edit", "all"]:
desc = f"{this_name} can edit details for {org} such as address and name"
elif role.action == "view":
desc = f"{this_name} can access the club menu for {org}"
elif role.action in ["edit", "all"]:
desc = f"{this_name} can edit details for any organisation"
if role.model == "state":
# normal users have a model_id
if role.model_id:
state = Organisation.objects.get(pk=role.model_id)
if role.action in ["edit", "all"]:
desc = f"{this_name} add clubs etc for state: {state}"
# Add to list
english.append((role, desc))
return english
[docs]
def rbac_get_admins_for_group(group):
"""returns a queryset of admins who can change users for a given group"""
path = group.name
# Get the groups who have access to this part of the tree
# if path is rbac.abf.forums we also want to match on rbac.abf or rbac
# this needs a SQL query like WHERE 'rbac.abf.forums' like tree||'%'
# Django can't do this so we need to use extra to add out own SQL
tree = RBACAdminTree.objects.extra(where=["%s LIKE tree||'%%'"], params=[path])
tree_groups = tree.values("group")
# Get the members of the groups
admins = RBACAdminUserGroup.objects.filter(group__in=tree_groups).distinct("member")
return admins
[docs]
def rbac_add_role_to_admin_group(group, app, model, model_id=None):
"""adds a role to an admin group"""
if not RBACAdminGroupRole.objects.filter(
group=group, app=app, model=model, model_id=model_id
).exists():
item = RBACAdminGroupRole(group=group, app=app, model=model, model_id=model_id)
item.save()
[docs]
def rbac_user_role_list(user, app, model):
"""return list of roles a user has for part of the tree.
This takes in a user and and app/model combination and returns the list of
model_ids and actions that a user can perform. For example, if you provide::
app = "forums"
model = "forum"
This could return::
[(23, "edit"), (23, "delete"), (55, "edit")]
Only returns things with "Allow" so only works for Block default models.
"""
groups = RBACUserGroup.objects.filter(member=user).values_list("group")
matches = RBACGroupRole.objects.filter(
group__in=groups, app=app, model=model, rule_type="Allow"
)
ret = []
for match in matches:
item = (match.model_id, match.action)
ret.append(item)
return ret
[docs]
def rbac_get_groups_for_role(role):
"""takes a role and lists the groups that can provide it.
Only works for allow rules with model ids"""
(app, model, model_instance, action) = role_to_parts(role)
return RBACGroupRole.objects.filter(
app=app, model=model, model_id=model_instance
).filter(Q(action=action) | Q(action="All"))
[docs]
def rbac_get_roles_for_group(group):
"""list roles that are provided by a group.
e.g. if group 17 has RBACGroupRoles:
app.model.model_id.action
org.pencil.12.edit
payments.chair.12.view
This will return:
[
{'app': 'org', 'model': 'pencil', 'action': 'edit'},
{'app': 'payments', 'model': 'chair', 'action': 'view'}
]
Args:
group(RBACGroup): group to check
Returns:
queryset dictionary: app, model, action
"""
return RBACGroupRole.objects.filter(group=group).values("app", "model", "action")
[docs]
def rbac_get_users_in_group_by_name(group_name):
"""returns a list of users in a group using the group name
Args:
group_name(str): group name to check
Returns:
list: List of users
"""
group = rbac_get_group_by_name(group_name)
return rbac_get_users_in_group(group)
[docs]
def rbac_get_users_in_group(group):
"""returns a list of users in a group
Args:
group(RBACGroup): group to check
Returns:
list: List of users
"""
return User.objects.filter(rbacusergroup__group=group).order_by("first_name")
[docs]
def rbac_get_admin_users_in_group(admin_group):
"""returns a list of users in an admin group
Args:
admin_group(RBACAdminGroup): group to check
Returns:
list: List of users
"""
return User.objects.filter(rbacadminusergroup__group=admin_group).order_by(
"first_name"
)
[docs]
def rbac_get_users_with_role(role):
"""returns a list of all users who have a role, either specifically or
from having the equivalent generic role. E.g. forums.forum.5.view would
also return users with forums.forum.view or forums.forum.all"""
(app, model, model_instance, action) = role_to_parts(role)
group_roles_specific = RBACGroupRole.objects.filter(
app=app, model=model, model_id=model_instance
).filter(Q(action=action) | Q(action="all"))
if model_instance: # check for generic too
group_roles_higher = RBACGroupRole.objects.filter(
app=app, model=model, model_id=None
).filter(Q(action=action) | Q(action="all"))
else:
group_roles_higher = RBACGroupRole.objects.none()
group_roles = group_roles_higher | group_roles_specific
groups = group_roles.distinct("group").values("group")
user_ids = (
RBACUserGroup.objects.filter(group__in=groups)
.distinct("member")
.values("member")
)
return User.objects.filter(id__in=user_ids).order_by("first_name")
[docs]
def rbac_get_users_with_exact_role(role):
"""returns a list of all users who have a role specifically,
NOT from having the equivalent generic role. E.g. forums.forum.5.view would
not also return users with forums.forum.view or forums.forum.all"""
(app, model, model_instance, action) = role_to_parts(role)
group_roles_specific = RBACGroupRole.objects.filter(
app=app,
model=model,
model_id=model_instance,
action=action,
)
groups = group_roles_specific.distinct("group").values("group")
user_ids = (
RBACUserGroup.objects.filter(group__in=groups)
.distinct("member")
.values("member")
)
return User.objects.filter(id__in=user_ids).order_by("first_name")
[docs]
def rbac_admin_tree_access(user):
"""returns a list of where in the tree a user had admin access.
Args:
user(User): standard user object
Returns:
list: list of trees
"""
groups = RBACAdminUserGroup.objects.filter(member=user).values_list("group")
matches = (
RBACAdminTree.objects.filter(group__in=groups)
.distinct("tree")
.values_list("tree")
)
return [item for match in matches for item in match]
[docs]
def rbac_user_has_admin_tree_access(user, admin_tree):
"""returns whether a user has admin access to this exact part of the tree.
Used initially by orgs for club admin so only checks the provided path, not anything higher.
e.g. if user has admin.b this will not match on admin.b.c
Args:
user(User): standard user object
admin_tree(str): tree path to check
Returns:
boolean
"""
return (
RBACAdminUserGroup.objects.filter(member=user)
.filter(group__rbacadmintree__tree=admin_tree)
.exists()
)
[docs]
def rbac_group_id_from_name(name_qualifier, name_item):
"""returns the id of a group based upon its name
Args:
name_qualifier(Str): Group qualifier
name_item(Str): Group name
Returns:
id: group id
"""
group = (
RBACGroup.objects.filter(name_qualifier=name_qualifier)
.filter(name_item=name_item)
.first()
)
return group.id if group else None
[docs]
def rbac_show_admin(request):
"""Decide whether to show the admin link on the main template to this user
Args:
request(Request): Standard request object
Returns:
boolean: True to show it, False to not show it
"""
# Show admin menu if user has any RBAC role that is not generated
return (
RBACUserGroup.objects.filter(member=request.user)
.exclude(group__name_qualifier__icontains="generated")
.exists()
)
[docs]
def rbac_user_has_any_model(member, app, model):
"""check if a user has access to any model in a role
Args:
member(User): standard user object
app(str): app to check
model(str): model to check
Returns:
bool: True or False for user role
"""
return (
RBACGroupRole.objects.filter(app=app, model=model)
.filter(group__rbacusergroup__member=member)
.exists()
)
[docs]
def rbac_admin_add_tree_to_group(group, tree):
"""Add tree to an admin group
Args:
group(RBACAdminGroup): group to update
tree(str): Tree tp add
Returns:
Nothing
"""
if not RBACAdminTree.objects.filter(group=group, tree=tree).exists():
RBACAdminTree(group=group, tree=tree).save()