source: fedd/federation/access.py @ 6bedbdba

compt_changesinfo-ops
Last change on this file since 6bedbdba was 6bedbdba, checked in by Ted Faber <faber@…>, 12 years ago

Split topdl and fedid out to different packages. Add differential
installs

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