Coverage for osm_nbi/authconn_keystone.py: 10%

338 statements  

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

1# -*- coding: utf-8 -*- 

2 

3# Copyright 2018 Whitestack, LLC 

4# 

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

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

7# a copy of the License at 

8# 

9# http://www.apache.org/licenses/LICENSE-2.0 

10# 

11# Unless required by applicable law or agreed to in writing, software 

12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

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

14# License for the specific language governing permissions and limitations 

15# under the License. 

16# 

17# For those usages not covered by the Apache License, Version 2.0 please 

18# contact: esousa@whitestack.com or glavado@whitestack.com 

19## 

20 

21""" 

22AuthconnKeystone implements implements the connector for 

23Openstack Keystone and leverages the RBAC model, to bring 

24it for OSM. 

25""" 

26 

27 

28__author__ = ( 

29 "Eduardo Sousa <esousa@whitestack.com>, " 

30 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>" 

31) 

32__date__ = "$27-jul-2018 23:59:59$" 

33 

34from osm_nbi.authconn import ( 

35 Authconn, 

36 AuthException, 

37 AuthconnOperationException, 

38 AuthconnNotFoundException, 

39 AuthconnConflictException, 

40) 

41 

42import logging 

43import requests 

44import time 

45from keystoneauth1 import session 

46from keystoneauth1.identity import v3 

47from keystoneauth1.exceptions.base import ClientException 

48from keystoneauth1.exceptions.http import Conflict 

49from keystoneclient.v3 import client 

50from http import HTTPStatus 

51from osm_nbi.validation import is_valid_uuid, validate_input, http_schema 

52 

53 

54class AuthconnKeystone(Authconn): 

55 def __init__(self, config, db, role_permissions): 

56 Authconn.__init__(self, config, db, role_permissions) 

57 

58 self.logger = logging.getLogger("nbi.authenticator.keystone") 

59 self.domains_id2name = {} 

60 self.domains_name2id = {} 

61 

62 self.auth_url = config.get("auth_url") 

63 if config.get("auth_url"): 

64 validate_input(self.auth_url, http_schema) 

65 else: 

66 self.auth_url = "http://{0}:{1}/v3".format( 

67 config.get("auth_host", "keystone"), config.get("auth_port", "5000") 

68 ) 

69 self.user_domain_name_list = config.get("user_domain_name", "default") 

70 self.user_domain_name_list = self.user_domain_name_list.split(",") 

71 # read only domain list 

72 self.user_domain_ro_list = [ 

73 x[:-3] for x in self.user_domain_name_list if x.endswith(":ro") 

74 ] 

75 # remove the ":ro" 

76 self.user_domain_name_list = [ 

77 x if not x.endswith(":ro") else x[:-3] for x in self.user_domain_name_list 

78 ] 

79 

80 self.admin_project = config.get("service_project", "service") 

81 self.admin_username = config.get("service_username", "nbi") 

82 self.admin_password = config.get("service_password", "nbi") 

83 self.project_domain_name_list = config.get("project_domain_name", "default") 

84 self.project_domain_name_list = self.project_domain_name_list.split(",") 

85 if len(self.user_domain_name_list) != len(self.project_domain_name_list): 

86 raise ValueError( 

87 "Invalid configuration parameter fo authenticate. 'project_domain_name' and " 

88 "'user_domain_name' must be a comma-separated list with the same size. Revise " 

89 "configuration or/and 'OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME', " 

90 "'OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME' Variables" 

91 ) 

92 

93 # Waiting for Keystone to be up 

94 available = None 

95 counter = 300 

96 while available is None: 

97 time.sleep(1) 

98 try: 

99 result = requests.get(self.auth_url) 

100 available = True if result.status_code == 200 else None 

101 except Exception: 

102 counter -= 1 

103 if counter == 0: 

104 raise AuthException("Keystone not available after 300s timeout") 

105 

106 self.auth = v3.Password( 

107 user_domain_name=self.user_domain_name_list[0], 

108 username=self.admin_username, 

109 password=self.admin_password, 

110 project_domain_name=self.project_domain_name_list[0], 

111 project_name=self.admin_project, 

112 auth_url=self.auth_url, 

113 ) 

114 self.sess = session.Session(auth=self.auth) 

115 self.keystone = client.Client( 

116 session=self.sess, endpoint_override=self.auth_url 

117 ) 

118 

119 def authenticate(self, credentials, token_info=None): 

120 """ 

121 Authenticate a user using username/password or token_info, plus project 

122 :param credentials: dictionary that contains: 

123 username: name, id or None 

124 password: password or None 

125 project_id: name, id, or None. If None first found project will be used to get an scope token 

126 project_domain_name: (Optional) To use a concrete domain for the project 

127 user_domain_name: (Optional) To use a concrete domain for the project 

128 other items are allowed and ignored 

129 :param token_info: previous token_info to obtain authorization 

130 :return: the scoped token info or raises an exception. The token is a dictionary with: 

131 _id: token string id, 

132 username: username, 

133 project_id: scoped_token project_id, 

134 project_name: scoped_token project_name, 

135 expires: epoch time when it expires, 

136 

137 """ 

138 username = None 

139 user_id = None 

140 project_id = None 

141 project_name = None 

142 if credentials.get("project_domain_name"): 

143 project_domain_name_list = (credentials["project_domain_name"],) 

144 else: 

145 project_domain_name_list = self.project_domain_name_list 

146 if credentials.get("user_domain_name"): 

147 user_domain_name_list = (credentials["user_domain_name"],) 

148 else: 

149 user_domain_name_list = self.user_domain_name_list 

150 

151 for index, project_domain_name in enumerate(project_domain_name_list): 

152 user_domain_name = user_domain_name_list[index] 

153 try: 

154 if credentials.get("username"): 

155 if is_valid_uuid(credentials["username"]): 

156 user_id = credentials["username"] 

157 else: 

158 username = credentials["username"] 

159 

160 # get an unscoped token firstly 

161 unscoped_token = self.keystone.get_raw_token_from_identity_service( 

162 auth_url=self.auth_url, 

163 user_id=user_id, 

164 username=username, 

165 password=credentials.get("password"), 

166 user_domain_name=user_domain_name, 

167 project_domain_name=project_domain_name, 

168 ) 

169 elif token_info: 

170 unscoped_token = self.keystone.tokens.validate( 

171 token=token_info.get("_id") 

172 ) 

173 else: 

174 raise AuthException( 

175 "Provide credentials: username/password or Authorization Bearer token", 

176 http_code=HTTPStatus.UNAUTHORIZED, 

177 ) 

178 

179 if not credentials.get("project_id"): 

180 # get first project for the user 

181 project_list = self.keystone.projects.list( 

182 user=unscoped_token["user"]["id"] 

183 ) 

184 if not project_list: 

185 raise AuthException( 

186 "The user {} has not any project and cannot be used for authentication".format( 

187 credentials.get("username") 

188 ), 

189 http_code=HTTPStatus.UNAUTHORIZED, 

190 ) 

191 project_id = project_list[0].id 

192 else: 

193 if is_valid_uuid(credentials["project_id"]): 

194 project_id = credentials["project_id"] 

195 else: 

196 project_name = credentials["project_id"] 

197 

198 scoped_token = self.keystone.get_raw_token_from_identity_service( 

199 auth_url=self.auth_url, 

200 project_name=project_name, 

201 project_id=project_id, 

202 user_domain_name=user_domain_name, 

203 project_domain_name=project_domain_name, 

204 token=unscoped_token["auth_token"], 

205 ) 

206 

207 auth_token = { 

208 "_id": scoped_token.auth_token, 

209 "id": scoped_token.auth_token, 

210 "user_id": scoped_token.user_id, 

211 "username": scoped_token.username, 

212 "project_id": scoped_token.project_id, 

213 "project_name": scoped_token.project_name, 

214 "project_domain_name": scoped_token.project_domain_name, 

215 "user_domain_name": scoped_token.user_domain_name, 

216 "expires": scoped_token.expires.timestamp(), 

217 "issued_at": scoped_token.issued.timestamp(), 

218 } 

219 

220 return auth_token 

221 except ClientException as e: 

222 if ( 

223 index >= len(user_domain_name_list) - 1 

224 or index >= len(project_domain_name_list) - 1 

225 ): 

226 # if last try, launch exception 

227 # self.logger.exception("Error during user authentication using keystone: {}".format(e)) 

228 raise AuthException( 

229 "Error during user authentication using Keystone: {}".format(e), 

230 http_code=HTTPStatus.UNAUTHORIZED, 

231 ) 

232 

233 def validate_token(self, token): 

234 """ 

235 Check if the token is valid. 

236 

237 :param token: token id to be validated 

238 :return: dictionary with information associated with the token: 

239 "expires": 

240 "_id": token_id, 

241 "project_id": project_id, 

242 "username": , 

243 "roles": list with dict containing {name, id} 

244 If the token is not valid an exception is raised. 

245 """ 

246 if not token: 

247 return 

248 

249 try: 

250 token_info = self.keystone.tokens.validate(token=token) 

251 ses = { 

252 "_id": token_info["auth_token"], 

253 "id": token_info["auth_token"], 

254 "project_id": token_info["project"]["id"], 

255 "project_name": token_info["project"]["name"], 

256 "user_id": token_info["user"]["id"], 

257 "username": token_info["user"]["name"], 

258 "roles": token_info["roles"], 

259 "expires": token_info.expires.timestamp(), 

260 "issued_at": token_info.issued.timestamp(), 

261 } 

262 

263 return ses 

264 except ClientException as e: 

265 # self.logger.exception("Error during token validation using keystone: {}".format(e)) 

266 raise AuthException( 

267 "Error during token validation using Keystone: {}".format(e), 

268 http_code=HTTPStatus.UNAUTHORIZED, 

269 ) 

270 

271 def revoke_token(self, token): 

272 """ 

273 Invalidate a token. 

274 

275 :param token: token to be revoked 

276 """ 

277 try: 

278 self.logger.info("Revoking token: " + token) 

279 self.keystone.tokens.revoke_token(token=token) 

280 

281 return True 

282 except ClientException as e: 

283 # self.logger.exception("Error during token revocation using keystone: {}".format(e)) 

284 raise AuthException( 

285 "Error during token revocation using Keystone: {}".format(e), 

286 http_code=HTTPStatus.UNAUTHORIZED, 

287 ) 

288 

289 def _get_domain_id(self, domain_name, fail_if_not_found=True): 

290 """ 

291 Get the domain id from the domain_name 

292 :param domain_name: Can be the name or id 

293 :param fail_if_not_found: If False it returns None instead of raising an exception if not found 

294 :return: str or None/exception if domain is not found 

295 """ 

296 domain_id = self.domains_name2id.get(domain_name) 

297 if not domain_id: 

298 self._get_domains() 

299 domain_id = self.domains_name2id.get(domain_name) 

300 if not domain_id and domain_name in self.domains_id2name: 

301 # domain_name is already an id 

302 return domain_name 

303 if not domain_id and fail_if_not_found: 

304 raise AuthconnNotFoundException( 

305 "Domain {} cannot be found".format(domain_name) 

306 ) 

307 return domain_id 

308 

309 def _get_domains(self): 

310 """ 

311 Obtain a dictionary with domain_id to domain_name, stored at self.domains_id2name 

312 and from domain_name to domain_id, sored at self.domains_name2id 

313 :return: None. Exceptions are ignored 

314 """ 

315 try: 

316 domains = self.keystone.domains.list() 

317 self.domains_id2name = {x.id: x.name for x in domains} 

318 self.domains_name2id = {x.name: x.id for x in domains} 

319 except Exception: 

320 pass 

321 

322 def create_user(self, user_info): 

323 """ 

324 Create a user. 

325 

326 :param user_info: full user info. 

327 :raises AuthconnOperationException: if user creation failed. 

328 :return: returns the id of the user in keystone. 

329 """ 

330 try: 

331 if ( 

332 user_info.get("domain_name") 

333 and user_info["domain_name"] in self.user_domain_ro_list 

334 ): 

335 raise AuthconnConflictException( 

336 "Cannot create a user in the read only domain {}".format( 

337 user_info["domain_name"] 

338 ) 

339 ) 

340 

341 new_user = self.keystone.users.create( 

342 user_info["username"], 

343 password=user_info["password"], 

344 domain=self._get_domain_id( 

345 user_info.get("domain_name", self.user_domain_name_list[0]) 

346 ), 

347 _admin=user_info["_admin"], 

348 ) 

349 if "project_role_mappings" in user_info.keys(): 

350 for mapping in user_info["project_role_mappings"]: 

351 self.assign_role_to_user( 

352 new_user, mapping["project"], mapping["role"] 

353 ) 

354 return {"username": new_user.name, "_id": new_user.id} 

355 except Conflict as e: 

356 # self.logger.exception("Error during user creation using keystone: {}".format(e)) 

357 raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT) 

358 except ClientException as e: 

359 # self.logger.exception("Error during user creation using keystone: {}".format(e)) 

360 raise AuthconnOperationException( 

361 "Error during user creation using Keystone: {}".format(e) 

362 ) 

363 

364 def update_user(self, user_info): 

365 """ 

366 Change the user name and/or password. 

367 

368 :param user_info: user info modifications 

369 :raises AuthconnOperationException: if change failed. 

370 """ 

371 try: 

372 user = user_info.get("_id") or user_info.get("username") 

373 try: 

374 user_obj = self.keystone.users.get(user) 

375 except Exception: 

376 user_obj = None 

377 if not user_obj: 

378 for user_domain in self.user_domain_name_list: 

379 domain_id = self._get_domain_id( 

380 user_domain, fail_if_not_found=False 

381 ) 

382 if not domain_id: 

383 continue 

384 user_obj_list = self.keystone.users.list( 

385 name=user, domain=domain_id 

386 ) 

387 if user_obj_list: 

388 user_obj = user_obj_list[0] 

389 break 

390 else: # user not found 

391 raise AuthconnNotFoundException("User '{}' not found".format(user)) 

392 

393 user_id = user_obj.id 

394 domain_id = user_obj.domain_id 

395 domain_name = self.domains_id2name.get(domain_id) 

396 

397 if domain_name in self.user_domain_ro_list: 

398 if user_info.get("password") or user_info.get("username"): 

399 raise AuthconnConflictException( 

400 "Cannot update the user {} belonging to a read only domain {}".format( 

401 user, domain_name 

402 ) 

403 ) 

404 

405 elif ( 

406 user_info.get("password") 

407 or user_info.get("username") 

408 or user_info.get("add_project_role_mappings") 

409 or user_info.get("remove_project_role_mappings") 

410 ): 

411 # if user_index>0, it is an external domain, that should not be updated 

412 ctime = ( 

413 user_obj._admin.get("created", 0) 

414 if hasattr(user_obj, "_admin") 

415 else 0 

416 ) 

417 try: 

418 self.keystone.users.update( 

419 user_id, 

420 password=user_info.get("password"), 

421 name=user_info.get("username"), 

422 _admin={"created": ctime, "modified": time.time()}, 

423 ) 

424 except Exception as e: 

425 if user_info.get("username") or user_info.get("password"): 

426 raise AuthconnOperationException( 

427 "Error during username/password change: {}".format(str(e)) 

428 ) 

429 self.logger.error( 

430 "Error during updating user profile: {}".format(str(e)) 

431 ) 

432 

433 for mapping in user_info.get("remove_project_role_mappings", []): 

434 self.remove_role_from_user( 

435 user_obj, mapping["project"], mapping["role"] 

436 ) 

437 for mapping in user_info.get("add_project_role_mappings", []): 

438 self.assign_role_to_user(user_obj, mapping["project"], mapping["role"]) 

439 except ClientException as e: 

440 # self.logger.exception("Error during user password/name update using keystone: {}".format(e)) 

441 raise AuthconnOperationException( 

442 "Error during user update using Keystone: {}".format(e) 

443 ) 

444 

445 def delete_user(self, user_id): 

446 """ 

447 Delete user. 

448 

449 :param user_id: user identifier. 

450 :raises AuthconnOperationException: if user deletion failed. 

451 """ 

452 try: 

453 user_obj = self.keystone.users.get(user_id) 

454 domain_id = user_obj.domain_id 

455 domain_name = self.domains_id2name.get(domain_id) 

456 if domain_name in self.user_domain_ro_list: 

457 raise AuthconnConflictException( 

458 "Cannot delete user {} belonging to a read only domain {}".format( 

459 user_id, domain_name 

460 ) 

461 ) 

462 

463 result, detail = self.keystone.users.delete(user_id) 

464 if result.status_code != 204: 

465 raise ClientException("error {} {}".format(result.status_code, detail)) 

466 return True 

467 except ClientException as e: 

468 # self.logger.exception("Error during user deletion using keystone: {}".format(e)) 

469 raise AuthconnOperationException( 

470 "Error during user deletion using Keystone: {}".format(e) 

471 ) 

472 

473 def get_user_list(self, filter_q=None): 

474 """ 

475 Get user list. 

476 

477 :param filter_q: dictionary to filter user list by one or several 

478 _id: 

479 name (username is also admitted). If a user id is equal to the filter name, it is also provided 

480 domain_id, domain_name 

481 :return: returns a list of users. 

482 """ 

483 try: 

484 self._get_domains() 

485 filter_name = filter_domain = None 

486 if filter_q: 

487 filter_name = filter_q.get("name") or filter_q.get("username") 

488 if filter_q.get("domain_name"): 

489 filter_domain = self._get_domain_id( 

490 filter_q["domain_name"], fail_if_not_found=False 

491 ) 

492 # If domain is not found, use the same name to obtain an empty list 

493 filter_domain = filter_domain or filter_q["domain_name"] 

494 if filter_q.get("domain_id"): 

495 filter_domain = filter_q["domain_id"] 

496 

497 users = self.keystone.users.list(name=filter_name, domain=filter_domain) 

498 # get users from user_domain_name_list[1:], because it will not be provided in case of LDAP 

499 if filter_domain is None and len(self.user_domain_name_list) > 1: 

500 for user_domain in self.user_domain_name_list[1:]: 

501 domain_id = self._get_domain_id( 

502 user_domain, fail_if_not_found=False 

503 ) 

504 if not domain_id: 

505 continue 

506 # find if users of this domain are already provided. In this case ignore 

507 for u in users: 

508 if u.domain_id == domain_id: 

509 break 

510 else: 

511 users += self.keystone.users.list( 

512 name=filter_name, domain=domain_id 

513 ) 

514 

515 # if filter name matches a user id, provide it also 

516 if filter_name: 

517 try: 

518 user_obj = self.keystone.users.get(filter_name) 

519 if user_obj not in users: 

520 users.append(user_obj) 

521 except Exception: 

522 pass 

523 

524 users = [ 

525 { 

526 "username": user.name, 

527 "_id": user.id, 

528 "id": user.id, 

529 "_admin": user.to_dict().get("_admin", {}), # TODO: REVISE 

530 "domain_name": self.domains_id2name.get(user.domain_id), 

531 } 

532 for user in users 

533 if user.name != self.admin_username 

534 ] 

535 

536 if filter_q and filter_q.get("_id"): 

537 users = [user for user in users if filter_q["_id"] == user["_id"]] 

538 

539 for user in users: 

540 user["project_role_mappings"] = [] 

541 user["projects"] = [] 

542 projects = self.keystone.projects.list(user=user["_id"]) 

543 for project in projects: 

544 user["projects"].append(project.name) 

545 

546 roles = self.keystone.roles.list( 

547 user=user["_id"], project=project.id 

548 ) 

549 for role in roles: 

550 prm = { 

551 "project": project.id, 

552 "project_name": project.name, 

553 "role_name": role.name, 

554 "role": role.id, 

555 } 

556 user["project_role_mappings"].append(prm) 

557 

558 return users 

559 except ClientException as e: 

560 # self.logger.exception("Error during user listing using keystone: {}".format(e)) 

561 raise AuthconnOperationException( 

562 "Error during user listing using Keystone: {}".format(e) 

563 ) 

564 

565 def get_role_list(self, filter_q=None): 

566 """ 

567 Get role list. 

568 

569 :param filter_q: dictionary to filter role list by _id and/or name. 

570 :return: returns the list of roles. 

571 """ 

572 try: 

573 filter_name = None 

574 if filter_q: 

575 filter_name = filter_q.get("name") 

576 roles_list = self.keystone.roles.list(name=filter_name) 

577 

578 roles = [ 

579 { 

580 "name": role.name, 

581 "_id": role.id, 

582 "_admin": role.to_dict().get("_admin", {}), 

583 "permissions": role.to_dict().get("permissions", {}), 

584 } 

585 for role in roles_list 

586 if role.name != "service" 

587 ] 

588 

589 if filter_q and filter_q.get("_id"): 

590 roles = [role for role in roles if filter_q["_id"] == role["_id"]] 

591 

592 return roles 

593 except ClientException as e: 

594 # self.logger.exception("Error during user role listing using keystone: {}".format(e)) 

595 raise AuthException( 

596 "Error during user role listing using Keystone: {}".format(e), 

597 http_code=HTTPStatus.UNAUTHORIZED, 

598 ) 

599 

600 def create_role(self, role_info): 

601 """ 

602 Create a role. 

603 

604 :param role_info: full role info. 

605 :raises AuthconnOperationException: if role creation failed. 

606 """ 

607 try: 

608 result = self.keystone.roles.create( 

609 role_info["name"], 

610 permissions=role_info.get("permissions"), 

611 _admin=role_info.get("_admin"), 

612 ) 

613 return result.id 

614 except Conflict as ex: 

615 raise AuthconnConflictException(str(ex)) 

616 except ClientException as e: 

617 # self.logger.exception("Error during role creation using keystone: {}".format(e)) 

618 raise AuthconnOperationException( 

619 "Error during role creation using Keystone: {}".format(e) 

620 ) 

621 

622 def delete_role(self, role_id): 

623 """ 

624 Delete a role. 

625 

626 :param role_id: role identifier. 

627 :raises AuthconnOperationException: if role deletion failed. 

628 """ 

629 try: 

630 result, detail = self.keystone.roles.delete(role_id) 

631 

632 if result.status_code != 204: 

633 raise ClientException("error {} {}".format(result.status_code, detail)) 

634 

635 return True 

636 except ClientException as e: 

637 # self.logger.exception("Error during role deletion using keystone: {}".format(e)) 

638 raise AuthconnOperationException( 

639 "Error during role deletion using Keystone: {}".format(e) 

640 ) 

641 

642 def update_role(self, role_info): 

643 """ 

644 Change the name of a role 

645 :param role_info: full role info 

646 :return: None 

647 """ 

648 try: 

649 rid = role_info["_id"] 

650 if not is_valid_uuid(rid): # Is this required? 

651 role_obj_list = self.keystone.roles.list(name=rid) 

652 if not role_obj_list: 

653 raise AuthconnNotFoundException("Role '{}' not found".format(rid)) 

654 rid = role_obj_list[0].id 

655 self.keystone.roles.update( 

656 rid, 

657 name=role_info["name"], 

658 permissions=role_info.get("permissions"), 

659 _admin=role_info.get("_admin"), 

660 ) 

661 except ClientException as e: 

662 # self.logger.exception("Error during role update using keystone: {}".format(e)) 

663 raise AuthconnOperationException( 

664 "Error during role updating using Keystone: {}".format(e) 

665 ) 

666 

667 def get_project_list(self, filter_q=None): 

668 """ 

669 Get all the projects. 

670 

671 :param filter_q: dictionary to filter project list. 

672 :return: list of projects 

673 """ 

674 try: 

675 self._get_domains() 

676 filter_name = filter_domain = None 

677 if filter_q: 

678 filter_name = filter_q.get("name") 

679 if filter_q.get("domain_name"): 

680 filter_domain = self.domains_name2id.get(filter_q["domain_name"]) 

681 if filter_q.get("domain_id"): 

682 filter_domain = filter_q["domain_id"] 

683 

684 projects = self.keystone.projects.list( 

685 name=filter_name, domain=filter_domain 

686 ) 

687 

688 projects = [ 

689 { 

690 "name": project.name, 

691 "_id": project.id, 

692 "_admin": project.to_dict().get("_admin", {}), # TODO: REVISE 

693 "quotas": project.to_dict().get("quotas", {}), # TODO: REVISE 

694 "domain_name": self.domains_id2name.get(project.domain_id), 

695 } 

696 for project in projects 

697 ] 

698 

699 if filter_q and filter_q.get("_id"): 

700 projects = [ 

701 project for project in projects if filter_q["_id"] == project["_id"] 

702 ] 

703 

704 return projects 

705 except ClientException as e: 

706 # self.logger.exception("Error during user project listing using keystone: {}".format(e)) 

707 raise AuthException( 

708 "Error during user project listing using Keystone: {}".format(e), 

709 http_code=HTTPStatus.UNAUTHORIZED, 

710 ) 

711 

712 def create_project(self, project_info): 

713 """ 

714 Create a project. 

715 

716 :param project_info: full project info. 

717 :return: the internal id of the created project 

718 :raises AuthconnOperationException: if project creation failed. 

719 """ 

720 try: 

721 result = self.keystone.projects.create( 

722 project_info["name"], 

723 domain=self._get_domain_id( 

724 project_info.get("domain_name", self.project_domain_name_list[0]) 

725 ), 

726 _admin=project_info["_admin"], 

727 quotas=project_info.get("quotas", {}), 

728 ) 

729 return result.id 

730 except ClientException as e: 

731 # self.logger.exception("Error during project creation using keystone: {}".format(e)) 

732 raise AuthconnOperationException( 

733 "Error during project creation using Keystone: {}".format(e) 

734 ) 

735 

736 def delete_project(self, project_id): 

737 """ 

738 Delete a project. 

739 

740 :param project_id: project identifier. 

741 :raises AuthconnOperationException: if project deletion failed. 

742 """ 

743 try: 

744 # projects = self.keystone.projects.list() 

745 # project_obj = [project for project in projects if project.id == project_id][0] 

746 # result, _ = self.keystone.projects.delete(project_obj) 

747 

748 result, detail = self.keystone.projects.delete(project_id) 

749 if result.status_code != 204: 

750 raise ClientException("error {} {}".format(result.status_code, detail)) 

751 

752 return True 

753 except ClientException as e: 

754 # self.logger.exception("Error during project deletion using keystone: {}".format(e)) 

755 raise AuthconnOperationException( 

756 "Error during project deletion using Keystone: {}".format(e) 

757 ) 

758 

759 def update_project(self, project_id, project_info): 

760 """ 

761 Change the name of a project 

762 :param project_id: project to be changed 

763 :param project_info: full project info 

764 :return: None 

765 """ 

766 try: 

767 self.keystone.projects.update( 

768 project_id, 

769 name=project_info["name"], 

770 _admin=project_info["_admin"], 

771 quotas=project_info.get("quotas", {}), 

772 ) 

773 except ClientException as e: 

774 # self.logger.exception("Error during project update using keystone: {}".format(e)) 

775 raise AuthconnOperationException( 

776 "Error during project update using Keystone: {}".format(e) 

777 ) 

778 

779 def assign_role_to_user(self, user_obj, project, role): 

780 """ 

781 Assigning a role to a user in a project. 

782 

783 :param user_obj: user object, obtained with keystone.users.get or list. 

784 :param project: project name. 

785 :param role: role name. 

786 :raises AuthconnOperationException: if role assignment failed. 

787 """ 

788 try: 

789 try: 

790 project_obj = self.keystone.projects.get(project) 

791 except Exception: 

792 project_obj_list = self.keystone.projects.list(name=project) 

793 if not project_obj_list: 

794 raise AuthconnNotFoundException( 

795 "Project '{}' not found".format(project) 

796 ) 

797 project_obj = project_obj_list[0] 

798 

799 try: 

800 role_obj = self.keystone.roles.get(role) 

801 except Exception: 

802 role_obj_list = self.keystone.roles.list(name=role) 

803 if not role_obj_list: 

804 raise AuthconnNotFoundException("Role '{}' not found".format(role)) 

805 role_obj = role_obj_list[0] 

806 

807 self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj) 

808 except ClientException as e: 

809 # self.logger.exception("Error during user role assignment using keystone: {}".format(e)) 

810 raise AuthconnOperationException( 

811 "Error during role '{}' assignment to user '{}' and project '{}' using " 

812 "Keystone: {}".format(role, user_obj.name, project, e) 

813 ) 

814 

815 def remove_role_from_user(self, user_obj, project, role): 

816 """ 

817 Remove a role from a user in a project. 

818 

819 :param user_obj: user object, obtained with keystone.users.get or list. 

820 :param project: project name or id. 

821 :param role: role name or id. 

822 

823 :raises AuthconnOperationException: if role assignment revocation failed. 

824 """ 

825 try: 

826 try: 

827 project_obj = self.keystone.projects.get(project) 

828 except Exception: 

829 project_obj_list = self.keystone.projects.list(name=project) 

830 if not project_obj_list: 

831 raise AuthconnNotFoundException( 

832 "Project '{}' not found".format(project) 

833 ) 

834 project_obj = project_obj_list[0] 

835 

836 try: 

837 role_obj = self.keystone.roles.get(role) 

838 except Exception: 

839 role_obj_list = self.keystone.roles.list(name=role) 

840 if not role_obj_list: 

841 raise AuthconnNotFoundException("Role '{}' not found".format(role)) 

842 role_obj = role_obj_list[0] 

843 

844 self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj) 

845 except ClientException as e: 

846 # self.logger.exception("Error during user role revocation using keystone: {}".format(e)) 

847 raise AuthconnOperationException( 

848 "Error during role '{}' revocation to user '{}' and project '{}' using " 

849 "Keystone: {}".format(role, user_obj.name, project, e) 

850 )