source: fedd/federation/access.py @ b15ecc6

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

Deal with errors where no attribute will grant access

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