Coverage for osmclient/cli_commands/rbac.py: 41%

221 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2024-06-22 09:01 +0000

1# Copyright ETSI Contributors and Others. 

2# All Rights Reserved. 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# 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, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16import click 

17from osmclient.common.exceptions import ClientException 

18from osmclient.cli_commands import utils 

19from prettytable import PrettyTable 

20import json 

21import logging 

22import time 

23 

24logger = logging.getLogger("osmclient") 

25 

26 

27############################## 

28# Role Management Operations # 

29############################## 

30 

31 

32@click.command(name="role-create", short_help="creates a new role") 

33@click.argument("name") 

34@click.option("--permissions", default=None, help="role permissions using a dictionary") 

35@click.pass_context 

36def role_create(ctx, name, permissions): 

37 """ 

38 Creates a new role. 

39 

40 \b 

41 NAME: Name or ID of the role. 

42 DEFINITION: Definition of grant/denial of access to resources. 

43 """ 

44 logger.debug("") 

45 utils.check_client_version(ctx.obj, ctx.command.name) 

46 ctx.obj.role.create(name, permissions) 

47 

48 

49@click.command(name="role-update", short_help="updates a role") 

50@click.argument("name") 

51@click.option("--set-name", default=None, help="change name of rle") 

52@click.option( 

53 "--add", 

54 default=None, 

55 help="yaml format dictionary with permission: True/False to access grant/denial", 

56) 

57@click.option("--remove", default=None, help="yaml format list to remove a permission") 

58@click.pass_context 

59def role_update(ctx, name, set_name, add, remove): 

60 """ 

61 Updates a role. 

62 

63 \b 

64 NAME: Name or ID of the role. 

65 DEFINITION: Definition overwrites the old definition. 

66 ADD: Grant/denial of access to resource to add. 

67 REMOVE: Grant/denial of access to resource to remove. 

68 """ 

69 logger.debug("") 

70 utils.check_client_version(ctx.obj, ctx.command.name) 

71 ctx.obj.role.update(name, set_name, None, add, remove) 

72 

73 

74@click.command(name="role-delete", short_help="deletes a role") 

75@click.argument("name") 

76@click.pass_context 

77def role_delete(ctx, name): 

78 """ 

79 Deletes a role. 

80 

81 \b 

82 NAME: Name or ID of the role. 

83 """ 

84 logger.debug("") 

85 utils.check_client_version(ctx.obj, ctx.command.name) 

86 ctx.obj.role.delete(name) 

87 

88 

89@click.command(name="role-list", short_help="list all roles") 

90@click.option( 

91 "--filter", 

92 default=None, 

93 multiple=True, 

94 help="restricts the list to the projects matching the filter", 

95) 

96@click.pass_context 

97def role_list(ctx, filter): 

98 """ 

99 List all roles. 

100 """ 

101 logger.debug("") 

102 utils.check_client_version(ctx.obj, ctx.command.name) 

103 if filter: 

104 filter = "&".join(filter) 

105 resp = ctx.obj.role.list(filter) 

106 table = PrettyTable(["name", "id"]) 

107 for role in resp: 

108 table.add_row([role["name"], role["_id"]]) 

109 table.align = "l" 

110 print(table) 

111 

112 

113@click.command(name="role-show", short_help="show specific role") 

114@click.argument("name") 

115@click.pass_context 

116def role_show(ctx, name): 

117 """ 

118 Shows the details of a role. 

119 

120 \b 

121 NAME: Name or ID of the role. 

122 """ 

123 logger.debug("") 

124 utils.check_client_version(ctx.obj, ctx.command.name) 

125 resp = ctx.obj.role.get(name) 

126 

127 table = PrettyTable(["key", "attribute"]) 

128 for k, v in resp.items(): 

129 table.add_row([k, json.dumps(v, indent=2)]) 

130 table.align = "l" 

131 print(table) 

132 

133 

134#################### 

135# Project mgmt operations 

136#################### 

137 

138 

139@click.command(name="project-create", short_help="creates a new project") 

140@click.argument("name") 

141# @click.option('--description', 

142# default='no description', 

143# help='human readable description') 

144@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") 

145@click.option( 

146 "--quotas", 

147 "quotas", 

148 multiple=True, 

149 default=None, 

150 help="provide quotas. Can be used several times: 'quota1=number[,quota2=number,...]'. Quotas can be one " 

151 "of vnfds, nsds, nsts, pdus, nsrs, nsis, vim_accounts, wim_accounts, sdns, k8sclusters, k8srepos", 

152) 

153@click.pass_context 

154def project_create(ctx, name, domain_name, quotas): 

155 """Creates a new project 

156 

157 NAME: name of the project 

158 DOMAIN_NAME: optional domain name for the project when keystone authentication is used 

159 QUOTAS: set quotas for the project 

160 """ 

161 logger.debug("") 

162 project = {"name": name} 

163 if domain_name: 

164 project["domain_name"] = domain_name 

165 quotas_dict = _process_project_quotas(quotas) 

166 if quotas_dict: 

167 project["quotas"] = quotas_dict 

168 

169 utils.check_client_version(ctx.obj, ctx.command.name) 

170 ctx.obj.project.create(name, project) 

171 

172 

173def _process_project_quotas(quota_list): 

174 quotas_dict = {} 

175 if not quota_list: 

176 return quotas_dict 

177 try: 

178 for quota in quota_list: 

179 for single_quota in quota.split(","): 

180 k, v = single_quota.split("=") 

181 quotas_dict[k] = None if v in ("None", "null", "") else int(v) 

182 except (ValueError, TypeError): 

183 raise ClientException( 

184 "invalid format for 'quotas'. Use 'k1=v1,v1=v2'. v must be a integer or null" 

185 ) 

186 return quotas_dict 

187 

188 

189@click.command(name="project-delete", short_help="deletes a project") 

190@click.argument("name") 

191@click.pass_context 

192def project_delete(ctx, name): 

193 """deletes a project 

194 

195 NAME: name or ID of the project to be deleted 

196 """ 

197 logger.debug("") 

198 utils.check_client_version(ctx.obj, ctx.command.name) 

199 ctx.obj.project.delete(name) 

200 

201 

202@click.command(name="project-list", short_help="list all projects") 

203@click.option( 

204 "--filter", 

205 default=None, 

206 multiple=True, 

207 help="restricts the list to the projects matching the filter", 

208) 

209@click.pass_context 

210def project_list(ctx, filter): 

211 """list all projects""" 

212 logger.debug("") 

213 utils.check_client_version(ctx.obj, ctx.command.name) 

214 if filter: 

215 filter = "&".join(filter) 

216 resp = ctx.obj.project.list(filter) 

217 table = PrettyTable(["name", "id"]) 

218 for proj in resp: 

219 table.add_row([proj["name"], proj["_id"]]) 

220 table.align = "l" 

221 print(table) 

222 

223 

224@click.command(name="project-show", short_help="shows the details of a project") 

225@click.argument("name") 

226@click.pass_context 

227def project_show(ctx, name): 

228 """shows the details of a project 

229 

230 NAME: name or ID of the project 

231 """ 

232 logger.debug("") 

233 utils.check_client_version(ctx.obj, ctx.command.name) 

234 resp = ctx.obj.project.get(name) 

235 

236 table = PrettyTable(["key", "attribute"]) 

237 for k, v in resp.items(): 

238 table.add_row([k, json.dumps(v, indent=2)]) 

239 table.align = "l" 

240 print(table) 

241 

242 

243@click.command( 

244 name="project-update", short_help="updates a project (only the name can be updated)" 

245) 

246@click.argument("project") 

247@click.option("--name", default=None, help="new name for the project") 

248@click.option( 

249 "--quotas", 

250 "quotas", 

251 multiple=True, 

252 default=None, 

253 help="change quotas. Can be used several times: 'quota1=number|empty[,quota2=...]' " 

254 "(use empty to reset quota to default", 

255) 

256@click.pass_context 

257def project_update(ctx, project, name, quotas): 

258 """ 

259 Update a project name 

260 

261 :param ctx: 

262 :param project: id or name of the project to modify 

263 :param name: new name for the project 

264 :param quotas: change quotas of the project 

265 :return: 

266 """ 

267 logger.debug("") 

268 project_changes = {} 

269 if name: 

270 project_changes["name"] = name 

271 quotas_dict = _process_project_quotas(quotas) 

272 if quotas_dict: 

273 project_changes["quotas"] = quotas_dict 

274 

275 utils.check_client_version(ctx.obj, ctx.command.name) 

276 ctx.obj.project.update(project, project_changes) 

277 

278 

279#################### 

280# User mgmt operations 

281#################### 

282 

283 

284@click.command(name="user-create", short_help="creates a new user") 

285@click.argument("username") 

286@click.option( 

287 "--password", 

288 prompt=True, 

289 hide_input=True, 

290 confirmation_prompt=True, 

291 help="user password", 

292) 

293@click.option( 

294 "--projects", 

295 # prompt="Comma separate list of projects", 

296 multiple=True, 

297 callback=lambda ctx, param, value: ( 

298 "".join(value).split(",") if all(len(x) == 1 for x in value) else value 

299 ), 

300 help="list of project ids that the user belongs to", 

301) 

302@click.option( 

303 "--project-role-mappings", 

304 "project_role_mappings", 

305 default=None, 

306 multiple=True, 

307 help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", 

308) 

309@click.option("--domain-name", "domain_name", default=None, help="assign to a domain") 

310@click.pass_context 

311def user_create(ctx, username, password, projects, project_role_mappings, domain_name): 

312 """Creates a new user 

313 

314 \b 

315 USERNAME: name of the user 

316 PASSWORD: password of the user 

317 PROJECTS: projects assigned to user (internal only) 

318 PROJECT_ROLE_MAPPING: roles in projects assigned to user (keystone) 

319 DOMAIN_NAME: optional domain name for the user when keystone authentication is used 

320 """ 

321 logger.debug("") 

322 user = {} 

323 user["username"] = username 

324 user["password"] = password 

325 user["projects"] = projects 

326 user["project_role_mappings"] = project_role_mappings 

327 if domain_name: 

328 user["domain_name"] = domain_name 

329 

330 utils.check_client_version(ctx.obj, ctx.command.name) 

331 ctx.obj.user.create(username, user) 

332 

333 

334@click.command(name="user-update", short_help="updates user information") 

335@click.argument("username") 

336@click.option("--set-username", "set_username", default=None, help="change username") 

337@click.option( 

338 "--set-project", 

339 "set_project", 

340 default=None, 

341 multiple=True, 

342 help="create/replace the roles for this project: 'project,role1[,role2,...]'", 

343) 

344@click.option( 

345 "--remove-project", 

346 "remove_project", 

347 default=None, 

348 multiple=True, 

349 help="removes project from user: 'project'", 

350) 

351@click.option( 

352 "--add-project-role", 

353 "add_project_role", 

354 default=None, 

355 multiple=True, 

356 help="assign role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", 

357) 

358@click.option( 

359 "--remove-project-role", 

360 "remove_project_role", 

361 default=None, 

362 multiple=True, 

363 help="remove role(s) in a project. Can be used several times: 'project,role1[,role2,...]'", 

364) 

365@click.option("--current-password", "current_password", help="user's current password") 

366@click.option( 

367 "--new-password", 

368 "new_password", 

369 # prompt=True, 

370 # hide_input=True, 

371 # confirmation_prompt=True, 

372 help="new user password", 

373) 

374@click.option( 

375 "--unlock", 

376 is_flag=True, 

377 help="unlock user", 

378) 

379@click.option( 

380 "--renew", 

381 is_flag=True, 

382 help="renew user", 

383) 

384@click.pass_context 

385def user_update( 

386 ctx, 

387 username, 

388 set_username, 

389 set_project, 

390 remove_project, 

391 add_project_role, 

392 remove_project_role, 

393 current_password, 

394 new_password, 

395 unlock, 

396 renew, 

397): 

398 """Update a user information 

399 

400 \b 

401 USERNAME: name of the user 

402 SET_USERNAME: new username 

403 SET_PROJECT: creating mappings for project/role(s) 

404 REMOVE_PROJECT: deleting mappings for project/role(s) 

405 ADD_PROJECT_ROLE: adding mappings for project/role(s) 

406 REMOVE_PROJECT_ROLE: removing mappings for project/role(s) 

407 CURRENT_PASSWORD: user's current password to change 

408 NEW_PASSWORD: user's new password to be updated 

409 UNLOCK: unlock user 

410 RENEW: renew user 

411 """ 

412 logger.debug("") 

413 user = {} 

414 user["username"] = set_username 

415 user["set-project"] = set_project 

416 user["remove-project"] = remove_project 

417 user["add-project-role"] = add_project_role 

418 user["remove-project-role"] = remove_project_role 

419 user["current_password"] = current_password 

420 user["new_password"] = new_password 

421 user["unlock"] = unlock 

422 user["renew"] = renew 

423 

424 utils.check_client_version(ctx.obj, ctx.command.name) 

425 if not user.get("current_password"): 

426 # In case the password is valid but the end user wants to update it 

427 ctx.obj.user.update(username, user) 

428 else: 

429 # In case the password has expired (also applies in first login) 

430 ctx.obj.user.update(username, user, pwd_change=True) 

431 

432 

433@click.command(name="user-delete", short_help="deletes a user") 

434@click.argument("name") 

435# @click.option('--force', is_flag=True, help='forces the deletion bypassing pre-conditions') 

436@click.pass_context 

437def user_delete(ctx, name): 

438 """deletes a user 

439 

440 \b 

441 NAME: name or ID of the user to be deleted 

442 """ 

443 logger.debug("") 

444 utils.check_client_version(ctx.obj, ctx.command.name) 

445 ctx.obj.user.delete(name) 

446 

447 

448@click.command(name="user-list", short_help="list all users") 

449@click.option( 

450 "--filter", 

451 default=None, 

452 multiple=True, 

453 help="restricts the list to the users matching the filter", 

454) 

455@click.pass_context 

456def user_list(ctx, filter): 

457 """list all users""" 

458 utils.check_client_version(ctx.obj, ctx.command.name) 

459 if filter: 

460 filter = "&".join(filter) 

461 resp, admin_show = ctx.obj.user.list(filter) 

462 for user in resp: 

463 if user["username"] == "admin": 

464 user["_admin"]["account_expire_time"] = "N/A" 

465 if admin_show: 

466 table = PrettyTable(["name", "id", "user_status", "expires_in"]) 

467 for user in resp: 

468 table.add_row( 

469 [ 

470 user["username"], 

471 user["_id"], 

472 user["_admin"]["user_status"].upper(), 

473 ( 

474 time.strftime( 

475 "%b-%d-%Y %X", 

476 time.gmtime(user["_admin"]["account_expire_time"]), 

477 ) 

478 if not user["username"] == "admin" 

479 else user["_admin"]["account_expire_time"] 

480 ), 

481 ] 

482 ) 

483 else: 

484 table = PrettyTable(["name", "id"]) 

485 for user in resp: 

486 table.add_row([user["username"], user["_id"]]) 

487 table.align = "l" 

488 print(table) 

489 

490 

491@click.command(name="user-show", short_help="shows the details of a user") 

492@click.argument("name") 

493@click.pass_context 

494def user_show(ctx, name): 

495 """shows the details of a user 

496 

497 NAME: name or ID of the user 

498 """ 

499 logger.debug("") 

500 utils.check_client_version(ctx.obj, ctx.command.name) 

501 resp = ctx.obj.user.get(name) 

502 if "password" in resp: 

503 resp["password"] = "********" 

504 

505 table = PrettyTable(["key", "attribute"]) 

506 for k, v in resp.items(): 

507 table.add_row([k, json.dumps(v, indent=2)]) 

508 table.align = "l" 

509 print(table)