Repository: StackStorm/st2

Issue:
RBAC LDAP Mapping causes rule creation to fail when the workflow is not available
Hello Team, 

We have integrated St2 authentication with enterprise LDAP and users are mapped with RBAC in AD. I've created a new rule with trigger/criteria/workflow and however the workflow is not created for the rule. 

When I try to create a rule it failed with below internal error. My account is part of "admin" role and its mapped to LDAP. The same rule creation is succeeded If I disable the RBAC in st2.conf or create the workflow before the rule. Please take a look at the below failures and investigate the cause of the issue. 

# st2 rule create dns_alert.yaml 
ERROR: 500 Server Error: Internal Server Error
MESSAGE: Internal Server Error for url: http://127.0.0.1:9101/v1/rules

*RBAC Mapping*
cat /opt/stackstorm/rbac/mappings/sysadmin.yaml 
---
  group: "CN=xxxx,xxxx,xxxx"
  description: "Automatically grant observer role to all sysadmin group members."
  roles:
    - "admin"

**Rule:**
---
name: dns_alert
pack: ops
description: Check alert for dns  and trigger remediation
enabled: true
trigger:
  type: ops.event_handler
criteria:
  trigger.Hostname:
    pattern: "testdns1"
    type: matchregex  
  trigger.AlertType:
    pattern: "alert"
    type: matchregex
  trigger.Severity:
    pattern: "(white|blue)"
    type: regex
action:
  ref: ops.remediate_dns_alert_workflow
  parameters:
    hostname: "{{ trigger.Hostname }}"
    alertsid: "{{ trigger.AlertsID }}"


**Failure log in st2api:**
2017-07-17 04:56:18,340 77894832 INFO logging [-] 5837c4eb-01e7-43ca-aaec-12998458e349 - POST /v1/rules with query={} (remote_addr='127.0.0.1',method='POST',request_id='5837c4eb-01e7-43ca-aaec-12998458e349',query={},path='/v1/rules')
2017-07-17 04:56:18,345 77894832 AUDIT auth [-] Token with id "xxxxxxxxxxxxxxxx" is validated.
2017-07-17 04:56:18,399 77894832 ERROR router [-] Failed to call controller function "post" for operation "st2api.controllers.v1.rules:rule_controller.post": 'NoneType' object has no attribute 'get_uid'
Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 414, in __call__
    resp = func(**kw)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2api/controllers/v1/rules.py", line 103, in post
    rule_api=rule)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/utils.py", line 206, in assert_user_has_rule_trigger_and_action_permission
    action_ref=action_ref)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/utils.py", line 172, in user_has_rule_action_permission
    user_db=user_db, resource_db=action_db, permission_type=PermissionType.ACTION_EXECUTE)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/resolvers.py", line 407, in user_has_resource_db_permission
    action_uid = resource_db.get_uid()
AttributeError: 'NoneType' object has no attribute 'get_uid'
2017-07-17 04:56:18,400 77894832 ERROR error_handling [-] API call failed: 'NoneType' object has no attribute 'get_uid'
Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/middleware/error_handling.py", line 46, in __call__
    return self.app(environ, start_response)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 445, in as_wsgi
    resp = self(req)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 418, in __call__
    raise e
AttributeError: 'NoneType' object has no attribute 'get_uid' (_exception_data={},_exception_class='AttributeError',_exception_message="'NoneType' object has no attribute 'get_uid'")
2017-07-17 04:56:18,401 77894832 INFO logging [-] 5837c4eb-01e7-43ca-aaec-12998458e349 - 500 46 61.774ms
{
    "faultstring": "Internal Server Error"
} (result='{\n    "faultstring": "Internal Server Error"\n}',content_length=46,request_id='5837c4eb-01e7-43ca-aaec-12998458e349',runtime=61.774,remote_addr='127.0.0.1',status=500,method='POST',path='/v1/rules')
2017-07-17 04:56:39,980 77894832 INFO logging [-] d989b4b3-c945-4920-b395-f4245d8120e5 - POST /v1/rules with query={} (remote_addr='127.0.0.1',method='POST',request_id='d989b4b3-c945-4920-b395-f4245d8120e5',query={},path='/v1/rules')
2017-07-17 04:56:39,986 77894832 AUDIT auth [-] Token with id "596c43808888b26e2c7a7f45" is validated.
2017-07-17 04:56:40,038 77894832 ERROR router [-] Failed to call controller function "post" for operation "st2api.controllers.v1.rules:rule_controller.post": 'NoneType' object has no attribute 'get_uid'
Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 414, in __call__
    resp = func(**kw)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2api/controllers/v1/rules.py", line 103, in post
    rule_api=rule)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/utils.py", line 206, in assert_user_has_rule_trigger_and_action_permission
    action_ref=action_ref)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/utils.py", line 172, in user_has_rule_action_permission
    user_db=user_db, resource_db=action_db, permission_type=PermissionType.ACTION_EXECUTE)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/resolvers.py", line 407, in user_has_resource_db_permission
    action_uid = resource_db.get_uid()
AttributeError: 'NoneType' object has no attribute 'get_uid'
2017-07-17 04:56:40,039 77894832 ERROR error_handling [-] API call failed: 'NoneType' object has no attribute 'get_uid'
Traceback (most recent call last):
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/middleware/error_handling.py", line 46, in __call__
    return self.app(environ, start_response)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 445, in as_wsgi
    resp = self(req)
  File "/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/router.py", line 418, in __call__
    raise e
AttributeError: 'NoneType' object has no attribute 'get_uid' (_exception_data={},_exception_class='AttributeError',_exception_message="'NoneType' object has no attribute 'get_uid'")


Discussion:
Thanks for reporting this.

It looks like a general RBAC issue on rule creation - will look into it.
Small thing - next time please use Github gist for code snippets, etc. - https://gist.github.com/

This way indentation, etc. is preserved so it's easier to look into it.

Resolving patch:
diff --git a/st2common/st2common/rbac/resolvers.py b/st2common/st2common/rbac/resolvers.py
--- a/st2common/st2common/rbac/resolvers.py
+++ b/st2common/st2common/rbac/resolvers.py
@@ -212,6 +212,7 @@ def _user_has_resource_permission(self, user_db, pack_uid, resource_uid, permiss
         self._log('Checking user resource permissions', extra=log_context)
 
         # First check the system role permissions
+        self._log('Checking grants via system role permissions', extra=log_context)
         has_system_role_permission = self._user_has_system_role_permission(
             user_db=user_db, permission_type=permission_type)
 
@@ -235,6 +236,7 @@ def _user_has_resource_permission(self, user_db, pack_uid, resource_uid, permiss
             permission_types = [permission_type]
 
         # Check direct grants on the specified resource
+        self._log('Checking direct grans on the specified resource', extra=log_context)
         resource_types = [self.resource_type]
         permission_grants = get_all_permission_grants_for_user(user_db=user_db,
                                                                resource_uid=resource_uid,
@@ -245,6 +247,7 @@ def _user_has_resource_permission(self, user_db, pack_uid, resource_uid, permiss
             return True
 
         # Check grants on the parent pack
+        self._log('Checking grants on the parent resource', extra=log_context)
         resource_types = [ResourceType.PACK]
         permission_grants = get_all_permission_grants_for_user(user_db=user_db,
                                                                resource_uid=pack_uid,
diff --git a/st2common/st2common/rbac/utils.py b/st2common/st2common/rbac/utils.py
--- a/st2common/st2common/rbac/utils.py
+++ b/st2common/st2common/rbac/utils.py
@@ -21,6 +21,8 @@
 
 from oslo_config import cfg
 
+from st2common.models.db.action import ActionDB
+from st2common.models.system.common import ResourceReference
 from st2common.exceptions.rbac import AccessDeniedError
 from st2common.exceptions.rbac import ResourceTypeAccessDeniedError
 from st2common.exceptions.rbac import ResourceAccessDeniedError
@@ -162,11 +164,20 @@ def user_has_rule_action_permission(user_db, action_ref):
     """
     Check that the currently logged-in has necessary permissions on the action used / referenced
     inside the rule.
+
+    Note: Rules can reference actions which don't yet exist in the system.
     """
     if not cfg.CONF.rbac.enable:
         return True
 
     action_db = action_utils.get_action_by_ref(ref=action_ref)
+
+    if not action_db:
+        # We allow rules to be created for actions which don't yet exist in the
+        # system
+        ref = ResourceReference.from_string_reference(ref=action_ref)
+        action_db = ActionDB(pack=ref.pack, name=ref.name, ref=action_ref)
+
     action_resolver = resolvers.get_resolver_for_resource_type(ResourceType.ACTION)
     has_action_permission = action_resolver.user_has_resource_db_permission(
         user_db=user_db, resource_db=action_db, permission_type=PermissionType.ACTION_EXECUTE)


Test patch:
diff --git a/st2api/tests/unit/controllers/v1/test_rules_rbac.py b/st2api/tests/unit/controllers/v1/test_rules_rbac.py
--- a/st2api/tests/unit/controllers/v1/test_rules_rbac.py
+++ b/st2api/tests/unit/controllers/v1/test_rules_rbac.py
@@ -65,6 +65,11 @@ def setUp(self):
             fixtures_pack=FIXTURES_PACK,
             fixtures_dict={'rules': [file_name]})['rules'][file_name]
 
+        file_name = 'rule_action_doesnt_exist.yaml'
+        RuleControllerRBACTestCase.RULE_3 = self.fixtures_loader.load_fixtures(
+            fixtures_pack=FIXTURES_PACK,
+            fixtures_dict={'rules': [file_name]})['rules'][file_name]
+
         # Insert mock users, roles and assignments
 
         # Users
@@ -156,6 +161,17 @@ def test_post_webhook_trigger_no_trigger_and_action_permission(self):
         self.assertEqual(resp.status_code, httplib.FORBIDDEN)
         self.assertEqual(resp.json['faultstring'], expected_msg)
 
+    def test_post_user_has_no_permission_on_action_which_doesnt_exist_in_db(self):
+        # User has rule_create, but no action_execute on the action which doesn't exist in the db
+        user_db = self.users['rule_create_webhook_create']
+        self.use_user(user_db)
+
+        resp = self.__do_post(RuleControllerRBACTestCase.RULE_3)
+        expected_msg = ('User "rule_create_webhook_create" doesn\'t have required (action_execute)'
+                        ' permission to use action wolfpack.action-doesnt-exist-woo')
+        self.assertEqual(resp.status_code, httplib.FORBIDDEN)
+        self.assertEqual(resp.json['faultstring'], expected_msg)
+
     def test_post_no_webhook_trigger(self):
         # Test a scenario when user with only "rule_create" permission selects a non-webhook
         # trigger for which we don't perform any permission checking right now