GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Push — develop-v1.6.0 ( 9d5181...7efb31 )
by
unknown
04:49
created

RBACDefinitionsDBSyncer.sync_roles()   F

Complexity

Conditions 12

Size

Total Lines 105

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
dl 0
loc 105
rs 2
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like RBACDefinitionsDBSyncer.sync_roles() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
"""
17
Module for syncing RBAC definitions in the database with the ones from the filesystem.
18
"""
19
20
from st2common import log as logging
21
from st2common.models.db.auth import UserDB
22
from st2common.persistence.auth import User
23
from st2common.persistence.rbac import Role
24
from st2common.persistence.rbac import UserRoleAssignment
25
from st2common.persistence.rbac import PermissionGrant
26
from st2common.services import rbac as rbac_services
27
from st2common.util.uid import parse_uid
28
29
30
LOG = logging.getLogger(__name__)
31
32
__all__ = [
33
    'RBACDefinitionsDBSyncer'
34
]
35
36
37
class RBACDefinitionsDBSyncer(object):
38
    """
39
    A class which makes sure that the role definitions and user role assignments in the database
40
    match ones specified in the role definition files.
41
42
    The class works by simply deleting all the obsolete roles (either removed or updated) and
43
    creating new roles (either new roles or one which have been updated).
44
45
    Note #1: Our current datastore doesn't support transactions or similar which means that with
46
    the current data model there is a short time frame during sync when the definitions inside the
47
    DB are out of sync with the ones in the file.
48
49
    Note #2: The operation of this class is idempotent meaning that if it's ran multiple time with
50
    the same dataset, the end result / outcome will be the same.
51
    """
52
53
    def sync(self, role_definition_apis, role_assignment_apis):
54
        """
55
        Synchronize all the role definitions and user role assignments.
56
        """
57
        result = {}
58
59
        result['roles'] = self.sync_roles(role_definition_apis)
60
        result['role_assignments'] = self.sync_users_role_assignments(role_assignment_apis)
61
62
        return result
63
64
    def sync_roles(self, role_definition_apis):
65
        """
66
        Synchronize all the role definitions in the database.
67
68
        :param role_dbs: RoleDB objects for the roles which are currently in the database.
69
        :type role_dbs: ``list`` of :class:`RoleDB`
70
71
        :param role_definition_apis: RoleDefinition API objects for the definitions loaded from
72
                                     the files.
73
        :type role_definition_apis: ``list`` of :class:RoleDefinitionFileFormatAPI`
74
75
        :rtype: ``tuple``
76
        """
77
        LOG.info('Synchronizing roles...')
78
79
        # Retrieve all the roles currently in the DB
80
        role_dbs = rbac_services.get_all_roles(exclude_system=True)
81
82
        role_db_names = [role_db.name for role_db in role_dbs]
83
        role_db_names = set(role_db_names)
84
        role_api_names = [role_definition_api.name for role_definition_api in role_definition_apis]
85
        role_api_names = set(role_api_names)
86
87
        # A list of new roles which should be added to the database
88
        new_role_names = role_api_names.difference(role_db_names)
89
90
        # A list of roles which need to be updated in the database
91
        updated_role_names = role_db_names.intersection(role_api_names)
92
93
        # A list of roles which should be removed from the database
94
        removed_role_names = (role_db_names - role_api_names)
95
96
        LOG.debug('New roles: %r' % (new_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
97
        LOG.debug('Updated roles: %r' % (updated_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
98
        LOG.debug('Removed roles: %r' % (removed_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
99
100
        # Build a list of roles to delete
101
        role_names_to_delete = updated_role_names.union(removed_role_names)
102
        role_dbs_to_delete = [role_db for role_db in role_dbs if
103
                              role_db.name in role_names_to_delete]
104
105
        # Build a list of roles to create
106
        role_names_to_create = new_role_names.union(updated_role_names)
107
        role_apis_to_create = [role_definition_api for role_definition_api in role_definition_apis
108
                               if role_definition_api.name in role_names_to_create]
109
110
        ########
111
        # 1. Remove obsolete roles and associated permission grants from the DB
112
        ########
113
114
        # Remove roles
115
        role_ids_to_delete = []
116
        for role_db in role_dbs_to_delete:
117
            role_ids_to_delete.append(role_db.id)
118
119
        LOG.debug('Deleting %s stale roles' % (len(role_ids_to_delete)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
120
        Role.query(id__in=role_ids_to_delete, system=False).delete()
121
        LOG.debug('Deleted %s stale roles' % (len(role_ids_to_delete)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
122
123
        # Remove associated permission grants
124
        permission_grant_ids_to_delete = []
125
        for role_db in role_dbs_to_delete:
126
            permission_grant_ids_to_delete.extend(role_db.permission_grants)
127
128
        LOG.debug('Deleting %s stale permission grants' % (len(permission_grant_ids_to_delete)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
129
        PermissionGrant.query(id__in=permission_grant_ids_to_delete).delete()
130
        LOG.debug('Deleted %s stale permission grants' % (len(permission_grant_ids_to_delete)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
131
132
        ########
133
        # 2. Add new / updated roles to the DB
134
        ########
135
136
        LOG.debug('Creating %s new roles' % (len(role_apis_to_create)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
137
138
        # Create new roles
139
        created_role_dbs = []
140
        for role_api in role_apis_to_create:
141
            role_db = rbac_services.create_role(name=role_api.name,
142
                                                description=role_api.description)
143
144
            # Create associated permission grants
145
            permission_grants = getattr(role_api, 'permission_grants', [])
146
            for permission_grant in permission_grants:
147
                resource_uid = permission_grant.get('resource_uid', None)
148
149
                if resource_uid:
150
                    resource_type, _ = parse_uid(resource_uid)
151
                else:
152
                    resource_type = None
153
154
                permission_types = permission_grant['permission_types']
155
                assignment_db = rbac_services.create_permission_grant(
156
                    role_db=role_db,
157
                    resource_uid=resource_uid,
158
                    resource_type=resource_type,
159
                    permission_types=permission_types)
160
161
                role_db.permission_grants.append(str(assignment_db.id))
162
            created_role_dbs.append(role_db)
163
164
        LOG.debug('Created %s new roles' % (len(created_role_dbs)))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
165
        LOG.info('Roles synchronized (%s created, %s updated, %s removed)' %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
166
                 (len(new_role_names), len(updated_role_names), len(removed_role_names)))
167
168
        return [created_role_dbs, role_dbs_to_delete]
169
170
    def sync_users_role_assignments(self, role_assignment_apis):
171
        """
172
        Synchronize role assignments for all the users in the database.
173
174
        :param role_assignment_apis: Role assignments API objects for the assignments loaded
175
                                      from the files.
176
        :type role_assignment_apis: ``list`` of :class:`UserRoleAssignmentFileFormatAPI`
177
178
        :return: Dictionary with created and removed role assignments for each user.
179
        :rtype: ``dict``
180
        """
181
        LOG.info('Synchronizing users role assignments...')
182
183
        user_dbs = User.get_all()
184
        username_to_user_db_map = dict([(user_db.name, user_db) for user_db in user_dbs])
185
186
        results = {}
187
        for role_assignment_api in role_assignment_apis:
188
            username = role_assignment_api.username
189
            user_db = username_to_user_db_map.get(username, None)
190
191
            if not user_db:
192
                # Note: We allow assignments to be created for the users which don't exist in the
193
                # DB yet because user creation in StackStorm is lazy (we only create UserDB) object
194
                # when user first logs in.
195
                user_db = UserDB(name=username)
196
                LOG.debug(('User "%s" doesn\'t exist in the DB, creating assignment anyway' %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
197
                          (username)))
198
199
            role_assignment_dbs = rbac_services.get_role_assignments_for_user(user_db=user_db)
200
201
            result = self._sync_user_role_assignments(user_db=user_db,
202
                                                      role_assignment_dbs=role_assignment_dbs,
203
                                                      role_assignment_api=role_assignment_api)
204
            results[username] = result
205
206
        LOG.info('User role assignments synchronized')
207
        return results
208
209
    def _sync_user_role_assignments(self, user_db, role_assignment_dbs, role_assignment_api):
210
        """
211
        Synchronize role assignments for a particular user.
212
213
        :param user_db: User to synchronize the assignments for.
214
        :type user_db: :class:`UserDB`
215
216
        :param role_assignment_dbs: Existing user role assignments.
217
        :type role_assignment_dbs: ``list`` of :class:`UserRoleAssignmentDB`
218
219
        :param role_assignment_api: Role assignment API for a particular user.
220
        :param role_assignment_api: :class:`UserRoleAssignmentFileFormatAPI`
221
222
        :rtype: ``tuple``
223
        """
224
        db_role_names = [role_assignment_db.role for role_assignment_db in role_assignment_dbs]
225
        db_role_names = set(db_role_names)
226
        api_role_names = role_assignment_api.roles if role_assignment_api else []
227
        api_role_names = set(api_role_names)
228
229
        # A list of new assignments which should be added to the database
230
        new_role_names = api_role_names.difference(db_role_names)
231
232
        # A list of assgignments which need to be updated in the database
233
        updated_role_names = db_role_names.intersection(api_role_names)
234
235
        # A list of assignments which should be removed from the database
236
        removed_role_names = (db_role_names - api_role_names)
237
238
        LOG.debug('New assignments for user "%s": %r' % (user_db.name, new_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
239
        LOG.debug('Updated assignments for user "%s": %r' % (user_db.name, updated_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
240
        LOG.debug('Removed assignments for user "%s": %r' % (user_db.name, removed_role_names))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
241
242
        # Build a list of role assignments to delete
243
        role_names_to_delete = updated_role_names.union(removed_role_names)
244
        role_assignment_dbs_to_delete = [role_assignment_db for role_assignment_db
245
                                         in role_assignment_dbs
246
                                         if role_assignment_db.role in role_names_to_delete]
247
248
        UserRoleAssignment.query(user=user_db.name, role__in=role_names_to_delete).delete()
249
        LOG.debug('Removed %s assignments for user "%s"' %
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
250
                (len(role_assignment_dbs_to_delete), user_db.name))
251
252
        # Build a list of roles assignments to create
253
        role_names_to_create = new_role_names.union(updated_role_names)
254
        role_dbs_to_assign = Role.query(name__in=role_names_to_create)
255
256
        created_role_assignment_dbs = []
257
        for role_db in role_dbs_to_assign:
258
            if role_db.name in role_assignment_api.roles:
259
                description = getattr(role_assignment_api, 'description', None)
260
            else:
261
                description = None
262
            assignment_db = rbac_services.assign_role_to_user(role_db=role_db, user_db=user_db,
263
                                                              description=description)
264
            created_role_assignment_dbs.append(assignment_db)
265
266
        LOG.debug('Created %s new assignments for user "%s"' % (len(role_dbs_to_assign),
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
267
                                                                user_db.name))
268
269
        return (created_role_assignment_dbs, role_assignment_dbs_to_delete)
270