update from RIFT as of 696b75d2fe9fb046261b08c616f1bcf6c0b54a9b second try
[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 import asyncio
23 import gi
24
25 gi.require_version('RwDts', '1.0')
26 gi.require_version('RwRbacInternalYang', '1.0')
27 gi.require_version('RwProjectManoYang', '1.0')
28 from gi.repository import (
29 RwDts as rwdts,
30 ProtobufC,
31 RwTypes,
32 RwRbacInternalYang,
33 RwProjectManoYang,
34 )
35 gi.require_version('RwKeyspec', '1.0')
36 from gi.repository.RwKeyspec import quoted_key
37
38 import rift.tasklets
39 from rift.tasklets.rwidmgr.rbac import (
40 StateMachine,
41 User,
42 UserState,
43 RoleKeys,
44 RoleKeysUsers,
45 encode_role_instance_key,
46 )
47 from rift.mano.utils.project import (
48 NS_PROJECT,
49 get_add_delete_update_cfgs,
50 )
51
52
53 from .projectmano import MANO_PROJECT_ROLES
54
55
56 class ProjectConfigSubscriber(object):
57 """Config subscriber for rw-user config"""
58
59 def __init__(self, project):
60 self.project_name = project.name
61 self._log = project.log
62 self._dts = project.dts
63
64 self.users = {}
65 self.pub = ProjectMgrManoRoleConfigPublisher(project)
66 self.proj_roles = [role['mano-role'] for role in MANO_PROJECT_ROLES]
67
68 def get_xpath(self):
69 return "C,/{}[name={}]/project-config/user".format(NS_PROJECT, quoted_key(self.project_name))
70
71 def get_reg_flags(self):
72 return (rwdts.Flag.SUBSCRIBER |
73 rwdts.Flag.DELTA_READY |
74 rwdts.Flag.CACHE |
75 rwdts.Flag.DATASTORE)
76
77 def role_inst(self, role, keys=None):
78 if not keys:
79 keys = encode_role_instance_key(self.project_name)
80
81 r = RoleKeys()
82 r.role = role.role
83 r.keys = keys
84 return r
85
86 def delete_user(self, cfg):
87 user = User().pb(cfg)
88 self._log.info("Delete user {} for project {}".
89 format(user.key, self.project_name))
90 if user.key in self.users:
91 roles = self.users[user.key]
92 for role_key in list(roles):
93 self.delete_role(user, role_key)
94 self.users.pop(user.key)
95
96 def update_user(self, cfg):
97 user = User().pb(cfg)
98 self._log.debug("Update user {} for project {} cfg {}".
99 format(user.key, self.project_name, cfg))
100 cfg_roles = {}
101 for cfg_role in cfg.mano_role:
102 r = self.role_inst(cfg_role)
103 cfg_roles[r.key] = r
104
105 if not user.key in self.users:
106 self.users[user.key] = set()
107 else:
108 #Check if any roles are deleted for the user
109 for role_key in list(self.users[user.key]):
110 if role_key not in cfg_roles:
111 self.delete_role(user, role_key)
112
113 for role_key in cfg_roles.keys():
114 if role_key not in self.users[user.key]:
115 self.update_role(user, cfg_roles[role_key])
116
117 def delete_role(self, user, role_key):
118 self._log.info("Delete role {} for user {}".
119 format(role_key, user.key))
120 user_key = user.key
121
122 try:
123 roles = self.users[user_key]
124 except KeyError:
125 roles = set()
126 self.users[user.key] = roles
127
128 if role_key in roles:
129 roles.remove(role_key)
130 self.pub.delete_role(role_key, user_key)
131
132 def update_role(self, user, role):
133 self._log.debug("Update role {} for user {}".
134 format(role.role, user.key))
135
136 user_key = user.key
137
138 try:
139 roles = self.users[user.key]
140 except KeyError:
141 roles = set()
142 self.users[user_key] = roles
143
144 role_key = role.key
145
146 if not role_key in roles:
147 roles.add(role_key)
148 self.pub.add_update_role(role_key, user_key)
149
150 def delete_project(self):
151 # Clean up rw-rbac-intenal
152 self._log.error("Project {} delete".format(self.project_name))
153 for user_key, roles in self.users.items():
154 for role_key in roles:
155 self._log.error("delete role {} for user {}".format(role_key, user_key))
156 self.pub.delete_role(user_key, role_key)
157
158 @asyncio.coroutine
159 def register(self):
160 @asyncio.coroutine
161 def apply_config(dts, acg, xact, action, scratch):
162 self._log.debug("Got user apply config (xact: %s) (action: %s)",
163 xact, action)
164
165 if xact.xact is None:
166 if action == rwdts.AppconfAction.INSTALL:
167 curr_cfg = self._reg.elements
168 for cfg in curr_cfg:
169 self._log.info("Project {} user being restored: {}.".
170 format(self.project_name, cfg.as_dict()))
171 self.update_user(cfg)
172 else:
173 # When RIFT first comes up, an INSTALL is called with the current config
174 # Since confd doesn't actally persist data this never has any data so
175 # skip this for now.
176 self._log.debug("No xact handle. Skipping apply config")
177
178 return
179
180 # TODO: There is user-name and user-domain as keys. Need to fix
181 # this
182 add_cfgs, delete_cfgs, update_cfgs = get_add_delete_update_cfgs(
183 dts_member_reg=self._reg,
184 xact=xact,
185 key_name="user_name",
186 )
187
188 self._log.debug("Added: {}, Deleted: {}, Modified: {}".
189 format(add_cfgs, delete_cfgs, update_cfgs))
190 # Handle Deletes
191 for cfg in delete_cfgs:
192 self.delete_user(cfg)
193
194 # Handle Adds
195 for cfg in add_cfgs:
196 self.update_user(cfg)
197
198 # Handle Updates
199 for cfg in update_cfgs:
200 self.update_user(cfg)
201
202 return RwTypes.RwStatus.SUCCESS
203
204 @asyncio.coroutine
205 def on_prepare(dts, acg, xact, xact_info, ks_path, msg, scratch):
206 """ Prepare callback from DTS for Project """
207
208 action = xact_info.query_action
209
210 self._log.debug("Project %s on_prepare config received (action: %s): %s",
211 self.project_name, xact_info.query_action, msg)
212
213 user = User().pb(msg)
214 if action in [rwdts.QueryAction.CREATE, rwdts.QueryAction.UPDATE]:
215 if user.key in self.users:
216 self._log.debug("User {} update request".
217 format(user.key))
218
219 else:
220 self._log.debug("User {}: on_prepare add request".
221 format(user.key))
222
223 for role in msg.mano_role:
224 if role.role not in self.proj_roles:
225 errmsg = "Invalid MANO role {} for user {}". \
226 format(role.role, user.key)
227 self._log.error(errmsg)
228 xact_info.send_error_xpath(RwTypes.RwStatus.FAILURE,
229 self.get_xpath(),
230 errmsg)
231 xact_info.respond_xpath(rwdts.XactRspCode.NACK)
232 return
233
234 elif action == rwdts.QueryAction.DELETE:
235 # Check if the user got deleted
236 fref = ProtobufC.FieldReference.alloc()
237 fref.goto_whole_message(msg.to_pbcm())
238 if fref.is_field_deleted():
239 if user.key in self.users:
240 self._log.debug("User {} being deleted".format(user.key))
241 else:
242 self._log.warning("Delete on unknown user: {}".
243 format(user.key))
244
245 try:
246 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
247 except rift.tasklets.dts.ResponseError as e:
248 xpath = ks_path.to_xpath(RwProjectManoYang.get_schema())
249 self._log.debug("Exception sending response for {}: {}".
250 format(xpath, e))
251 return
252
253 else:
254 self._log.error("Action (%s) NOT SUPPORTED", action)
255 xact_info.respond_xpath(rwdts.XactRspCode.NA)
256 return
257
258 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
259
260 xpath = self.get_xpath()
261 self._log.debug("Registering for project config using xpath: %s",
262 xpath,
263 )
264
265 acg_handler = rift.tasklets.AppConfGroup.Handler(
266 on_apply=apply_config,
267 )
268
269 with self._dts.appconf_group_create(acg_handler) as acg:
270 self._reg = acg.register(
271 xpath=xpath,
272 flags=self.get_reg_flags(),
273 on_prepare=on_prepare,
274 )
275
276 yield from self.pub.register()
277 self.pub.create_project_roles()
278
279 def deregister(self):
280 self._log.debug("De-registering DTS handler for project {}".
281 format(self.project_name))
282
283 if self._reg:
284 self._reg.deregister()
285 self._reg = None
286
287 self.pub.delete_project_roles()
288 self.pub.deregister()
289
290 class ProjectMgrManoRoleConfigPublisher(rift.tasklets.DtsConfigPublisher):
291
292 def __init__(self, project):
293 super().__init__(project._tasklet)
294 self.project_name = project.name
295 self.notify = project._tasklet.notify
296 self.rbac_int = RwRbacInternalYang.YangData_RwRbacInternal_RwRbacInternal()
297 self.roles = {}
298 self.proj_roles = [role['mano-role'] for role in MANO_PROJECT_ROLES]
299 self.proj_roles_published = False
300
301 def get_xpath(self):
302 return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role"
303
304 def get_reg_flags(self):
305 return super().get_reg_flags() | rwdts.Flag.DATASTORE
306
307 def role_xpath(self, role_key):
308 return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role" + \
309 "[rw-rbac-internal:role={}]".format(quoted_key(role_key[0])) + \
310 "[rw-rbac-internal:keys={}]".format(quoted_key(role_key[1]))
311
312 def role_user_xpath(self, role_key, user_key):
313 return self.role_xpath(role_key) + \
314 "/rw-rbac-internal:user" + \
315 "[rw-rbac-internal:user-name={}]".format(quoted_key(user_key[0])) + \
316 "[rw-rbac-internal:user-domain={}]".format(quoted_key(user_key[1]))
317
318 def pb_role(self, role, user):
319 pbRole = self.rbac_int.create_role()
320 pbRole.role = role.role
321 pbRole.keys = role.keys
322 pbRole.state_machine.state = role.state.name
323
324 pbUser = pbRole.create_user()
325 pbUser.user_name = user.user_name
326 pbUser.user_domain = user.user_domain
327 pbUser.state_machine.state = user.state.name
328
329 pbRole.user.append(pbUser)
330
331 return pbRole
332
333 def pb_project_role(self, role):
334 pbRole = self.rbac_int.create_role()
335 pbRole.role = role.role
336 pbRole.keys = role.keys
337 pbRole.state_machine.state = role.state.name
338 return pbRole
339
340 def add_update_role(self, role_key, user_key):
341 try:
342 role = self.roles[role_key]
343 except KeyError:
344 role = RoleKeysUsers(role_key)
345 self.roles[role_key] = role
346
347 try:
348 user = role.user(user_key)
349 except KeyError:
350 user = UserState(user_key)
351 role.add_user(user)
352
353 user.state = StateMachine.new
354
355 xpath = self.role_xpath(role_key)
356 self.log.debug("add/update role: {} user: {} ".format(role_key, user_key))
357
358 pb_role = self.pb_role(role, user)
359 self._regh.update_element(xpath, pb_role)
360
361 event_desc = "Role '{}' with key '{}' assigned to user '{}' in domain '{}'". \
362 format(role.role, role.keys, user.user_name, user.user_domain)
363 self.notify.send_event("role-assigned", event_desc)
364
365 def delete_role(self, role_key, user_key):
366 try:
367 role = self.roles[role_key]
368 user = role.user(user_key)
369 except KeyError:
370 self.log.error("delete_role: invalid role/user {}/{}".format(role_key, user_key))
371 return
372
373 user.state = StateMachine.delete
374 xpath = self.role_xpath(role_key)
375 self.log.debug("deleting role: {} user: {}".format(role_key, user_key))
376
377 pb_role = self.pb_role(role, user)
378 self._regh.update_element(xpath, pb_role)
379
380 event_desc = "Role '{}' with key '{}' unassigned from user '{}' in domain '{}'". \
381 format(role.role, role.keys, user.user_name, user.user_domain)
382 self.notify.send_event("role-unassigned", event_desc)
383
384 def create_project_roles(self):
385 for name in self.proj_roles:
386 role = RoleKeys()
387 role.role = name
388 role.keys = encode_role_instance_key(self.project_name)
389 self.create_project_role(role)
390
391 def create_project_role(self, role):
392 role_key = role.key
393 try:
394 role = self.roles[role_key]
395 # already exist
396 return
397 except KeyError:
398 role = RoleKeysUsers(role_key)
399 self.roles[role_key] = role
400
401 xpath = self.role_xpath(role.key)
402
403 pb_role = self.pb_project_role(role)
404
405 # print("create_project_role path:{} role:{}".format(xpath, pb_role))
406 self._regh.update_element(xpath, pb_role)
407
408 def delete_project_roles(self):
409 for name in self.proj_roles:
410 role = RoleKeys()
411 role.role = name
412 role.keys = encode_role_instance_key(self.project_name)
413 self.delete_project_role(role)
414
415 def delete_project_role(self, role):
416 xpath = self.role_xpath(role.key)
417
418 self._regh.delete_element(xpath)
419
420 def do_prepare(self, xact_info, action, ks_path, msg):
421 """Handle on_prepare. To be overridden by Concreate Publisher Handler
422 """
423 role_key = tuple([msg.role, msg.keys])
424 try:
425 role = self.roles[role_key]
426 except KeyError:
427 xact_info.respond_xpath(rwdts.XactRspCode.NA)
428 return
429
430 self.log.debug("do_prepare (MANO-ROLES): action: {}, path: {}, msg: {}".format(action, ks_path, msg))
431 xact_info.respond_xpath(rwdts.XactRspCode.ACK)
432 xpath = self.role_xpath(role_key)
433
434 if msg.state_machine.state == 'init_done':
435 msg.state_machine.state = 'active'
436 role.state = StateMachine.active
437 self._regh.update_element(xpath, msg)
438 elif msg.state_machine.state == 'delete_done':
439 self._regh.delete_element(xpath)
440 del self.roles[role_key]
441 # deleted at role level, skip processing users under it
442 return
443
444 if msg.user:
445 for pbUser in msg.user:
446 user_key = tuple([pbUser.user_name, pbUser.user_domain])
447 try:
448 user = role.user(user_key)
449 except KeyError:
450 self._log.debug("**** User {} not found".format(user_key))
451 continue
452 user_xpath = self.role_user_xpath(role_key, user_key)
453 state = pbUser.state_machine.state
454 if state == 'init_done':
455 pbUser.state_machine.state = 'active'
456 user.state = StateMachine.active
457 self._regh.update_element(xpath, msg)
458 elif state == 'delete_done':
459 role.delete_user(user)
460 self._regh.delete_element(user_xpath)
461
462 def deregister(self):
463 if self.reg:
464 self.delete_project_roles()
465 super().deregister()