Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authz: Allow global maintainers/owner to add Product Types #5410

Merged
merged 5 commits into from
Nov 16, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/content/en/usage/permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ draft: false

* Administrators (aka super users) have no limitations in the system. They can change all settings, manage users and have read and write access to all data.
* Staff users can add Product Types, and have access to data according to their role in a Product or Product Type. There is the parameter `AUTHORIZATION_STAFF_OVERRIDE` in the settings to give all staff users full access to all Products and Product Types.
* Guest users have limited functionality available. They cannot add Product Types but have access to data according to their role in a Product or Product Type
* Regular users have limited functionality available. They cannot add Product Types but have access to data according to their role in a Product or Product Type

## Product and Product Type permissions

Expand All @@ -19,7 +19,7 @@ Users can be assigned as members to Products and Product Types, giving them one

| | Reader | Writer | Maintainer | Owner | API Importer |
|-----------------------------|:------:|:------:|:----------:|:-----:|:------------:|
| Add Product Type <sup>1)</sup> | | | | | |
| Add Product Type | | | <sup>1)</sup> |<sup>1)</sup> | |
| View Product Type | x | x | x | x | x |
| Remove yourself as a member | x | x | x | x | |
| Manage Product Type members | | | x | x | |
Expand Down Expand Up @@ -73,7 +73,7 @@ Users can be assigned as members to Products and Product Types, giving them one
| Delete Note | | (x) <sup>2)</sub> | x | x | |


<sup>1)</sup> Every staff user and administrator can add Product Types. Guest users are not allowed to add Product Types.
<sup>1)</sup> Every staff user and administrator can add Product Types. Regular users are not allowed to add Product Types, unless they are Global Owner or Maintainer.

<sup>2)</sup> Every user is allowed to delete his own notes.

Expand Down Expand Up @@ -105,7 +105,7 @@ The membership of a group itself has a role that determines what permissions the
| Add Group member as Owner | | | x |
| Delete Group | | | x |

<sup>1)</sup> Every staff user and administrator can add groups. Guest users are not allowed to add groups.
<sup>1)</sup> Every staff user and administrator can add groups. Regular users are not allowed to add groups.

The permissions to manage the roles of Products and Product types for a group is defined by the role of the user in the respective Product or Product Type.

Expand Down
4 changes: 2 additions & 2 deletions dojo/api_v2/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from dojo.models import Endpoint, Engagement, Finding, Product_Type, Product, Test, Dojo_Group
from django.shortcuts import get_object_or_404
from rest_framework import permissions, serializers
from dojo.authorization.authorization import user_has_permission
from dojo.authorization.authorization import user_has_global_permission, user_has_permission
from dojo.authorization.roles_permissions import Permissions


Expand Down Expand Up @@ -215,7 +215,7 @@ def has_object_permission(self, request, view, obj):
class UserHasProductTypePermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.method == 'POST':
return request.user.is_staff
return user_has_global_permission(request.user, Permissions.Product_Type_Add)
else:
return True

Expand Down
43 changes: 38 additions & 5 deletions dojo/authorization/authorization.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.core.exceptions import PermissionDenied
from django.conf import settings
from dojo.request_cache import cache_for_request
from dojo.authorization.roles_permissions import Permissions, Roles, get_roles_with_permissions
from dojo.authorization.roles_permissions import Permissions, Roles, get_global_roles_with_permissions, get_roles_with_permissions
from dojo.models import Product_Type, Product_Type_Member, Product, Product_Member, Engagement, \
Test, Finding, Endpoint, Finding_Group, Product_Group, Product_Type_Group, Dojo_Group, Dojo_Group_Member, \
Languages, App_Analysis, Stub_Finding, Product_API_Scan_Configuration
Expand All @@ -17,11 +17,8 @@ def user_has_permission(user, obj, permission):

if isinstance(obj, Product_Type) or isinstance(obj, Product):
# Global roles are only relevant for product types, products and their dependent objects
if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return True
for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return True

if isinstance(obj, Product_Type):
# Check if the user has a role for the product type with the requested permissions
Expand Down Expand Up @@ -95,11 +92,33 @@ def user_has_permission(user, obj, permission):
format(type(obj).__name__, permission))


def user_has_global_permission(user, permission):
if user.is_superuser:
return True

if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return True

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_global_permission(user.global_role.role.id, permission):
return True

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_global_permission(group.global_role.role.id, permission):
return True

return False


def user_has_permission_or_403(user, obj, permission):
if not user_has_permission(user, obj, permission):
raise PermissionDenied


def user_has_global_permission_or_403(user, permission):
if not user_has_global_permission(user, permission):
raise PermissionDenied


def get_roles_for_permission(permission):
if not Permissions.has_value(permission):
raise PermissionDoesNotExistError('Permission {} does not exist'.format(permission))
Expand All @@ -119,9 +138,23 @@ def role_has_permission(role, permission):
raise RoleDoesNotExistError('Role {} does not exist'.format(role))
roles = get_roles_with_permissions()
permissions = roles.get(role)
if not permissions:
return False
return permission in permissions


def role_has_global_permission(role, permission):
if role is None:
return False
if not Roles.has_value(role):
raise RoleDoesNotExistError('Role {} does not exist'.format(role))
roles = get_global_roles_with_permissions()
permissions = roles.get(role)
if permissions and permission in permissions:
return True
return role_has_permission(role, permission)


class NoAuthorizationImplementedError(Exception):
def __init__(self, message):
self.message = message
Expand Down
19 changes: 17 additions & 2 deletions dojo/authorization/authorization_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404
from dojo.authorization.authorization import user_has_permission_or_403
from dojo.authorization.authorization import user_has_global_permission_or_403, user_has_permission_or_403
from dojo.user.helper import user_is_authorized as legacy_check


def user_is_authorized(model, permission, arg, legacy_permission=None, lookup="pk", func=None):
"""Decorator for functions that ensures the user has permission on an object.
"""Decorator for functions that ensures the user has an object permission.
"""

if func is None:
Expand Down Expand Up @@ -39,3 +39,18 @@ def _wrapped(request, *args, **kwargs):
return func(request, *args, **kwargs)

return _wrapped


def user_has_global_permission(permission, func=None):
"""Decorator for functions that ensures the user has a (global) permission
"""

if func is None:
return functools.partial(user_has_global_permission, permission)

@functools.wraps(func)
def _wrapped(request, *args, **kwargs):
user_has_global_permission_or_403(request.user, permission)
return func(request, *args, **kwargs)

return _wrapped
15 changes: 15 additions & 0 deletions dojo/authorization/roles_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Permissions(IntEnum):
Product_Type_Member_Add_Owner = 1005
Product_Type_Edit = 1006
Product_Type_Delete = 1007
Product_Type_Add = 1008

Product_View = 1102
Product_Member_Delete = 1103
Expand Down Expand Up @@ -448,3 +449,17 @@ def get_roles_with_permissions():
Permissions.Product_API_Scan_Configuration_Delete
}
}


def get_global_roles_with_permissions():
"""
Extra permissions for global roles, on top of the permissions granted to the "normal" roles above.
"""
return {
Roles.Maintainer: {
Permissions.Product_Type_Add
},
Roles.Owner: {
Permissions.Product_Type_Add
}
}
15 changes: 3 additions & 12 deletions dojo/endpoint/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.db.models import Exists, OuterRef, Q
from dojo.models import Endpoint, Endpoint_Status, Product_Member, Product_Type_Member, \
Product_Group, Product_Type_Group
from dojo.authorization.authorization import get_roles_for_permission, role_has_permission, \
get_groups
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission


def get_authorized_endpoints(permission, queryset=None, user=None):
Expand All @@ -27,13 +26,9 @@ def get_authorized_endpoints(permission, queryset=None, user=None):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return endpoints

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return endpoints

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return endpoints

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('product__prod_type_id'),
Expand Down Expand Up @@ -87,13 +82,9 @@ def get_authorized_endpoint_status(permission, queryset=None, user=None):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return endpoint_status

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return endpoint_status

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return endpoint_status

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('endpoint__product__prod_type_id'),
Expand Down
9 changes: 2 additions & 7 deletions dojo/engagement/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.db.models import Exists, OuterRef, Q
from dojo.models import Engagement, Product_Member, Product_Type_Member, \
Product_Group, Product_Type_Group
from dojo.authorization.authorization import get_roles_for_permission, role_has_permission, \
get_groups
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission


def get_authorized_engagements(permission):
Expand All @@ -20,13 +19,9 @@ def get_authorized_engagements(permission):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return Engagement.objects.all()

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return Engagement.objects.all()

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return Engagement.objects.all()

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('product__prod_type_id'),
Expand Down
15 changes: 3 additions & 12 deletions dojo/finding/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.db.models import Exists, OuterRef, Q
from dojo.models import Finding, Product_Member, Product_Type_Member, Stub_Finding, \
Product_Group, Product_Type_Group
from dojo.authorization.authorization import get_roles_for_permission, role_has_permission, \
get_groups
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission


def get_authorized_findings(permission, queryset=None, user=None):
Expand All @@ -27,13 +26,9 @@ def get_authorized_findings(permission, queryset=None, user=None):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return findings

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return findings

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return findings

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('test__engagement__product__prod_type_id'),
Expand Down Expand Up @@ -82,13 +77,9 @@ def get_authorized_stub_findings(permission):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return Stub_Finding.objects.all()

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return Stub_Finding.objects.all()

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return Stub_Finding.objects.all()

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('test__engagement__product__prod_type_id'),
Expand Down
9 changes: 2 additions & 7 deletions dojo/finding_group/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
from django.db.models import Exists, OuterRef, Q
from dojo.models import Finding_Group, Product_Member, Product_Type_Member, \
Product_Group, Product_Type_Group
from dojo.authorization.authorization import get_roles_for_permission, role_has_permission, \
get_groups
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission


def get_authorized_finding_groups(permission, queryset=None, user=None):
Expand All @@ -27,13 +26,9 @@ def get_authorized_finding_groups(permission, queryset=None, user=None):
if user.is_staff and settings.AUTHORIZATION_STAFF_OVERRIDE:
return finding_groups

if hasattr(user, 'global_role') and user.global_role.role is not None and role_has_permission(user.global_role.role.id, permission):
if user_has_global_permission(user, permission):
return finding_groups

for group in get_groups(user):
if hasattr(group, 'global_role') and group.global_role.role is not None and role_has_permission(group.global_role.role.id, permission):
return finding_groups

roles = get_roles_for_permission(permission)
authorized_product_type_roles = Product_Type_Member.objects.filter(
product_type=OuterRef('test__engagement__product__prod_type_id'),
Expand Down
Loading