Repository: StackStorm/st2

Issue:
ST2 v2.3: Error installing action pack user "stanley" doesn't have required permission
Got the following error when I authenticated a user declared in a flat file. rbac is enabled.
```
export ST2_AUTH_TOKEN=fcdad4b9b69b4d8db67f3ce983d333b6 && st2 pack install https://myrepo/my_action_pack.git
ERROR: 403 Client Error: Forbidden
MESSAGE: User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install" for url: http://127.0.0.1:xxx/v1/packs/install
```


Discussion:
```
vagrant@st2test:~$ st2 role list
+--------------------------+--------------------+--------+-----------------------+
| id                       | name               | system | description           |
+--------------------------+--------------------+--------+-----------------------+
| 599b619f55fc8c372be075f4 | admin              | True   | admin                 |
| 599b845655fc8c65ac1b65af | example_pack_owner | False  | Owner of pack example |
| 599b619f55fc8c372be075f5 | observer           | True   | observer              |
| 599b619f55fc8c372be075f6 | system_admin       | True   | system_admin          |
+--------------------------+--------------------+--------+-----------------------+
vagrant@st2test:~$ st2 role-assignment list
+--------------------------+--------------------+----------+-----------+----------------------+
| id                       | role               | user     | is_remote | description          |
+--------------------------+--------------------+----------+-----------+----------------------+
| 599b845655fc8c65ac1b65b1 | admin              | st2admin | False     |                      |
| 599b845655fc8c65ac1b65b2 | example_pack_owner | user1    | False     | Grant                |
|                          |                    |          |           | example_pack_owner   |
|                          |                    |          |           | role to rbac_user1   |
|                          |                    |          |           | user.                |
+--------------------------+--------------------+----------+-----------+----------------------+
vagrant@st2test:~$ export ST2_AUTH_TOKEN=`st2 auth st2admin -t -p Ch@ngeMe`
vagrant@st2test:~$ echo $ST2_AUTH_TOKEN
2449f944969243abaf56c0a63f0df903
vagrant@st2test:~$ st2 pack install twilio
ERROR: 403 Client Error: Forbidden
MESSAGE: User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install" for url: http://127.0.0.1:9101/v1/packs/install
```

st2api.log
```
2017-08-22 01:25:02,482 139896769722320 INFO logging [-] c6281e1d-5c0b-4a9c-864a-54a272bd24cc - POST /v1/packs/install with query={} (remote_addr='127.0.0.1',method='POST',request_id='c6281e1d-5c0b-4a9c-864a-54a272bd24cc',query={},path='/v1/packs/install')
2017-08-22 01:25:02,483 139896769722320 DEBUG router [-] Recieved call with WebOb: POST /v1/packs/install HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 37
Content-Type: application/json
Host: 127.0.0.1:9101
User-Agent: python-requests/2.14.2
X-Auth-Token: 7d41ec29c4b44c09b63e6602d4af9fed
X-Request-Id: c6281e1d-5c0b-4a9c-864a-54a272bd24cc

{"force": false, "packs": ["twilio"]}
2017-08-22 01:25:02,486 139896769722320 DEBUG router [-] Match path: /v1/packs/install
2017-08-22 01:25:02,595 139896769722320 DEBUG router [-] Parsed endpoint: {'x-permissions': 'pack_install', 'operationId': 'st2api.controllers.v1.packs:packs_controller.install.post', 'responses': {'default': {'description': 'Unexpected error', 'schema': {'$ref': '#/definitions/Error'}}, '202': {'schema': {'$ref': '#/definitions/AsyncRequest'}, 'examples': {'application/json': {'ref': 'core.webhook'}}, 'description': 'Pack installation request has been accepted'}}, 'parameters': [{'in': 'body', 'description': 'Packs to be installed', 'name': 'pack_install_request', 'schema': {'$ref': '#/definitions/PacksInstall'}}], 'description': 'Install new packs..\n'}
2017-08-22 01:25:02,596 139896769722320 DEBUG router [-] Parsed path_vars: {}
2017-08-22 01:25:02,605 139896769722320 AUDIT auth [-] Token with id "599b86da55fc8c63202ee828" is validated.
2017-08-22 01:25:02,620 139896769722320 DEBUG resolvers [-] PackPermissionsResolver._user_has_global_permission: Checking user permissions (resolver='PackPermissionsResolver',user_db={'is_service': False, 'id': '599b61b455fc8c3787e8c1ff', 'nicknames': {}, 'name': u'st2admin'},permission_type='pack_install')
2017-08-22 01:25:02,637 139896769722320 DEBUG resolvers [-] PackPermissionsResolver._user_has_global_permission: Found a matching grant via system role (resolver='PackPermissionsResolver',user_db={'is_service': False, 'id': '599b61b455fc8c3787e8c1ff', 'nicknames': {}, 'name': u'st2admin'},permission_type='pack_install')
2017-08-22 01:25:02,639 139896769722320 DEBUG router [-] Missing x-api-model definition for st2api.controllers.v1.packs:packs_controller.install.post, using generic Body model.
2017-08-22 01:25:02,646 139896769722320 DEBUG resolvers [-] ActionPermissionsResolver._user_has_resource_permission: Checking user resource permissions (resolver='ActionPermissionsResolver',pack_uid=u'pack:packs',resource_uid=u'action:packs:install',user_db={'is_service': False, 'id': None, 'nicknames': {}, 'name': u'stanley'},resource_type='action',permission_type='action_execute')
2017-08-22 01:25:02,648 139896769722320 DEBUG resolvers [-] ActionPermissionsResolver._user_has_resource_permission: Checking grants via system role permissions (resolver='ActionPermissionsResolver',pack_uid=u'pack:packs',resource_uid=u'action:packs:install',user_db={'is_service': False, 'id': None, 'nicknames': {}, 'name': u'stanley'},resource_type='action',permission_type='action_execute')
2017-08-22 01:25:02,651 139896769722320 DEBUG resolvers [-] ActionPermissionsResolver._user_has_resource_permission: Checking direct grans on the specified resource (resolver='ActionPermissionsResolver',pack_uid=u'pack:packs',resource_uid=u'action:packs:install',user_db={'is_service': False, 'id': None, 'nicknames': {}, 'name': u'stanley'},resource_type='action',permission_type='action_execute')
2017-08-22 01:25:02,660 139896769722320 DEBUG resolvers [-] ActionPermissionsResolver._user_has_resource_permission: Checking grants on the parent resource (resolver='ActionPermissionsResolver',pack_uid=u'pack:packs',resource_uid=u'action:packs:install',user_db={'is_service': False, 'id': None, 'nicknames': {}, 'name': u'stanley'},resource_type='action',permission_type='action_execute')
2017-08-22 01:25:02,675 139896769722320 DEBUG resolvers [-] ActionPermissionsResolver._user_has_resource_permission: No matching grants found (resolver='ActionPermissionsResolver',pack_uid=u'pack:packs',resource_uid=u'action:packs:install',user_db={'is_service': False, 'id': None, 'nicknames': {}, 'name': u'stanley'},resource_type='action',permission_type='action_execute')
2017-08-22 01:25:02,677 139896769722320 ERROR router [-] Failed to call controller function "post" for operation "st2api.controllers.v1.packs:packs_controller.install.post": User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install"
Traceback (most recent call last):
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 426, in __call__
    resp = func(**kw)
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2api/controllers/v1/packs.py", line 90, in post
    requester_user=None)
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2api/controllers/v1/actionexecutions.py", line 105, in _handle_schedule_execution
    permission_type=PermissionType.ACTION_EXECUTE)
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/rbac/utils.py", line 142, in assert_user_has_resource_db_permission
    permission_type=permission_type)
ResourceAccessDeniedError: User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install"
2017-08-22 01:25:02,678 139896769722320 DEBUG error_handling [-] API call failed: User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install" (exception_message='User "stanley" doesn\'t have required permission "action_execute" on resource "action:packs:install"',exception_data={'user_db': <UserDB: UserDB(id=None, is_service=False, name="stanley", nicknames={})>, 'resource_db': <ActionDB: ActionDB(description="Installs or upgrades a pack into local content repository, either by git URL or a short name matching an index entry. Will download pack, load the actions, sensors and rules from the pack. Note that install requires reboot of some st2 services.", enabled=True, entry_point="workflows/install.yaml", id=599b61a755fc8c3822099430, name="install", notify=NotifySchema@139896768265552(on_complete="None", on_success="None", on_failure="None"), pack="packs", parameters={u'register': {u'default': u'all', u'type': u'string', u'description': u'Possible options are all, sensors, actions, rules, aliases, runners, triggers, rule_types, policiy_types, policies, configs.'}, u'force': {u'default': False, u'required': False, u'type': u'boolean', u'description': u'Set to True to force install the pack and skip StackStorm version compatibility check and also delete and ignore lock file if one exists.'}, u'env': {u'required': False, u'type': u'object', u'description': u'Optional environment variables.'}, u'packs': {u'items': {u'type': u'string'}, u'required': True, u'type': u'array', u'description': u'Name of the pack in Exchange or a git repo URL.'}}, ref="packs.install", runner_type={u'name': u'action-chain'}, tags=[], uid="action:packs:install")>, 'permission_type': 'action_execute'},exception_class='ResourceAccessDeniedError')
2017-08-22 01:25:02,693 139896769722320 DEBUG error_handling [-] Traceback (most recent call last):
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/middleware/error_handling.py", line 46, in __call__
    return self.app(environ, start_response)
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 472, in as_wsgi
    resp = self(req)
  File "/opt/stackstorm/st2/local/lib/python2.7/site-packages/st2common/router.py", line 430, in __call__
    raise e
ResourceAccessDeniedError: User "stanley" doesn't have required permission "action_execute" on resource "action:packs:install"

```
I think that this PR is related: https://github.com/StackStorm/bwc-installer/pull/21

In the installer script, we now automatically create an admin role for `stanley`. But we haven't updated the docs to note this. Looks like we should update st2docs, to include role assignment in the manual install section, and update docs.stackstorm.com/rbac.html to remove the "automatic admin assignment for stanley" section. Probably also add something there about needing to create that manually.

I have a suspicion that there is still a code issue here though. If a pack install is started by a user, then that user's permissions should be used all the way through that. It should only be falling back to stanley if the pack install was started by a rule.
Here's more info. 
I added the `stanley` user with role=admin and performed the following:
```
$ st2 auth -t myuser
Password:
7609cdfcaef3400c9427de84a6412986

$ export ST2_AUTH_TOKEN=7609cdfcaef3400c9427de84a6412986 && st2 pack install https://myrepo/my_action_pack.git
```
When I checked the UI, it had "stanley"  vs "myuser" as the user who executed the above pack installation. This was unexpected.

When I install the pack via the UI as "myuser", the UI shows "myuser" as the one who executed the pack installation (as expected).
Could be a bug with the way user is handled when the action is executed using the CLI - will look into it.
I was able to reproduce it - it looks like it's a regression which has been introduced when we moved to the ``/v1/packs/install`` API.

Resolving patch:
diff --git a/st2api/st2api/controllers/v1/packs.py b/st2api/st2api/controllers/v1/packs.py
--- a/st2api/st2api/controllers/v1/packs.py
+++ b/st2api/st2api/controllers/v1/packs.py
@@ -20,6 +20,7 @@
 from collections import OrderedDict
 
 import six
+from oslo_config import cfg
 
 import st2common
 from st2common import log as logging
@@ -34,6 +35,7 @@
 import st2common.bootstrap.ruletypesregistrar as rule_types_registrar
 from st2common.bootstrap.configsregistrar import ConfigsRegistrar
 import st2common.content.utils as content_utils
+from st2common.models.db.auth import UserDB
 from st2common.models.api.action import LiveActionCreateAPI
 from st2common.models.api.pack import PackAPI
 from st2common.models.api.pack import PackAsyncAPI
@@ -93,7 +95,7 @@ def _get_proxy_config():
 
 class PackInstallController(ActionExecutionsControllerMixin):
 
-    def post(self, pack_install_request):
+    def post(self, pack_install_request, requester_user=None):
         parameters = {
             'packs': pack_install_request.packs,
         }
@@ -101,12 +103,15 @@ def post(self, pack_install_request):
         if pack_install_request.force:
             parameters['force'] = True
 
+        if not requester_user:
+            requester_user = UserDB(cfg.CONF.system_user.user)
+
         new_liveaction_api = LiveActionCreateAPI(action='packs.install',
                                                  parameters=parameters,
-                                                 user=None)
+                                                 user=requester_user.name)
 
         execution_resp = self._handle_schedule_execution(liveaction_api=new_liveaction_api,
-                                                         requester_user=None)
+                                                         requester_user=requester_user)
 
         exec_id = PackAsyncAPI(execution_id=execution_resp.json['id'])
 
@@ -115,7 +120,7 @@ def post(self, pack_install_request):
 
 class PackUninstallController(ActionExecutionsControllerMixin):
 
-    def post(self, pack_uninstall_request, ref_or_id=None):
+    def post(self, pack_uninstall_request, ref_or_id=None, requester_user=None):
         if ref_or_id:
             parameters = {
                 'packs': [ref_or_id]
@@ -125,12 +130,15 @@ def post(self, pack_uninstall_request, ref_or_id=None):
                 'packs': pack_uninstall_request.packs
             }
 
+        if not requester_user:
+            requester_user = UserDB(cfg.CONF.system_user.user)
+
         new_liveaction_api = LiveActionCreateAPI(action='packs.uninstall',
                                                  parameters=parameters,
-                                                 user=None)
+                                                 user=requester_user.name)
 
         execution_resp = self._handle_schedule_execution(liveaction_api=new_liveaction_api,
-                                                         requester_user=None)
+                                                         requester_user=requester_user)
 
         exec_id = PackAsyncAPI(execution_id=execution_resp.json['id'])
 


Test patch:
diff --git a/st2api/tests/unit/controllers/v1/test_packs_rbac.py b/st2api/tests/unit/controllers/v1/test_packs_rbac.py
new file mode 100644
--- /dev/null
+++ b/st2api/tests/unit/controllers/v1/test_packs_rbac.py
@@ -0,0 +1,79 @@
+# Licensed to the StackStorm, Inc ('StackStorm') under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import mock
+import six
+
+from st2common.router import Response
+from st2api.controllers.v1.actionexecutions import ActionExecutionsControllerMixin
+from tests.base import APIControllerWithRBACTestCase
+
+http_client = six.moves.http_client
+
+__all__ = [
+    'PackControllerRBACTestCase'
+]
+
+
+class PackControllerRBACTestCase(APIControllerWithRBACTestCase):
+    @mock.patch.object(ActionExecutionsControllerMixin, '_handle_schedule_execution')
+    def test_install(self, _handle_schedule_execution):
+        user_db = self.users['system_admin']
+        self.use_user(user_db)
+
+        _handle_schedule_execution.return_value = Response(json={'id': '123'})
+        payload = {'packs': ['some']}
+
+        resp = self.app.post_json('/v1/packs/install', payload)
+
+        self.assertEqual(resp.status_int, 202)
+        self.assertEqual(resp.json, {'execution_id': '123'})
+
+        # Verify created execution correctly used the user which performed the API operation
+        call_kwargs = _handle_schedule_execution.call_args[1]
+        self.assertEqual(call_kwargs['requester_user'], user_db)
+        self.assertEqual(call_kwargs['liveaction_api'].user, user_db.name)
+
+        # Try with a different user
+        user_db = self.users['admin']
+        self.use_user(user_db)
+
+        resp = self.app.post_json('/v1/packs/install', payload)
+
+        self.assertEqual(resp.status_int, 202)
+        self.assertEqual(resp.json, {'execution_id': '123'})
+
+        # Verify created execution correctly used the user which performed the API operation
+        call_kwargs = _handle_schedule_execution.call_args[1]
+        self.assertEqual(call_kwargs['requester_user'], user_db)
+        self.assertEqual(call_kwargs['liveaction_api'].user, user_db.name)
+
+    @mock.patch.object(ActionExecutionsControllerMixin, '_handle_schedule_execution')
+    def test_uninstall(self, _handle_schedule_execution):
+        user_db = self.users['system_admin']
+        self.use_user(user_db)
+
+        _handle_schedule_execution.return_value = Response(json={'id': '123'})
+        payload = {'packs': ['some']}
+
+        resp = self.app.post_json('/v1/packs/uninstall', payload)
+
+        self.assertEqual(resp.status_int, 202)
+        self.assertEqual(resp.json, {'execution_id': '123'})
+
+        # Verify created execution correctly used the user which performed the API operation
+        call_kwargs = _handle_schedule_execution.call_args[1]
+        self.assertEqual(call_kwargs['requester_user'], user_db)
+        self.assertEqual(call_kwargs['liveaction_api'].user, user_db.name)