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