Repository: inventree/InvenTree

Issue:
[BUG] Incorrect permissions for delete action on BOM items 
**Describe the bug**

Cannot delete a selection of BOM items even with Parts>Change permission enabled.

**Steps to Reproduce**

Steps to reproduce the behavior:
1. Navigate to part of assembly type
2. Load the Bill of Materials tab
3. Click on the edit BOM button
4. Select a number of items
5. Click on the delete button for the selection (not individual)
6. Click "Accept", nothing happens

**Expected behavior**

All selected BOM items should be deleted from the BOM.

**Deployment Method**
- [ ] Docker
- [X] Bare Metal

**Version Information**
# Version Information:
InvenTree-Version: 0.5.4
Django Version: 3.2.5
Commit Hash: 66a08fe91
Commit Date: 2021-11-03
Database: postgresql
Debug-Mode: False
Deployed using Docker: False


Discussion:
@eeintech i've just tried to replicate on `0.5.4` without success.. Can you try again? Are you seeing any error messages in the javascript console?
Oops I found out why... the permission for this user profile are:

![image](https://user-images.githubusercontent.com/4020546/144623907-b7d49561-0180-4e20-b4fe-a134d8d98b7a.png)

And the BoM items can only be deleted if Parts-Delete permission is on... I think BoM items should be able to be deleted if Parts-Change permission is on (it's a _change_ on a part). I'll create a PR for that.
Actually @SchrodingersGat now that BomItems are handled through API calls, I am not sure how permissions can be altered...
@eeintech you can just override the permission-check on the api view if it the method is 'delete'
@eeintech happy to close this one, then?
@SchrodingersGat Not really, this is still a persisting issue. Either the delete button shouldn't be shown or the permission corrected. I was intending to do the later today but didn't have time to dig more. I can look into @matmair comment next week
Hiding the button would be a pretty straight forward approach, and probably should be done independent of any decision made on permissions.


@SchrodingersGat Is the `wontfix` label for `0.6.0` or only for `0.5.5`?
I believe this has been fixed in 0.6.x and given the imminent release of 0.6.0 I don't think there will be an 0.5.5
@SchrodingersGat Just checked but it is not fixed in `0.6.x`, see below.

## Group permission
![image](https://user-images.githubusercontent.com/4020546/152186079-a1519c81-313c-4340-aee1-ae362fb574ed.png)

## Attempt to delete all BOM items
![image](https://user-images.githubusercontent.com/4020546/152186734-583c2a85-299e-4c71-bad3-f5274c88ea6e.png)

![image](https://user-images.githubusercontent.com/4020546/152186687-0152afc5-7cf9-444b-b13d-2831158b6f2a.png)

## Attempt to delete single BOM item
![image](https://user-images.githubusercontent.com/4020546/152186544-83cce93d-6f95-4082-89e1-f9a085fd0909.png)

![image](https://user-images.githubusercontent.com/4020546/152186596-a1fbaed9-3ad7-4060-9ce2-e4d9b0fa4e3b.png)

I started to look into this and it is a bit more complex than I originally thought as `Roles` are the entry point for permissions and the per-model permissions are mirroring the permissions of the role they belong to, example:
- Role `Part` is set to Add+Change
- Role `Part` controls permissions for models `PartParameter`, `BomItem`, etc
- A permission check for a model (eg. `PartParameter`) will solve for the role it is attached to (eg. `Part`)

In summary, if user tries to delete either `PartParameter` or `BomItem` from a part, user will get denied permission.
Unless I'm mistaken, **there is no mechanism** to bypass the role permission as of now.
So if one added the permission to delete `BomItem` (which I started working on), request will still get denied because parent role (eg. Part) has no `delete` permission.

My thought would be to enable a parent-children relationship for permissions, which would be different than the `RULESET_MODELS` mapping.
In this new lookup table, a list of children models could be associated to a parent role. If the parent role has the `change` permission, all the children models action permissions (eg. `add`, `change` and `delete`) will be automatically enabled.
This lookup table should be checked upon permission request (eg. `RuleSet.check_table_permission` should be updated to take it into account).

Going back to the example, This will allow a user to delete a part parameter or a BOM item whenever this user is in a group with `part.change` permission.

@matmair @SchrodingersGat Thoughts?

Resolving patch:
diff --git a/InvenTree/users/models.py b/InvenTree/users/models.py
--- a/InvenTree/users/models.py
+++ b/InvenTree/users/models.py
@@ -176,6 +176,11 @@ class RuleSet(models.Model):
         'django_q_success',
     ]
 
+    RULESET_CHANGE_INHERIT = [
+        ('part', 'partparameter'),
+        ('part', 'bomitem'),
+    ]
+
     RULE_OPTIONS = [
         'can_view',
         'can_add',
@@ -228,6 +233,16 @@ def check_table_permission(cls, user, table, permission):
                 if check_user_role(user, role, permission):
                     return True
 
+        # Check for children models which inherits from parent role
+        for (parent, child) in cls.RULESET_CHANGE_INHERIT:
+            # Get child model name
+            parent_child_string = f'{parent}_{child}'
+
+            if parent_child_string == table:
+                # Check if parent role has change permission
+                if check_user_role(user, parent, 'change'):
+                    return True
+
         # Print message instead of throwing an error
         name = getattr(user, 'name', user.pk)
 
@@ -453,6 +468,28 @@ def get_permission_object(permission_string):
         if debug:
             print(f"Removing permission {perm} from group {group.name}")
 
+    # Enable all action permissions for certain children models
+    # if parent model has 'change' permission
+    for (parent, child) in RuleSet.RULESET_CHANGE_INHERIT:
+        parent_change_perm = f'{parent}.change_{parent}'
+        parent_child_string = f'{parent}_{child}'
+
+        # Check if parent change permission exists
+        if parent_change_perm in group_permissions:
+            # Add child model permissions
+            for action in ['add', 'change', 'delete']:
+                child_perm = f'{parent}.{action}_{child}'
+
+                # Check if child permission not already in group
+                if child_perm not in group_permissions:
+                    # Create permission object
+                    add_model(parent_child_string, action, ruleset.can_delete)
+                    # Add to group
+                    permission = get_permission_object(child_perm)
+                    if permission:
+                        group.permissions.add(permission)
+                        print(f"Adding permission {child_perm} to group {group.name}")
+
 
 @receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets')
 def create_missing_rule_sets(sender, instance, **kwargs):


Test patch: