source: fedd/federation/access.py @ b931822

compt_changes
Last change on this file since b931822 was bcc6fd6, checked in by Ted Faber <faber@…>, 12 years ago

Handle null fedids in get requests

  • Property mode set to 100644
File size: 26.8 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import stat # for chmod constants
5import re
6import random
7import string
8import copy
9import pickle
10import logging
11import subprocess
12import shlex
13
14from threading import *
15from M2Crypto.SSL import SSLError
16
17from util import *
18from deter import fedid, generate_fedid
19from authorizer import authorizer
20from service_error import service_error
21from remote_service import xmlrpc_handler, soap_handler, service_caller
22
23import httplib
24import tempfile
25from urlparse import urlparse
26
27from deter import topdl
28import list_log
29
30
31# Make log messages disappear if noone configures a fedd logger
32class nullHandler(logging.Handler):
33    def emit(self, record): pass
34
35fl = logging.getLogger("fedd.access")
36fl.addHandler(nullHandler())
37
38class access_base:
39    """
40    The implementation of access control based on mapping users to projects.
41
42    Users can be mapped to existing projects or have projects created
43    dynamically.  This implements both direct requests and proxies.
44    """
45
46    class parse_error(RuntimeError): pass
47
48    class access_attribute:
49        def __init__(self, attr, value, pri=1):
50            self.attr = attr
51            self.value = value
52            self.priority = pri
53        def __str__(self):
54            return "%s: %s (%d)" % (self.attr, self.value, self.priority)
55
56    def __init__(self, config=None, auth=None):
57        """
58        Initializer.  Pulls parameters out of the ConfigParser's access section.
59        """
60
61        # Make sure that the configuration is in place
62        if not config: 
63            raise RunTimeError("No config to fedd.access")
64
65        self.project_priority = config.getboolean("access", "project_priority")
66
67        self.certdir = config.get("access","certdir")
68        self.create_debug = config.getboolean("access", "create_debug")
69        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
70        self.access_type = config.get("access", "type")
71        self.log = logging.getLogger("fedd.access")
72        set_log_level(config, "access", self.log)
73        self.state_lock = Lock()
74        self.state = { }
75        # subclasses fill with what and how they export.
76        self.exports = { }
77        # XXX: Configurable
78        self.imports = set(('SMB', 'seer', 'userconfig', 'seer_master',
79            'hide_hosts'))
80
81        if auth: self.auth = auth
82        else:
83            self.log.error(\
84                    "[access]: No authorizer initialized, creating local one.")
85            auth = authorizer()
86
87        self.state_filename = config.get("access", "access_state")
88        self.read_state()
89
90        # Keep cert_file and cert_pwd coming from the same place
91        self.cert_file = config.get("access", "cert_file")
92        if self.cert_file:
93            self.cert_pwd = config.get("access", "cert_pw")
94        else:
95            self.cert_file = config.get("globals", "cert_file")
96            self.sert_pwd = config.get("globals", "cert_pw")
97
98        self.trusted_certs = config.get("access", "trusted_certs") or \
99                config.get("globals", "trusted_certs")
100
101
102    @staticmethod
103    def software_list(v):
104        """
105        From a string containing a sequence of space separated pairs, return a
106        list of tuples with pairs of location and file.
107        """
108        l = [ ]
109        if v:
110            ps = v.split(" ")
111            while len(ps):
112                loc, file = ps[0:2]
113                del ps[0:2]
114                l.append((loc, file))
115        return l
116
117    @staticmethod
118    def add_kit(e, kit):
119        """
120        Add a Software object created from the list of (install, location)
121        tuples passed as kit  to the software attribute of an object e.  We
122        do this enough to break out the code, but it's kind of a hack to
123        avoid changing the old tuple rep.
124        """
125
126        s = [ topdl.Software(install=i, location=l) for i, l in kit]
127
128        if isinstance(e.software, list): e.software.extend(s)
129        else: e.software = s
130
131
132    def read_access(self, fn, access_obj=None, default=[]):
133        """
134        Read an access DB of the form
135            abac.attribute -> local_auth_data
136        The access dict is filled with mappings from the abac attributes (as
137        strings) to the access objects.  The objects are strings by default,
138        but the class constructor is called with the string following the ->
139        and whitespace in the file.
140        """
141
142        map_re = re.compile("(\S+)\s+->\s+(.*)")
143        priority_re = re.compile("([^,]+),\s*(\d+)")
144
145        if access_obj is None:
146            access_obj = lambda(x): "%s" % x
147
148        self.access = []
149        priorities = { }
150
151        f = open(fn, 'r')
152        try:
153            lineno = 0
154            for line in f:
155                lineno += 1
156                line = line.strip();
157                if len(line) == 0 or line.startswith('#'):
158                    continue
159                m = map_re.match(line)
160                if m != None:
161                    self.access.append(access_base.access_attribute(m.group(1),
162                        access_obj(m.group(2))))
163                    continue
164
165                # If a priority is found, collect them
166                m = priority_re.match(line)
167                if m:
168                    try:
169                        priorities[m.group(1)] = int(m.group(2))
170                    except ValueError, e:
171                        if self.log:
172                            self.log.debug("Bad priority in %s line %d" % \
173                                    (fn, lineno))
174                    continue
175
176                # Nothing matched to here: unknown line - raise exception
177                # (finally will close f)
178                raise self.parse_error(
179                        "Unknown statement at line %d of %s" % \
180                        (lineno, fn))
181        finally:
182            if f: f.close()
183
184        # Set priorities
185        for a in self.access:
186            if a.attr in priorities:
187                a.priority = priorities[a.attr]
188
189        # default access mappings
190        for a, v in default:
191            self.access.append(
192                    access_base.access_attribute(attr=a, value=v, pri=0))
193
194
195
196    def write_state(self):
197        if self.state_filename:
198            try:
199                f = open(self.state_filename, 'w')
200                pickle.dump(self.state, f)
201                self.log.debug("Wrote state to %s" % self.state_filename)
202            except EnvironmentError, e:
203                self.log.error("Can't write file %s: %s" % \
204                        (self.state_filename, e))
205            except pickle.PicklingError, e:
206                self.log.error("Pickling problem: %s" % e)
207            except TypeError, e:
208                self.log.error("Pickling problem (TypeError): %s" % e)
209
210
211    def read_state(self):
212        """
213        Read a new copy of access state.  Old state is overwritten.
214
215        State format is a simple pickling of the state dictionary.
216        """
217        if self.state_filename:
218            try:
219                f = open(self.state_filename, "r")
220                self.state = pickle.load(f)
221                self.log.debug("[read_state]: Read state from %s" % \
222                        self.state_filename)
223            except EnvironmentError, e:
224                self.log.warning(("[read_state]: No saved state: " +\
225                        "Can't open %s: %s") % (self.state_filename, e))
226            except EOFError, e:
227                self.log.warning(("[read_state]: " +\
228                        "Empty or damaged state file: %s:") % \
229                        self.state_filename)
230            except pickle.UnpicklingError, e:
231                self.log.warning(("[read_state]: No saved state: " + \
232                        "Unpickling failed: %s") % e)
233
234    def append_allocation_authorization(self, aid, attrs, 
235            need_state_lock=False, write_state_file=False, state_attr='state'):
236        """
237        Append the authorization information to system state.  By default we
238        assume this is called with the state lock and with a write of the state
239        file in the near future, need_state_lock and write_state_file can
240        override this.  The state_attr is the attribute in the access class
241        that holds the per allocation information.  Some complex classes use
242        different names for the dict.
243        """
244
245        for p, a in attrs:
246            self.auth.set_attribute(p, a)
247        self.auth.save()
248
249        if need_state_lock: self.state_lock.acquire()
250        d = getattr(self, state_attr)
251        if aid in d and 'auth' in d[aid]:
252            d[aid]['auth'].update(attrs)
253        if write_state_file: self.write_state()
254        if need_state_lock: self.state_lock.release()
255
256    def clear_allocation_authorization(self, aid, need_state_lock=False,
257            write_state_file=False, state_attr='state'):
258        """
259        Attrs is a set of attribute principal pairs that need to be removed
260        from the authenticator.  Remove them and save the authenticator.  See
261        append_allocation_authorization for the various overrides.
262        """
263
264        if need_state_lock: self.state_lock.acquire()
265        d = getattr(self, state_attr)
266        if aid in d and 'auth' in d[aid]:
267            for p, a in d[aid]['auth']:
268                self.auth.unset_attribute(p, a)
269            d[aid]['auth'] = set()
270        if write_state_file: self.write_state()
271        if need_state_lock: self.state_lock.release()
272        self.auth.save()
273
274    def lookup_access(self, req, fid, filter=None, compare=None): 
275        """
276        Check all the attributes that this controller knows how to map and see
277        if the requester is allowed to use any of them.  If so return one.
278        Filter defined the objects to check - it's a function that returns true
279        for the objects to check - and cmp defines the order to check them in
280        as the cmp field of sorted().  If filter is None, all possibilities are
281        checked.  If cmp is None, the choices are sorted by priority.
282        """
283
284        # Import request credentials into this (clone later??)
285        if self.auth.import_credentials(
286                data_list=req.get('abac_credential', [])):
287            self.auth.save()
288
289        # NB: in the default case (the else), the comparison order is reversed
290        # so numerically larger priorities are checked first.
291        if compare: c = compare
292        else: c = lambda a, b: cmp(b,a)
293
294        if filter: f = filter
295        else: f = lambda(x): True
296
297        check = sorted([ a for a in self.access if f(a)], cmp=c)
298
299        # Check every attribute that we know how to map and take the first
300        # success.
301        fail_proofs = [ ]
302        for attr in check:
303            access_ok, proof = self.auth.check_attribute(fid, attr.attr,
304                    with_proof=True)
305            if access_ok:
306                self.log.debug("Access succeeded for %s %s" % (attr.attr, fid))
307                return copy.copy(attr.value), [ fid ], proof
308            else:
309                fail_proofs.append(proof)
310                self.log.debug("Access failed for %s %s" % (attr.attr, fid))
311        else:
312            self.log.debug("Access denied for for %s" % fid)
313            # We only return one fail proof because returning hundreds (which
314            # is easy to do) locks up the fault mechanism.
315            raise service_error(service_error.access, "Access denied",
316                    proof=fail_proofs[0])
317
318
319
320    def get_handler(self, path, fid):
321        """
322        This function is somewhat oddly named.  It doesn't get a handler, it
323        handles GETs.  Specifically, it handls https GETs for retrieving data
324        from the repository exported by the access server.
325        """
326        self.log.info("Get handler %s %s" % (path, fid))
327        if len("%s" % fid) == 0:
328            return (None, None)
329        if self.auth.check_attribute(fid, path) and self.userconfdir:
330            return ("%s/%s" % (self.userconfdir, path), "application/binary")
331        else:
332            return (None, None)
333
334    def export_userconf(self, project):
335        dev_null = None
336        confid, confcert = generate_fedid("test", dir=self.userconfdir, 
337                log=self.log)
338        conffilename = "%s/%s" % (self.userconfdir, str(confid))
339        cf = None
340        try:
341            cf = open(conffilename, "w")
342            os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
343        except EnvironmentError, e:
344            raise service_error(service_error.internal, 
345                    "Cannot create user configuration data")
346
347        try:
348            dev_null = open("/dev/null", "a")
349        except EnvironmentError, e:
350            self.log.error("export_userconf: can't open /dev/null: %s" % e)
351
352        cmd = "%s %s" % (self.userconfcmd, project)
353        conf = subprocess.call(shlex.split(cmd),
354                stdout=cf, stderr=dev_null, close_fds=True)
355
356        self.auth.set_attribute(confid, "/%s" % str(confid))
357
358        return confid, confcert
359
360    def export_SMB(self, id, state, project, user, attrs):
361        if project and user:
362            return [{ 
363                    'id': id,
364                    'name': 'SMB',
365                    'visibility': 'export',
366                    'server': 'http://fs:139',
367                    'fedAttr': [
368                            { 'attribute': 'SMBSHARE', 'value': 'USERS' },
369                            { 'attribute': 'SMBUSER', 'value': user },
370                            { 'attribute': 'SMBPROJ', 'value': project },
371                        ]
372                    }]
373        else:
374            self.log.warn("Cannot export SMB w/o user and project")
375            return [ ]
376
377    def export_seer(self, id, state, project, user, attrs):
378        return [{ 
379                'id': id,
380                'name': 'seer',
381                'visibility': 'export',
382                'server': 'http://control:16606',
383                }]
384
385    def export_local_seer(self, id, state, project, user, attrs):
386        return [{ 
387                'id': id,
388                'name': 'local_seer_control',
389                'visibility': 'export',
390                'server': 'http://control:16606',
391                }]
392
393    def export_seer_master(self, id, state, project, user, attrs):
394        return [{ 
395                'id': id,
396                'name': 'seer_master',
397                'visibility': 'export',
398                'server': 'http://seer-master:17707',
399                }]
400
401    def export_tmcd(self, id, state, project, user, attrs):
402        return [{ 
403                'id': id,
404                'name': 'seer',
405                'visibility': 'export',
406                'server': 'http://boss:7777',
407                }]
408
409    def export_userconfig(self, id, state, project, user, attrs):
410        if self.userconfdir and self.userconfcmd \
411                and self.userconfurl:
412            cid, cert = self.export_userconf(project)
413            state['userconfig'] = unicode(cid)
414            return [{
415                    'id': id,
416                    'name': 'userconfig',
417                    'visibility': 'export',
418                    'server': "%s/%s" % (self.userconfurl, str(cid)),
419                    'fedAttr': [
420                        { 'attribute': 'cert', 'value': cert },
421                    ]
422                    }]
423        else:
424            return [ ]
425
426    def export_hide_hosts(self, id, state, project, user, attrs):
427        return [{
428                'id': id, 
429                'name': 'hide_hosts',
430                'visibility': 'export',
431                'fedAttr': [ x for x in attrs \
432                        if x.get('attribute', "") == 'hosts'],
433                }]
434
435    def export_project_export(self, id, state, project, user, attrs):
436        rv = [ ]
437        rv.extend(self.export_SMB(id, state, project, user, attrs))
438        rv.extend(self.export_userconfig(id, state, project, user, attrs))
439        return rv
440
441    def export_services(self, sreq, project=None, user=None):
442        exp = [ ]
443        state = { }
444        for s in sreq:
445            sname = s.get('name', '')
446            svis = s.get('visibility', '')
447            sattrs = s.get('fedAttr', [])
448            if svis == 'export':
449                if sname in self.exports:
450                    id = s.get('id', 'no_id')
451                    exp.extend(self.exports[sname](id, state, project, user,
452                            sattrs))
453
454        return (exp, state)
455
456    def build_access_response(self, alloc_id, pname, services, proof):
457        """
458        Create the SOAP response.
459
460        Build the dictionary description of the response and use
461        fedd_utils.pack_soap to create the soap message.  ap is the allocate
462        project message returned from a remote project allocation (even if that
463        allocation was done locally).
464        """
465        # Because alloc_id is already a fedd_services_types.IDType_Holder,
466        # there's no need to repack it
467        msg = { 
468                'allocID': alloc_id,
469                'proof': proof.to_dict(),
470                'fedAttr': [
471                    { 'attribute': 'domain', 'value': self.domain } , 
472                ]
473            }
474
475        if pname:
476            msg['fedAttr'].append({ 'attribute': 'project', 'value': pname })
477        if self.dragon_endpoint:
478            msg['fedAttr'].append({'attribute': 'dragon',
479                'value': self.dragon_endpoint})
480        if self.deter_internal:
481            msg['fedAttr'].append({'attribute': 'deter_internal',
482                'value': self.deter_internal})
483        #XXX: ??
484        if self.dragon_vlans:
485            msg['fedAttr'].append({'attribute': 'vlans',
486                'value': self.dragon_vlans})
487
488        if services:
489            msg['service'] = services
490        return msg
491
492    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
493            tmpdir, lproj, leid, connInfo, services):
494
495        def conninfo_to_dict(key, info):
496            """
497            Make a cpoy of the connection information about key, and flatten it
498            into a single dict by parsing out any feddAttrs.
499            """
500
501            rv = None
502            for i in info:
503                if key == i.get('portal', "") or \
504                        key in [e.get('element', "") \
505                        for e in i.get('member', [])]:
506                    rv = i.copy()
507                    break
508
509            else:
510                return rv
511
512            if 'fedAttr' in rv:
513                for a in rv['fedAttr']:
514                    attr = a.get('attribute', "")
515                    val = a.get('value', "")
516                    if attr and attr not in rv:
517                        rv[attr] = val
518                del rv['fedAttr']
519            return rv
520
521        # XXX: un hardcode this
522        def client_null(f, s):
523            print >>f, "Service: %s" % s['name']
524
525        def client_seer_master(f, s):
526            print >>f, 'PortalAlias: seer-master'
527
528        def client_smb(f, s):
529            print >>f, "Service: %s" % s['name']
530            smbshare = None
531            smbuser = None
532            smbproj = None
533            for a in s.get('fedAttr', []):
534                if a.get('attribute', '') == 'SMBSHARE':
535                    smbshare = a.get('value', None)
536                elif a.get('attribute', '') == 'SMBUSER':
537                    smbuser = a.get('value', None)
538                elif a.get('attribute', '') == 'SMBPROJ':
539                    smbproj = a.get('value', None)
540
541            if all((smbshare, smbuser, smbproj)):
542                print >>f, "SMBshare: %s" % smbshare
543                print >>f, "ProjectUser: %s" % smbuser
544                print >>f, "ProjectName: %s" % smbproj
545
546        def client_hide_hosts(f, s):
547            for a in s.get('fedAttr', [ ]):
548                if a.get('attribute', "") == 'hosts':
549                    print >>f, "Hide: %s" % a.get('value', "")
550
551        client_service_out = {
552                'SMB': client_smb,
553                'tmcd': client_null,
554                'seer': client_null,
555                'userconfig': client_null,
556                'project_export': client_null,
557                'seer_master': client_seer_master,
558                'hide_hosts': client_hide_hosts,
559            }
560
561        def client_seer_master_export(f, s):
562            print >>f, "AddedNode: seer-master"
563
564        def client_seer_local_export(f, s):
565            print >>f, "AddedNode: control"
566
567        client_export_service_out = {
568                'seer_master': client_seer_master_export,
569                'local_seer_control': client_seer_local_export,
570            }
571
572        def server_port(f, s):
573            p = urlparse(s.get('server', 'http://localhost'))
574            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
575
576        def server_null(f,s): pass
577
578        def server_seer(f, s):
579            print >>f, 'seer: True'
580
581        server_service_out = {
582                'SMB': server_port,
583                'tmcd': server_port,
584                'userconfig': server_null,
585                'project_export': server_null,
586                'seer': server_seer,
587                'seer_master': server_port,
588                'hide_hosts': server_null,
589            }
590        # XXX: end un hardcode this
591
592
593        seer_out = False
594        client_out = False
595        mproj = None
596        mexp = None
597        control_gw = None
598        testbed = ""
599        # Create configuration files for the portals
600        for e in [ e for e in topo.elements \
601                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
602            myname = e.name
603            type = e.get_attribute('portal_type')
604
605            info = conninfo_to_dict(myname, connInfo)
606
607            if not info:
608                raise service_error(service_error.req,
609                        "No connectivity info for %s" % myname)
610
611            peer = info.get('peer', "")
612            ldomain = self.domain
613            ssh_port = info.get('ssh_port', 22)
614
615            # Collect this for the client.conf file
616            if 'masterexperiment' in info:
617                mproj, meid = info['masterexperiment'].split("/", 1)
618
619            if type in ('control', 'both'):
620                testbed = e.get_attribute('testbed')
621                control_gw = myname
622
623            active = info.get('active', 'False')
624
625            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
626            tunnelconfig = self.tunnel_config
627            try:
628                f = open(cfn, "w")
629                if active == 'True':
630                    print >>f, "active: True"
631                    print >>f, "ssh_port: %s" % ssh_port
632                    if type in ('control', 'both'):
633                        for s in [s for s in services \
634                                if s.get('name', "") in self.imports]:
635                            server_service_out[s['name']](f, s)
636
637                if tunnelconfig:
638                    print >>f, "tunnelip: %s" % tunnelconfig
639                print >>f, "peer: %s" % peer.lower()
640                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
641                        (lproj, leid, pubkey_base)
642                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
643                        (lproj, leid, secretkey_base)
644                f.close()
645            except EnvironmentError, e:
646                raise service_error(service_error.internal,
647                        "Can't write protal config %s: %s" % (cfn, e))
648
649        # Done with portals, write the client config file.
650        try:
651            f = open("%s/client.conf" % tmpdir, "w")
652            if control_gw:
653                print >>f, "ControlGateway: %s.%s.%s%s" % \
654                    (myname.lower(), leid.lower(), lproj.lower(),
655                            ldomain.lower())
656            for s in services:
657                if s.get('name',"") in self.imports and \
658                        s.get('visibility','') == 'import':
659                    client_service_out[s['name']](f, s)
660                if s.get('name', '') in self.exports and \
661                        s.get('visibility', '') == 'export' and \
662                        s['name'] in client_export_service_out:
663                    client_export_service_out[s['name']](f, s)
664            # Seer uses this.
665            if mproj and meid:
666                print >>f, "ExperimentID: %s/%s" % (mproj, meid)
667            f.close()
668        except EnvironmentError, e:
669            raise service_error(service_error.internal,
670                    "Cannot write client.conf: %s" %s)
671
672    def configure_userconf(self, services, tmpdir):
673        """
674        If the userconf service was imported, collect the configuration data.
675        """
676        for s in services:
677            s_name = s.get('name', '')
678            s_vis = s.get('visibility','')
679            if s_name  == 'userconfig' and s_vis == 'import':
680                # Collect ther server and certificate info.
681                u = s.get('server', None)
682                for a in s.get('fedAttr', []):
683                    if a.get('attribute',"") == 'cert':
684                        cert = a.get('value', None)
685                        break
686                else:
687                    cert = None
688
689                if cert:
690                    # Make a temporary certificate file for get_url.  The
691                    # finally clause removes it whether something goes
692                    # wrong (including an exception from get_url) or not.
693                    try:
694                        tfos, tn = tempfile.mkstemp(suffix=".pem")
695                        tf = os.fdopen(tfos, 'w')
696                        print >>tf, cert
697                        tf.close()
698                        self.log.debug("Getting userconf info: %s" % u)
699                        get_url(u, tn, tmpdir, "userconf")
700                        self.log.debug("Got userconf info: %s" % u)
701                    except EnvironmentError, e:
702                        raise service_error(service.error.internal, 
703                                "Cannot create temp file for " + 
704                                "userconfig certificates: %s" % e)
705                    except:
706                        t, v, st = sys.exc_info()
707                        raise service_error(service_error.internal,
708                                "Error retrieving %s: %s" % (u, v))
709                    finally:
710                        if tn: os.remove(tn)
711                else:
712                    raise service_error(service_error.req,
713                            "No certificate for retreiving userconfig")
714                break
715
716    def import_store_info(self, cf, connInfo):
717        """
718        Pull any import parameters in connInfo in.  We translate them either
719        into known member names or fedAddrs.
720        """
721
722        for c in connInfo:
723            for p in [ p for p in c.get('parameter', []) \
724                    if p.get('type', '') == 'input']:
725                name = p.get('name', None)
726                key = p.get('key', None)
727                store = p.get('store', None)
728
729                if name and key and store :
730                    req = { 'name': key, 'wait': True }
731                    self.log.debug("Waiting for %s (%s) from %s" % \
732                            (name, key, store))
733                    r = self.call_GetValue(store, req, cf)
734                    r = r.get('GetValueResponseBody', None)
735                    if r :
736                        if r.get('name', '') == key:
737                            v = r.get('value', None)
738                            if v is not None:
739                                if name == 'peer':
740                                    self.log.debug("Got peer %s" % v)
741                                    c['peer'] = v
742                                else:
743                                    self.log.debug("Got %s %s" % (name, v))
744                                    if c.has_key('fedAttr'):
745                                        c['fedAttr'].append({
746                                            'attribute': name, 'value': v})
747                                    else:
748                                        c['fedAttr']= [{
749                                            'attribute': name, 'value': v}]
750                            else:
751                                raise service_error(service_error.internal, 
752                                        'None value exported for %s'  % key)
753                        else:
754                            raise service_error(service_error.internal, 
755                                    'Different name returned for %s: %s' \
756                                            % (key, r.get('name','')))
757                    else:
758                        raise service_error(service_error.internal, 
759                            'Badly formatted response: no GetValueResponseBody')
760                else:
761                    raise service_error(service_error.internal, 
762                        'Bad Services missing info for import %s' % c)
763
764    def remove_dirs(self, dir):
765        """
766        Remove the directory tree and all files rooted at dir.  Log any errors,
767        but continue.
768        """
769        self.log.debug("[removedirs]: removing %s" % dir)
770        try:
771            for path, dirs, files in os.walk(dir, topdown=False):
772                for f in files:
773                    os.remove(os.path.join(path, f))
774                for d in dirs:
775                    os.rmdir(os.path.join(path, d))
776            os.rmdir(dir)
777        except EnvironmentError, e:
778            self.log.error("Error deleting directory tree in %s" % e);
779
780    def RequestAccess(self, req, fid):
781        """
782        Handle an access request.  Success here maps the requester into the
783        local access control space and establishes state about that user keyed
784        to a fedid.  We also save a copy of the certificate underlying that
785        fedid so this allocation can access configuration information and
786        shared parameters on the experiment controller.
787        """
788
789        self.log.info("RequestAccess called by %s" % fid)
790        # The dance to get into the request body
791        if req.has_key('RequestAccessRequestBody'):
792            req = req['RequestAccessRequestBody']
793        else:
794            raise service_error(service_error.req, "No request!?")
795
796        # Base class lookup routine.  If this fails, it throws a service
797        # exception denying access that triggers a fault response back to the
798        # caller.
799        found,  owners, proof = self.lookup_access(req, fid)
800        self.log.info(
801                "[RequestAccess] Access granted local creds %s" % found)
802        # Make a fedid for this allocation
803        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
804        aid = unicode(allocID)
805
806        # Store the data about this allocation:
807        self.state_lock.acquire()
808        self.state[aid] = { }
809        self.state[aid]['user'] = found
810        self.state[aid]['owners'] = owners
811        self.state[aid]['auth'] = set()
812        # Authorize the creating fedid and the principal representing the
813        # allocation to manipulate it.
814        self.append_allocation_authorization(aid, 
815                ((fid, allocID), (allocID, allocID)))
816        self.write_state()
817        self.state_lock.release()
818
819        # Create a directory to stash the certificate in, ans stash it.
820        try:
821            f = open("%s/%s.pem" % (self.certdir, aid), "w")
822            print >>f, alloc_cert
823            f.close()
824        except EnvironmentError, e:
825            raise service_error(service_error.internal, 
826                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
827        self.log.debug('[RequestAccess] Returning allocation ID: %s' % allocID)
828        return { 'allocID': { 'fedid': allocID }, 'proof': proof.to_dict() }
829
830    def ReleaseAccess(self, req, fid):
831        """
832        Release the allocation granted earlier.  Access to the allocation is
833        checked and if valid, the state and cached certificate are destroyed.
834        """
835        self.log.info("ReleaseAccess called by %s" % fid)
836        # The dance to get into the request body
837        if req.has_key('ReleaseAccessRequestBody'):
838            req = req['ReleaseAccessRequestBody']
839        else:
840            raise service_error(service_error.req, "No request!?")
841
842        # Pull a key out of the request.  One can request to delete an
843        # allocation by a local human readable name or by a fedid.  This finds
844        # both choices.
845        try:
846            if 'localname' in req['allocID']:
847                auth_attr = aid = req['allocID']['localname']
848            elif 'fedid' in req['allocID']:
849                aid = unicode(req['allocID']['fedid'])
850                auth_attr = req['allocID']['fedid']
851            else:
852                raise service_error(service_error.req,
853                        "Only localnames and fedids are understood")
854        except KeyError:
855            raise service_error(service_error.req, "Badly formed request")
856
857        self.log.debug("[ReleaseAccess] deallocation requested for %s", aid)
858        #  Confirm access
859        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
860                with_proof=True)
861        if not access_ok:
862            self.log.debug("[ReleaseAccess] deallocation denied for %s", aid)
863            raise service_error(service_error.access, "Access Denied",
864                    proof=proof)
865
866        # If there is an allocation in the state, delete it.  Note the locking.
867        self.state_lock.acquire()
868        if aid in self.state:
869            self.log.debug("[ReleaseAccess] Found allocation for %s" %aid)
870            self.clear_allocation_authorization(aid)
871            del self.state[aid]
872            self.write_state()
873            self.state_lock.release()
874            # And remove the access cert
875            cf = "%s/%s.pem" % (self.certdir, aid)
876            self.log.debug("[ReleaseAccess] Removing %s" % cf)
877            os.remove(cf)
878            self.log.info("ReleaseAccess succeeded for %s" % fid)
879            return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
880        else:
881            self.state_lock.release()
882            raise service_error(service_error.req, "No such allocation")
Note: See TracBrowser for help on using the repository browser.