694a704da62d8e4b42bec196ee73fc373d3227ab
[osm/SO.git] / rwprojectmano / plugins / rwprojectmano / rift / tasklets / rwprojectmano / rolesmano.py
1 #
2 # Copyright 2017 RIFT.IO Inc
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # 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 """
18 Project Manager tasklet is responsible for managing the Projects
19 configurations required for Role Based Access Control feature.
20 """
21
22
23 import asyncio
24 from enum import Enum
25
26 import gi
27 gi.require_version('RwDts', '1.0')
28 gi.require_version('RwRbacInternalYang', '1.0')
29 from gi.repository import (
30 RwDts as rwdts,
31 ProtobufC,
32 RwTypes,
33 RwRbacInternalYang,
34 )
35
36 import rift.tasklets
37 #TODO: Fix once merged to latest platform
38 from rift.tasklets.rwproject.project import (
39 StateMachine,
40 User,
41 UserState,
42 RoleKeys,
43 RoleKeysUsers,
44 )
45 from rift.mano.utils.project import (
46 NS_PROJECT,
47 get_add_delete_update_cfgs,
48 )
49
50
51 MANO_PROJECT_ROLES = [
52 'rw-project-mano:catalog-oper',
53 'rw-project-mano:catalog-admin',
54 ]
55
56
57 class ProjectConfigSubscriber(object):
58 """Config subscriber for rw-user config"""
59
60 def __init__(self, project):
61 self.project_name = project.name
62 self._log = project.log
63 self._dts = project.dts
64
65 self.users = {}
66 self.pub = RoleConfigPublisher(project)
67
68 def get_xpath(self):
69 return "C,/{}[name='{}']/project-config/user".format(NS_PROJECT, self.project_name)
70
71
72 def role_inst(self, role, keys=None):
73 if not keys:
74 keys = self.project_name
75
76 r = RoleKeys()
77 r.role = role.role
78 r.keys = keys
79 return r
80
81 def delete_user(self, cfg):
82 user = User().pb(cfg)
83 self._log.error("Delete user {} for project {}".
84 format(user.key, self.project_name))
85 if user.key in self.users:
86 roles = self.users[user.key]
87 for role_key in list(roles):
88 self.delete_role(user, role_key)
89 self.users.pop(user.key)
90
91 def update_user(self, cfg):
92 user = User().pb(cfg)
93 self._log.debug("Update user {} for project {}".
94 format(user.key, self.project_name))
95 cfg_roles = {}
96 for cfg_role in cfg.mano_role:
97 r = self.role_inst(cfg_role)
98 cfg_roles[r.key] = r
99
100 if not user.key in self.users:
101 self.users[user.key] = set()
102 else:
103 #Check if any roles are deleted for the user
104 for role_key in list(self.users[user.key]):
105 if role_key not in cfg_roles:
106 self.delete_role(user, role_key)
107
108 for role_key in cfg_roles.keys():
109 if role_key not in self.users[user.key]:
110 self.update_role(user, cfg_roles[role_key])
111
112 def delete_role(self, user, role_key):
113 self._log.error("Delete role {} for user {}".
114 format(role_key, user.key))
115 user_key = user.key
116
117 try:
118 roles = self.users[user_key]
119 except KeyError:
120 roles = set()
121 self.users[user.key] = roles
122
123 if role_key in roles:
124 roles.remove(role_key)
125 self.pub.delete_role(role_key, user_key)
126
127 def update_role(self, user, role):
128 self._log.debug("Update role {} for user {}".
129 format(role.role, user.key))
130 user_key = user.key
131
132 try:
133 roles = self.users[user.key]
134 except KeyError:
135 roles = set()
136 self.users[user_key] = roles
137
138 role_key = role.key
139
140 if not role_key in roles:
141 roles.add(role_key)
142 self.pub.add_update_role(role_key, user_key)
143
144 @asyncio.coroutine
145 def register(self):
146 @asyncio.coroutine
147 def apply_config(dts, acg, xact, action, scratch):
148 self._log.debug("Got user apply config (xact: %s) (action: %s)",
149 xact, action)
150
151 if xact.xact is None:
152 if action == rwdts.AppconfAction.INSTALL:
153 curr_cfg = self._reg.elements
154 for cfg in curr_cfg:
155 self._log.debug("Project being re-added after restart.")
156 self.add_user(cfg)
157 else:
158 # When RIFT first comes up, an INSTALL is called with the current config
159 # Since confd doesn't actally persist data this never has any data so
160 # skip this for now.
161 self._log.debug("No xact handle. Skipping apply config")
162
163 return
164
165 # TODO: There is user-name and user-domain as keys. Need to fix
166 # this
167 add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
168 dts_member_reg=self._reg,
169 xact=xact,
170 key_name="user_name",
171 )
172
173 self._log.debug("Added: {}, Deleted: {}, Modified: {}".
174 format(add_cfgs, delete_cfgs, update_cfgs))
175 # Handle Deletes
176 for cfg in delete_cfgs:
177 self.delete_user(cfg)
178
179 # Handle Adds
180 for cfg in add_cfgs:
181 self.update_user(cfg)
182
183 # Handle Updates
184 for cfg in update_cfgs:
185 self.update_user(cfg)
186
187 return RwTypes.RwStatus.SUCCESS
188
189 @asyncio.coroutine
190 def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
191 """ Prepare callback from DTS for Project """
192
193 action = xact_info.query_action
194
195 self._log.debug("Project %s on_prepare config received (action: %s): %s",
196 self.project_name, xact_info.query_action, msg)
197
198 user = User().pb(msg)
199 if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
200 if user.key in self.users:
201 self._log.debug("User {} update request".
202 format(user.key))
203
204 else:
205 self._log.debug("User {}: on_prepare add request".
206 format(user.key))
207
208 elif action == rwdts.QueryAction.DELETE:
209 # Check if the user got deleted
210 fref = ProtobufC.FieldReference.alloc()
211 fref.goto_whole_message(msg.to_pbcm())
212 if fref.is_field_deleted():
213 if user.key in self.users:
214 self._log.debug("User {} being deleted".format(user.key))
215 else:
216 self._log.warning("Delete on unknown user: {}".
217 format(user.key))
218
219 else:
220 self._log.error("Action (%s) NOT SUPPORTED", action)
221 xact_info.respond_xpath(rwdts.XactRspCode.NACK)
222 return
223
224 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
225
226 xpath = self.get_xpath()
227 self._log.debug("Registering for project config using xpath: %s",
228 xpath,
229 )
230
231 acg_handler = rift.tasklets.AppConfGroup.Handler(
232 on_apply=apply_config,
233 )
234
235 with self._dts.appconf_group_create(acg_handler) as acg:
236 self._reg = acg.register(
237 xpath=xpath,
238 flags=rwdts.Flag.SUBSCRIBER | rwdts.Flag.DELTA_READY | rwdts.Flag.CACHE,
239 on_prepare=on_prepare,
240 )
241
242 yield from self.pub.register()
243 self.pub.create_project_roles()
244
245 def deregister(self):
246 self._log.debug("De-registering DTS handler for project {}".
247 format(self.project_name))
248
249 if self._reg:
250 self._reg.deregister()
251 self._reg = None
252
253 self.pub.deregister()
254
255
256 class RoleConfigPublisher(rift.tasklets.DtsConfigPublisher):
257
258 def __init__(self, project):
259 super().__init__(project._tasklet)
260 self.project_name = project.name
261 self.rbac_int = RwRbacInternalYang.YangData_RwRbacInternal_RwRbacInternal()
262 self.roles = {}
263 self.proj_roles = MANO_PROJECT_ROLES
264 self.proj_roles_published = False
265
266 def get_xpath(self):
267 return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role"
268
269 def role_xpath(self, role_key):
270 return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role" + \
271 "[rw-rbac-internal:role='{}']".format(role_key[0]) + \
272 "[rw-rbac-internal:keys='{}']".format(role_key[1])
273
274 def role_user_xpath(self, role_key, user_key):
275 return self.role_xpath(role_key) + \
276 "/rw-rbac-internal:user" + \
277 "[rw-rbac-internal:user-name='{}']".format(user_key[1]) + \
278 "[rw-rbac-internal:user-domain='{}']".format(user_key[0])
279
280 def create_project_roles(self):
281 for name in self.proj_roles:
282 role = RoleKeys()
283 role.role = name
284 role.keys = self.project_name
285 self.create_project_role(role)
286
287 def create_project_role(self, role):
288 self.log.error("Create project role for {}: {}".
289 format(self.project_name, role.role))
290 xpath = self.role_xpath(role.key)
291 pb_role = self.pb_role(role)
292 self._regh.update_element(xpath, pb_role)
293
294 def delete_project_roles(self):
295 for name in self.proj_roles:
296 role = RoleKeys()
297 role.role = name
298 role.keys = self.project_name
299 self.delete_project_role(role)
300
301 def delete_project_role(self, role):
302 self.log.error("Delete project role for {}: {}".
303 format(self.project_name, role.role))
304 xpath = self.role_xpath(role.key)
305 self._regh.delete_element(xpath)
306
307 def create_role(self, role_key, user_key):
308 return RoleKeysUsers(role_key, user_key)
309
310 def pb_role(self, role):
311
312 pbRole = self.rbac_int.create_role()
313 pbRole.role = role.role
314 pbRole.keys = role.keys
315 pbRole.state_machine.state = role.state.name
316
317 return pbRole
318
319 def pb_role_user(self, role, user):
320
321 pbRole = self.rbac_int.create_role()
322 pbRole.role = role.role
323 pbRole.keys = role.keys
324
325 pbUser = pbRole.create_user()
326 pbUser.user_name = user.user_name
327 pbUser.user_domain = user.user_domain
328 pbUser.state_machine.state = user.state.name
329
330 pbRole.user.append(pbUser)
331
332 return pbRole
333
334 def add_update_role(self, role_key, user_key):
335 update = True
336 try:
337 role = self.roles[role_key]
338 except KeyError:
339 role = RoleKeysUsers(role_key)
340 self.roles[role_key] = role
341 update = False
342
343 try:
344 user = role.user(user_key)
345 except KeyError:
346 user = UserState(user_key)
347 role.add_user(user)
348 update = False
349
350 if update:
351 user.state = StateMachine.new
352 else:
353 user.state = StateMachine.new
354
355 xpath = self.role_xpath(role_key)
356 self.log.debug("update role: {} user: {} ".format(role_key, user_key))
357
358
359 pb_role_user = self.pb_role_user(role, user)
360
361 self._regh.update_element(xpath, pb_role_user)
362
363 def delete_role(self, role_key, user_key):
364 try:
365 role = self.roles[role_key]
366 user = role.user(user_key)
367 except KeyError:
368 return
369
370 user.state = StateMachine.delete
371 xpath = self.role_xpath(role_key)
372 self.log.error("deleting role: {} user: {} ".format(role_key, user_key))
373
374 pb_role = self.pb_role_user(role, user)
375 self._regh.update_element(xpath, pb_role)
376
377 def do_prepare(self, xact_info, action, ks_path, msg):
378 """Handle on_prepare.
379 """
380 self.log.debug("do_prepare: action: {}, path: {} ks_path, msg: {}".format(action, ks_path, msg))
381
382 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
383
384 # TODO: See if we need this as this would be called in the platform also
385 # role_key = tuple([msg.role, msg.keys])
386
387 # state = msg.state_machine.state
388 # if state == 'init_done':
389 # msg.state_machine.state = 'active'
390 # xpath = self.role_xpath(role_key)
391 # self._regh.update_element(xpath, msg)
392
393 # for user in msg.users:
394 # user_key = tuple([user.user_domain, user.user_name])
395 # state = user.state_machine.state
396 # if state == 'init_done':
397 # user.state_machine.state = 'active'
398 # xpath = self.role_xpath(role_key)
399 # self._regh.update_element(xpath, msg)
400
401 def deregister(self):
402 if self._regh:
403 self.delete_project_roles()
404 self._regh.deregister()
405 self._regh = None