source: fedd/federation/access.py @ 227f558

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

change package name to avoid conflicts with fedd on install

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