source: fedd/federation/access.py @ 45016ae

compt_changes
Last change on this file since 45016ae was 8cab4c2, checked in by Ted Faber <faber@…>, 13 years ago

More improved logging

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