Test Failed
Push — master ( 0496d3...626f66 )
by W
01:28
created

st2api/st2api/controllers/exp/inquiries.py (1 issue)

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
16
import copy
17
import json
18
19
from oslo_config import cfg
20
from six.moves import http_client
21
22
from st2api.controllers import resource
23
from st2api.controllers.v1 import execution_views
24
from st2common.constants import action as action_constants
25
from st2common.exceptions import db as db_exceptions
26
from st2common.exceptions import rbac as rbac_exceptions
27
from st2common import log as logging
28
from st2common.models.api import inquiry as inqy_api_models
29
from st2common.persistence import execution as ex_db_access
30
from st2common.rbac import types as rbac_types
31
from st2common.rbac import utils as rbac_utils
32
from st2common import router as api_router
33
from st2common.services import inquiry as inquiry_service
34
35
36
__all__ = [
37
    'InquiriesController'
38
]
39
40
LOG = logging.getLogger(__name__)
41
42
INQUIRY_RUNNER = 'inquirer'
43
44
45
class InquiriesController(resource.ResourceController):
46
    """API controller for Inquiries
47
    """
48
49
    supported_filters = copy.deepcopy(execution_views.SUPPORTED_FILTERS)
50
51
    # No data model currently exists for Inquiries, so we're "borrowing" ActionExecutions
52
    # for the DB layer
53
    model = inqy_api_models.InquiryAPI
54
    access = ex_db_access.ActionExecution
55
56
    def get_all(self, requester_user=None, limit=None, **raw_filters):
57
        """Retrieve multiple Inquiries
58
59
            Handles requests:
60
                GET /inquiries/
61
        """
62
63
        raw_inquiries = super(InquiriesController, self)._get_all(
64
            limit=limit,
65
            raw_filters={
66
                'status': action_constants.LIVEACTION_STATUS_PENDING,
67
                'runner': INQUIRY_RUNNER
68
            },
69
            requester_user=requester_user
70
        )
71
72
        # Since "model" is set to InquiryAPI (for good reasons), _get_all returns a list of
73
        # InquiryAPI instances, already converted to JSON. So in order to convert these to
74
        # InquiryResponseAPI instances, we first have to convert raw_inquiries.body back to
75
        # a list of dicts, and then individually convert these to InquiryResponseAPI instances
76
        inquiries = [
77
            inqy_api_models.InquiryResponseAPI.from_model(raw_inquiry, skip_db=True)
78
            for raw_inquiry in json.loads(raw_inquiries.body)
79
        ]
80
81
        # Repackage into Response with correct headers
82
        resp = api_router.Response(json=inquiries)
83
        resp.headers['X-Total-Count'] = raw_inquiries.headers['X-Total-Count']
84
85
        if limit:
86
            resp.headers['X-Limit'] = str(limit)
87
88
        return resp
89
90
    def get_one(self, inquiry_id, requester_user=None):
91
        """Retrieve a single Inquiry
92
93
            Handles requests:
94
                GET /inquiries/<inquiry id>
95
        """
96
97
        # Retrieve the inquiry by id.
98
        # (Passing permission_type here leverages inquiry service built-in RBAC assertions)
99
        try:
100
            inquiry = self._get_one_by_id(
101
                id=inquiry_id,
102
                requester_user=requester_user,
103
                permission_type=rbac_types.PermissionType.INQUIRY_VIEW
104
            )
105
        except db_exceptions.StackStormDBObjectNotFoundError as e:
106
            LOG.exception('Unable to identify inquiry with id "%s".' % inquiry_id)
107
            api_router.abort(http_client.NOT_FOUND, str(e))
108
        except rbac_exceptions.ResourceAccessDeniedError as e:
109
            LOG.exception('User is denied access to inquiry "%s".' % inquiry_id)
110
            api_router.abort(http_client.FORBIDDEN, str(e))
111
        except Exception as e:
112
            LOG.exception('Unable to get record for inquiry "%s".' % inquiry_id)
113
            api_router.abort(http_client.INTERNAL_SERVER_ERROR, str(e))
114
115
        try:
116
            inquiry_service.check_inquiry(inquiry)
117
        except Exception as e:
118
            api_router.abort(http_client.BAD_REQUEST, str(e))
119
120
        return inqy_api_models.InquiryResponseAPI.from_inquiry_api(inquiry)
121
122
    def put(self, inquiry_id, response_data, requester_user):
123
        """Provide response data to an Inquiry
124
125
            In general, provided the response data validates against the provided
126
            schema, and the user has the appropriate permissions to respond,
127
            this will set the Inquiry execution to a successful status, and resume
128
            the parent workflow.
129
130
            Handles requests:
131
                PUT /inquiries/<inquiry id>
132
        """
133
        LOG.debug("Inquiry %s received response payload: %s" % (inquiry_id, response_data.response))
134
135
        # Set requester to system user if not provided.
136
        if not requester_user:
137
            requester_user = cfg.CONF.system_user.user
138
139
        # Retrieve the inquiry by id.
140
        try:
141
            inquiry = self._get_one_by_id(
142
                id=inquiry_id,
143
                requester_user=requester_user,
144
                permission_type=rbac_types.PermissionType.INQUIRY_RESPOND
145
            )
146
        except db_exceptions.StackStormDBObjectNotFoundError as e:
147
            LOG.exception('Unable to identify inquiry with id "%s".' % inquiry_id)
148
            api_router.abort(http_client.NOT_FOUND, str(e))
149
        except rbac_exceptions.ResourceAccessDeniedError as e:
150
            LOG.exception('User is denied access to inquiry "%s".' % inquiry_id)
151
            api_router.abort(http_client.FORBIDDEN, str(e))
152
        except Exception as e:
153
            LOG.exception('Unable to get record for inquiry "%s".' % inquiry_id)
154
            api_router.abort(http_client.INTERNAL_SERVER_ERROR, str(e))
155
156
        # Check if inquiry can still be respond to.
157
        try:
158
            inquiry_service.check_inquiry(inquiry)
159
        except Exception as e:
160
            LOG.exception('Fail checking validity of inquiry "%s".' % inquiry_id)
161
            api_router.abort(http_client.BAD_REQUEST, str(e))
162
163
        # Check if user has permission to respond to this inquiry.
164
        try:
165
            inquiry_service.check_permission(inquiry, requester_user)
166
        except Exception as e:
167
            LOG.exception('Fail checking permission for inquiry "%s".' % inquiry_id)
168
            api_router.abort(http_client.FORBIDDEN, str(e))
169
170
        # Validate the body of the response against the schema parameter for this inquiry.
171
        try:
172
            inquiry_service.validate_response(inquiry, response_data.response)
173
        except Exception as e:
174
            LOG.exception('Fail checking response for inquiry "%s".' % inquiry_id)
175
            api_router.abort(http_client.BAD_REQUEST, str(e))
176
177
        # Respond to inquiry and update if there is a partial response.
178
        try:
179
            inquiry_service.respond(inquiry, response_data.response, requester=requester_user)
180
        except Exception as e:
181
            LOG.exception('Fail to update response for inquiry "%s".' % inquiry_id)
182
            api_router.abort(http_client.INTERNAL_SERVER_ERROR, str(e))
183
184
        return {
185
            'id': inquiry_id,
186
            'response': response_data.response
187
        }
188
189
    def _get_one_by_id(self, id, requester_user, permission_type,
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
190
                       exclude_fields=None, from_model_kwargs=None):
191
        """Override ResourceController._get_one_by_id to contain scope of Inquiries UID hack
192
        :param exclude_fields: A list of object fields to exclude.
193
        :type exclude_fields: ``list``
194
        """
195
        LOG.debug('Retrieving action execution for inquiry "%s".' % id)
196
197
        execution_db = self._get_by_id(resource_id=id, exclude_fields=exclude_fields)
198
199
        if not execution_db:
200
            raise db_exceptions.StackStormDBObjectNotFoundError()
201
202
        # Inquiry currently does not have it's own database model and share with ActionExecution.
203
        # The object uid is in the format of "execution:<id>". To allow RBAC to resolve correctly
204
        # for inquiries, we're overriding the "get_uid" function so the object uid can be set to
205
        # "inquiry:<id>".
206
        #
207
        # TODO (mierdin): All of this should be removed once Inquiries get their own DB model.
208
        if (execution_db and getattr(execution_db, 'runner', None) and
209
                execution_db.runner.get('runner_module') == INQUIRY_RUNNER):
210
            execution_db.get_uid = get_uid
211
212
        LOG.debug('Checking permission on inquiry "%s".' % id)
213
214
        if permission_type:
215
            rbac_utils.assert_user_has_resource_db_permission(
216
                user_db=requester_user,
217
                resource_db=execution_db,
218
                permission_type=permission_type
219
            )
220
221
        from_model_kwargs = from_model_kwargs or {}
222
        from_model_kwargs.update(self.from_model_kwargs)
223
        result = self.model.from_model(execution_db, **from_model_kwargs)
224
225
        return result
226
227
228
def get_uid():
229
    """Inquiry UID hack for RBAC
230
    """
231
    return 'inquiry'
232
233
234
inquiries_controller = InquiriesController()
235