source: fedd/fedd_access.py @ d90f0fa

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

remove debugging

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