2 # Copyright 2017 RIFT.IO Inc
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 Project Manager tasklet is responsible for managing the Projects
19 configurations required for Role Based Access Control feature.
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 (
35 gi
.require_version('RwKeyspec', '1.0')
36 from gi
.repository
.RwKeyspec
import quoted_key
39 from rift
.tasklets
.rwidmgr
.rbac
import (
45 encode_role_instance_key
,
47 from rift
.mano
.utils
.project
import (
49 get_add_delete_update_cfgs
,
53 from .projectmano
import MANO_PROJECT_ROLES
56 class ProjectConfigSubscriber(object):
57 """Config subscriber for rw-user config"""
59 def __init__(self
, project
):
60 self
.project_name
= project
.name
61 self
._log
= project
.log
62 self
._dts
= project
.dts
65 self
.pub
= ProjectMgrManoRoleConfigPublisher(project
)
66 self
.proj_roles
= [role
['mano-role'] for role
in MANO_PROJECT_ROLES
]
69 return "C,/{}[name={}]/project-config/user".format(NS_PROJECT
, quoted_key(self
.project_name
))
71 def get_reg_flags(self
):
72 return (rwdts
.Flag
.SUBSCRIBER |
73 rwdts
.Flag
.DELTA_READY |
77 def role_inst(self
, role
, keys
=None):
79 keys
= encode_role_instance_key(self
.project_name
)
86 def delete_user(self
, 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
)
96 def update_user(self
, cfg
):
98 self
._log
.debug("Update user {} for project {} cfg {}".
99 format(user
.key
, self
.project_name
, cfg
))
101 for cfg_role
in cfg
.mano_role
:
102 r
= self
.role_inst(cfg_role
)
105 if not user
.key
in self
.users
:
106 self
.users
[user
.key
] = set()
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
)
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
])
117 def delete_role(self
, user
, role_key
):
118 self
._log
.info("Delete role {} for user {}".
119 format(role_key
, user
.key
))
123 roles
= self
.users
[user_key
]
126 self
.users
[user
.key
] = roles
128 if role_key
in roles
:
129 roles
.remove(role_key
)
130 self
.pub
.delete_role(role_key
, user_key
)
132 def update_role(self
, user
, role
):
133 self
._log
.debug("Update role {} for user {}".
134 format(role
.role
, user
.key
))
139 roles
= self
.users
[user
.key
]
142 self
.users
[user_key
] = roles
146 if not role_key
in roles
:
148 self
.pub
.add_update_role(role_key
, user_key
)
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
)
161 def apply_config(dts
, acg
, xact
, action
, scratch
):
162 self
._log
.debug("Got user apply config (xact: %s) (action: %s)",
165 if xact
.xact
is None:
166 if action
== rwdts
.AppconfAction
.INSTALL
:
167 curr_cfg
= self
._reg
.elements
169 self
._log
.info("Project {} user being restored: {}.".
170 format(self
.project_name
, cfg
.as_dict()))
171 self
.update_user(cfg
)
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
176 self
._log
.debug("No xact handle. Skipping apply config")
180 # TODO: There is user-name and user-domain as keys. Need to fix
182 add_cfgs
, delete_cfgs
, update_cfgs
= get_add_delete_update_cfgs(
183 dts_member_reg
=self
._reg
,
185 key_name
="user_name",
188 self
._log
.debug("Added: {}, Deleted: {}, Modified: {}".
189 format(add_cfgs
, delete_cfgs
, update_cfgs
))
191 for cfg
in delete_cfgs
:
192 self
.delete_user(cfg
)
196 self
.update_user(cfg
)
199 for cfg
in update_cfgs
:
200 self
.update_user(cfg
)
202 return RwTypes
.RwStatus
.SUCCESS
205 def on_prepare(dts
, acg
, xact
, xact_info
, ks_path
, msg
, scratch
):
206 """ Prepare callback from DTS for Project """
208 action
= xact_info
.query_action
210 self
._log
.debug("Project %s on_prepare config received (action: %s): %s",
211 self
.project_name
, xact_info
.query_action
, msg
)
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".
220 self
._log
.debug("User {}: on_prepare add request".
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
,
231 xact_info
.respond_xpath(rwdts
.XactRspCode
.NACK
)
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
))
242 self
._log
.warning("Delete on unknown user: {}".
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 {}: {}".
254 self
._log
.error("Action (%s) NOT SUPPORTED", action
)
255 xact_info
.respond_xpath(rwdts
.XactRspCode
.NA
)
258 xact_info
.respond_xpath(rwdts
.XactRspCode
.ACK
)
260 xpath
= self
.get_xpath()
261 self
._log
.debug("Registering for project config using xpath: %s",
265 acg_handler
= rift
.tasklets
.AppConfGroup
.Handler(
266 on_apply
=apply_config
,
269 with self
._dts
.appconf_group_create(acg_handler
) as acg
:
270 self
._reg
= acg
.register(
272 flags
=self
.get_reg_flags(),
273 on_prepare
=on_prepare
,
276 yield from self
.pub
.register()
277 self
.pub
.create_project_roles()
279 def deregister(self
):
280 self
._log
.debug("De-registering DTS handler for project {}".
281 format(self
.project_name
))
284 self
._reg
.deregister()
287 self
.pub
.delete_project_roles()
288 self
.pub
.deregister()
290 class ProjectMgrManoRoleConfigPublisher(rift
.tasklets
.DtsConfigPublisher
):
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()
298 self
.proj_roles
= [role
['mano-role'] for role
in MANO_PROJECT_ROLES
]
299 self
.proj_roles_published
= False
302 return "D,/rw-rbac-internal:rw-rbac-internal/rw-rbac-internal:role"
304 def get_reg_flags(self
):
305 return super().get_reg_flags() | rwdts
.Flag
.DATASTORE
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]))
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]))
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
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
329 pbRole
.user
.append(pbUser
)
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
340 def add_update_role(self
, role_key
, user_key
):
342 role
= self
.roles
[role_key
]
344 role
= RoleKeysUsers(role_key
)
345 self
.roles
[role_key
] = role
348 user
= role
.user(user_key
)
350 user
= UserState(user_key
)
353 user
.state
= StateMachine
.new
355 xpath
= self
.role_xpath(role_key
)
356 self
.log
.debug("add/update role: {} user: {} ".format(role_key
, user_key
))
358 pb_role
= self
.pb_role(role
, user
)
359 self
._regh
.update_element(xpath
, pb_role
)
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
)
365 def delete_role(self
, role_key
, user_key
):
367 role
= self
.roles
[role_key
]
368 user
= role
.user(user_key
)
370 self
.log
.error("delete_role: invalid role/user {}/{}".format(role_key
, user_key
))
373 user
.state
= StateMachine
.delete
374 xpath
= self
.role_xpath(role_key
)
375 self
.log
.debug("deleting role: {} user: {}".format(role_key
, user_key
))
377 pb_role
= self
.pb_role(role
, user
)
378 self
._regh
.update_element(xpath
, pb_role
)
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
)
384 def create_project_roles(self
):
385 for name
in self
.proj_roles
:
388 role
.keys
= encode_role_instance_key(self
.project_name
)
389 self
.create_project_role(role
)
391 def create_project_role(self
, role
):
394 role
= self
.roles
[role_key
]
398 role
= RoleKeysUsers(role_key
)
399 self
.roles
[role_key
] = role
401 xpath
= self
.role_xpath(role
.key
)
403 pb_role
= self
.pb_project_role(role
)
405 # print("create_project_role path:{} role:{}".format(xpath, pb_role))
406 self
._regh
.update_element(xpath
, pb_role
)
408 def delete_project_roles(self
):
409 for name
in self
.proj_roles
:
412 role
.keys
= encode_role_instance_key(self
.project_name
)
413 self
.delete_project_role(role
)
415 def delete_project_role(self
, role
):
416 xpath
= self
.role_xpath(role
.key
)
418 self
._regh
.delete_element(xpath
)
420 def do_prepare(self
, xact_info
, action
, ks_path
, msg
):
421 """Handle on_prepare. To be overridden by Concreate Publisher Handler
423 role_key
= tuple([msg
.role
, msg
.keys
])
425 role
= self
.roles
[role_key
]
427 xact_info
.respond_xpath(rwdts
.XactRspCode
.NA
)
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
)
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
445 for pbUser
in msg
.user
:
446 user_key
= tuple([pbUser
.user_name
, pbUser
.user_domain
])
448 user
= role
.user(user_key
)
450 self
._log
.debug("**** User {} not found".format(user_key
))
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
)
462 def deregister(self
):
464 self
.delete_project_roles()