Changeset 866c983 for fedd/federation/access.py
- Timestamp:
- Jul 21, 2009 2:23:40 PM (15 years ago)
- Branches:
- axis_example, compt_changes, info-ops, master, version-1.30, version-2.00, version-3.01, version-3.02
- Children:
- 55c074c
- Parents:
- 8780cbec
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
fedd/federation/access.py
r8780cbec r866c983 41 41 42 42 def __init__(self, config=None, auth=None): 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 43 """ 44 Initializer. Pulls parameters out of the ConfigParser's access section. 45 """ 46 47 # Make sure that the configuration is in place 48 if not config: 49 raise RunTimeError("No config to fedd.access") 50 51 self.project_priority = config.getboolean("access", "project_priority") 52 self.allow_proxy = config.getboolean("access", "allow_proxy") 53 54 self.boss = config.get("access", "boss") 55 self.ops = config.get("access", "ops") 56 self.domain = config.get("access", "domain") 57 self.fileserver = config.get("access", "fileserver") 58 self.eventserver = config.get("access", "eventserver") 59 60 self.attrs = { } 61 self.access = { } 62 self.restricted = [ ] 63 self.projects = { } 64 self.keys = { } 65 self.types = { } 66 self.allocation = { } 67 self.state = { 68 'projects': self.projects, 69 'allocation' : self.allocation, 70 'keys' : self.keys, 71 'types': self.types 72 } 73 self.log = logging.getLogger("fedd.access") 74 set_log_level(config, "access", self.log) 75 self.state_lock = Lock() 76 77 if auth: self.auth = auth 78 else: 79 self.log.error(\ 80 "[access]: No authorizer initialized, creating local one.") 81 auth = authorizer() 82 83 tb = config.get('access', 'testbed') 84 if tb: self.testbed = [ t.strip() for t in tb.split(',') ] 85 else: self.testbed = [ ] 86 87 if config.has_option("access", "accessdb"): 88 self.read_access(config.get("access", "accessdb")) 89 90 self.state_filename = config.get("access", "access_state") 91 self.read_state() 92 93 # Keep cert_file and cert_pwd coming from the same place 94 self.cert_file = config.get("access", "cert_file") 95 if self.cert_file: 96 self.sert_pwd = config.get("access", "cert_pw") 97 else: 98 self.cert_file = config.get("globals", "cert_file") 99 self.sert_pwd = config.get("globals", "cert_pw") 100 101 self.trusted_certs = config.get("access", "trusted_certs") or \ 102 config.get("globals", "trusted_certs") 103 104 self.soap_services = {\ 105 'RequestAccess': soap_handler("RequestAccess", self.RequestAccess), 106 'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess), 107 } 108 self.xmlrpc_services = {\ 109 'RequestAccess': xmlrpc_handler('RequestAccess', 110 self.RequestAccess), 111 'ReleaseAccess': xmlrpc_handler('ReleaseAccess', 112 self.ReleaseAccess), 113 } 114 115 116 if not config.has_option("allocate", "uri"): 117 self.allocate_project = \ 118 allocate_project_local(config, auth) 119 else: 120 self.allocate_project = \ 121 allocate_project_remote(config, auth) 122 123 # If the project allocator exports services, put them in this object's 124 # maps so that classes that instantiate this can call the services. 125 self.soap_services.update(self.allocate_project.soap_services) 126 self.xmlrpc_services.update(self.allocate_project.xmlrpc_services) 127 127 128 128 129 129 def read_access(self, config): 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 130 """ 131 Read a configuration file and set internal parameters. 132 133 The format is more complex than one might hope. The basic format is 134 attribute value pairs separated by colons(:) on a signle line. The 135 attributes in bool_attrs, emulab_attrs and id_attrs can all be set 136 directly using the name: value syntax. E.g. 137 boss: hostname 138 sets self.boss to hostname. In addition, there are access lines of the 139 form (tb, proj, user) -> (aproj, auser) that map the first tuple of 140 names to the second for access purposes. Names in the key (left side) 141 can include "<NONE> or <ANY>" to act as wildcards or to require the 142 fields to be empty. Similarly aproj or auser can be <SAME> or 143 <DYNAMIC> indicating that either the matching key is to be used or a 144 dynamic user or project will be created. These names can also be 145 federated IDs (fedid's) if prefixed with fedid:. Finally, the aproj 146 can be followed with a colon-separated list of node types to which that 147 project has access (or will have access if dynamic). 148 Testbed attributes outside the forms above can be given using the 149 format attribute: name value: value. The name is a single word and the 150 value continues to the end of the line. Empty lines and lines startin 151 with a # are ignored. 152 153 Parsing errors result in a self.parse_error exception being raised. 154 """ 155 lineno=0 156 name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+" 157 fedid_expr = "fedid:[" + string.hexdigits + "]+" 158 key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")" 159 access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \ 160 "<SAME>" + "(?::" + name_expr + ")*|" + \ 161 fedid_expr + "(?::" + name_expr + ")*|" + \ 162 name_expr + "(?::" + name_expr + ")*)" 163 access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")" 164 165 restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE) 166 attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)', 167 re.IGNORECASE) 168 access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ 169 key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 170 access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE) 171 172 def parse_name(n): 173 if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):]) 174 else: return n 175 176 def auth_name(n): 177 if isinstance(n, basestring): 178 if n =='<any>' or n =='<none>': return None 179 else: return unicode(n) 180 else: 181 return n 182 183 f = open(config, "r"); 184 for line in f: 185 lineno += 1 186 line = line.strip(); 187 if len(line) == 0 or line.startswith('#'): 188 continue 189 190 # Extended (attribute: x value: y) attribute line 191 m = attr_re.match(line) 192 if m != None: 193 attr, val = m.group(1,2) 194 self.attrs[attr] = val 195 continue 196 197 # Restricted entry 198 m = restricted_re.match(line) 199 if m != None: 200 val = m.group(1) 201 self.restricted.append(val) 202 continue 203 204 # Access line (t, p, u) -> (ap, cu, su) line 205 m = access_re.match(line) 206 if m != None: 207 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)]) 208 auth_key = tuple([ auth_name(x) for x in access_key]) 209 aps = m.group(4).split(":"); 210 if aps[0] == 'fedid:': 211 del aps[0] 212 aps[0] = fedid(hexstr=aps[0]) 213 214 cu = parse_name(m.group(5)) 215 su = parse_name(m.group(6)) 216 217 access_val = (access_project(aps[0], aps[1:]), 218 parse_name(m.group(5)), parse_name(m.group(6))) 219 220 self.access[access_key] = access_val 221 self.auth.set_attribute(auth_key, "access") 222 continue 223 224 # Nothing matched to here: unknown line - raise exception 225 f.close() 226 raise self.parse_error("Unknown statement at line %d of %s" % \ 227 (lineno, config)) 228 f.close() 229 229 230 230 def get_users(self, obj): 231 232 233 234 235 236 237 238 231 """ 232 Return a list of the IDs of the users in dict 233 """ 234 if obj.has_key('user'): 235 return [ unpack_id(u['userID']) \ 236 for u in obj['user'] if u.has_key('userID') ] 237 else: 238 return None 239 239 240 240 def write_state(self): 241 242 243 244 245 246 247 248 249 250 251 241 if self.state_filename: 242 try: 243 f = open(self.state_filename, 'w') 244 pickle.dump(self.state, f) 245 except IOError, e: 246 self.log.error("Can't write file %s: %s" % \ 247 (self.state_filename, e)) 248 except pickle.PicklingError, e: 249 self.log.error("Pickling problem: %s" % e) 250 except TypeError, e: 251 self.log.error("Pickling problem (TypeError): %s" % e) 252 252 253 253 254 254 def read_state(self): 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 255 """ 256 Read a new copy of access state. Old state is overwritten. 257 258 State format is a simple pickling of the state dictionary. 259 """ 260 if self.state_filename: 261 try: 262 f = open(self.state_filename, "r") 263 self.state = pickle.load(f) 264 265 self.allocation = self.state['allocation'] 266 self.projects = self.state['projects'] 267 self.keys = self.state['keys'] 268 self.types = self.state['types'] 269 270 self.log.debug("[read_state]: Read state from %s" % \ 271 self.state_filename) 272 except IOError, e: 273 self.log.warning(("[read_state]: No saved state: " +\ 274 "Can't open %s: %s") % (self.state_filename, e)) 275 except EOFError, e: 276 self.log.warning(("[read_state]: " +\ 277 "Empty or damaged state file: %s:") % \ 278 self.state_filename) 279 except pickle.UnpicklingError, e: 280 self.log.warning(("[read_state]: No saved state: " + \ 281 "Unpickling failed: %s") % e) 282 283 # Add the ownership attributes to the authorizer. Note that the 284 # indices of the allocation dict are strings, but the attributes are 285 # fedids, so there is a conversion. 286 for k in self.allocation.keys(): 287 for o in self.allocation[k].get('owners', []): 288 self.auth.set_attribute(o, fedid(hexstr=k)) 289 289 290 290 291 291 def permute_wildcards(self, a, p): 292 293 294 295 296 297 298 299 300 301 302 303 304 292 """Return a copy of a with various fields wildcarded. 293 294 The bits of p control the wildcards. A set bit is a wildcard 295 replacement with the lowest bit being user then project then testbed. 296 """ 297 if p & 1: user = ["<any>"] 298 else: user = a[2] 299 if p & 2: proj = "<any>" 300 else: proj = a[1] 301 if p & 4: tb = "<any>" 302 else: tb = a[0] 303 304 return (tb, proj, user) 305 305 306 306 def find_access(self, search): 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 307 """ 308 Search the access DB for a match on this tuple. Return the matching 309 access tuple and the user that matched. 310 311 NB, if the initial tuple fails to match we start inserting wildcards in 312 an order determined by self.project_priority. Try the list of users in 313 order (when wildcarded, there's only one user in the list). 314 """ 315 if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7) 316 else: perm = (0, 2, 1, 3, 4, 6, 5, 7) 317 318 for p in perm: 319 s = self.permute_wildcards(search, p) 320 # s[2] is None on an anonymous, unwildcarded request 321 if s[2] != None: 322 for u in s[2]: 323 if self.access.has_key((s[0], s[1], u)): 324 return (self.access[(s[0], s[1], u)], u) 325 else: 326 if self.access.has_key(s): 327 return (self.access[s], None) 328 return None, None 329 329 330 330 def lookup_access(self, req, fid): 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 331 """ 332 Determine the allowed access for this request. Return the access and 333 which fields are dynamic. 334 335 The fedid is needed to construct the request 336 """ 337 # Search keys 338 tb = None 339 project = None 340 user = None 341 # Return values 342 rp = access_project(None, ()) 343 ru = None 344 345 if req.has_key('project'): 346 p = req['project'] 347 if p.has_key('name'): 348 project = unpack_id(p['name']) 349 user = self.get_users(p) 350 else: 351 user = self.get_users(req) 352 353 user_fedids = [ u for u in user if isinstance(u, fedid)] 354 # Determine how the caller is representing itself. If its fedid shows 355 # up as a project or a singleton user, let that stand. If neither the 356 # usernames nor the project name is a fedid, the caller is a testbed. 357 if project and isinstance(project, fedid): 358 if project == fid: 359 # The caller is the project (which is already in the tuple 360 # passed in to the authorizer) 361 owners = user_fedids 362 owners.append(project) 363 else: 364 raise service_error(service_error.req, 365 "Project asserting different fedid") 366 else: 367 if fid not in user_fedids: 368 tb = fid 369 owners = user_fedids 370 owners.append(fid) 371 else: 372 if len(fedids) > 1: 373 raise service_error(service_error.req, 374 "User asserting different fedid") 375 else: 376 # Which is a singleton 377 owners = user_fedids 378 # Confirm authorization 379 380 for u in user: 381 self.log.debug("[lookup_access] Checking access for %s" % \ 382 ((tb, project, u),)) 383 if self.auth.check_attribute((tb, project, u), 'access'): 384 self.log.debug("[lookup_access] Access granted") 385 break 386 else: 387 self.log.debug("[lookup_access] Access Denied") 388 else: 389 raise service_error(service_error.access, "Access denied") 390 391 # This maps a valid user to the Emulab projects and users to use 392 found, user_match = self.find_access((tb, project, user)) 393 394 if found == None: 395 raise service_error(service_error.access, 396 "Access denied - cannot map access") 397 398 # resolve <dynamic> and <same> in found 399 dyn_proj = False 400 dyn_create_user = False 401 dyn_service_user = False 402 403 if found[0].name == "<same>": 404 if project != None: 405 rp.name = project 406 else : 407 raise service_error(\ 408 service_error.server_config, 409 "Project matched <same> when no project given") 410 elif found[0].name == "<dynamic>": 411 rp.name = None 412 dyn_proj = True 413 else: 414 rp.name = found[0].name 415 rp.node_types = found[0].node_types; 416 417 if found[1] == "<same>": 418 if user_match == "<any>": 419 if user != None: rcu = user[0] 420 else: raise service_error(\ 421 service_error.server_config, 422 "Matched <same> on anonymous request") 423 else: 424 rcu = user_match 425 elif found[1] == "<dynamic>": 426 rcu = None 427 dyn_create_user = True 428 else: 429 rcu = found[1] 430 431 if found[2] == "<same>": 432 if user_match == "<any>": 433 if user != None: rsu = user[0] 434 else: raise service_error(\ 435 service_error.server_config, 436 "Matched <same> on anonymous request") 437 else: 438 rsu = user_match 439 elif found[2] == "<dynamic>": 440 rsu = None 441 dyn_service_user = True 442 else: 443 rsu = found[2] 444 445 return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\ 446 owners 447 447 448 448 def build_response(self, alloc_id, ap): 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 449 """ 450 Create the SOAP response. 451 452 Build the dictionary description of the response and use 453 fedd_utils.pack_soap to create the soap message. ap is the allocate 454 project message returned from a remote project allocation (even if that 455 allocation was done locally). 456 """ 457 # Because alloc_id is already a fedd_services_types.IDType_Holder, 458 # there's no need to repack it 459 msg = { 460 'allocID': alloc_id, 461 'emulab': { 462 'domain': self.domain, 463 'boss': self.boss, 464 'ops': self.ops, 465 'fileServer': self.fileserver, 466 'eventServer': self.eventserver, 467 'project': ap['project'] 468 }, 469 } 470 if len(self.attrs) > 0: 471 msg['emulab']['fedAttr'] = \ 472 [ { 'attribute': x, 'value' : y } \ 473 for x,y in self.attrs.iteritems()] 474 return msg 475 475 476 476 def RequestAccess(self, req, fid): 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 477 """ 478 Handle the access request. Proxy if not for us. 479 480 Parse out the fields and make the allocations or rejections if for us, 481 otherwise, assuming we're willing to proxy, proxy the request out. 482 """ 483 484 def gateway_hardware(h): 485 if h == 'GWTYPE': return self.attrs.get('connectorType', 'GWTYPE') 486 else: return h 487 488 # The dance to get into the request body 489 if req.has_key('RequestAccessRequestBody'): 490 req = req['RequestAccessRequestBody'] 491 else: 492 raise service_error(service_error.req, "No request!?") 493 494 if req.has_key('destinationTestbed'): 495 dt = unpack_id(req['destinationTestbed']) 496 497 if dt == None or dt in self.testbed: 498 # Request for this fedd 499 found, dyn, owners = self.lookup_access(req, fid) 500 restricted = None 501 ap = None 502 503 # If this is a request to export a project and the access project 504 # is not the project to export, access denied. 505 if req.has_key('exportProject'): 506 ep = unpack_id(req['exportProject']) 507 if ep != found[0].name: 508 raise service_error(service_error.access, 509 "Cannot export %s" % ep) 510 511 # Check for access to restricted nodes 512 if req.has_key('resources') and req['resources'].has_key('node'): 513 resources = req['resources'] 514 restricted = [ gateway_hardware(t) for n in resources['node'] \ 515 if n.has_key('hardware') \ 516 for t in n['hardware'] \ 517 if gateway_hardware(t) \ 518 in self.restricted ] 519 inaccessible = [ t for t in restricted \ 520 if t not in found[0].node_types] 521 if len(inaccessible) > 0: 522 raise service_error(service_error.access, 523 "Access denied (nodetypes %s)" % \ 524 str(', ').join(inaccessible)) 525 # These collect the keys for the two roles into single sets, one 526 # for creation and one for service. The sets are a simple way to 527 # eliminate duplicates 528 create_ssh = set([ x['sshPubkey'] \ 529 for x in req['createAccess'] \ 530 if x.has_key('sshPubkey')]) 531 532 service_ssh = set([ x['sshPubkey'] \ 533 for x in req['serviceAccess'] \ 534 if x.has_key('sshPubkey')]) 535 536 if len(create_ssh) > 0 and len(service_ssh) >0: 537 if dyn[1]: 538 # Compose the dynamic project request 539 # (only dynamic, dynamic currently allowed) 540 preq = { 'AllocateProjectRequestBody': \ 541 { 'project' : {\ 542 'user': [ \ 543 { \ 544 'access': [ { 'sshPubkey': s } \ 545 for s in service_ssh ], 546 'role': "serviceAccess",\ 547 }, \ 548 { \ 549 'access': [ { 'sshPubkey': s } \ 550 for s in create_ssh ], 551 'role': "experimentCreation",\ 552 }, \ 553 ], \ 554 }\ 555 }\ 556 } 557 if restricted != None and len(restricted) > 0: 558 preq['AllocateProjectRequestBody']['resources'] = \ 559 {'node': [ { 'hardware' : [ h ] } \ 560 for h in restricted ] } 561 ap = self.allocate_project.dynamic_project(preq) 562 else: 563 preq = {'StaticProjectRequestBody' : \ 564 { 'project': \ 565 { 'name' : { 'localname' : found[0].name },\ 566 'user' : [ \ 567 {\ 568 'userID': { 'localname' : found[1] }, \ 569 'access': [ { 'sshPubkey': s } 570 for s in create_ssh ], 571 'role': 'experimentCreation'\ 572 },\ 573 {\ 574 'userID': { 'localname' : found[2] }, \ 575 'access': [ { 'sshPubkey': s } 576 for s in service_ssh ], 577 'role': 'serviceAccess'\ 578 },\ 579 ]}\ 580 }\ 581 } 582 if restricted != None and len(restricted) > 0: 583 preq['StaticProjectRequestBody']['resources'] = \ 584 {'node': [ { 'hardware' : [ h ] } \ 585 for h in restricted ] } 586 ap = self.allocate_project.static_project(preq) 587 else: 588 raise service_error(service_error.req, 589 "SSH access parameters required") 590 # keep track of what's been added 591 allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log) 592 aid = unicode(allocID) 593 594 self.state_lock.acquire() 595 self.allocation[aid] = { } 596 try: 597 pname = ap['project']['name']['localname'] 598 except KeyError: 599 pname = None 600 601 if dyn[1]: 602 if not pname: 603 self.state_lock.release() 604 raise service_error(service_error.internal, 605 "Misformed allocation response?") 606 if self.projects.has_key(pname): self.projects[pname] += 1 607 else: self.projects[pname] = 1 608 self.allocation[aid]['project'] = pname 609 610 if ap.has_key('resources'): 611 if not pname: 612 self.state_lock.release() 613 raise service_error(service_error.internal, 614 "Misformed allocation response?") 615 self.allocation[aid]['types'] = set() 616 nodes = ap['resources'].get('node', []) 617 for n in nodes: 618 for h in n.get('hardware', []): 619 if self.types.has_key((pname, h)): 620 self.types[(pname, h)] += 1 621 else: 622 self.types[(pname, h)] = 1 623 self.allocation[aid]['types'].add((pname,h)) 624 625 626 self.allocation[aid]['keys'] = [ ] 627 628 try: 629 for u in ap['project']['user']: 630 uname = u['userID']['localname'] 631 for k in [ k['sshPubkey'] for k in u['access'] \ 632 if k.has_key('sshPubkey') ]: 633 kv = "%s:%s" % (uname, k) 634 if self.keys.has_key(kv): self.keys[kv] += 1 635 else: self.keys[kv] = 1 636 self.allocation[aid]['keys'].append((uname, k)) 637 except KeyError: 638 self.state_lock.release() 639 raise service_error(service_error.internal, 640 "Misformed allocation response?") 641 642 643 self.allocation[aid]['owners'] = owners 644 self.write_state() 645 self.state_lock.release() 646 for o in owners: 647 self.auth.set_attribute(o, allocID) 648 resp = self.build_response({ 'fedid': allocID } , ap) 649 return resp 650 else: 651 if self.allow_proxy: 652 resp = self.proxy_RequestAccess.call_service(dt, req, 653 self.cert_file, self.cert_pwd, 654 self.trusted_certs) 655 if resp.has_key('RequestAccessResponseBody'): 656 return resp['RequestAccessResponseBody'] 657 else: 658 return None 659 else: 660 raise service_error(service_error.access, 661 "Access proxying denied") 662 662 663 663 def ReleaseAccess(self, req, fid): 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 664 # The dance to get into the request body 665 if req.has_key('ReleaseAccessRequestBody'): 666 req = req['ReleaseAccessRequestBody'] 667 else: 668 raise service_error(service_error.req, "No request!?") 669 670 if req.has_key('destinationTestbed'): 671 dt = unpack_id(req['destinationTestbed']) 672 else: 673 dt = None 674 675 if dt == None or dt in self.testbed: 676 # Local request 677 try: 678 if req['allocID'].has_key('localname'): 679 auth_attr = aid = req['allocID']['localname'] 680 elif req['allocID'].has_key('fedid'): 681 aid = unicode(req['allocID']['fedid']) 682 auth_attr = req['allocID']['fedid'] 683 else: 684 raise service_error(service_error.req, 685 "Only localnames and fedids are understood") 686 except KeyError: 687 raise service_error(service_error.req, "Badly formed request") 688 689 self.log.debug("[access] deallocation requested for %s", aid) 690 if not self.auth.check_attribute(fid, auth_attr): 691 self.log.debug("[access] deallocation denied for %s", aid) 692 raise service_error(service_error.access, "Access Denied") 693 694 # If we know this allocation, reduce the reference counts and 695 # remove the local allocations. Otherwise report an error. If 696 # there is an allocation to delete, del_users will be a dictonary 697 # of sets where the key is the user that owns the keys in the set. 698 # We use a set to avoid duplicates. del_project is just the name 699 # of any dynamic project to delete. We're somewhat lazy about 700 # deleting authorization attributes. Having access to something 701 # that doesn't exist isn't harmful. 702 del_users = { } 703 del_project = None 704 del_types = set() 705 706 if self.allocation.has_key(aid): 707 self.log.debug("Found allocation for %s" %aid) 708 self.state_lock.acquire() 709 for k in self.allocation[aid]['keys']: 710 kk = "%s:%s" % k 711 self.keys[kk] -= 1 712 if self.keys[kk] == 0: 713 if not del_users.has_key(k[0]): 714 del_users[k[0]] = set() 715 del_users[k[0]].add(k[1]) 716 del self.keys[kk] 717 718 if self.allocation[aid].has_key('project'): 719 pname = self.allocation[aid]['project'] 720 self.projects[pname] -= 1 721 if self.projects[pname] == 0: 722 del_project = pname 723 del self.projects[pname] 724 725 if self.allocation[aid].has_key('types'): 726 for t in self.allocation[aid]['types']: 727 self.types[t] -= 1 728 if self.types[t] == 0: 729 if not del_project: del_project = t[0] 730 del_types.add(t[1]) 731 del self.types[t] 732 733 del self.allocation[aid] 734 self.write_state() 735 self.state_lock.release() 736 # If we actually have resources to deallocate, prepare the call. 737 if del_project or del_users: 738 msg = { 'project': { }} 739 if del_project: 740 msg['project']['name']= {'localname': del_project} 741 users = [ ] 742 for u in del_users.keys(): 743 users.append({ 'userID': { 'localname': u },\ 744 'access' : \ 745 [ {'sshPubkey' : s } for s in del_users[u]]\ 746 }) 747 if users: 748 msg['project']['user'] = users 749 if len(del_types) > 0: 750 msg['resources'] = { 'node': \ 751 [ {'hardware': [ h ] } for h in del_types ]\ 752 } 753 if self.allocate_project.release_project: 754 msg = { 'ReleaseProjectRequestBody' : msg} 755 self.allocate_project.release_project(msg) 756 return { 'allocID': req['allocID'] } 757 else: 758 raise service_error(service_error.req, "No such allocation") 759 760 else: 761 if self.allow_proxy: 762 resp = self.proxy_ReleaseAccess.call_service(dt, req, 763 self.cert_file, self.cert_pwd, 764 self.trusted_certs) 765 if resp.has_key('ReleaseAccessResponseBody'): 766 return resp['ReleaseAccessResponseBody'] 767 else: 768 return None 769 else: 770 raise service_error(service_error.access, 771 "Access proxying denied") 772 773
Note: See TracChangeset
for help on using the changeset viewer.