Passed
Push — develop ( f534b1...a82689 )
by Plexxi
06:09 queued 03:13
created

ApiKeyController.get_one()   B

Complexity

Conditions 3

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 27
rs 8.8571
c 0
b 0
f 0
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 six
17
18
from oslo_config import cfg
19
from mongoengine import ValidationError
20
21
from st2api.controllers.base import BaseRestControllerMixin
22
from st2common import log as logging
23
from st2common.models.api.auth import ApiKeyAPI, ApiKeyCreateResponseAPI
24
from st2common.models.db.auth import UserDB
25
from st2common.constants.secrets import MASKED_ATTRIBUTE_VALUE
26
from st2common.exceptions.auth import ApiKeyNotFoundError
27
from st2common.exceptions.db import StackStormDBObjectNotFoundError
28
from st2common.persistence.auth import ApiKey, User
29
from st2common.rbac.types import PermissionType
30
from st2common.rbac import utils as rbac_utils
31
from st2common.router import abort
32
from st2common.router import Response
33
from st2common.util import auth as auth_util
34
35
http_client = six.moves.http_client
36
37
LOG = logging.getLogger(__name__)
38
39
__all__ = [
40
    'ApiKeyController'
41
]
42
43
44
# See st2common.rbac.resolvers.ApiKeyPermissionResolver#user_has_resource_db_permission for resaon
45
# why RBAC is disabled for the controller
46
class ApiKeyController(BaseRestControllerMixin):
47
    """
48
    Implements the REST endpoint for managing the key value store.
49
    """
50
51
    supported_filters = {
52
        'user': 'user'
53
    }
54
55
    query_options = {
56
        'sort': ['user']
57
    }
58
59
    def __init__(self):
60
        super(ApiKeyController, self).__init__()
61
        self.get_one_db_method = ApiKey.get_by_key_or_id
62
63
    def get_one(self, api_key_id_or_key, requester_user, show_secrets=None):
64
        """
65
            List api keys.
66
67
            Handle:
68
                GET /apikeys/1
69
        """
70
        api_key_db = None
71
        try:
72
            api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key)
73
        except ApiKeyNotFoundError:
74
            msg = 'ApiKey matching %s for reference and id not found.', api_key_id_or_key
75
            LOG.exception(msg)
76
            abort(http_client.NOT_FOUND, msg)
77
78
        permission_type = PermissionType.API_KEY_VIEW
79
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
80
                                                          resource_db=api_key_db,
81
                                                          permission_type=permission_type)
82
83
        try:
84
            mask_secrets = self._get_mask_secrets(show_secrets=show_secrets,
85
                                                  requester_user=requester_user)
86
            return ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets)
87
        except (ValidationError, ValueError) as e:
88
            LOG.exception('Failed to serialize API key.')
89
            abort(http_client.INTERNAL_SERVER_ERROR, str(e))
90
91
    def get_all(self, requester_user, show_secrets=None, **kw):
92
        """
93
            List all keys.
94
95
            Handles requests:
96
                GET /apikeys/
97
        """
98
        mask_secrets = self._get_mask_secrets(show_secrets=show_secrets,
99
                                              requester_user=requester_user)
100
        api_key_dbs = ApiKey.get_all(**kw)
101
        api_keys = [ApiKeyAPI.from_model(api_key_db, mask_secrets=mask_secrets)
102
                    for api_key_db in api_key_dbs]
103
104
        return api_keys
105
106
    def post(self, api_key_api, requester_user):
107
        """
108
        Create a new entry.
109
        """
110
111
        permission_type = PermissionType.API_KEY_CREATE
112
        rbac_utils.assert_user_has_resource_api_permission(user_db=requester_user,
113
                                                           resource_api=api_key_api,
114
                                                           permission_type=permission_type)
115
116
        api_key_db = None
117
        api_key = None
118
        try:
119
            if not getattr(api_key_api, 'user', None):
120
                api_key_api.user = requester_user.name or cfg.CONF.system_user.user
121
122
            try:
123
                User.get_by_name(api_key_api.user)
124
            except StackStormDBObjectNotFoundError:
125
                user_db = UserDB(name=api_key_api.user)
126
                User.add_or_update(user_db)
127
128
                extra = {'username': api_key_api.user, 'user': user_db}
129
                LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra)
130
131
            # If key_hash is provided use that and do not create a new key. The assumption
132
            # is user already has the original api-key
133
            if not getattr(api_key_api, 'key_hash', None):
134
                api_key, api_key_hash = auth_util.generate_api_key_and_hash()
135
                # store key_hash in DB
136
                api_key_api.key_hash = api_key_hash
137
            api_key_db = ApiKey.add_or_update(ApiKeyAPI.to_model(api_key_api))
138
        except (ValidationError, ValueError) as e:
139
            LOG.exception('Validation failed for api_key data=%s.', api_key_api)
140
            abort(http_client.BAD_REQUEST, str(e))
141
142
        extra = {'api_key_db': api_key_db}
143
        LOG.audit('ApiKey created. ApiKey.id=%s' % (api_key_db.id), extra=extra)
144
145
        api_key_create_response_api = ApiKeyCreateResponseAPI.from_model(api_key_db)
146
        # Return real api_key back to user. A one-way hash of the api_key is stored in the DB
147
        # only the real value only returned at create time. Also, no masking of key here since
148
        # the user needs to see this value atleast once.
149
        api_key_create_response_api.key = api_key
150
151
        return Response(json=api_key_create_response_api, status=http_client.CREATED)
152
153
    def put(self, api_key_api, api_key_id_or_key, requester_user):
154
        api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key)
155
156
        permission_type = PermissionType.API_KEY_MODIFY
157
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
158
                                                          resource_db=api_key_db,
159
                                                          permission_type=permission_type)
160
161
        old_api_key_db = api_key_db
162
        api_key_db = ApiKeyAPI.to_model(api_key_api)
163
164
        try:
165
            User.get_by_name(api_key_api.user)
166
        except StackStormDBObjectNotFoundError:
167
            user_db = UserDB(name=api_key_api.user)
168
            User.add_or_update(user_db)
169
170
            extra = {'username': api_key_api.user, 'user': user_db}
171
            LOG.audit('Registered new user "%s".' % (api_key_api.user), extra=extra)
172
173
        # Passing in key_hash as MASKED_ATTRIBUTE_VALUE is expected since we do not
174
        # leak it out therefore it is expected we get the same value back. Interpret
175
        # this special code and empty value as no-change
176
        if api_key_db.key_hash == MASKED_ATTRIBUTE_VALUE or not api_key_db.key_hash:
177
            api_key_db.key_hash = old_api_key_db.key_hash
178
179
        # Rather than silently ignore any update to key_hash it is better to explicitly
180
        # disallow and notify user.
181
        if old_api_key_db.key_hash != api_key_db.key_hash:
182
            raise ValueError('Update of key_hash is not allowed.')
183
184
        api_key_db.id = old_api_key_db.id
185
        api_key_db = ApiKey.add_or_update(api_key_db)
186
187
        extra = {'old_api_key_db': old_api_key_db, 'new_api_key_db': api_key_db}
188
        LOG.audit('API Key updated. ApiKey.id=%s.' % (api_key_db.id), extra=extra)
189
        api_key_api = ApiKeyAPI.from_model(api_key_db)
190
191
        return api_key_api
192
193
    def delete(self, api_key_id_or_key, requester_user):
194
        """
195
            Delete the key value pair.
196
197
            Handles requests:
198
                DELETE /apikeys/1
199
        """
200
        api_key_db = ApiKey.get_by_key_or_id(api_key_id_or_key)
201
202
        permission_type = PermissionType.API_KEY_DELETE
203
        rbac_utils.assert_user_has_resource_db_permission(user_db=requester_user,
204
                                                          resource_db=api_key_db,
205
                                                          permission_type=permission_type)
206
207
        ApiKey.delete(api_key_db)
208
209
        extra = {'api_key_db': api_key_db}
210
        LOG.audit('ApiKey deleted. ApiKey.id=%s' % (api_key_db.id), extra=extra)
211
212
        return Response(status=http_client.NO_CONTENT)
213
214
215
api_key_controller = ApiKeyController()
216