source: fedd/federation/access.py @ 968b84b

Last change on this file since 968b84b was d846cbe, checked in by Ted Faber <faber@…>, 11 years ago

Make error handling clearer

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