source: fedd/federation/access.py @ 266e866

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

Added some logging

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