source: fedd/fedd_access.py @ c922f23

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since c922f23 was c922f23, checked in by Ted Faber <faber@…>, 16 years ago

Start the move to unified service calling routines

  • Property mode set to 100644
File size: 24.5 KB
RevLine 
[19cc408]1#!/usr/local/bin/python
2
3import os,sys
4
5from BaseHTTPServer import BaseHTTPRequestHandler
6from ZSI import *
7from M2Crypto import SSL
8from M2Crypto.m2xmlrpclib import SSL_Transport
9from M2Crypto.SSL.SSLServer import SSLServer
10import M2Crypto.httpslib
11import xmlrpclib
12
13import re
14import string
15import copy
[d81971a]16import pickle
[19cc408]17
[f8582c9]18from threading import *
19
[19cc408]20from fedd_access_project import access_project
21from fedd_services import *
22from fedd_util import *
23from fedd_allocate_project import *
24import parse_detail
25from service_error import *
[11a08b0]26import logging
27
[0ea11af]28
29# Make log messages disappear if noone configures a fedd logger
[11a08b0]30class nullHandler(logging.Handler):
31    def emit(self, record): pass
32
33fl = logging.getLogger("fedd.access")
34fl.addHandler(nullHandler())
[19cc408]35
36class fedd_access:
37    """
38    The implementation of access control based on mapping users to projects.
39
40    Users can be mapped to existing projects or have projects created
41    dynamically.  This implements both direct requests and proxies.
42    """
43
[4ed10ae]44    class parse_error(RuntimeError): pass
45
[72ed6e4]46    bool_attrs = ("dynamic_projects", "project_priority")
47    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
48    id_attrs = ("testbed", "proxy",
49            "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
50            "dynamic_projects_url", "dynamic_projects_cert_file", 
51            "dynamic_projects_cert_pwd", "dynamic_projects_trusted_certs")
52    id_list_attrs = ("restricted",)
53
[c922f23]54    proxy_request, proxy_xmlrpc_request = make_service_callers('RequestAccess', 
55            'getfeddPortType', RequestAccessRequestMessage,
56            'RequestAccessRequestBody')
57
[f8582c9]58    def __init__(self, config=None):
59        """
60        Initializer.  Pulls parameters out of the ConfigParser's access section.
61        """
62
63        # Make sure that the configuration is in place
64        if config: 
65            if not config.has_section("access"):
66                config.add_section("access")
67            if not config.has_section("globals"):
68                config.add_section("globals")
69        else:
70            raise RunTimeError("No config to fedd_access")
71
72
73        # Create instance attributes from the static lists
74        for a in fedd_access.bool_attrs:
75            if config.has_option("access", a):
76                setattr(self, a, config.get("access", a))
77            else:
78                setattr(self, a, False)
79
80        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
81            if config.has_option("access", a):
82                setattr(self, a, config.get("access",a))
83            else:
84                setattr(self, a, None)
85
86        self.attrs = { }
87        self.access = { }
88        self.restricted = [ ]
89        self.fedid_category = { }
90        self.projects = { }
91        self.keys = { }
92        self.allocation = { }
93        self.state = { 
94            'projects': self.projects,
95            'allocation' : self.allocation,
96            'keys' : self.keys
97        }
98        self.state_lock = Lock()
99        self.fedid_default = "testbed"
100        if config.has_option("access", "accessdb"):
101            self.read_access(config.get("access", "accessdb"))
102        if config.has_option("access", "trustdb"):
103            self.read_trust(config.get("access", "trustdb"))
104
105        self.state_filename = config.get("access", "access_state", "")
106        self.log = logging.getLogger("fedd.access")
107        set_log_level(config, "access", self.log)
108        self.read_state()
109
110
111        # Certs are promoted from the generic to the specific, so without a
112        # specific proxy certificate, the main certificates are used for
113        # proxy interactions. If no dynamic project certificates, then
114        # proxy certs are used, and if none of those the main certs.
115
116        if config.has_option("globals", "proxy_cert_file"):
117            if not self.dynamic_projects_cert_file:
118                self.dynamic_projects_cert_file = \
119                        config.get("globals", "proxy_cert_file")
120                if config.has_option("globals", "porxy_cert_pwd"):
121                    self.dynamic_projects_cert_pwd = \
122                            config.get("globals", "proxy_cert_pwd")
123
124        if config.has_option("globals", "proxy_trusted_certs"):
125            if not self.dynamic_projects_trusted_certs:
126                self.dynamic_projects_trusted_certs =\
127                        config.get("globals", proxy_trusted_certs)
128
129        if config.has_option("globals", "cert_file"):
130            has_pwd = config.has_option("globals", "cert_pwd")
131            if not self.dynamic_projects_cert_file:
132                self.dynamic_projects_cert_file = \
133                        config.get("globals", "cert_file")
134                if has_pwd: 
135                    self.dynamic_projects_cert_pwd = \
136                            config.get("globals", "cert_pwd")
137            if not self.proxy_cert_file:
138                self.proxy_cert_file = config.get("globals", "cert_file")
139                if has_pwd:
140                    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
141
142        if config.get("globals", "trusted_certs"):
143            if not self.proxy_trusted_certs:
144                self.proxy_trusted_certs = \
145                        config.get("globals", "trusted_certs")
146            if not self.dynamic_projects_trusted_certs:
147                self.dynamic_projects_trusted_certs = \
148                        config.get("globals", "trusted_certs")
149
150        proj_certs = (self.dynamic_projects_cert_file, 
151                self.dynamic_projects_trusted_certs,
152                self.dynamic_projects_cert_pwd)
153
154        self.soap_services = {\
155            'RequestAccess': make_soap_handler(\
156                RequestAccessRequestMessage.typecode,\
157                self.RequestAccess, RequestAccessResponseMessage,\
158                "RequestAccessResponseBody"), \
159            'ReleaseAccess': make_soap_handler(\
160                ReleaseAccessRequestMessage.typecode,\
161                self.ReleaseAccess, ReleaseAccessResponseMessage,\
162                "ReleaseAccessResponseBody")\
163            }
164        self.xmlrpc_services =  {\
165            'RequestAccess': make_xmlrpc_handler(\
166                self.RequestAccess, "RequestAccessResponseBody"),\
167            'ReleaseAccess': make_xmlrpc_handler(\
168                self.ReleaseAccess, "ReleaseAccessResponseBody")\
169            }
170
171
172        if not config.has_option("access", "dynamic_projects_url"):
173            self.allocate_project = \
174                fedd_allocate_project_local(config)
175        else:
176            self.allocate_project = \
177                fedd_allocate_project_remote(config)
178
179        # If the project allocator exports services, put them in this object's
180        # maps so that classes that instantiate this can call the services.
181        self.soap_services.update(self.allocate_project.soap_services)
182        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
183
[72ed6e4]184    def read_trust(self, trust):
185        """
186        Read a trust file that splits fedids into testbeds, users or projects
187
188        Format is:
189
190        [type]
191        fedid
192        fedid
193        default: type
194        """
195        lineno = 0;
196        cat = None
197        cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE)
198        fedid_re = re.compile("[" + string.hexdigits + "]+$")
199        default_re = re.compile("default:\s*(user|testbed|project)$", 
200                re.IGNORECASE)
201
202        f = open(trust, "r")
203        for line in f:
204            lineno += 1
205            line = line.strip()
206            if len(line) == 0 or line.startswith("#"):
207                continue
208            # Category line
209            m = cat_re.match(line)
210            if m != None:
211                cat = m.group(1).lower()
212                continue
213            # Fedid line
214            m = fedid_re.match(line)
215            if m != None:
216                if cat != None:
217                    self.fedid_category[fedid(hexstr=m.string)] = cat
218                else:
[4ed10ae]219                    raise self.parse_error(\
[72ed6e4]220                            "Bad fedid in trust file (%s) line: %d" % \
221                            (trust, lineno))
222                continue
223            # default line
224            m = default_re.match(line)
225            if m != None:
226                self.fedid_default = m.group(1).lower()
227                continue
228            # Nothing matched - bad line, raise exception
229            f.close()
[4ed10ae]230            raise self.parse_error(\
[72ed6e4]231                    "Unparsable line in trustfile %s line %d" % (trust, lineno))
232        f.close()
233
234    def read_access(self, config):
235        """
236        Read a configuration file and set internal parameters.
237
238        The format is more complex than one might hope.  The basic format is
239        attribute value pairs separated by colons(:) on a signle line.  The
240        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
241        directly using the name: value syntax.  E.g.
242        boss: hostname
243        sets self.boss to hostname.  In addition, there are access lines of the
244        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
245        names to the second for access purposes.  Names in the key (left side)
246        can include "<NONE> or <ANY>" to act as wildcards or to require the
247        fields to be empty.  Similarly aproj or auser can be <SAME> or
248        <DYNAMIC> indicating that either the matching key is to be used or a
249        dynamic user or project will be created.  These names can also be
250        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
251        can be followed with a colon-separated list of node types to which that
252        project has access (or will have access if dynamic).
253        Testbed attributes outside the forms above can be given using the
254        format attribute: name value: value.  The name is a single word and the
255        value continues to the end of the line.  Empty lines and lines startin
256        with a # are ignored.
257
[4ed10ae]258        Parsing errors result in a self.parse_error exception being raised.
[72ed6e4]259        """
260        lineno=0
261        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
262        fedid_expr = "fedid:[" + string.hexdigits + "]+"
263        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
264        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
265                "<SAME>" + "(?::" + name_expr + ")*|" + \
266                fedid_expr + "(?::" + name_expr + ")*|" + \
267                name_expr + "(?::" + name_expr + ")*)"
268        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
269
270        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
271        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
272                re.IGNORECASE)
273        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
274                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
[4ed10ae]275                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
[72ed6e4]276
277        def parse_name(n):
278            if n.startswith('fedid:'): return fedid(n[len('fedid:'):])
279            else: return n
280
281        f = open(config, "r");
282        for line in f:
283            lineno += 1
284            line = line.strip();
285            if len(line) == 0 or line.startswith('#'):
286                continue
287
288            # Extended (attribute: x value: y) attribute line
289            m = attr_re.match(line)
290            if m != None:
291                attr, val = m.group(1,2)
292                self.attrs[attr] = val
293                continue
294
295            # Restricted entry
296            m = restricted_re.match(line)
297            if m != None:
298                val = m.group(1)
299                self.restricted.append(val)
300                continue
301
[4ed10ae]302            # Access line (t, p, u) -> (ap, cu, su) line
[72ed6e4]303            m = access_re.match(line)
304            if m != None:
305                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
306                aps = m.group(4).split(":");
307                if aps[0] == 'fedid:':
308                    del aps[0]
309                    aps[0] = fedid(hexstr=aps[0])
310
[4ed10ae]311                cu = parse_name(m.group(5))
312                su = parse_name(m.group(6))
[72ed6e4]313
[4ed10ae]314                access_val = (access_project(aps[0], aps[1:]),
315                        parse_name(m.group(5)), parse_name(m.group(6)))
[72ed6e4]316
317                self.access[access_key] = access_val
318                continue
319
320            # Nothing matched to here: unknown line - raise exception
321            f.close()
[4ed10ae]322            raise self.parse_error("Unknown statement at line %d of %s" % \
[72ed6e4]323                    (lineno, config))
324        f.close()
325
326
[19cc408]327    def dump_state(self):
328        """
329        Dump the state read from a configuration file.  Mostly for debugging.
330        """
[72ed6e4]331        for a in fedd_access.bool_attrs:
[19cc408]332            print "%s: %s" % (a, getattr(self, a ))
[72ed6e4]333        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
[19cc408]334            print "%s: %s" % (a, getattr(self, a))
335        for k, v in self.attrs.iteritems():
336            print "%s %s" % (k, v)
337        print "Access DB:"
338        for k, v in self.access.iteritems():
339            print "%s %s" % (k, v)
340        print "Trust DB:"
341        for k, v in self.fedid_category.iteritems():
342            print "%s %s" % (k, v)
343        print "Restricted: %s" % str(',').join(sorted(self.restricted))
344
345    def get_users(self, obj):
346        """
347        Return a list of the IDs of the users in dict
348        """
349        if obj.has_key('user'):
350            return [ unpack_id(u['userID']) \
351                    for u in obj['user'] if u.has_key('userID') ]
352        else:
353            return None
354
[d81971a]355    def write_state(self):
356        try:
357            f = open(self.state_filename, 'w')
358            pickle.dump(self.state, f)
359        except IOError, e:
360            self.log.error("Can't write file %s: %s" % \
361                    (self.state_filename, e))
362        except pickle.PicklingError, e:
363            self.log.error("Pickling problem: %s" % e)
364        except TypeError, e:
365            self.log.error("Pickling problem (TypeError): %s" % e)
366
367
368    def read_state(self):
369        """
370        Read a new copy of access state.  Old state is overwritten.
371
372        State format is a simple pickling of the state dictionary.
373        """
374        try:
375            f = open(self.state_filename, "r")
376            self.state = pickle.load(f)
377
378            self.allocation = self.state['allocation']
379            self.projects = self.state['projects']
380            self.keys = self.state['keys']
381
382            self.log.debug("[read_state]: Read state from %s" % \
383                    self.state_filename)
384        except IOError, e:
385            self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
386                    % (self.state_filename, e))
387        except EOFError, e:
388            self.log.warning("[read_state]: Empty or damaged state file: %s:"\
389                    % self.state_filename)
390        except pickle.UnpicklingError, e:
391            self.log.warning(("[read_state]: No saved state: " + \
392                    "Unpickling failed: %s") % e)
393
[19cc408]394    def permute_wildcards(self, a, p):
395        """Return a copy of a with various fields wildcarded.
396
397        The bits of p control the wildcards.  A set bit is a wildcard
398        replacement with the lowest bit being user then project then testbed.
399        """
400        if p & 1: user = ["<any>"]
401        else: user = a[2]
402        if p & 2: proj = "<any>"
403        else: proj = a[1]
404        if p & 4: tb = "<any>"
405        else: tb = a[0]
406
407        return (tb, proj, user)
408
409    def find_access(self, search):
410        """
411        Search the access DB for a match on this tuple.  Return the matching
412        access tuple and the user that matched.
413       
414        NB, if the initial tuple fails to match we start inserting wildcards in
415        an order determined by self.project_priority.  Try the list of users in
416        order (when wildcarded, there's only one user in the list).
417        """
418        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
419        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
420
421        for p in perm: 
422            s = self.permute_wildcards(search, p)
423            # s[2] is None on an anonymous, unwildcarded request
424            if s[2] != None:
425                for u in s[2]:
426                    if self.access.has_key((s[0], s[1], u)):
427                        return (self.access[(s[0], s[1], u)], u)
428            else:
429                if self.access.has_key(s):
430                    return (self.access[s], None)
431        return None, None
432
433    def lookup_access(self, req, fid):
434        """
435        Determine the allowed access for this request.  Return the access and
436        which fields are dynamic.
437
438        The fedid is needed to construct the request
439        """
440        # Search keys
441        tb = None
442        project = None
443        user = None
444        # Return values
445        rp = access_project(None, ())
446        ru = None
447
448
449        principal_type = self.fedid_category.get(fid, self.fedid_default)
450
451        if principal_type == "testbed": tb = fid
452
453        if req.has_key('project'):
454            p = req['project']
455            if p.has_key('name'):
456                project = unpack_id(p['name'])
457            user = self.get_users(p)
458        else:
459            user = self.get_users(req)
460
461        # Now filter by prinicpal type
462        if principal_type == "user":
463            if user != None:
464                fedids = [ u for u in user if isinstance(u, type(fid))]
465                if len(fedids) > 1:
466                    raise service_error(service_error.req,
467                            "User asserting multiple fedids")
468                elif len(fedids) == 1 and fedids[0] != fid:
469                    raise service_error(service_error.req,
470                            "User asserting different fedid")
471            project = None
472            tb = None
473        elif principal_type == "project":
474            if isinstance(project, type(fid)) and fid != project:
475                raise service_error(service_error.req,
476                        "Project asserting different fedid")
477            tb = None
478
479        # Ready to look up access
480        found, user_match = self.find_access((tb, project, user))
481       
482        if found == None:
483            raise service_error(service_error.access,
484                    "Access denied")
485
486        # resolve <dynamic> and <same> in found
487        dyn_proj = False
[4ed10ae]488        dyn_create_user = False
489        dyn_service_user = False
[19cc408]490
491        if found[0].name == "<same>":
492            if project != None:
493                rp.name = project
494            else : 
495                raise service_error(\
496                        service_error.server_config,
497                        "Project matched <same> when no project given")
498        elif found[0].name == "<dynamic>":
499            rp.name = None
500            dyn_proj = True
501        else:
502            rp.name = found[0].name
503        rp.node_types = found[0].node_types;
504
505        if found[1] == "<same>":
506            if user_match == "<any>":
[4ed10ae]507                if user != None: rcu = user[0]
[19cc408]508                else: raise service_error(\
509                        service_error.server_config,
510                        "Matched <same> on anonymous request")
511            else:
[4ed10ae]512                rcu = user_match
[19cc408]513        elif found[1] == "<dynamic>":
[4ed10ae]514            rcu = None
515            dyn_create_user = True
[19cc408]516       
[4ed10ae]517        if found[2] == "<same>":
518            if user_match == "<any>":
519                if user != None: rsu = user[0]
520                else: raise service_error(\
521                        service_error.server_config,
522                        "Matched <same> on anonymous request")
523            else:
524                rsu = user_match
525        elif found[2] == "<dynamic>":
526            rsu = None
527            dyn_service_user = True
528
529        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj)
[19cc408]530
531    def build_response(self, alloc_id, ap):
532        """
533        Create the SOAP response.
534
535        Build the dictionary description of the response and use
536        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
537        a fedd_services_types.IDType_Holder pulled from the incoming message.
538        ap is the allocate project message returned from a remote project
539        allocation (even if that allocation was done locally).
540        """
541        # Because alloc_id is already a fedd_services_types.IDType_Holder,
542        # there's no need to repack it
543        msg = { 
544                'allocID': alloc_id,
545                'emulab': { 
546                    'domain': self.domain,
547                    'boss': self.boss,
548                    'ops': self.ops,
549                    'fileServer': self.fileserver,
550                    'eventServer': self.eventserver,
551                    'project': ap['project']
552                },
553            }
554        if len(self.attrs) > 0:
555            msg['emulab']['fedAttr'] = \
556                [ { 'attribute': x, 'value' : y } \
557                        for x,y in self.attrs.iteritems()]
558        return msg
559
560    def RequestAccess(self, req, fid):
[0ea11af]561        """
562        Handle the access request.  Proxy if not for us.
563
564        Parse out the fields and make the allocations or rejections if for us,
565        otherwise, assuming we're willing to proxy, proxy the request out.
566        """
[19cc408]567
[0ea11af]568        # The dance to get into the request body
[19cc408]569        if req.has_key('RequestAccessRequestBody'):
570            req = req['RequestAccessRequestBody']
571        else:
572            raise service_error(service_error.req, "No request!?")
573
574        if req.has_key('destinationTestbed'):
575            dt = unpack_id(req['destinationTestbed'])
576
577        if dt == None or dt == self.testbed:
578            # Request for this fedd
579            found, dyn = self.lookup_access(req, fid)
580            restricted = None
581            ap = None
582
583            # Check for access to restricted nodes
584            if req.has_key('resources') and req['resources'].has_key('node'):
585                resources = req['resources']
586                restricted = [ t for n in resources['node'] \
587                                if n.has_key('hardware') \
588                                    for t in n['hardware'] \
589                                        if t in self.restricted ]
590                inaccessible = [ t for t in restricted \
591                                    if t not in found[0].node_types]
592                if len(inaccessible) > 0:
593                    raise service_error(service_error.access,
594                            "Access denied (nodetypes %s)" % \
595                            str(', ').join(inaccessible))
[4ed10ae]596            # These collect the keys for teh two roles into single sets, one
597            # for creation and one for service.  The sets are a simple way to
598            # eliminate duplicates
599            create_ssh = set([ x['sshPubkey'] \
600                    for x in req['createAccess'] \
601                        if x.has_key('sshPubkey')])
602
603            service_ssh = set([ x['sshPubkey'] \
604                    for x in req['serviceAccess'] \
605                        if x.has_key('sshPubkey')])
606
607            if len(create_ssh) > 0 and len(service_ssh) >0: 
[19cc408]608                if dyn[1]: 
609                    # Compose the dynamic project request
610                    # (only dynamic, dynamic currently allowed)
611                    preq = { 'AllocateProjectRequestBody': \
612                                { 'project' : {\
613                                    'user': [ \
[4ed10ae]614                                    { \
615                                        'access': [ { 'sshPubkey': s } \
616                                            for s in service_ssh ], 
617                                         'role': "serviceAccess",\
618                                    }, \
619                                    { \
620                                        'access': [ { 'sshPubkey': s } \
621                                            for s in create_ssh ], 
622                                         'role': "experimentCreation",\
623                                    }, \
624                                    ], \
[19cc408]625                                    }\
626                                }\
627                            }
628                    if restricted != None and len(restricted) > 0:
629                        preq['AllocateProjectRequestBody']['resources'] = \
630                            [ {'node': { 'hardware' :  [ h ] } } \
631                                    for h in restricted ]
632                               
633                    ap = self.allocate_project.dynamic_project(preq)
634                else:
[4ed10ae]635                    preq = {'StaticProjectRequestBody' : \
636                            { 'project': \
637                                { 'name' : { 'localname' : found[0].name },\
638                                  'user' : [ \
639                                    {\
640                                        'userID': { 'localname' : found[1] }, \
641                                        'access': [ { 'sshPubkey': s } 
642                                            for s in create_ssh ],
643                                        'role': 'experimentCreation'\
644                                    },\
645                                    {\
646                                        'userID': { 'localname' : found[2] }, \
647                                        'access': [ { 'sshPubkey': s } 
648                                            for s in service_ssh ],
649                                        'role': 'serviceAccess'\
650                                    },\
651                                ]}\
[19cc408]652                            }\
653                    }
[4ed10ae]654                    if restricted != None and len(restricted) > 0:
655                        preq['StaticProjectRequestBody']['resources'] = \
656                            [ {'node': { 'hardware' :  [ h ] } } \
657                                    for h in restricted ]
658                    ap = self.allocate_project.static_project(preq)
[19cc408]659            else:
660                raise service_error(service_error.req, 
661                        "SSH access parameters required")
[d81971a]662            # keep track of what's been added
[f8582c9]663            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
664            aid = unicode(allocID)
[19cc408]665
[f8582c9]666            self.state_lock.acquire()
[d81971a]667            self.allocation[aid] = { }
668            if dyn[1]:
669                try:
670                    pname = ap['project']['name']['localname']
671                except KeyError:
[f8582c9]672                    self.state_lock.release()
[d81971a]673                    raise service_error(service_error.internal,
674                            "Misformed allocation response?")
675                if self.projects.has_key(pname): self.projects[pname] += 1
676                else: self.projects[pname] = 1
677                self.allocation[aid]['project'] = pname
678
679            self.allocation[aid]['keys'] = [ ]
680
681            try:
682                for u in ap['project']['user']:
683                    uname = u['userID']['localname']
684                    for k in [ k['sshPubkey'] for k in u['access'] \
685                            if k.has_key('sshPubkey') ]:
686                        kv = "%s:%s" % (uname, k)
687                        if self.keys.has_key(kv): self.keys[kv] += 1
688                        else: self.keys[kv] = 1
689                        self.allocation[aid]['keys'].append((uname, k))
690            except KeyError:
[f8582c9]691                self.state_lock.release()
[d81971a]692                raise service_error(service_error.internal,
693                        "Misformed allocation response?")
694
695            self.write_state()
[f8582c9]696            self.state_lock.release()
697            resp = self.build_response({ 'fedid': allocID } , ap)
[19cc408]698            return resp
699        else:
700            p_fault = None      # Any SOAP failure (sent unless XMLRPC works)
701            try:
702                # Proxy the request using SOAP
[c922f23]703                self.log.debug("Sending proxy message to %s" % dt)
704                resp = self.proxy_request(dt, req, feddServiceLocator,
705                        self.proxy_cert_file, self.proxy_cert_pwd,
706                        self.proxy_trusted_certs)
707                if resp.has_key('RequestAccessResponseBody'):
708                    return resp['RequestAccessResponseBody']
709                elif resp.has_key('Fedd_FaultBody'):
710                    raise service_error(resp['FeddFaultBody']['code'],
711                            resp['FeddFaultBody']['desc'])
[19cc408]712            except service_error, e:
713                if e.code == service_error.proxy: p_fault = None
714                else: raise
715            except ZSI.FaultException, f:
716                p_fault = f.fault.detail[0]
717                   
718
719            # If we could not get a valid SOAP response to the request above,
720            # try the same address using XMLRPC and let any faults flow back
721            # out.
722            if p_fault == None:
[c922f23]723                resp = self.proxy_xmlrpc_request(dt, req, self.proxy_cert_file,
724                    self.proxy_cert_pwd, self.proxy_trusted_certs)
725                if resp.has_key('RequestAccessResponseBody'):
726                    return resp['RequestAccessResponseBody']
[19cc408]727            else:
728                # Build the fault
729                body = p_fault.get_element_FeddFaultBody()
730                if body != None:
731                    raise service_error(body.get_element_code(),
732                                body.get_element_desc());
733                else:
734                    raise service_error(\
735                            service_error.proxy,
736                            "Undefined fault from proxy??");
[d81971a]737
738    def ReleaseAccess(self, req, fid):
739        # The dance to get into the request body
740        if req.has_key('ReleaseAccessRequestBody'):
741            req = req['ReleaseAccessRequestBody']
742        else:
743            raise service_error(service_error.req, "No request!?")
744
745        try:
746            if req['allocID'].has_key('localname'):
747                aid = req['allocID']['localname']
748            elif req['allocID'].has_key('fedid'):
749                aid = unicode(req['allocID']['fedid'])
750            else:
751                raise service_error(service_error.req,
752                        "Only localnames and fedids are understood")
753        except KeyError:
754            raise service_error(service_error.req, "Badly formed request")
755
756        # If we know this allocation, reduce the reference counts and remove
757        # the local allocations.  Otherwise report an error.  If there is an
758        # allocation to delete, del_users will be a dictonary of sets where the
759        # key is the user that owns the keys in the set.  We use a set to avoid
760        # duplicates.  del_project is just the name of any dynamic project to
761        # delete.
762        del_users = { }
763        del_project = None
764        if self.allocation.has_key(aid):
[f8582c9]765            self.state_lock.acquire()
[d81971a]766            for k in self.allocation[aid]['keys']:
767                kk = "%s:%s" % k
768                self.keys[kk] -= 1
769                if self.keys[kk] == 0:
770                    if not del_users.has_key(k[0]):
771                        del_users[k[0]] = set()
772                    del_users[k[0]].add(k[1])
773                    del self.keys[kk]
774
775            if self.allocation[aid].has_key('project'):
776                pname = self.allocation[aid]['project']
777                self.projects[pname] -= 1
778                if self.projects[pname] == 0:
779                    del_project = pname
780                    del self.projects[pname]
781
782            del self.allocation[aid]
[f8582c9]783            self.write_state()
784            self.state_lock.release()
[d81971a]785            # If we actually have resources to deallocate, prepare the call.
786            if del_project or del_users:
787                msg = { 'project': { }}
788                if del_project:
[f8582c9]789                    msg['project']['name']= {'localname': del_project}
[d81971a]790                users = [ ]
791                for u in del_users.keys():
792                    users.append({ 'userID': { 'localname': u },\
793                        'access' :  \
794                                [ {'sshPubkey' : s } for s in del_users[u]]\
795                    })
796                if users: 
797                    msg['project']['user'] = users
[7583a62]798                if self.allocate_project.release_project:
799                    msg = { 'ReleaseProjectRequestBody' : msg}
800                    self.allocate_project.release_project(msg)
[d81971a]801            return { 'allocID': req['allocID'] } 
802        else:
803            raise service_error(service_error.req, "No such allocation")
804
805
806
Note: See TracBrowser for help on using the repository browser.