source: fedd/federation/access.py @ 2303ca1

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

Properly parse userconf command

  • 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
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 self.auth.check_attribute(fid, path) and self.userconfdir:
328            return ("%s/%s" % (self.userconfdir, path), "application/binary")
329        else:
330            return (None, None)
331
332    def export_userconf(self, project):
333        dev_null = None
334        confid, confcert = generate_fedid("test", dir=self.userconfdir, 
335                log=self.log)
336        conffilename = "%s/%s" % (self.userconfdir, str(confid))
337        cf = None
338        try:
339            cf = open(conffilename, "w")
340            os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
341        except EnvironmentError, e:
342            raise service_error(service_error.internal, 
343                    "Cannot create user configuration data")
344
345        try:
346            dev_null = open("/dev/null", "a")
347        except EnvironmentError, e:
348            self.log.error("export_userconf: can't open /dev/null: %s" % e)
349
350        cmd = "%s %s" % (self.userconfcmd, project)
351        conf = subprocess.call(shlex.split(cmd),
352                stdout=cf, stderr=dev_null, close_fds=True)
353
354        self.auth.set_attribute(confid, "/%s" % str(confid))
355
356        return confid, confcert
357
358    def export_SMB(self, id, state, project, user, attrs):
359        if project and user:
360            return [{ 
361                    'id': id,
362                    'name': 'SMB',
363                    'visibility': 'export',
364                    'server': 'http://fs:139',
365                    'fedAttr': [
366                            { 'attribute': 'SMBSHARE', 'value': 'USERS' },
367                            { 'attribute': 'SMBUSER', 'value': user },
368                            { 'attribute': 'SMBPROJ', 'value': project },
369                        ]
370                    }]
371        else:
372            self.log.warn("Cannot export SMB w/o user and project")
373            return [ ]
374
375    def export_seer(self, id, state, project, user, attrs):
376        return [{ 
377                'id': id,
378                'name': 'seer',
379                'visibility': 'export',
380                'server': 'http://control:16606',
381                }]
382
383    def export_local_seer(self, id, state, project, user, attrs):
384        return [{ 
385                'id': id,
386                'name': 'local_seer_control',
387                'visibility': 'export',
388                'server': 'http://control:16606',
389                }]
390
391    def export_seer_master(self, id, state, project, user, attrs):
392        return [{ 
393                'id': id,
394                'name': 'seer_master',
395                'visibility': 'export',
396                'server': 'http://seer-master:17707',
397                }]
398
399    def export_tmcd(self, id, state, project, user, attrs):
400        return [{ 
401                'id': id,
402                'name': 'seer',
403                'visibility': 'export',
404                'server': 'http://boss:7777',
405                }]
406
407    def export_userconfig(self, id, state, project, user, attrs):
408        if self.userconfdir and self.userconfcmd \
409                and self.userconfurl:
410            cid, cert = self.export_userconf(project)
411            state['userconfig'] = unicode(cid)
412            return [{
413                    'id': id,
414                    'name': 'userconfig',
415                    'visibility': 'export',
416                    'server': "%s/%s" % (self.userconfurl, str(cid)),
417                    'fedAttr': [
418                        { 'attribute': 'cert', 'value': cert },
419                    ]
420                    }]
421        else:
422            return [ ]
423
424    def export_hide_hosts(self, id, state, project, user, attrs):
425        return [{
426                'id': id, 
427                'name': 'hide_hosts',
428                'visibility': 'export',
429                'fedAttr': [ x for x in attrs \
430                        if x.get('attribute', "") == 'hosts'],
431                }]
432
433    def export_project_export(self, id, state, project, user, attrs):
434        rv = [ ]
435        rv.extend(self.export_SMB(id, state, project, user, attrs))
436        rv.extend(self.export_userconfig(id, state, project, user, attrs))
437        return rv
438
439    def export_services(self, sreq, project=None, user=None):
440        exp = [ ]
441        state = { }
442        for s in sreq:
443            sname = s.get('name', '')
444            svis = s.get('visibility', '')
445            sattrs = s.get('fedAttr', [])
446            if svis == 'export':
447                if sname in self.exports:
448                    id = s.get('id', 'no_id')
449                    exp.extend(self.exports[sname](id, state, project, user,
450                            sattrs))
451
452        return (exp, state)
453
454    def build_access_response(self, alloc_id, pname, services, proof):
455        """
456        Create the SOAP response.
457
458        Build the dictionary description of the response and use
459        fedd_utils.pack_soap to create the soap message.  ap is the allocate
460        project message returned from a remote project allocation (even if that
461        allocation was done locally).
462        """
463        # Because alloc_id is already a fedd_services_types.IDType_Holder,
464        # there's no need to repack it
465        msg = { 
466                'allocID': alloc_id,
467                'proof': proof.to_dict(),
468                'fedAttr': [
469                    { 'attribute': 'domain', 'value': self.domain } , 
470                ]
471            }
472
473        if pname:
474            msg['fedAttr'].append({ 'attribute': 'project', 'value': pname })
475        if self.dragon_endpoint:
476            msg['fedAttr'].append({'attribute': 'dragon',
477                'value': self.dragon_endpoint})
478        if self.deter_internal:
479            msg['fedAttr'].append({'attribute': 'deter_internal',
480                'value': self.deter_internal})
481        #XXX: ??
482        if self.dragon_vlans:
483            msg['fedAttr'].append({'attribute': 'vlans',
484                'value': self.dragon_vlans})
485
486        if services:
487            msg['service'] = services
488        return msg
489
490    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
491            tmpdir, lproj, leid, connInfo, services):
492
493        def conninfo_to_dict(key, info):
494            """
495            Make a cpoy of the connection information about key, and flatten it
496            into a single dict by parsing out any feddAttrs.
497            """
498
499            rv = None
500            for i in info:
501                if key == i.get('portal', "") or \
502                        key in [e.get('element', "") \
503                        for e in i.get('member', [])]:
504                    rv = i.copy()
505                    break
506
507            else:
508                return rv
509
510            if 'fedAttr' in rv:
511                for a in rv['fedAttr']:
512                    attr = a.get('attribute', "")
513                    val = a.get('value', "")
514                    if attr and attr not in rv:
515                        rv[attr] = val
516                del rv['fedAttr']
517            return rv
518
519        # XXX: un hardcode this
520        def client_null(f, s):
521            print >>f, "Service: %s" % s['name']
522
523        def client_seer_master(f, s):
524            print >>f, 'PortalAlias: seer-master'
525
526        def client_smb(f, s):
527            print >>f, "Service: %s" % s['name']
528            smbshare = None
529            smbuser = None
530            smbproj = None
531            for a in s.get('fedAttr', []):
532                if a.get('attribute', '') == 'SMBSHARE':
533                    smbshare = a.get('value', None)
534                elif a.get('attribute', '') == 'SMBUSER':
535                    smbuser = a.get('value', None)
536                elif a.get('attribute', '') == 'SMBPROJ':
537                    smbproj = a.get('value', None)
538
539            if all((smbshare, smbuser, smbproj)):
540                print >>f, "SMBshare: %s" % smbshare
541                print >>f, "ProjectUser: %s" % smbuser
542                print >>f, "ProjectName: %s" % smbproj
543
544        def client_hide_hosts(f, s):
545            for a in s.get('fedAttr', [ ]):
546                if a.get('attribute', "") == 'hosts':
547                    print >>f, "Hide: %s" % a.get('value', "")
548
549        client_service_out = {
550                'SMB': client_smb,
551                'tmcd': client_null,
552                'seer': client_null,
553                'userconfig': client_null,
554                'project_export': client_null,
555                'seer_master': client_seer_master,
556                'hide_hosts': client_hide_hosts,
557            }
558
559        def client_seer_master_export(f, s):
560            print >>f, "AddedNode: seer-master"
561
562        def client_seer_local_export(f, s):
563            print >>f, "AddedNode: control"
564
565        client_export_service_out = {
566                'seer_master': client_seer_master_export,
567                'local_seer_control': client_seer_local_export,
568            }
569
570        def server_port(f, s):
571            p = urlparse(s.get('server', 'http://localhost'))
572            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
573
574        def server_null(f,s): pass
575
576        def server_seer(f, s):
577            print >>f, 'seer: True'
578
579        server_service_out = {
580                'SMB': server_port,
581                'tmcd': server_port,
582                'userconfig': server_null,
583                'project_export': server_null,
584                'seer': server_seer,
585                'seer_master': server_port,
586                'hide_hosts': server_null,
587            }
588        # XXX: end un hardcode this
589
590
591        seer_out = False
592        client_out = False
593        mproj = None
594        mexp = None
595        control_gw = None
596        testbed = ""
597        # Create configuration files for the portals
598        for e in [ e for e in topo.elements \
599                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
600            myname = e.name
601            type = e.get_attribute('portal_type')
602
603            info = conninfo_to_dict(myname, connInfo)
604
605            if not info:
606                raise service_error(service_error.req,
607                        "No connectivity info for %s" % myname)
608
609            peer = info.get('peer', "")
610            ldomain = self.domain
611            ssh_port = info.get('ssh_port', 22)
612
613            # Collect this for the client.conf file
614            if 'masterexperiment' in info:
615                mproj, meid = info['masterexperiment'].split("/", 1)
616
617            if type in ('control', 'both'):
618                testbed = e.get_attribute('testbed')
619                control_gw = myname
620
621            active = info.get('active', 'False')
622
623            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
624            tunnelconfig = self.tunnel_config
625            try:
626                f = open(cfn, "w")
627                if active == 'True':
628                    print >>f, "active: True"
629                    print >>f, "ssh_port: %s" % ssh_port
630                    if type in ('control', 'both'):
631                        for s in [s for s in services \
632                                if s.get('name', "") in self.imports]:
633                            server_service_out[s['name']](f, s)
634
635                if tunnelconfig:
636                    print >>f, "tunnelip: %s" % tunnelconfig
637                print >>f, "peer: %s" % peer.lower()
638                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
639                        (lproj, leid, pubkey_base)
640                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
641                        (lproj, leid, secretkey_base)
642                f.close()
643            except EnvironmentError, e:
644                raise service_error(service_error.internal,
645                        "Can't write protal config %s: %s" % (cfn, e))
646
647        # Done with portals, write the client config file.
648        try:
649            f = open("%s/client.conf" % tmpdir, "w")
650            if control_gw:
651                print >>f, "ControlGateway: %s.%s.%s%s" % \
652                    (myname.lower(), leid.lower(), lproj.lower(),
653                            ldomain.lower())
654            for s in services:
655                if s.get('name',"") in self.imports and \
656                        s.get('visibility','') == 'import':
657                    client_service_out[s['name']](f, s)
658                if s.get('name', '') in self.exports and \
659                        s.get('visibility', '') == 'export' and \
660                        s['name'] in client_export_service_out:
661                    client_export_service_out[s['name']](f, s)
662            # Seer uses this.
663            if mproj and meid:
664                print >>f, "ExperimentID: %s/%s" % (mproj, meid)
665            f.close()
666        except EnvironmentError, e:
667            raise service_error(service_error.internal,
668                    "Cannot write client.conf: %s" %s)
669
670    def configure_userconf(self, services, tmpdir):
671        """
672        If the userconf service was imported, collect the configuration data.
673        """
674        for s in services:
675            s_name = s.get('name', '')
676            s_vis = s.get('visibility','')
677            if s_name  == 'userconfig' and s_vis == 'import':
678                # Collect ther server and certificate info.
679                u = s.get('server', None)
680                for a in s.get('fedAttr', []):
681                    if a.get('attribute',"") == 'cert':
682                        cert = a.get('value', None)
683                        break
684                else:
685                    cert = None
686
687                if cert:
688                    # Make a temporary certificate file for get_url.  The
689                    # finally clause removes it whether something goes
690                    # wrong (including an exception from get_url) or not.
691                    try:
692                        tfos, tn = tempfile.mkstemp(suffix=".pem")
693                        tf = os.fdopen(tfos, 'w')
694                        print >>tf, cert
695                        tf.close()
696                        self.log.debug("Getting userconf info: %s" % u)
697                        get_url(u, tn, tmpdir, "userconf")
698                        self.log.debug("Got userconf info: %s" % u)
699                    except EnvironmentError, e:
700                        raise service_error(service.error.internal, 
701                                "Cannot create temp file for " + 
702                                "userconfig certificates: %s" % e)
703                    except:
704                        t, v, st = sys.exc_info()
705                        raise service_error(service_error.internal,
706                                "Error retrieving %s: %s" % (u, v))
707                    finally:
708                        if tn: os.remove(tn)
709                else:
710                    raise service_error(service_error.req,
711                            "No certificate for retreiving userconfig")
712                break
713
714    def import_store_info(self, cf, connInfo):
715        """
716        Pull any import parameters in connInfo in.  We translate them either
717        into known member names or fedAddrs.
718        """
719
720        for c in connInfo:
721            for p in [ p for p in c.get('parameter', []) \
722                    if p.get('type', '') == 'input']:
723                name = p.get('name', None)
724                key = p.get('key', None)
725                store = p.get('store', None)
726
727                if name and key and store :
728                    req = { 'name': key, 'wait': True }
729                    self.log.debug("Waiting for %s (%s) from %s" % \
730                            (name, key, store))
731                    r = self.call_GetValue(store, req, cf)
732                    r = r.get('GetValueResponseBody', None)
733                    if r :
734                        if r.get('name', '') == key:
735                            v = r.get('value', None)
736                            if v is not None:
737                                if name == 'peer':
738                                    self.log.debug("Got peer %s" % v)
739                                    c['peer'] = v
740                                else:
741                                    self.log.debug("Got %s %s" % (name, v))
742                                    if c.has_key('fedAttr'):
743                                        c['fedAttr'].append({
744                                            'attribute': name, 'value': v})
745                                    else:
746                                        c['fedAttr']= [{
747                                            'attribute': name, 'value': v}]
748                            else:
749                                raise service_error(service_error.internal, 
750                                        'None value exported for %s'  % key)
751                        else:
752                            raise service_error(service_error.internal, 
753                                    'Different name returned for %s: %s' \
754                                            % (key, r.get('name','')))
755                    else:
756                        raise service_error(service_error.internal, 
757                            'Badly formatted response: no GetValueResponseBody')
758                else:
759                    raise service_error(service_error.internal, 
760                        'Bad Services missing info for import %s' % c)
761
762    def remove_dirs(self, dir):
763        """
764        Remove the directory tree and all files rooted at dir.  Log any errors,
765        but continue.
766        """
767        self.log.debug("[removedirs]: removing %s" % dir)
768        try:
769            for path, dirs, files in os.walk(dir, topdown=False):
770                for f in files:
771                    os.remove(os.path.join(path, f))
772                for d in dirs:
773                    os.rmdir(os.path.join(path, d))
774            os.rmdir(dir)
775        except EnvironmentError, e:
776            self.log.error("Error deleting directory tree in %s" % e);
777
778    def RequestAccess(self, req, fid):
779        """
780        Handle an access request.  Success here maps the requester into the
781        local access control space and establishes state about that user keyed
782        to a fedid.  We also save a copy of the certificate underlying that
783        fedid so this allocation can access configuration information and
784        shared parameters on the experiment controller.
785        """
786
787        self.log.info("RequestAccess called by %s" % fid)
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,  owners, proof = self.lookup_access(req, fid)
798        self.log.info(
799                "[RequestAccess] Access granted local creds %s" % found)
800        # Make a fedid for this allocation
801        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
802        aid = unicode(allocID)
803
804        # Store the data about this allocation:
805        self.state_lock.acquire()
806        self.state[aid] = { }
807        self.state[aid]['user'] = found
808        self.state[aid]['owners'] = owners
809        self.state[aid]['auth'] = set()
810        # Authorize the creating fedid and the principal representing the
811        # allocation to manipulate it.
812        self.append_allocation_authorization(aid, 
813                ((fid, allocID), (allocID, allocID)))
814        self.write_state()
815        self.state_lock.release()
816
817        # Create a directory to stash the certificate in, ans stash it.
818        try:
819            f = open("%s/%s.pem" % (self.certdir, aid), "w")
820            print >>f, alloc_cert
821            f.close()
822        except EnvironmentError, e:
823            raise service_error(service_error.internal, 
824                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
825        self.log.debug('[RequestAccess] Returning allocation ID: %s' % allocID)
826        return { 'allocID': { 'fedid': allocID }, 'proof': proof.to_dict() }
827
828    def ReleaseAccess(self, req, fid):
829        """
830        Release the allocation granted earlier.  Access to the allocation is
831        checked and if valid, the state and cached certificate are destroyed.
832        """
833        self.log.info("ReleaseAccess called by %s" % fid)
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            self.log.info("ReleaseAccess succeeded for %s" % fid)
877            return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
878        else:
879            self.state_lock.release()
880            raise service_error(service_error.req, "No such allocation")
Note: See TracBrowser for help on using the repository browser.