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