source: fedd/federation/access.py @ 6031c9d

compt_changesinfo-ops
Last change on this file since 6031c9d was e83f2f2, checked in by Ted Faber <faber@…>, 14 years ago

Move proofs around. Lots of changes, including fault handling.

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